/*
    This file was generated by "mtxrun --script "mtx-wtoc.lua" from the metapost cweb
    files but now maintained as C file.
*/

# include "mp.h"
# include "mpmathscaled.h"
# include "mpmathdouble.h"
# include "mpmathbinary.h"
# include "mpmathdecimal.h"
# include "mpmathposit.h"
# include "mpmathinterval.h"
# include "mpstrings.h"

/*tex

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. Well, this will no happen, mainly
because the original library can't change (part of the frozen \LUATEX).

I'm pretty sure that the \TEX part of this file doesn't process but I might look into that later.
The comments are kept as they were but there are occasional remarks 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 now 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 conducted, 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.

\startlines
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
\stoplines

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.

(Hans Hagen, 2019+)

At some point a new round of cleanup started (2025) as preparation for some extensions that Mikael
S and I had in mind. Part of that involved some reconfiguring of memory management so that we got
more insight in memory usage. Being more specific (node wise) and share less the memory footprint
could be brought down a bit.

*/

/*tex

Some more comments

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 problem 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.

\startlines
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
\stoplines

The current code that deals with paths is too messy (a side effect of merging snippets) but when I've
moved to C (maybe 2024/2025) and cleanup a bit (maybe split the code into smaller pieces too) we can
consider:

\startlines
Todo: primitive --
Todo: primitive hmoveto
Todo: primitive vmoveto
Todo: primitive rmoveto
Todo: primitive hlineto
Todo: primitive vlineto
Todo: primitive rlineto
Todo: primitive curveto
\stoplines

(Hans Hagen, 2019+)
*/

/*tex

As with \LUATEX\ (and \LUAMETATEX) we've tried to keep the original documentation close to where it
makes sense but sometimes we have to compromise. Sorry for that.

*/

/*tex

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 \CCODE.

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 {\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.

*/

/*

More comments:

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
likely 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).

In March 2024 I decided to merge the documentation into the C files and work from those instead of
the WEB files. The main reason for this move was that we (Mikael Sundqvist and HH) were picking up
on some pending extensions and it's easier to implement these after a little more reshuffling (and
maybe breaking up large functions). There has been extensions without bumping the version number
we now went to 3.15 (normally one can rely on the version number of \LUAMETATEX\ anyway).

(Hans Hagen, 2022+)

*/

# define mp_default_banner "This is MPLIB for LuaMetaTeX, version 3.15"

/*tex

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.Like the
preceding parameters, the following quantities can be changed to extend or reduce \MP's capacity.

Here are some macros for common programming idioms (incr and decr are now inlined). The order of
comments is (still) sub-optimal but will be adapted over time (years). We also have macros that
interface to (previously global) variables. Comments might refer to these without showing the
definitions as these are in the header file.

*/

/*tex

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.

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.

*/

# define mp_inf_t                     mp->math->md_inf_t
# define mp_negative_inf_t            mp->math->md_negative_inf_t

# define mp_arc_tol_k                 mp->math->md_arc_tol_k
# define mp_coef_bound_k              mp->math->md_coef_bound_k
# define mp_coef_bound_minus_1        mp->math->md_coef_bound_minus_1
# define mp_sqrt_8_e_k                mp->math->md_sqrt_8_e_k
# define mp_twelve_ln_2_k             mp->math->md_twelve_ln_2_k
# define mp_twelvebits_3              mp->math->md_twelvebits_3
# define mp_one_k                     mp->math->md_one_k
# define mp_epsilon_t                 mp->math->md_epsilon_t
# define mp_unity_t                   mp->math->md_unity_t
# define mp_zero_t                    mp->math->md_zero_t
# define mp_two_t                     mp->math->md_two_t
# define mp_three_t                   mp->math->md_three_t
# define mp_half_unit_t               mp->math->md_half_unit_t
# define mp_three_quarter_unit_t      mp->math->md_three_quarter_unit_t
# define mp_twentysixbits_sqrt2_t     mp->math->md_twentysixbits_sqrt2_t
# define mp_twentyeightbits_d_t       mp->math->md_twentyeightbits_d_t
# define mp_twentysevenbits_sqrt2_d_t mp->math->md_twentysevenbits_sqrt2_d_t
# define mp_warning_limit_t           mp->math->md_warning_limit_t
# define mp_precision_default         mp->math->md_precision_default
# define mp_precision_min             mp->math->md_precision_min
# define mp_precision_max             mp->math->md_precision_max
# define mp_fraction_one_t            mp->math->md_fraction_one_t
# define mp_fraction_half_t           mp->math->md_fraction_half_t
# define mp_fraction_three_t          mp->math->md_fraction_three_t
# define mp_fraction_four_t           mp->math->md_fraction_four_t
# define mp_one_eighty_deg_t          mp->math->md_one_eighty_deg_t
# define mp_negative_one_eighty_deg_t mp->math->md_negative_one_eighty_deg_t
# define mp_three_sixty_deg_t         mp->math->md_three_sixty_deg_t

/*tex

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 possible.

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 |mp_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 possible.

The random related code is now in the number system modules!

*/

# define mp_max_quarterword    0x3FFF
# define mp_max_halfword    0xFFFFFFF

/*tex

The reader should study the following definitions closely. 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.

*/

# define mp_link(A)       (A)->link
# define mp_type(A)       (A)->type
# define mp_name_type(A)  (A)->name_type
# define mp_set_link(A,B) (A)->link = (mp_node) (B)

/*tex

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.

*/

# define mp_max_command_code       mp_undefined_cs_command
# define mp_max_pre_command        mp_etex_command
# define mp_min_command            (mp_defined_macro_command + 1)
# define mp_max_statement_command  mp_type_name_command
# define mp_min_primary_command    mp_type_name_command
# define mp_min_suffix_token       mp_internal_command
# define mp_max_suffix_token       mp_numeric_command
# define mp_max_primary_command    mp_plus_or_minus_command
# define mp_min_tertiary_command   mp_plus_or_minus_command
# define mp_max_tertiary_command   mp_tertiary_binary_command
# define mp_min_expression_command mp_left_brace_command
# define mp_max_expression_command mp_equals_command
# define mp_min_secondary_command  mp_and_command
# define mp_max_secondary_command  mp_secondary_binary_command
# define mp_end_of_statement       (cur_cmd > mp_comma_command)

/*tex

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.

*/

# define mp_unknown_tag 1

/*tex

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.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 enumerations 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).

*/

/*tex

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.The class numbers:If changes are made to accommodate non-ASCII
character sets, they should follow the guidelines in Appendix~C of {\sl The {\logos
METAFONT}book}.

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.

*/

# define mp_set_eq_text(A) { \
    (A)->text = B ; \
}
# define mp_set_eq_type(A,B) { \
    (A)->type = B ; \
}
# define mp_set_eq_property(A,B) { \
    (A)->property = B ; \
}
# define mp_set_eq_valent(A,B) { \
    (A)->v.data.node = NULL ; \
    (A)->v.data.indep.serial = B; \
}
# define mp_set_eq_node(A,B) { \
    (A)->v.data.node = B ; \
    (A)->v.data.indep.serial = 0; \
}
# define mp_set_eq_symbol(A,B) { \
    (A)->v.data.node = (mp_node) (B); \
    (A)->v.data.indep.serial = 0; \
}

/*tex

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.

Certain symbols are \quote {frozen} and not redefinable, since they are used in error recovery.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_symbol|) in the |mp| instance object for the handful. Okay, we
also have |:=| so maybe only for single character ones ... not worth the trouble.

*/

# define mp_id_lookup(A,B,C,D) mp_do_id_lookup((A), mp->symbols, (B), (C), (D))

/*

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: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.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 \quote {|def| primitive}.

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:

\startitemize[n]

\startitem
    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.
\stopitem

\startitem
    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|.
\stopitem

\startitem
    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|.
\stopitem

\startitem
    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).
\stopitem

\startitem
    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|.
\stopitem

\startitem
    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|.
\stopitem

\stopitemize

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.

*/

# define mp_set_value_sym(A,B)    mp_do_set_value_sym   (mp, (mp_token_node) (A), (B))
# define mp_set_value_number(A,B) mp_do_set_value_number(mp, (mp_token_node) (A), &(B))
# define mp_set_value_node(A,B)   mp_do_set_value_node  (mp, (mp_token_node) (A), (B))
# define mp_set_value_str(A,B)    mp_do_set_value_str   (mp, (mp_token_node) (A), (B))
# define mp_set_value_knot(A,B)   mp_do_set_value_knot  (mp, (mp_token_node) (A), (B))

/*tex

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.

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.

*/

# define mp_get_ref_count(A)   mp_get_indep_value(A)
# define mp_set_ref_count(A,B) mp_set_indep_value(A,B)
# define mp_add_mac_ref(A)     mp_set_ref_count((A), mp_get_ref_count((A))+1)
# define mp_decr_mac_ref(A)    mp_set_ref_count((A), mp_get_ref_count((A))-1)

/*tex

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.

Note TH: 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.

*/

# define mp_get_attribute_head(A)   mp_do_get_attribute_head(mp, (mp_value_node) (A))
# define mp_set_attribute_head(A,B) mp_do_set_attribute_head(mp, (mp_value_node) (A),(mp_node) (B))
# define mp_get_subscr_head(A)      mp_do_get_subscr_head   (mp, (mp_value_node) (A))
# define mp_set_subscr_head(A,B)    mp_do_set_subscr_head   (mp, (mp_value_node) (A),(mp_node) (B))

/*tex

Setting the |hashloc| field of |end_attr| to a value greater than any legal hash address is done by
assigning $-1$ typecast to |mp_symbol|, 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.

*/

# define mp_collective_subscript (void *)0 /* code for the attribute |[]| */

# define mp_subscript(A) ((mp_value_node)(A))->subscript

/*tex

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.

*/

# define mp_x_part(A) ((mp_pair_node) (A))->x_part
# define mp_y_part(A) ((mp_pair_node) (A))->y_part
# define mp_z_part(A) ((mp_pair_node) (A))->z_part
# define mp_w_part(A) ((mp_pair_node) (A))->w_part

/*tex

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|.

*/

# define mp_tx_part(A) ((mp_transform_node) (A))->tx_part
# define mp_ty_part(A) ((mp_transform_node) (A))->ty_part
# define mp_xx_part(A) ((mp_transform_node) (A))->xx_part
# define mp_xy_part(A) ((mp_transform_node) (A))->xy_part
# define mp_yx_part(A) ((mp_transform_node) (A))->yx_part
# define mp_yy_part(A) ((mp_transform_node) (A))->yy_part

/*tex

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|.

*/

# define mp_red_part(A)     ((mp_color_node) (A))->red_part
# define mp_green_part(A)   ((mp_color_node) (A))->green_part
# define mp_blue_part(A)    ((mp_color_node) (A))->blue_part
# define mp_grey_part(A)    ((mp_color_node) (A))->grey_part
# define mp_cyan_part(A)    ((mp_color_node) (A))->cyan_part
# define mp_magenta_part(A) ((mp_color_node) (A))->magenta_part
# define mp_yellow_part(A)  ((mp_color_node) (A))->yellow_part
# define mp_black_part(A)   ((mp_color_node) (A))->black_part

/*tex

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
Bezier's formula

$$
\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.

*/

# define mp_next_knot(A)  (A)->next
# define mp_left_type(A)  (A)->left_type
# define mp_right_type(A) (A)->right_type
# define mp_prev_knot(A)  (A)->prev
# define mp_knot_info(A)  (A)->info

/*tex

Before the Bezier 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:

\startitemize

\startitem
    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.
\stopitem

\startitem
    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|.
\stopitem

\startitem
    If |mp_right_type = mp_given|, the curve should leave the knot in a nonzero direction stored as an
    |angle| in |right_given|.
\stopitem

\startitem
    If |mp_right_type = mp_explicit|, the Bezier control point for leaving this knot has already been
    computed; it is in the |mp_right_x| and |mp_right_y| fields.
\stopitem

\stopitemize

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.

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:

\startitemize[r]
\startitem
    |open| type never appears in the same node together with |endpoint|, |given|, or |curl|.
 \stopitem
 \startitem
    The |mp_right_type| of a node is |explicit| if and only if the |mp_left_type| of the following
    node is |explicit|.
 \stopitem
 \startitem
   |endpoint| types occur only at the ends, as mentioned above.
\stopitem
\stopitemize

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.

*/

# define mp_originator(A) (A)->originator
# define mp_knotstate(A)  (A)->state

# define mp_minx mp->bbmin[mp_x_code]
# define mp_maxx mp->bbmax[mp_x_code]
# define mp_miny mp->bbmin[mp_y_code]
# define mp_maxy mp->bbmax[mp_y_code]

# define mp_one_third_inf_t mp->math->md_one_third_inf_t

/*tex

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.

*/

# define mp_copy_pen(mp,A) mp_make_pen(mp, mp_copy_path(mp, (A)),0)

/*tex

The only information required about an elliptical pen is the overall transformation that has been
applied to the original |pencircle|. 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.

*/

# define mp_pen_is_elliptical(A) ((A)==mp_next_knot((A)))

/*tex

The first set of numerical values goes into the header

*/

# define mp_new_number(A)                           mp->math->md_allocate(mp, &(A), mp_scaled_type)
# define mp_new_fraction(A)                         mp->math->md_allocate(mp, &(A), mp_fraction_type)
# define mp_new_angle(A)                            mp->math->md_allocate(mp, &(A), mp_angle_type)
# define mp_new_number_clone(A,B)                   mp->math->md_allocate_clone(mp, &(A), mp_scaled_type, &(B))
# define mp_new_fraction_clone(A,B)                 mp->math->md_allocate_clone(mp, &(A), mp_fraction_type, &(B))
# define mp_new_angle_clone(A,B)                    mp->math->md_allocate_clone(mp, &(A), mp_angle_type, &(B))
# define mp_new_number_from_double(mp,A,B)          mp->math->md_allocate_double(mp, &(A), B)
# define mp_new_number_abs(A,B)                     mp->math->md_allocate_abs(mp, &(A), mp_scaled_type, &(B))
# define mp_new_number_from_div(A,B,C)              mp->math->md_allocate_div(mp, &(A), mp_scaled_type, &(B), &(C))
# define mp_new_number_from_mul(A,B,C)              mp->math->md_allocate_mul(mp, &(A), mp_scaled_type, &(B), &(C))
# define mp_new_number_from_add(A,B,C)              mp->math->md_allocate_add(mp, &(A), mp_scaled_type, &(B), &(C))
# define mp_new_number_from_sub(A,B,C)              mp->math->md_allocate_sub(mp, &(A), mp_scaled_type, &(B), &(C))
# define mp_free_number(A)                          mp->math->md_free(mp, &(A))
# define mp_set_precision()                         mp->math->md_set_precision(mp)
# define mp_free_math()                             mp->math->md_free_math(mp)
# define mp_scan_numeric_token(A)                   mp->math->md_scan_numeric(mp,A)
# define mp_scan_fractional_token(A)                mp->math->md_scan_fractional(mp,A)
# define mp_set_number_from_of_the_way(A,t,B,C)     mp->math->md_from_of_the_way(mp,&(A),&(t),&(B),&(C))
# define mp_set_number_from_int(A,B)                mp->math->md_from_int(&(A),B)
# define mp_set_number_from_scaled(A,B)             mp->math->md_from_scaled(&(A),B)
# define mp_set_number_from_boolean(A,B)            mp->math->md_from_boolean(&(A),B)
# define mp_set_number_from_double(A,B)             mp->math->md_from_double(&(A),B)
# define mp_set_number_from_addition(A,B,C)         mp->math->md_from_addition(&(A),&(B),&(C))
# define mp_set_number_half_from_addition(A,B,C)    mp->math->md_half_from_addition(&(A),&(B),&(C))
# define mp_set_number_from_subtraction(A,B,C)      mp->math->md_from_subtraction(&(A),&(B),&(C))
# define mp_set_number_half_from_subtraction(A,B,C) mp->math->md_half_from_subtraction(&(A),&(B),&(C))
# define mp_set_number_from_div(A,B,C)              mp->math->md_from_div(&(A),&(B),&(C))
# define mp_set_number_from_mul(A,B,C)              mp->math->md_from_mul(&(A),&(B),&(C))
# define mp_set_number_int_div(A,C)                 mp->math->md_from_int_div(&(A),&(A),C)
# define mp_set_number_from_int_mul(A,B,C)          mp->math->md_from_int_mul(&(A),&(B),C)
# define mp_set_number_to_unity(A)                  mp->math->md_clone(&(A), &mp_unity_t)
# define mp_set_number_to_zero(A)                   mp->math->md_clone(&(A), &mp_zero_t)
# define mp_set_number_to_inf(A)                    mp->math->md_clone(&(A), &mp_inf_t)
# define mp_set_number_to_negative_inf(A)           mp->math->md_clone(&(A), &mp_negative_inf_t)
# define mp_init_randoms(A)                         mp->math->md_init_randoms(mp,A)
# define mp_number_tostring(A)                      mp->math->md_tostring(mp,&(A))
# define mp_make_scaled(R,A,B)                      mp->math->md_make_scaled(mp,&(R),&(A),&(B))
# define mp_take_scaled(R,A,B)                      mp->math->md_take_scaled(mp,&(R),&(A),&(B))
# define mp_make_fraction(R,A,B)                    mp->math->md_make_fraction(mp,&(R),&(A),&(B))
# define mp_take_fraction(R,A,B)                    mp->math->md_take_fraction(mp,&(R),&(A),&(B))
# define mp_pyth_add(R,A,B)                         mp->math->md_pyth_add(mp,&(R),&(A),&(B))
# define mp_pyth_add3(R,A,B,C)                      mp->math->md_pyth_add3(mp,&(R),&(A),&(B),&(C))
# define mp_pyth_sub(R,A,B)                         mp->math->md_pyth_sub(mp,&(R),&(A),&(B))
# define mp_power_of(R,A,B)                         mp->math->md_power_of(mp,&(R),&(A),&(B))
# define mp_n_arg(R,A,B)                            mp->math->md_n_arg(mp,&(R),&(A),&(B))
# define mp_m_log(R,A)                              mp->math->md_m_log(mp,&(R),&(A))
# define mp_m_exp(R,A)                              mp->math->md_m_exp(mp,&(R),&(A))
# define mp_m_unif_rand(R,A)                        mp->math->md_m_unif_rand(mp,&(R),&(A))
# define mp_m_norm_rand(R)                          mp->math->md_m_norm_rand(mp,&(R))
# define mp_velocity(R,A,B,C,D,E)                   mp->math->md_velocity(mp,&(R),&(A),&(B),&(C),&(D),&(E))
# define mp_ab_vs_cd(A,B,C,D)                       mp->math->md_ab_vs_cd(&(A),&(B),&(C),&(D))
# define mp_crossing_point(R,A,B,C)                 mp->math->md_crossing_point(mp,&(R),&(A),&(B),&(C))
# define mp_n_sin_cos(A,S,C)                        mp->math->md_sin_cos(mp,&(A),&(S),&(C))
# define mp_square_rt(A,S)                          mp->math->md_sqrt(mp,&(A),&(S))
# define mp_slow_add(R,A,B)                         mp->math->md_slow_add(mp,&(R),&(A),&(B))
# define mp_slow_sub(R,A,B)                         mp->math->md_slow_sub(mp,&(R),&(A),&(B))
# define mp_round_unscaled(A)                       mp->math->md_round_unscaled(&(A))
# define mp_floor_scaled(A)                         mp->math->md_floor_scaled(&(A))
# define mp_fraction_to_round_scaled(A)             mp->math->md_fraction_to_round_scaled(&(A))
# define mp_number_to_int(A)                        mp->math->md_to_int(&(A))
# define mp_number_to_boolean(A)              (int) mp->math->md_to_boolean(&(A))
# define mp_number_to_scaled(A)                     mp->math->md_to_scaled(&(A))
# define mp_number_to_double(A)                     mp->math->md_to_double(&(A))
# define mp_number_negate(A)                        mp->math->md_negate(&(A))
# define mp_number_add(A,B)                         mp->math->md_add(&(A),&(B))
# define mp_number_subtract(A,B)                    mp->math->md_subtract(&(A),&(B))
# define mp_number_half(A)                          mp->math->md_half(&(A))
# define mp_number_double(A)                        mp->math->md_do_double(&(A))
# define mp_number_add_scaled(A,B)                  mp->math->md_add_scaled(&(A),B)
# define mp_number_multiply_int(A,B)                mp->math->md_multiply_int(&(A),B)
# define mp_number_divide_int(A,B)                  mp->math->md_divide_int(&(A),B)
# define mp_number_abs(A)                           mp->math->md_abs(&(A))
# define mp_number_modulo(A,B)                      mp->math->md_modulo(&(A),&(B))
# define mp_number_non_equal_abs(A,B)               mp->math->md_non_equal_abs(&(A),&(B))
# define mp_number_odd(A)                           mp->math->md_odd(&(A))
# define mp_number_equal(A,B)                       mp->math->md_equal(&(A),&(B))
# define mp_number_greater(A,B)                     mp->math->md_greater(&(A),&(B))
# define mp_number_less(A,B)                        mp->math->md_less(&(A),&(B))
# define mp_number_clone(A,B)                       mp->math->md_clone(&(A),&(B))
# define mp_number_negated_clone(A,B)               mp->math->md_negated_clone(&(A),&(B))
# define mp_number_abs_clone(A,B)                   mp->math->md_abs_clone(&(A),&(B))
# define mp_number_swap(A,B)                        mp->math->md_swap(&(A),&(B));
# define mp_convert_scaled_to_angle(A)              mp->math->md_scaled_to_angle(&(A));
# define mp_convert_angle_to_scaled(A)              mp->math->md_angle_to_scaled(&(A));
# define mp_convert_fraction_to_scaled(A)           mp->math->md_fraction_to_scaled(&(A));
# define mp_convert_scaled_to_fraction(A)           mp->math->md_scaled_to_fraction(&(A));

# define mp_number_zero(A)           mp_number_equal(A, mp_zero_t)
# define mp_number_infinite(A)       mp_number_equal(A, mp_inf_t)
# define mp_number_unity(A)          mp_number_equal(A, mp_unity_t)
# define mp_number_negative(A)       mp_number_less(A, mp_zero_t)
# define mp_number_nonnegative(A)    (! mp_number_negative(A))
# define mp_number_positive(A)       mp_number_greater(A, mp_zero_t)
# define mp_number_nonpositive(A)    (! mp_number_positive(A))
# define mp_number_nonzero(A)        (! mp_number_zero(A))
# define mp_number_greaterequal(A,B) (! mp_number_less(A,B))
# define mp_number_lessequal(A,B)    (! mp_number_greater(A,B))

/*tex

Now we come to \MP's internal scheme for representing pictures: edge structures. The representation
is very different from \MF's edge structures because \MP\ pictures contain \ps\ graphics objects
instead of pixel images. However, the basic idea is somewhat similar in that shapes are represented
via their boundaries.

The main purpose of edge structures is to keep track of graphical objects until it is time to
translate them into \ps. Since \MP\ does not need to know anything about an edge structure other than
how to translate it into \ps\ and how to find its bounding box, edge structures can be just linked
lists of graphical objects. \MP\ has no easy way to determine whether two such objects overlap, but
it suffices to draw the first one first and let the second one overwrite it if necessary.Let's
consider the types of graphical objects one at a time. First of all, a filled contour is represented
by a 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.

*/

//define bm_current_y(ny,y)  (ny-y-1)
//define bm_first_y(ny,y,dy) (bm_current_y(ny,y)-dy+1)
//define bm_last_y(ny,y,dy)  (bm_current_y(ny,y)-1)

# define mp_path_ptr(A)         (A)->path
# define mp_pen_ptr(A)          (A)->pen
# define mp_dash_ptr(A)         ((mp_shape_node) (A))->dash
# define mp_line_cap(A)         ((mp_shape_node) (A))->linecap
# define mp_line_join(A)        ((mp_shape_node) (A))->linejoin
# define mp_miterlimit(A)       ((mp_shape_node) (A))->miterlimit
# define mp_curvature(A)        ((mp_shape_node) (A))->curvature
# define mp_set_linecap(A,B)    ((mp_shape_node) (A))->linecap = (unsigned char) (B)
# define mp_set_linejoin(A,B)   ((mp_shape_node) (A))->linejoin = (unsigned char) (B)
# define mp_set_curvature(A,B)  ((mp_shape_node) (A))->curvature = (unsigned char) (B)
# define mp_set_mesh(A,B)       ((mp_shape_node) (A))->mesh = (short) (B)
# define mp_set_bytemap(A,B)    ((mp_shape_node) (A))->bytemap = (short) (B)
# define mp_pre_script(A)       ((mp_shape_node) (A))->pre_script
# define mp_post_script(A)      ((mp_shape_node) (A))->post_script
# define mp_color_model(A)      ((mp_shape_node) (A))->color_model
# define mp_stacking(A)         ((mp_shape_node) (A))->stacking
# define mp_pen_type(A)         ((mp_shape_node) (A))->pen_type
# define mp_cyan_color(A)       ((mp_shape_node) (A))->cyan
# define mp_magenta_color(A)    ((mp_shape_node) (A))->magenta
# define mp_yellow_color(A)     ((mp_shape_node) (A))->yellow
# define mp_black_color(A)      ((mp_shape_node) (A))->black
# define mp_red_color(A)        ((mp_shape_node) (A))->red
# define mp_green_color(A)      ((mp_shape_node) (A))->green
# define mp_blue_color(A)       ((mp_shape_node) (A))->blue
# define mp_gray_color(A)       ((mp_shape_node) (A))->grey
# define mp_grey_color(A)       ((mp_shape_node) (A))->grey

# define mp_has_color(A)        ((A)->type <  mp_start_clip_node_type)
# define mp_has_script(A)       ((A)->type <= mp_start_bounds_node_type)
# define mp_has_pen(A)          ((A)->type <= mp_stroked_node_type)

# define mp_is_start_or_stop(A) ((A)->type >= mp_start_clip_node_type)
# define mp_is_stop(A)          ((A)->type >= mp_stop_clip_node_type)

/*tex

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 \POSTSCRIPT.

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.

*/

/* still needed ? */

# define mp_get_dash_list(A)   (mp_dash_node) (((mp_dash_node) (A))->link)
# define mp_set_dash_list(A,B) ((mp_dash_node) (A))->link = (mp_dash_node) ((B))

/*tex

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 data might not be valid for all values of this parameter.
Hence, the |bbtype| field is needed to keep track of this.

*/

# define mp_bblast(A)    ((mp_edge_header_node) (A))->bblast
# define mp_edge_list(A) ((mp_edge_header_node) (A))->list

/*tex

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.

*/

# define mp_obj_tail(A)       ((mp_edge_header_node) (A))->obj_tail
# define mp_edge_ref_count(A) ((mp_edge_header_node) (A))->ref_count

/*tex

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.

*/

# define mp_add_edge_ref(mp,A) mp_edge_ref_count((A)) += 1

# define 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; \
    } \
}

/*tex

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.

*/

# define mp_dash_info(A) ((mp_dash_node) (A))->dash_info

/*tex

This case involves a recursive call that advances |mp_bblast(h)| to the node of type
|mp_stop_clip_node| that matches |p|.

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_offset + l - k|. (The constant |zero_offset| 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.} 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.

*/

# define zero_offset 0

/*tex

After setting |p := mp_link(p)|, either |join_type = 1| or |q = mp_link(p)|. For very small angles,
adding a knot is unnecessary and would cause numerical problems, so we just set |r := NULL| in that
case.

*/

# define mp_near_zero_angle_k mp->math->md_near_zero_angle_t

/*tex

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|.) 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.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.

The size of stack for bisection algorithms; it should probably be left at this value. The stack bumps
by 20 or 5 so it is actually not that large. The bisect stack size is large enough and seldom goes
above 1000 anyway. There is some 31 mentioned in the explanations and 30 times 20 is indeed 1500.

*/

# define bisect_int_packets      20
# define bisect_int_step          5

# define bisect_stack_size     1500 /* so a mulitple of 20 */

# define stack_1(A)   mp->bisect_stack[(A)]
# define stack_2(A)   mp->bisect_stack[(A)+1]
# define stack_3(A)   mp->bisect_stack[(A)+2]
# define stack_min(A) mp->bisect_stack[(A)+3]
# define stack_max(A) mp->bisect_stack[(A)+4]

# define u_packet(A) ((A)- 5)
# define v_packet(A) ((A)-10)
# define x_packet(A) ((A)-15)
# define y_packet(A) ((A)-20)

# define l_packets (mp->bisect_ptr - bisect_int_packets)
# define r_packets  mp->bisect_ptr

# define ul_packet u_packet(l_packets)
# define vl_packet v_packet(l_packets)
# define xl_packet x_packet(l_packets)
# define yl_packet y_packet(l_packets)
# define ur_packet u_packet(r_packets)
# define vr_packet v_packet(r_packets)
# define xr_packet x_packet(r_packets)
# define yr_packet y_packet(r_packets)

# define u1l stack_1(ul_packet)
# define u2l stack_2(ul_packet)
# define u3l stack_3(ul_packet)
# define v1l stack_1(vl_packet)
# define v2l stack_2(vl_packet)
# define v3l stack_3(vl_packet)
# define x1l stack_1(xl_packet)
# define x2l stack_2(xl_packet)
# define x3l stack_3(xl_packet)
# define y1l stack_1(yl_packet)
# define y2l stack_2(yl_packet)
# define y3l stack_3(yl_packet)
# define u1r stack_1(ur_packet)
# define u2r stack_2(ur_packet)
# define u3r stack_3(ur_packet)
# define v1r stack_1(vr_packet)
# define v2r stack_2(vr_packet)
# define v3r stack_3(vr_packet)
# define x1r stack_1(xr_packet)
# define x2r stack_2(xr_packet)
# define x3r stack_3(xr_packet)
# define y1r stack_1(yr_packet)
# define y2r stack_2(yr_packet)
# define y3r stack_3(yr_packet)

# define stack_dx  mp->bisect_stack[mp->bisect_ptr    ]
# define stack_dy  mp->bisect_stack[mp->bisect_ptr + 1]
# define stack_tol mp->bisect_stack[mp->bisect_ptr + 2]
# define stack_uv  mp->bisect_stack[mp->bisect_ptr + 3]
# define stack_xy  mp->bisect_stack[mp->bisect_ptr + 4]

# define bisect_int_increment (bisect_int_packets + bisect_int_packets + bisect_int_step)

/*tex

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).

*/

# define mp_max_patience 5000

/*tex

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.

*/

# define half(A) ((A)/2)

/*tex

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.

We shall assume that the coordinates are sufficiently non-extreme that integer overflow will not
occur.

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|.

*/

# define mp_intersection_run_shift 8

# define mp_get_indep_scale(A)   ((mp_value_node) (A))->data.indep.scale
# define mp_set_indep_scale(A,B) ((mp_value_node) (A))->data.indep.scale = (B)
# define mp_get_indep_value(A)   ((mp_value_node) (A))->data.indep.serial
# define mp_set_indep_value(A,B) ((mp_value_node) (A))->data.indep.serial = (B)

/*tex

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) = ?| (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) = ?| (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.

*/

# define mp_get_dep_value(A)   ((mp_value_node) (A))->data.n
# define mp_get_dep_list(A)    ((mp_value_node) (A))->attr_head
# define mp_get_prev_dep(A)    ((mp_value_node) (A))->subscr_head
# define mp_get_dep_info(A)    ((mp_node) ((mp_value_node) (A))->parent)
# define mp_set_dep_value(A,B) do_set_dep_value(mp,(A),&(B))
# define mp_set_dep_list(A,B)  ((mp_value_node) (A))->attr_head = (mp_node) (B)
# define mp_set_prev_dep(A,B)  ((mp_value_node) (A))->subscr_head = (mp_node) (B)
# define mp_set_dep_info(A,B)  ((mp_value_node) (A))->parent = (mp_node) (B)

/*tex

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 |mp_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.

*/

# define mp_independent_needing_fix 0

/*tex

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.

*/

# define mp_fraction_threshold_k      mp->math->md_fraction_threshold_t
# define mp_half_fraction_threshold_k mp->math->md_half_fraction_threshold_t
# define mp_scaled_threshold_k        mp->math->md_scaled_threshold_t
# define mp_half_scaled_threshold_k   mp->math->md_half_scaled_threshold_t
# define mp_p_over_v_threshold_k      mp->math->md_p_over_v_threshold_t

# define mp_independent_being_fixed   1

/*tex

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|.

*/

# define two_to_the(A) (mp_scaled_t) (1 << (unsigned) (A))

# define cur_cmd               mp->cur_mod_->command
# define cur_mod               (int) mp_number_to_scaled(mp->cur_mod_->data.n)
# define cur_mod_number        mp->cur_mod_->data.n
# define cur_mod_node          mp->cur_mod_->data.node
# define cur_mod_str           mp->cur_mod_->data.str
# define cur_sym               mp->cur_mod_->data.sym
# define cur_sym_mod           mp->cur_mod_->name_type

# define set_cur_cmd(A)        mp->cur_mod_->command = (A)
# define set_cur_mod(A)        mp_set_number_from_scaled(mp->cur_mod_->data.n, (A))
# define set_cur_mod_number(A) mp_number_clone(mp->cur_mod_->data.n, (A))
# define set_cur_mod_node(A)   mp->cur_mod_->data.node = (A)
# define set_cur_mod_str(A)    mp->cur_mod_->data.str = (A)
# define set_cur_sym(A)        mp->cur_mod_->data.sym = (A)
# define set_cur_sym_mod(A)    mp->cur_mod_->name_type = (A)

/*tex

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:

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:

*/

# define mp_input_index    mp->cur_input.index_field
# define mp_input_start    mp->cur_input.start_field
# define mp_input_limit    mp->cur_input.limit_field
# define mp_input_name     mp->cur_input.name_field
# define mp_input_location mp->cur_input.loc_field

/*tex

Let's look more closely now at the five control variables (|index|, |start|, |loc|, |mp_input_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 |mp_input_limit| is the location of the last character present. We
always have |loc <= mp_input_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 |mp_input_from_terminal..mp_input_last_special| indicate other sources of input
text.

*/

# define mp_input_from_terminal (mp_string) 0
# define mp_input_from_file     (mp_string) 1
# define mp_input_from_tokens   (mp_string) 2

# define mp_input_last_special mp_input_from_tokens

/*tex

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_files[index]|. We use the notation
|terminal_input| as a convenient abbreviation for |inputname = mp_input_from_terminal|, and |input_files| as
an abbreviation for |input_files[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 \quote {|page_stack:array [0 ..
max_in_open] of integer|} by analogy with |line_stack|.

*/

# define mp_input_file mp->input_files[mp_input_index]
# define mp_input_line mp->input_lines[mp_input_index]

/*tex

This has to be more than |file_bottom|, so:

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 |mp_input_index > max_in_open|, and the conventions about the other state variables are
different:

\startitemize

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |token_type|, which takes the place of |mp_input_index| in the discussion above, is a code number that
    explains what kind of token list is being scanned.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |parameter_start|, which takes the place of |mp_input_limit|, tells where the parameters of the current
    macro or loop text begin in the |parameter_stack|.
\stopitem

\stopitemize

The |token_type| can take several values, depending on where the current token list came from:

\startitemize

\startitem
    |forever_text|, if the token list being scanned is the body of a |forever| loop;
\stopitem

\startitem
    |loop_text|, if the token list being scanned is the body of a |for| or |forsuffixes| loop;
\stopitem

\startitem
    |parameter|, if a |text| or |suffix| parameter is being scanned;
\stopitem

\startitem
    |backed_up|, if the token list being scanned has been inserted as `to be read again'.
\stopitem

\startitem
    |inserted|, if the token list being scanned has been inserted as part of error recovery;
\stopitem

\startitem
    |macro|, if the expansion of a user-defined symbolic token is being scanned.
\stopitem

\stopitemize

The token list begins with a reference count if and only if |token_type= macro|.

*/

# define nloc   mp->cur_input.nloc_field
# define nstart mp->cur_input.nstart_field

# define token_type      mp_input_index
# define token_state     (mp_input_index <= mp_macro_text)
# define file_state      (mp_input_index >  mp_macro_text)
# define parameter_start mp_input_limit

/*tex

In a construction like \quote {|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.

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.

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:

\startitemize

\startitem
|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.
\stopitem

\startitem
|loop_ptr.type = MP_VOID| means that the current loop is |forever|.
\stopitem

\startitem
|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.
\stopitem

\startitem
|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.
\stopitem

\stopitemize

*/

# define MP_VOID             (mp_node) (1)
# define MP_PROGRESSION_FLAG (mp_node) (2)

/*tex  MOVE

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 |scan_primary|, |scan_secondary|,
|scan_tertiary|, and |scan_expression|.

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.

*/

# define cur_exp_value_number  mp->cur_exp.data.n
# define cur_exp_value_boolean mp_number_to_int(mp->cur_exp.data.n)
# define cur_exp_node          mp->cur_exp.data.node
# define cur_exp_str           mp->cur_exp.data.str
# define cur_exp_knot          mp->cur_exp.data.p
# define cur_exp_type          mp->cur_exp.type

/*tex MOVE

The next procedure, |scan_tertiary|, is pretty much the same deal.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.The reader should review the data structure conventions for paths before hoping to understand
the next part of this code.

*/

# define mp_min_tension mp_three_quarter_unit_t

/* MOVE

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.

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. 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.

The function |an_angle| returns the value of the |angle| primitive, or $0$ if the argument is
|origin|.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.

*/

# define mp_bezier_error (720*(256*256*16)) + 1

# define mp_floor(a)  ((a) >= 0 ? (int) (a) : -(int) (-(a)))
# define mp_sign(v)   ((v) > 0 ? 1 : ((v) < 0 ? -1 : 0 ))
# define mp_out(A)    (double)((A)/16)

# define p_nextnext mp_next_knot(mp_next_knot(p))
# define p_next     mp_next_knot(p)

/*tex  MOVE

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.

*/

# define mp_equation_threshold_t mp->math->md_equation_threshold_t

/*tex  MOVE

We have all kind of with variants.We use enums so that it looks better in the editor:The |addto|
command needs the following additional primitives:

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.Forcing the color to be between |0|
and |unity| here guarantees that no picture will ever contain a color outside the legal range for
\POSTSCRIPT\ graphics.

*/

# define make_cp_a_colored_object(cp,p) do { \
    cp = p; \
    while (cp != NULL) { \
        if (mp_has_color(cp)) { \
            break; \
        } else { \
            cp = cp->link; \
        } \
    } \
} while (0)

# define set_color_val(A,B) do { \
    if (mp_number_negative(A)) { \
        mp_set_number_to_zero(A); \
    } else if (mp_number_greater(A,mp_unity_t)) { \
        mp_set_number_to_unity(A); \
    } else { \
        mp_number_clone(A, (B)); \
    } \
} while (0) \

/*tex  MOVE

The |everyjob| command simply assigns a nonzero value to the global variable |every_job_sym|.

*/

/*tex  MOVE

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.

To print |scaled| value to PDF output we need some subroutines to ensure accuracy.

*/

# define mp_max_integer 0x7FFFFFFF

/*tex MOVE

Shipping pictures out.

The |ship_out| procedure, to be described below, is given a pointer to an edge structure.
Originally the output was targeted at \POSTSCRIPT\ but the library has no backend. It provides 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 out figure because the  backend
can do that, but we keep the number.

*/

# define mp_knotstate(A)      (A)->state

# define graphic_next_knot(A)      (A)->next
# define graphic_originator(A)     (A)->originator
# define graphic_type(A)           (A)->type
# define graphic_link(A)           (A)->next
# define graphic_color_model(A)    (A)->color_model
# define graphic_red_val(A)        (A)->color.red
# define graphic_green_val(A)      (A)->color.green
# define graphic_blue_val(A)       (A)->color.blue
# define graphic_cyan_val(A)       (A)->color.cyan
# define graphic_magenta_val(A)    (A)->color.magenta
# define graphic_yellow_val(A)     (A)->color.yellow
# define graphic_black_val(A)      (A)->color.black
# define graphic_grey_val(A)       (A)->color.gray
# define graphic_path_ptr(A)       (A)->path
# define graphic_htap_ptr(A)       (A)->htap
# define graphic_pen_ptr(A)        (A)->pen
# define graphic_linejoin_val(A)   (A)->linejoin
# define graphic_linecap_val(A)    (A)->linecap
# define graphic_stacking_val(A)   (A)->stacking
# define graphic_bytemap_val(A)    (A)->bytemap
# define graphic_bytemap_nx_val(A) (A)->bytemapnx
# define graphic_bytemap_ny_val(A) (A)->bytemapny
# define graphic_bytemap_nz_val(A) (A)->bytemapnz
# define graphic_miterlimit_val(A) (A)->miterlimit
# define graphic_curvature_val(A)  (A)->curvature
# define graphic_mesh_val(A)       (A)->mesh
# define graphic_pre_script(A)     (A)->pre_script
# define graphic_post_script(A)    (A)->post_script
# define graphic_pre_length(A)     (A)->pre_length
# define graphic_post_length(A)    (A)->post_length
# define graphic_dash_ptr(A)       (A)->dash

# define mp_graphic_export_color(q,p) \
if (mp_color_model(p) == mp_uninitialized_model) { \
    graphic_color_model(q) = (unsigned char) (mp_number_to_scaled(internal_value(mp_default_color_model_internal))/65536); \
    graphic_cyan_val(q)    = 0; \
    graphic_magenta_val(q) = 0; \
    graphic_yellow_val(q)  = 0; \
    graphic_black_val(q)   = graphic_color_model(q) == mp_cmyk_model ? (mp_number_to_scaled(mp_unity_t)/65536.0) : 0; \
} else { \
    graphic_color_model(q) = (unsigned char) mp_color_model(p); \
    graphic_cyan_val(q)    = mp_number_to_double(p->cyan); \
    graphic_magenta_val(q) = mp_number_to_double(p->magenta); \
    graphic_yellow_val(q)  = mp_number_to_double(p->yellow); \
    graphic_black_val(q)   = mp_number_to_double(p->black); \
}

# define mp_graphic_export_scripts(q,p) \
if (mp_pre_script (p)) { \
    graphic_pre_script(q) = mp_strndup((const char *) mp_pre_script(p)->str, mp_pre_script(p)->len); \
    graphic_pre_length(q) = mp_pre_script(p)->len; \
} \
if (mp_post_script(p)) { \
    graphic_post_script(q) = mp_strndup((const char *) mp_post_script(p)->str, mp_post_script(p)->len); \
    graphic_post_length(q) = mp_post_script(p)->len; \
}

/*tex Declarations, most can go but we will split in modules anyway so ... */

MP_options  *mp_options      (void);
MP           mp_initialize   (MP_options * opt);

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 void  mp_run_status   (MP mp);

static char *mp_make_text    (MP mp, const char *str, size_t len, int mode);

/*tex

    These print functions are the main ones. We try to use the format variant when possible,
    although, unlike in \TEX\ we don't save much but that might change as we add more tracing
    options. It's a stepwise process to introduce this.

*/

static void                mp_print_char                  (MP mp, unsigned char k);
static void                mp_print_string_length         (MP mp, const char *s, size_t len);
static void                mp_print_string                (MP mp, const char *s);
static void                mp_print_flush_line            (MP mp);
static void                mp_print_format                (MP mp, const char *format, ...);

static void                mp_begin_diagnostic            (MP mp);
static void                mp_begin_diagnostic_print      (MP mp, const char *s, const char *t, int nuline);
static void                mp_end_diagnostic              (MP mp, int blank_line);

static void                mp_print_path                  (MP mp, mp_knot h, const char *s, int nuline); // diagnostic
static void                mp_print_pen                   (MP mp, mp_knot h, const char *s, int nuline); // diagnostic
static void                mp_print_edges                 (MP mp, mp_node h, const char *s, int nuline); // diagnostic

# define                   mp_print_number(mp,n)          mp->math->md_print(mp,&(n))

static void                mp_print_nl                    (MP mp, const char *s);
static void                mp_print_ln                    (MP mp);
static void                mp_print_mp_string             (MP mp, mp_string s);
static void                mp_print_type                  (MP mp, int t);
static void                mp_print_capsule               (MP mp, mp_node p);
static void                mp_print_variable_name         (MP mp, mp_node p);
static void                mp_print_dp                    (MP mp, int t, mp_value_node p, int verbosity);
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);

static void                mp_print_path_only             (MP mp, mp_knot h);
static void                mp_print_pen_only              (MP mp, mp_knot h);

static void                mp_print_macro_name            (MP mp, mp_node a, mp_symbol n);
static void                mp_print_argument              (MP mp, mp_node q, int n, int b, int bb);
static void                mp_print_cmd_mod               (MP mp, int c, int m);
static void                mp_print_obj_color             (MP mp, mp_node p);
static void                mp_print_dependency            (MP mp, mp_value_node p, int t);

/* */

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);

static void                mp_get_next                    (MP mp);

//     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_dash_node              (MP mp, mp_dash_node p);
static void                mp_free_value_node             (MP mp, mp_node p);
static void                mp_free_token_node             (MP mp, mp_node p);

static void                mp_flush_token_list            (MP mp, mp_node p);

static int                 mp_compare_symbols_entry       (void *p, const void *pa, const void *pb);
static mp_symbol           mp_new_symbols_entry           (MP mp, unsigned char *nam, size_t len);

static void                mp_fix_date_and_time           (MP mp);

/*tex
    Testing with as static mp_number actually gives a good idea where these numbers can get adapted
    when passed around. Sometimes it happens as side effect so we might look intio that one day.
*/

static inline void         mp_do_set_value_sym            (MP mp, mp_token_node A, mp_symbol B);
static inline void         mp_do_set_value_number         (MP mp, mp_token_node A, const mp_number *B);
static inline void         mp_do_set_value_str            (MP mp, mp_token_node A, mp_string B);
static inline void         mp_do_set_value_node           (MP mp, mp_token_node A, mp_node B);
static inline void         mp_do_set_value_knot           (MP mp, mp_token_node A, mp_knot B);
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);

static mp_node             mp_new_value_node              (MP mp);
static void                mp_flush_cur_exp               (MP mp, mp_value v);
static void                mp_flush_below_variable        (MP mp, mp_node p);
static mp_knot             mp_new_knot                    (MP mp);
static void                mp_toss_knot_list              (MP mp, mp_knot p);
static void                mp_free_knot                   (MP mp, mp_knot p);
static void                mp_reallocate_paths            (MP mp, int l);
static void                mp_solve_choices               (MP mp, mp_knot p, mp_knot q, int n);
static void                mp_reduce_angle                (MP mp, mp_number *a);
static void                mp_curl_ratio                  (MP mp, mp_number *ret, mp_number *gamma, mp_number *a_tension, mp_number *b_tension);
static void                mp_set_controls                (MP mp, mp_knot p, mp_knot q, int k);
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);
static mp_knot             mp_convex_hull                 (MP mp, mp_knot h);
void                       mp_simplify_path               (MP mp, mp_knot h);
static void                mp_move_knot                   (MP mp, mp_knot p, mp_knot q);
static void                mp_sqrt_det                    (MP mp, mp_number *ret, const mp_number *a_orig, const mp_number *b_orig, const mp_number *c_orig, const mp_number *d_orig);
static void                mp_flush_dash_list             (MP mp, mp_edge_header_node h);
static void                mp_toss_edges                  (MP mp, mp_edge_header_node h);
static mp_edge_header_node mp_copy_objects                (MP mp, mp_node p, mp_node q);
static void                mp_dash_offset                 (MP mp, mp_number *x, mp_dash_node h);
static void                mp_x_retrace_error             (MP mp);
static void                mp_set_bbox                    (MP mp, mp_edge_header_node h, int top_level);
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);
static void                mp_remove_cubic                (MP mp, mp_knot p);
static mp_knot             mp_pen_walk                    (MP mp, mp_knot w, int k);
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);
static int                 mp_get_turn_amt                (MP mp, mp_knot w, mp_number *dx, mp_number *dy, int ccw);
static mp_knot             mp_insert_knot                 (MP mp, mp_knot q, mp_number *x, mp_number *y);
static void                mp_set_min_max                 (MP mp, int v);
static void                mp_new_dep                     (MP mp, mp_node q, mp_variable_type newtype, mp_value_node p, int where);
static void                mp_new_indep                   (MP mp, mp_node p, int where);
static inline void         do_set_dep_value               (MP mp, mp_value_node p, mp_number *q);
static void                mp_free_dep_node               (MP mp, mp_value_node p, int where);
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);
static mp_value_node       mp_p_over_v                    (MP mp, mp_value_node p, mp_number *v, int t0, int t1);
static void                mp_val_too_big                 (MP mp, mp_number *x);
static void                mp_make_known                  (MP mp, mp_value_node p, mp_value_node q);
static void                mp_fix_dependencies            (MP mp);
static void                mp_ring_delete                 (MP mp, mp_node p);
static void                mp_exclaim_redundant_equation  (MP mp);
static const char         *mp_cmd_mod_string              (MP mp, int c, int m);
static void                mp_reallocate_input_stack      (MP mp, int newsize);
static int                 mp_true_line                   (MP mp);
static void                mp_push_input                  (MP mp);
static void                mp_pop_input                   (MP mp);
static void                mp_back_input                  (MP mp);
static void                mp_back_error                  (MP mp, const char *msg, const char *hlp) ;
static void                mp_runaway                     (MP mp);
static void                mp_firm_up_the_line            (MP mp);
static int                 mp_move_to_next_line           (MP mp);

static void                mp_flush_token_pool            (MP mp);
static void                mp_flush_pair_pool             (MP mp);
static void                mp_flush_color_pool            (MP mp);
static void                mp_flush_transform_pool        (MP mp);
static void                mp_flush_dash_pool             (MP mp);
static void                mp_flush_shape_pool            (MP mp);
static void                mp_flush_knot_pool             (MP mp);
static void                mp_flush_start_pool            (MP mp);
static void                mp_flush_stop_pool             (MP mp);
static void                mp_flush_value_pool            (MP mp);
static void                mp_flush_symbolic_pool         (MP mp);
static void                mp_flush_save_pool             (MP mp);
static void                mp_flush_if_pool               (MP mp);
static void                mp_flush_loop_pool             (MP mp);
static void                mp_flush_subst_pool            (MP mp);
static void                mp_flush_edge_object_pool      (MP mp);
static void                mp_flush_edge_header_pool      (MP mp);
static void                mp_flush_dash_object_pool      (MP mp);
static void                mp_flush_knot_object_pool      (MP mp);
static void                mp_flush_shape_object_pool     (MP mp);
static void                mp_flush_start_object_pool     (MP mp);
static void                mp_flush_stop_object_pool      (MP mp);

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_scan_text_arg               (MP mp, mp_symbol l_delim, mp_symbol r_delim);

static void                mp_push_condition_stack        (MP mp);
static void                mp_pop_condition_stack         (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);
static void                mp_check_script_result         (MP mp, char *s);
static void                mp_get_x_next                  (MP mp);
static void                mp_macro_call                  (MP mp, mp_node def_ref, mp_node arg_list, mp_symbol macro_name);
static void                mp_begin_name                  (MP mp);
static int                 mp_more_name                   (MP mp, unsigned char c);
static void                mp_end_name                    (MP mp);

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);

static mp_node             mp_stash_cur_exp               (MP mp);
static void                mp_unstash_cur_exp             (MP mp, mp_node p);
static void                mp_display_error               (MP mp, mp_node p);
static void                mp_recycle_value               (MP mp, mp_node p);
static void                mp_recycle_dependent_value     (MP mp, mp_node p);
static void                mp_recycle_independent_value   (MP mp, mp_node p);
static void                mp_show_transformed_dependency (MP mp, mp_number *v, mp_variable_type t, mp_node p);
static void                mp_known_pair                  (MP mp);
static void                mp_do_boolean_error            (MP mp);
static void                mp_push_of_path_result         (MP mp, int what, mp_knot p, mp_number i, mp_number n);
static mp_knot             mp_simple_int_knot             (MP mp, int x, int y);
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);
static int                 mp_pict_color_type             (MP mp, int c);
static void                mp_bad_color_part              (MP mp, int c);
static mp_edge_header_node mp_scale_edges                 (MP mp, mp_number *se_sf, mp_edge_header_node se_pic);
static void                mp_path_length                 (MP mp, mp_number *n);
static void                mp_path_no_length              (MP mp, mp_number *n);
static void                mp_pair_value                  (MP mp, mp_number *x, mp_number *y);

/*tex declare action procedures for use by |do_statement| */

static void                mp_do_type_declaration         (MP mp);
static void                mp_do_max_knot_pool            (MP mp);
static void                mp_do_random_seed              (MP mp);
static void                mp_do_protection               (MP mp);
static void                mp_do_property                 (MP mp);
static void                mp_do_delimiters               (MP mp);
static void                mp_do_statement                (MP mp);
static void                mp_do_interim                  (MP mp);
static void                mp_do_let                      (MP mp);
static void                mp_do_show                     (MP mp);
static void                mp_display_token               (MP mp);
static void                mp_do_show_token               (MP mp);
static void                mp_do_show_stats               (MP mp);
static void                mp_display_var                 (MP mp, mp_node p);
static void                mp_do_show_var                 (MP mp);
static void                mp_do_show_dependencies        (MP mp);
static void                mp_do_show_whatever            (MP mp);
static void                mp_scan_with_list              (MP mp, mp_node p, mp_node pp);
static mp_edge_header_node mp_find_edges_var              (MP mp, mp_node t);
static mp_node             mp_start_draw_cmd              (MP mp, int sep);
static void                mp_do_bounds                   (MP mp);
static void                mp_do_add_to                   (MP mp);

/*tex declare the output procedures */

static void                mp_ship_out                    (MP mp, mp_node h);
static void                mp_do_ship_out                 (MP mp);
static void                mp_do_message                  (MP mp);
static void                mp_do_write                    (MP mp);
static void                mp_do_write_string             (MP mp, mp_string t);

/*tex declare the procedure called |make_eq| */

static void                mp_make_eq                     (MP mp, mp_node lhs);
static void                mp_do_equation                 (MP mp);
static void                mp_do_assignment               (MP mp);
static void                mp_try_eq                      (MP mp, mp_node l, mp_node r);
static mp_node             mp_scan_declared_variable      (MP mp);
static void                mp_check_delimiter             (MP mp, mp_symbol l_delim, mp_symbol r_delim);
static void                mp_do_new_internal             (MP mp);

static void                mp_bytemap_new                 (MP mp);
static void                mp_bytemap_copy                (MP mp);
static void                mp_bytemap_set                 (MP mp);
static void                mp_bytemap_reset               (MP mp);
static void                mp_bytemap_reset_all           (MP mp);
static void                mp_bytemap_set_options         (MP mp);
static void                mp_bytemap_clip                (MP mp);
static void                mp_bytemap_reduce              (MP mp);
static void                mp_bytemap_path                (MP mp, mp_node p, int c);
static void                mp_bytemap_bounds              (MP mp, mp_node p, int c, int clip);
static void                mp_bytemap_found               (MP mp, mp_node p, int c);
static void                mp_bytemap_value               (MP mp, mp_node p, int c);
static void                mp_bytemap_set_byte            (MP mp);
static void                mp_bytemap_set_offset          (MP mp);
static int                 mp_bytemap_valid               (MP mp, int index);
static int                 mp_bytemap_valid_data          (MP mp, int index);
static char               *mp_bytemap_get_value           (MP mp, int index, int *nx, int *ny, int *nz);
static int                 mp_aux_weighted                (int r, int g, int b);
/*     void                mp_clear_color                 (MP mp, void *n); */

static void                mp_shipout_backend             (MP mp, void *h);
static void                mp_close_files                 (MP mp);
static void                mp_close_files_and_terminate   (MP mp);
static void                mp_final_cleanup               (MP mp);

/*tex

    The format function accepts these specifiers:

    %% : percent

    %l : flush line when there is content
    %s : C string
    %q : C string quoted
    %i : integer
    %b : boolean (TODO)

    %B : MP big mode (TODO)
    %C : MP command, operation (mod)
    %N : MP number
    %O : MP operator (TODO)
    %P : MP pointer (for capsule)
    %Q : MP string quoted
    %S : MP string
    %T : MP type (TODO)
    %V : MP variable name (TODO)

    \n : newline
    \r : newline
    \0 : finish

*/

/* todo: as in tex, use %h for last help */

static inline void mp_print_format_args(MP mp, const char *format, va_list args)
{
    while (1) {
        int chr = *format++;
        switch (chr) {
            case '\0':
                return;
            case '%':
                {
                    chr = *format++;
                    switch (chr) {
                        case '\0':
                            return;
                        case 'c':
                            mp_print_char(mp, (unsigned char) va_arg(args, int));
                            break;
                        case 'i':
                            {
                                char s[12];
                                snprintf(s, 12, "%d", (int) va_arg(args, int));
                                mp_print_string_length(mp, s, strlen(s));
                                break;
                            }
                        case 's':
                            {
                                const char *s = va_arg(args, char *);
                                mp_print_string_length(mp, s, strlen(s));
                                break;
                            }
                        case 'q':
                            {
                                const char *s = va_arg(args, char *);
                                mp_print_char(mp, '\"'); /* ' in tex */
                             // mp_print_str(mp, va_arg(args, char *));
                                mp_print_string_length(mp, s, strlen(s));
                                mp_print_char(mp, '\"'); /* ' in tex */
                                break;
                            }
                        case 'l':
                            mp_print_flush_line(mp);
                            break;
                        case 'N':
                            {
                                mp_number n;
                                n = va_arg(args, mp_number);
                                mp_print_number(mp, n);
                                break;
                            }
                        case 'P': /* pointer, for capsule */
                            {
                                char s[16];
                                snprintf(s, 16, "%p", va_arg(args, void *));
                                mp_print_string_length(mp, s, strlen(s));
                                break;
                            }
                        case 'C':
                            {
                                int cmd = va_arg(args, int);
                                int mod = va_arg(args, int);
                                const char *s = mp_cmd_mod_string(mp, cmd, mod);
                                mp_print_string_length(mp, s, strlen(s));
                             // mp_print_str(mp, mp_cmd_mod_string(mp, cmd, mod));
                                break;
                            }
                        case 'S':
                            {
                                mp_string s = va_arg(args, mp_string);
                                mp_print_string_length(mp, (const char *) s->str, s->len);
                                break;
                            }
                        case 'Q':
                            {
                                mp_string s = va_arg(args, mp_string);
                                mp_print_char(mp, '\"'); /* ' in tex */
                                mp_print_string_length(mp, (const char *) s->str, s->len);
                                mp_print_char(mp, '\"'); /* ' in tex */
                                break;
                            }
                        case '%':
                            mp_print_char(mp, '%');
                            break;
                        default:
                            /* ignore bad one */
                            break;
                    }
                }
                break;
            case '\n':
            case '\r':
                mp_print_ln(mp);
                break;
            default:
                mp_print_char(mp, (unsigned char) chr); /* todo: utf */
                break;
        }
    }
}

void mp_print_format(MP mp, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    mp_print_format_args(mp, format, args);
    va_end(args);
}

/*tex

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.

*/

static void mp_normalize_selector(MP mp)
{
    mp->selector = mp->interaction == mp_batch_mode ? mp_log_only_selector : mp_term_and_log_selector;
}

/*tex  error handling procedures */

static void mp_jump_out(MP mp)
{
    if (mp->internal != NULL && mp->history < mp_system_error_stop) {
        mp_close_files_and_terminate(mp);
    }
    longjmp(*(mp->jump_buffer), 1);
}

void mp_warn(MP mp, const char *msg)
{
    int selector = mp->selector;
    mp_normalize_selector(mp);
    mp_print_nl(mp, "Warning: ");
    mp_print_string(mp, msg);
    mp_print_ln(mp);
    mp->selector = selector;
}

/*tex

The following procedure prints \MP's last words before dying. 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.Consistency check violated; |s| tells
where.

*/

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);
}

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) {
        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";
    } 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.";
        snprintf(msg, 256, "I can't go on meeting you like this");
    }
    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);
}

MP_options *mp_options(void)
{
    return (MP_options *) mp_memory_clear_allocate(sizeof(MP_options));
}

/*tex

The whole instance structure is initialized with zeroes, this greatly reduces the number of
statements needed in the |Allocate or initialize variables| block.

*/

static MP mp_new_instance(void)
{
    jmp_buf *buf = mp_memory_allocate(sizeof(jmp_buf));
    if (buf == NULL || setjmp(*buf) != 0) {
        return NULL;
    } else {
        MP mp = mp_memory_clear_allocate(sizeof(MP_instance));
        if (mp) {
            mp->jump_buffer = buf;
        } else {
            mp_memory_free(buf);
        }
        return mp;
    }
}

static void mp_free_instance(MP mp)
{
    /* Close callback-owned handles before releasing the instance state. */
    mp_close_files(mp);
    mp_memory_free(mp->banner);
    mp_memory_free(mp->buffer);
    mp_free_strings(mp);
    for (int i = 0; i < 55; i++) {
        mp_free_number(mp->randoms[i]);
    }
    /*tex
        We keep the statistics! The flushers just do a free and don't deal with fields
        as these might have old values and pointers.
    */
    mp_flush_token_pool(mp);
    mp_flush_pair_pool(mp);
    mp_flush_color_pool(mp);
    mp_flush_transform_pool(mp);
    mp_flush_dash_pool(mp);
    mp_flush_shape_pool(mp);
    mp_flush_knot_pool(mp);
    mp_flush_start_pool(mp);
    mp_flush_stop_pool(mp);
    mp_flush_value_pool(mp);
    mp_flush_symbolic_pool(mp);
    mp_flush_save_pool(mp);
    mp_flush_if_pool(mp);
    mp_flush_loop_pool(mp);
    mp_flush_subst_pool(mp);
    mp_flush_edge_object_pool(mp);
    mp_flush_edge_header_pool(mp);
    mp_flush_dash_object_pool(mp);
    mp_flush_knot_object_pool(mp);
    mp_flush_shape_object_pool(mp);
    mp_flush_start_object_pool(mp);
    mp_flush_stop_object_pool(mp);
    /* */
    if (mp->symbols != NULL) {
        avl_destroy(mp->symbols);
    }
    if (mp->frozen_symbols != NULL) {
        avl_destroy(mp->frozen_symbols);
    }
    for (int k = 0; k<mp->path_size; k++) {
        mp_free_number(mp->delta_x[k]);
        mp_free_number(mp->delta_y[k]);
        mp_free_number(mp->delta[k]);
        mp_free_number(mp->psi[k]);
    }
    mp->bbox_nesting = 0;
    mp_memory_free(mp->delta_x);
    mp_memory_free(mp->delta_y);
    mp_memory_free(mp->delta);
    mp_memory_free(mp->psi);
    for (int k = 0; k < mp->path_size; k++) {
        mp_free_number(mp->theta[k]);
        mp_free_number(mp->uu[k]);
        mp_free_number(mp->vv[k]);
        mp_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);
    mp_free_number(mp->st);
    mp_free_number(mp->ct);
    mp_free_number(mp->sf);
    mp_free_number(mp->cf);
    for (int i = 0; i <= mp_y_code; i++) {
        mp_free_number(mp->bbmin[i]);
        mp_free_number(mp->bbmax[i]);
    }
    for (int k = 0; k <= 7; k++) {
        mp_free_number(mp->half_cos[k]);
        mp_free_number(mp->d_cos[k]);
    }
    mp_free_number(mp->cur_x);
    mp_free_number(mp->cur_y);
    for (int i = 0; i < bisect_stack_size + 1; i++) {
        mp_free_number(mp->bisect_stack[i]);
    }
    mp_memory_free(mp->bisect_stack);
    mp_free_number(mp->cur_t);
    mp_free_number(mp->cur_tt);
    mp_free_number(mp->max_t);
    mp_free_number(mp->delx);
    mp_free_number(mp->dely);
    mp_free_number(mp->appr_t);
    mp_free_number(mp->appr_tt);
    mp_memory_free(mp->input_stack);
    mp_memory_free(mp->input_files);
    mp_memory_free(mp->input_lines);
    mp_memory_free(mp->parameter_stack);
    mp_memory_free(mp->cur_name);
    mp_memory_free(mp->job_name);
    for (int i = 0; i < mp_proto_dependent_type + 1; i++) {
        mp_free_number(mp->max_c[i]);
    }
    for (int i = 0; i <= mp->memory_pool[mp_internals_pool].max; i++) {
        mp_free_number(mp->internal[i].v.data.n);
        mp_memory_free(internal_name(i));
    }
    mp_memory_free(mp->internal);
    /* */
    for (int i = 0; i < mp->memory_pool[mp_bytemaps_pool].max; i++) {
        mp_memory_free(mp->bytemaps[i].data);
    }
    mp_memory_free(mp->bytemaps);
    /* */
    if (mp->read_filenames != NULL) {
        mp_memory_free(mp->read_filehandles);
        mp_memory_free(mp->read_filenames);
        mp->read_filehandles = NULL;
        mp->read_filenames = NULL;
    }
    if (mp->write_filenames != NULL) {
        mp_memory_free(mp->write_filehandles);
        mp_memory_free(mp->write_filenames);
        mp->write_filehandles = NULL;
        mp->write_filenames = NULL;
    }
    mp_memory_free(mp->jump_buffer);
    /*tex  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);
    mp_free_value_node(mp, mp->end_attr);
    mp_free_dash_node(mp, mp->null_dash);
    mp_free_dep_node(mp, mp->dep_head, 0); /* 1 */
    mp_free_symbolic_node(mp, mp->cur_mod_);
    mp_free_value_node(mp, mp->bad_vardef);
    mp_free_number(cur_exp_value_number);
    mp_free_value_node(mp, mp->temp_val);
    mp_free_number(mp->txx);
    mp_free_number(mp->txy);
    mp_free_number(mp->tyx);
    mp_free_number(mp->tyy);
    mp_free_number(mp->tx);
    mp_free_number(mp->ty);
    mp_free_value_node(mp, mp->inf_val);
    mp_free_value_node(mp, mp->zero_val);
    mp_free_math();
    mp_memory_free(mp);
}

static void mp_do_initialize(MP mp)
{
    /*tex  set initial values of key variables */
    mp->memory_pool[mp_internals_pool].used = max_given_internal;
    for (int i = '0'; i <= '9'; i++) {
        mp->char_class[i] = mp_digit_class;
    }
    for (int i = 'A'; i <= 'Z'; i++) {
        mp->char_class[i] = mp_letter_class;
    }
    for (int i = 'a'; i <= 'z'; i++) {
        mp->char_class[i] = 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['\''] = mp_string_class; /* was 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 i = 0; i < ' '; i++) {
        mp->char_class[i] = 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 i = 127; i <= 255; i++) {
        mp->char_class[i] = 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 */
    }
    mp->save_ptr = NULL;
    /*tex
        The magic constant for |d_cos| is the distance between $({1 \over 2},0)$ and
        $({1 \over 4} \sqrt2,{1 \over 4} \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
        $$
    */
    for (int i = 0; i <= 7; i++) {
        mp_new_fraction(mp->half_cos[i]);
        mp_new_fraction(mp->d_cos[i]);
    }
    mp_number_clone(mp->half_cos[0], mp_fraction_half_t);
    mp_number_clone(mp->half_cos[1], mp_twentysixbits_sqrt2_t);
    mp_number_clone(mp->half_cos[2], mp_zero_t);
    mp_number_clone(mp->d_cos[0],    mp_twentyeightbits_d_t);
    mp_number_clone(mp->d_cos[1],    mp_twentysevenbits_sqrt2_d_t);
    mp_number_clone(mp->d_cos[2],    mp_zero_t);
    for (int i = 3; i <= 4; i++) {
        mp_number_negated_clone(mp->half_cos[i], mp->half_cos[4 - i]);
        mp_number_negated_clone(mp->d_cos[i], mp->d_cos[4 - i]);
    }
    for (int i = 5; i <= 7; i++) {
        mp_number_clone(mp->half_cos[i], mp->half_cos[8 - i]);
        mp_number_clone(mp->d_cos[i], mp->d_cos[8 - i]);
    }
    mp->spec_p1 = NULL;
    mp->spec_p2 = NULL;
    mp->fix_needed  = 0;
    mp->watch_coefs = 1;
    mp->expand_depth = 10000;
    mp->cond_ptr = NULL;
    mp->if_limit = mp_no_if_code;
    mp->cur_if   = 0;
    mp->if_line  = 0;
    mp->loop_ptr = NULL;
    mp->cur_name = mp_strdup("");
    memset(&mp->cur_exp.data, 0, sizeof(mp_value));
    mp_new_number(cur_exp_value_number);
    mp->var_flag = 0;
    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;
    mp->every_job_sym = NULL;
    mp->long_help_seen = 0;
    mp->ten_pow[0] = 1;
    for (int i = 1; i <= 9; i++) {
        mp->ten_pow[i] = 10 * mp->ten_pow[i - 1];
    }
}

int   mp_status  (MP mp) { return mp->history;  }
int   mp_finished(MP mp) { return mp->finished; }
void *mp_userdata(MP mp) { return mp->userdata; }

/*tex

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

\startitemize[n]
\startitem
    inputting and outputting of text, to or from a file or the user's terminal;
\stopitem
\startitem
    inputting and outputting of eight-bit bytes, to or from a file;
\stopitem
\startitem
    instructing the operating system to initiate \quote {open} or to terminate \quote {close} input
    or output from a specified file;
\stopitem
\startitem
    testing whether the end of an input file has been reached;
\stopitem
\startitem
    display of bits on the user's screen.
\stopitem
\stopitmize

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.

The default function for finding files is |mp_find_file|. It is pretty stupid: it will only find
files in the current directory.

*/

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");
}

/*tex

The logger has to deal with the console and the log file and gets information about the target.

*/

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");
}

/*tex

The overload catch is responsible for its own reporting and quitting if needed. The check only
happens when the mode is set.

*/

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_symbol p)
{
    /* not the fastest check */
    if (mp_number_nonzero(internal_value(mp_overloadmode_internal))) {
        if (mp->run_overload(mp, p->property, (const char *) p->text->str, (int) mp_number_to_int(internal_value(mp_overloadmode_internal)))) {
            p->property = 0;
        } else {
            /* we keep the property */
        }
    } else {
        /* we reset the mode */
        p->property = 0;
    }
}

/*tex

Error and warning handling can be delegated too. Warnings are not really used yet but they might
show up some day.

*/

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");
}

static void mp_run_status(MP mp)
{
    (void) mp;
    mp_fatal_error(mp, "no 'run_status' callback set");
}

/*tex

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).

*/

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;
}

/*tex

(Almost) all file names pass through |name_of_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.

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.

Watch out: at this moment we have |mp_find_file| as well as |open_file| and both need to be set.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.

*/

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;
}

static int mp_do_open_file(MP mp, void **f, int ftype, const char *mode)
{
    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");
}

/*tex

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.

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.

*/

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_clear_reallocate(void *p, size_t oldsize, size_t newsize)
{
    void *w = lmt_memory_recalloc(p, oldsize, newsize, 1);
    if (! w) {
        printf("mplib ran out of memory, case 4");
        exit(EXIT_FAILURE);
    }
    return w;
}

void mp_memory_free(void *p)
{
    lmt_memory_free(p);
}

/*tex

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.

*/

static void mp_reallocate_buffer(MP mp, size_t l)
{
    if (l > mp_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;
    }
}

/*tex

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] <>" "|.

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|.

*/

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_memory_free(s);
    return 1;
}

/*tex MOVE

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.The string handling functions
are in |mpstrings.w|, but strings need a bunch of globals and those are defined here in the main
file.

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:

\startitemize

\startitem
    |term_and_log|, the normal setting, prints on the terminal and on the transcript file.
\stopitem

\startitem
    |log_only|, prints only on the transcript file.
\stopitem

\startitem
    |term_only|, prints only on the terminal.
\stopitem

\startitem
    |no_print|, doesn't print at all. This is used only in rare cases before the transcript file is
    open.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |new_string|, appends the output to the current string in the string pool.
\stopitem

\startitem
    |>= first_file| prints on one of the files used for the |write| command.
\stopitem

\stopitemize

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.

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.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.

*/

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->write_filehandles[mp->selector - mp_first_file_selector]);
    }
}

/*tex

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.

*/

static void mp_print_char(MP mp, unsigned char chr)
{
    switch (mp->selector) {
        case mp_term_and_log_selector:
            mp_log_chr(mp_both_logging_target, chr);
            mp->term_offset = 1;
            mp->file_offset = 1;
            break;
        case mp_log_only_selector:
            mp_log_chr(mp_file_logging_target, chr);
            mp->file_offset = 1;
            break;
        case mp_term_only_selector:
            mp_log_chr(mp_term_logging_target, chr);
            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, chr);
            break;
        default:
            {
                unsigned char ss[2] = { chr, 0 };
                mp_fputs((char *) ss, mp->write_filehandles[mp->selector - mp_first_file_selector]);
            }
    }
}

static void mp_print_string_length(MP mp, const char *str, 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), str, len);
        mp->cur_length += len;
    } else {
        switch (mp->selector) {
            case mp_term_and_log_selector:
                mp_log_mpstr(mp_both_logging_target, str, (int) len);
                mp->term_offset = 1;
                mp->file_offset = 1;
                break;
            case mp_log_only_selector:
                mp_log_mpstr(mp_file_logging_target, str, (int) len);
                mp->file_offset = 1;
                break;
            case mp_term_only_selector:
                mp_log_mpstr(mp_term_logging_target, str, (int) len);
                mp->term_offset = 1;
                break;
            case mp_no_print_selector:
                break;
            case mp_new_string_selector:
                mp_append_str(mp, str, (int) len);
                break;
            default:
                mp_fputs(str, mp->write_filehandles[mp->selector - mp_first_file_selector]);
                break;
        }
    }
}

static void mp_print_string(MP mp, const char *str)
{
    mp_print_string_length(mp, str, strlen(str));
}

void mp_print_e_str(MP mp, const char *str)
{
    mp_print_string(mp, str);
}

static void mp_print_mp_string(MP mp, mp_string lstr)
{
    mp_print_string_length(mp, (const char *) lstr->str, lstr->len);
}

/*tex

Here is the very first thing that \MP\ prints: a headline that identifies the version number and base
name. Well, not really.

The procedure |print_nl| is like |print|, but it makes sure that the string appears at the beginning
of a new line.

*/

static void mp_print_flush_line(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 *str)
{
    mp_print_flush_line(mp);
    mp_print_string(mp, str);
}

/*tex

The global variable |interaction| has four settings, representing increasing amounts of user
interaction:

Set it here so it can be overwritten by the command line \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

\startitemize

\startitem
    |no_print| (when |interaction=mp_batch_mode| and |log_file| not yet open);
\stopitem
\startitem
    |term_only| (when |interaction>mp_batch_mode| and |log_file| not yet open);
\stopitem
\startitem
    |log_only| (when |interaction=mp_batch_mode| and |log_file| is open);
\stopitem
\startitem
    |term_and_log| (when |interaction>mp_batch_mode| and |log_file| is open).
\stopitem

\stopitemize

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.

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 is never more than two levels deep.

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. If the array of internals is still |NULL| when |jump_out| is called, a crash will occur
during initialization, and it is not safe to run the normal cleanup 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.

*/

/* Todo: use print_format here too. */

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);
    }
    if (++mp->error_count == 100) {
        mp_print_nl(mp, "(That makes 100 errors; please try again.)");
        mp->history = mp_fatal_error_stop;
        mp_jump_out(mp);
    }
    mp->selector = selector;
}

/*tex

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
|arithmic_error| to |true| instead of reporting errors directly to the user.

At crucial points the program will say |mp_check_arithmic|, to test if an arithmetic error has been
detected.

*/

int mp_error_code(MP mp, int n)
{
    if (0) { /* can be an internal, for debugging */
        mp_begin_diagnostic_print(mp, "arithmic", "error", 1);
        mp_print_format(mp, " mode %i, code %i", mp->math_mode, n);
        mp_end_diagnostic(mp, 1);
    }
    return n;
}

static void mp_check_arithmic(MP mp)
{
    if (mp->arithmic_error) {
        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."
        );
        mp->arithmic_error = 0;
    }
}

/*tex

In fact, the two sorts of scaling discussed above aren't quite sufficient; \MP\ has yet another, used
internally to keep track of angles.

*/

/*tex

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.

*/

void mp_new_randoms(MP mp)
{
    mp_number accumulator;
    mp_new_number(accumulator);
    for (int i = 0; i <= 23; i++) {
        mp_set_number_from_subtraction(accumulator, mp->randoms[i], mp->randoms[i + 31]);
        if (mp_number_negative(accumulator)) {
            mp_number_add(accumulator, mp_fraction_one_t);
        }
        mp_number_clone(mp->randoms[i], accumulator);
    }
    for (int i = 24; i <= 54; i++) {
        mp_set_number_from_subtraction(accumulator, mp->randoms[i], mp->randoms[i - 24]);
        if (mp_number_negative(accumulator)) {
            mp_number_add(accumulator, mp_fraction_one_t);
        }
        mp_number_clone(mp->randoms[i], accumulator);
    }
    mp_free_number(accumulator);
    mp->j_random = 54;
}

/*tex

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.

*/

# 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_symbol)(B)

/*tex

    The function |get_symbolic_node| returns a pointer to a new symbolic node whose |link| field is
    null. Watch out, this is not to be confused with |mp_symbol|!

*/

static mp_node mp_new_symbolic_node(MP mp)
{
    mp_symbolic_node p = mp->memory_pool[mp_symbolic_pool].list;
    mp->memory_pool[mp_symbolic_pool].used++;
    if (mp->memory_pool[mp_symbolic_pool].used > mp->memory_pool[mp_symbolic_pool].max) {
        mp->memory_pool[mp_symbolic_pool].max = mp->memory_pool[mp_symbolic_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_symbolic_pool].list = p->link;
        mp->memory_pool[mp_symbolic_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_node_data)); /* !! */
    }
    mp_new_number(p->data.n);
    p->link = NULL;
    p->type = mp_symbol_node_type;
    p->name_type = mp_normal_operation;
    return (mp_node) p;
}

static void mp_free_symbolic_node(MP mp, mp_node p)
{
    if (p) {
        if (mp->math_mode > mp_math_double_mode) { /* no need when posit so we can change the order */
            mp_free_number(((mp_value_node) p)->data.n);
        }
        mp->memory_pool[mp_symbolic_pool].used--;
        if (mp->memory_pool[mp_symbolic_pool].pool < mp->memory_pool[mp_symbolic_pool].kept) {
            mp->memory_pool[mp_symbolic_pool].pool++;
            p->link = mp->memory_pool[mp_symbolic_pool].list;
            mp->memory_pool[mp_symbolic_pool].list = p;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_symbolic_pool(MP mp)
{
    mp_symbolic_node p = mp->memory_pool[mp_symbolic_pool].list;
    while (p) {
        mp_symbolic_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex

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|. Actually, all node types now have
dedicated pool control. So, the original documentation with respect to memory allocation no longer
applies. We went from self-controlled (scaled) management to dynamic (multiple number systems) to
a more granular one. At some point we might split this large file in pieces and collect all memory
management in its own file.

Some nodes are created statically, since static allocation is more efficient than dynamic
allocation when we can get away with it. 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.

*/

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);
        }
    }
}

static const char *mp_type_string_names[] = {
    [mp_undefined_type]         = "undefined",
    [mp_vacuous_type]           = "vacuous",
    [mp_boolean_type]           = "boolean",
    [mp_unknown_boolean_type]   = "unknown boolean",
    [mp_string_type]            = "string",
    [mp_unknown_string_type]    = "unknown string",
    [mp_pen_type]               = "pen",
    [mp_unknown_pen_type]       = "unknown pen",
    [mp_nep_type]               = "pen",
    [mp_unknown_nep_type]       = "unknown pen",
    [mp_path_type]              = "path",
    [mp_unknown_path_type]      = "unknown path",
    [mp_picture_type]           = "picture",
    [mp_unknown_picture_type]   = "unknown picture",
    [mp_transform_type]         = "transform",
    [mp_color_type]             = "color",
    [mp_cmykcolor_type]         = "cmykcolor",
    [mp_pair_type]              = "pair",
    [mp_numeric_type]           = "numeric",
    [mp_known_type]             = "known numeric",
    [mp_dependent_type]         = "dependent",
    [mp_proto_dependent_type]   = "proto dependent",
    [mp_independent_type]       = "independent",
    [mp_token_list_type]        = "token list",
    [mp_structured_type]        = "mp_structured",
    [mp_unsuffixed_macro_type]  = "unsuffixed macro",
    [mp_suffixed_macro_type]    = "suffixed macro",
    [mp_symbol_node_type]       = "symbol node",
    [mp_token_node_type]        = "token node",
    [mp_value_node_type]        = "value node",
    [mp_attribute_node_type]    = "attribute node",
    [mp_subscript_node_type]    = "subscript node",
    [mp_pair_node_type]         = "pair node",
    [mp_transform_node_type]    = "transform node",
    [mp_color_node_type]        = "color node",
    [mp_cmykcolor_node_type]    = "cmykcolor node",
    [mp_fill_node_type]         = "fill node",
    [mp_stroked_node_type]      = "stroked node",
    [mp_start_clip_node_type]   = "start clip node",
    [mp_start_group_node_type]  = "start group node",
    [mp_start_bounds_node_type] = "start bounds node",
    [mp_stop_clip_node_type]    = "stop clip node",
    [mp_stop_group_node_type]   = "stop group node",
    [mp_stop_bounds_node_type]  = "stop bounds node",
    [mp_dash_node_type]         = "dash node",
    [mp_dep_node_type]          = "dependency node",
    [mp_if_node_type]           = "if node",
    [mp_edge_header_node_type]  = "edge header node",
};

static const char *mp_type_string(int t)
{
    if (t >= mp_undefined_type && t <= mp_edge_header_node_type) {
        return mp_type_string_names[t];
    } else {
        return "unknown node";
    }
}

static void mp_print_type(MP mp, int t)
{
    if (t >= mp_undefined_type && t <= mp_edge_header_node_type) {
        mp_print_string(mp, mp_type_string(t));
    } else {
        mp_print_string(mp, "unknown");
    }
}

/* This will change: just an array when we define primitives. */

static const char *mp_op_string_names[] = {
    [mp_root_operation]                = "root",
    [mp_saved_root_operation]          = "saved root",
    [mp_structured_root_operation]     = "structured root",
    [mp_subscript_operation]           = "subscript",
    [mp_attribute_operation]           = "attribute",
    [mp_x_part_operation]              = "xpart",
    [mp_y_part_operation]              = "ypart",
    [mp_z_part_operation]              = "zpart",
    [mp_w_part_operation]              = "wpart",
    [mp_xx_part_operation]             = "xxpart",
    [mp_xy_part_operation]             = "xypart",
    [mp_yx_part_operation]             = "yxpart",
    [mp_yy_part_operation]             = "yypart",
    [mp_red_part_operation]            = "redpart",
    [mp_green_part_operation]          = "greenpart",
    [mp_blue_part_operation]           = "bluepart",
    [mp_cyan_part_operation]           = "cyanpart",
    [mp_magenta_part_operation]        = "magentapart",
    [mp_yellow_part_operation]         = "yellowpart",
    [mp_black_part_operation]          = "blackpart",
    [mp_grey_part_operation]           = "greypart",
    [mp_capsule_operation]             = "capsule",
    [mp_token_operation]               = "token",
    [mp_boolean_type_operation]        = "boolean",
    [mp_string_type_operation]         = "string",
    [mp_pen_type_operation]            = "pen",
    [mp_nep_type_operation]            = "nep",
    [mp_path_type_operation]           = "path",
    [mp_picture_type_operation]        = "picture",
    [mp_transform_type_operation]      = "transform",
    [mp_color_type_operation]          = "color",
    [mp_cmykcolor_type_operation]      = "cmykcolor",
    [mp_pair_type_operation]           = "pair",
    [mp_numeric_type_operation]        = "numeric",
    [mp_normal_operation]              = "normal",
    [mp_internal_operation]            = "internal",
    [mp_macro_operation]               = "macro",
    [mp_expr_operation]                = "expr",
    [mp_suffix_operation]              = "suffix",
    [mp_text_operation]                = "text",
    [mp_true_operation]                = "true",
    [mp_false_operation]               = "false",
    [mp_null_picture_operation]        = "nullpicture",
    [mp_null_pen_operation]            = "nullpen",
    [mp_read_string_operation]         = "readstring",
    [mp_pen_circle_operation]          = "pencircle",
    [mp_normal_deviate_operation]      = "normaldeviate",
    [mp_read_from_operation]           = "readfrom",
    [mp_close_from_operation]          = "closefrom",
    [mp_odd_operation]                 = "odd",
    [mp_known_operation]               = "known",
    [mp_unknown_operation]             = "unknown",
    [mp_not_operation]                 = "not",
    [mp_decimal_operation]             = "decimal",
    [mp_reverse_operation]             = "reverse",
    [mp_uncycle_operation]             = "uncycle",
    [mp_singularity_operation]         = "singularity",
    [mp_make_path_operation]           = "makepath",
    [mp_make_pen_operation]            = "makepen",
    [mp_make_nep_operation]            = "makenep",
    [mp_convexed_operation]            = "convexed",
    [mp_uncontrolled_operation]        = "uncontrolled",
    [mp_oct_operation]                 = "oct",
    [mp_hex_operation]                 = "hex",
    [mp_ASCII_operation]               = "ASCII",
    [mp_char_operation]                = "char",
    [mp_segments_operation]            = "segments",
    [mp_length_operation]              = "length",
    [mp_prune_singularities_operation] = "prunesingularities",
    [mp_no_length_operation]           = "nolength",
    [mp_first_point_operation]         = "firstpoint",
    [mp_first_pre_control_operation]   = "firstprecontrol",
    [mp_first_post_control_operation]  = "firstpostcontrol",
    [mp_first_direction_operation]     = "firstdirection",
    [mp_first_unit_direction_operation]= "firstunitdirection",
    [mp_last_point_operation]          = "lastpoint",
    [mp_last_pre_control_operation]    = "lastprecontrol",
    [mp_last_post_control_operation]   = "lastpostcontrol",
    [mp_last_direction_operation]      = "lastdirection",
    [mp_last_unit_direction_operation] = "lastunitdirection",
    [mp_turning_operation]             = "turningnumber",
    [mp_color_model_operation]         = "colormodel",
    [mp_path_part_operation]           = "pathpart",
    [mp_pen_part_operation]            = "penpart",
    [mp_dash_part_operation]           = "dashpart",
    [mp_prescript_part_operation]      = "prescriptpart",
    [mp_postscript_part_operation]     = "postscriptpart",
    [mp_stacking_part_operation]       = "stackingpart",
    [mp_sqrt_operation]                = "sqrt",
    [mp_norm_operation]                = "knownnorm",
    [mp_m_exp_operation]               = "mexp",
    [mp_m_log_operation]               = "mlog",
    [mp_sin_d_operation]               = "sind",
    [mp_cos_d_operation]               = "cosd",
    [mp_floor_operation]               = "floor",
    [mp_normalize_operation]           = "normalize",
    [mp_uniform_deviate_operation]     = "uniformdeviate",
    [mp_ll_corner_operation]           = "llcorner",
    [mp_lr_corner_operation]           = "lrcorner",
    [mp_ul_corner_operation]           = "ulcorner",
    [mp_ur_corner_operation]           = "urcorner",
    [mp_corners_operation]             = "corners",
    [mp_center_of_operation]           = "centerof",
    [mp_center_of_mass_operation]      = "centerofmass",
    [mp_x_range_operation]             = "xrange",
    [mp_y_range_operation]             = "yrange",
    [mp_delta_point_operation]         = "deltapoint",
    [mp_delta_precontrol_operation]    = "deltaprecontrol",
    [mp_delta_postcontrol_operation]   = "deltapostcontrol",
    [mp_delta_direction_operation]     = "deltadirection",
    [mp_delta_unit_direction_operation]= "deltaunitdirection",
    [mp_delta_arclength_operation]     = "deltaarclength",
    [mp_delta_iterator_index_operation]= "deltaiteratorindex",
    [mp_arc_length_operation]          = "arclength",
    [mp_angle_operation]               = "angle",
    [mp_cycle_operation]               = "cycle",
    [mp_recycle_operation]             = "recycle",
    [mp_no_cycle_operation]            = "nocycle",
    [mp_x_relative_operation]          = "xrelative",
    [mp_y_relative_operation]          = "yrelative",
    [mp_xy_relative_operation]         = "xyrelative",
    [mp_x_absolute_operation]          = "xabsolute",
    [mp_y_absolute_operation]          = "yabsolute",
    [mp_xy_absolute_operation]         = "xyabsolute",
    [mp_filled_operation]              = "filled",
    [mp_stroked_operation]             = "stroked",
    [mp_clipped_operation]             = "clipped",
    [mp_grouped_operation]             = "grouped",
    [mp_bounded_operation]             = "bounded",
    [mp_plus_operation]                = "+",
    [mp_minus_operation]               = "-",
    [mp_times_operation]               = "*",
    [mp_over_operation]                = "/",
    [mp_power_operation]               = "^",
    [mp_pythag_add_operation]          = "++",
    [mp_pythag_sub_operation]          = "+-+",
    [mp_dotprod_operation]             = "dotprod",
    [mp_crossprod_operation]           = "crossprod",
    [mp_div_operation]                 = "div",
    [mp_mod_operation]                 = "mod",
    [mp_or_operation]                  = "or",
    [mp_and_operation]                 = "and",
    [mp_less_than_operation]           = "<",
    [mp_less_or_equal_operation]       = "<=",
    [mp_greater_than_operation]        = ">",
    [mp_greater_or_equal_operation]    = ">=",
    [mp_equal_operation]               = "=",
    [mp_unequal_operation]             = "<>",
    [mp_concat_operation]              = "&",
    [mp_just_append_operation]         = "&&",
    [mp_tolerant_concat_operation]     = "&&&",
    [mp_tolerant_append_operation]     = "&&&&",
    [mp_rotated_operation]             = "rotated",
    [mp_slanted_operation]             = "slanted",
    [mp_scaled_operation]              = "scaled",
    [mp_shifted_operation]             = "shifted",
    [mp_transformed_operation]         = "transformed",
    [mp_x_scaled_operation]            = "xscaled",
    [mp_y_scaled_operation]            = "yscaled",
    [mp_z_scaled_operation]            = "zscaled",
    [mp_xy_scaled_operation]           = "xyscaled",
    [mp_x_shifted_operation]           = "xshifted",
    [mp_y_shifted_operation]           = "yshifted",
    [mp_bytemap_scaled_operation]      = "bytemapscaled",
    [mp_uncycled_operation]            = "uncycled",
    [mp_intertimes_operation]          = "intersectiontimes",
    [mp_intertimes_list_operation]     = "intersectiontimeslist",
    [mp_double_dot_operation]          = "..",
    [mp_substring_operation]           = "substring",
    [mp_subpath_operation]             = "subpath",
    [mp_segment_operation]             = "segment",
    [mp_direction_time_operation]      = "directiontime",
    [mp_point_operation]               = "point",
    [mp_precontrol_operation]          = "precontrol",
    [mp_postcontrol_operation]         = "postcontrol",
    [mp_direction_operation]           = "direction",
    [mp_unit_direction_operation]      = "unitdirection",
    [mp_last_xy_operation]             = "lastxy",
    [mp_last_x_operation]              = "lastx",
    [mp_last_y_operation]              = "lasty",
    [mp_previous_xy_operation]         = "previousxy",
    [mp_previous_x_operation]          = "previousx",
    [mp_previous_y_operation]          = "previousy",
    [mp_path_point_operation]          = "pathpoint",
    [mp_path_precontrol_operation]     = "pathprecontrol",
    [mp_path_postcontrol_operation]    = "pathpostcontrol",
    [mp_path_direction_operation]      = "pathdirection",
    [mp_path_unit_direction_operation] = "pathunitdirection",
    [mp_path_state_operation]          = "pathstate",
    [mp_path_index_operation]          = "pathindex",
    [mp_path_lastindex_operation]      = "pathlastindex",
    [mp_path_length_operation]         = "pathlength",
    [mp_path_first_operation]          = "pathfirst",
    [mp_path_last_operation]           = "pathlast",
    [mp_path_xpart_operation]          = "pathxpart",
    [mp_path_ypart_operation]          = "pathypart",
    [mp_pen_offset_operation]          = "penoffset",
    [mp_arc_time_operation]            = "arctime",
    [mp_arc_point_operation]           = "arcpoint",
    [mp_arc_point_list_operation]      = "arcpointlist",
    [mp_subarc_length_operation]       = "subarclength",
    [mp_version_operation]             = "mpversion",
    [mp_iterator_index_operation]      = "interatorindex",
    [mp_envelope_operation]            = "envelope",
    [mp_boundingpath_operation]        = "boundingpath",
    [mp_bytemap_value_operation]       = "bytemapvalue",
    [mp_bytemap_found_operation]       = "bytemapfound",
    [mp_bytemap_path_operation]        = "bytemappath",
    [mp_bytemap_bounds_operation]      = "bytemapbounds",
};

static const char *mp_operator_string(int c)
{
    if (c >= mp_first_operation && c <= mp_last_operation) {
        return mp_op_string_names[c];
    } else {
        return "unknown operation";
    }
}

/* weird, these slightly different ranges, side effect from unknown */

static void mp_print_operator(MP mp, int c)
{
//  mp_print_string(mp, c <= mp_numeric_type ? mp_type_string(c) : mp_operator_string(c));
    mp_print_string(mp, mp_operator_string(c));
}

/*tex

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.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.

Well, we do have to list the names one more time, for use in symbolic printouts.The following
procedure, which is called just before \MP\ initializes its input and output, establishes the initial
values of the date and time.

Note that the values are |scaled| integers. Hence \MP\ can no longer be used after the year 32767.

*/

static void mp_fix_date_and_time(MP mp)
{
    time_t clock = time((time_t *) 0);
    struct tm *time = localtime(&clock);
    mp_number_clone(internal_value(mp_time_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_time_internal), (time->tm_hour * 60 + time->tm_min));
    mp_number_clone(internal_value(mp_hour_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_hour_internal), (time->tm_hour));
    mp_number_clone(internal_value(mp_minute_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_minute_internal), (time->tm_min));
    mp_number_clone(internal_value(mp_day_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_day_internal), (time->tm_mday));
    mp_number_clone(internal_value(mp_month_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_month_internal), (time->tm_mon + 1));
    mp_number_clone(internal_value(mp_year_internal), mp_unity_t);
    mp_number_multiply_int(internal_value(mp_year_internal), (time->tm_year + 1900));
}

/*tex

\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:

*/

static void mp_begin_diagnostic(MP mp)
{
    /* prepare to do some tracing */
    mp->old_selector = mp->selector;
    if (mp_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;
}

/*tex

We will occasionally use |begin_diagnostic| in connection with line-number printing, as follows.
The parameter |s| is typically \quote {Path} or \quote {Cycle spec}, etc.

*/

static void mp_begin_diagnostic_print(MP mp, const char *s, const char *t, int nuline)
{
    mp_begin_diagnostic(mp);
    if (nuline) {
        mp_print_flush_line(mp);
    }
    mp_print_format(mp, "%s at line %i %s:", s, mp_true_line(mp), t);
}

# define eq_text(A)     (A)->text
# define eq_type(A)     (A)->type
# define eq_property(A) (A)->property
# define eq_valent(A)   (A)->v.data.indep.equivalent
# define eq_node(A)     (A)->v.data.node
# define eq_symbol(A)   (mp_symbol) (A)->v.data.node

/*tex

Here are the functions needed for the avl construction. The avl comparison function is a
straight forward version of |strcmp|, except that checks for the string lengths first.

*/

static int mp_compare_symbols_entry(void *p, const void *pa, const void *pb)
{
    const mp_symbol a = (const mp_symbol) pa; /* avl uses anonymous pointers */
    const mp_symbol b = (const mp_symbol) pb; /* avl uses anonymous pointers */
    (void) p;
    if (a->text->len != b->text->len) {
        return (a->text->len > b->text->len ? 1 : -1);
    } else {
        return strncmp((const char *) a->text->str, (const char *) b->text->str, a->text->len);
    }
}

/*tex

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.

There is no pooling, just some statistics.

*/

/*tex
    In the current implementation, symbols are not freed until the end of the run.
*/

static mp_symbol mp_new_symbol_node(MP mp)
{
    mp_symbol p = mp_memory_clear_allocate(sizeof(mp_symbol_data)); /* no clear needed when copy */
    mp->memory_pool[mp_symbol_pool].used++;
    /*
    if (mp->memory_pool[mp_symbolic_pool].used > mp->memory_pool[mp_symbolic_pool].max) {
        mp->memory_pool[mp_symbol_pool].max = mp->memory_pool[mp_symbol_pool].used;
    }
    */
    return p;
}

static void mp_free_symbol_node(MP mp, mp_symbol p)
{
    (void) mp;
    mp_memory_free(p);
}

static void *mp_delete_symbols_entry(void *p)
{
    mp_symbol s = (mp_symbol) p; /*tex The avl library uses anonymous pointers. */
    MP mp = (MP) s->parent;
    mp_free_number(s->v.data.n);
    mp_memory_free(s->text->str);
    mp_memory_free(s->text);
    mp_free_symbol_node(mp, s);
    return NULL;
}

static void *mp_copy_symbols_entry(const void *p)
{
    mp_symbol fp = (mp_symbol) p; /*tex The avl library uses anonymous pointers. */
    MP mp = (MP) fp->parent;
    mp_symbol ff = mp_new_symbol_node(mp);
    if (! ff) {
        return NULL;
    }
    ff->text = mp_aux_copy_strings_entry(fp->text);
    if (! ff->text) {
        return NULL;
    }
    ff->v = fp->v;
    ff->type = fp->type;
    ff->property = fp->property;
    ff->parent = mp;
    mp_new_number_clone(ff->v.data.n, fp->v.data.n);
    return ff;
}

/*tex

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.

*/

static mp_symbol mp_new_symbols_entry(MP mp, unsigned char *nam, size_t len)
{
    mp_symbol s = mp_new_symbol_node(mp);
    s->parent = mp;
    s->text = mp_memory_allocate(sizeof(mp_lstring));
    s->text->str = nam;
    s->text->len = len;
    s->type = mp_tag_command;
    s->v.type = mp_known_type;
    mp_new_number(s->v.data.n);
    return s;
}

static mp_symbol mp_do_id_lookup(MP mp, avl_tree symbols, char *str, size_t len, int create)
{
    mp_symbol sym;
    mp->id_lookup_test->text->str = (unsigned char *) str;
    mp->id_lookup_test->text->len = len;
    sym = (mp_symbol) avl_find(mp->id_lookup_test, symbols);
    if (sym == NULL && create) {
        unsigned char *nam = (unsigned char *) mp_strndup(str, len);
        mp_symbol tmp = mp_new_symbols_entry(mp, nam, len);
        avl_ins(tmp, symbols, avl_false);
        sym = (mp_symbol) avl_find(tmp, symbols);
        mp_delete_symbols_entry(tmp);
        /* */
        mp->memory_pool[mp_identifiers_pool].used++;
        mp->memory_pool[mp_identifiers_pool].count += len;
    }
    return sym;
}

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);
}

/*tex

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.

*/

static int n_of_primitives = 0;
static int primitives_max  = 500;
static int primitives_set  = 0;

static mp_primitive_data primitivedata[500]; /* more than enough */

static void mp_primitive(MP mp, int origin, int legacy, const char *str, int command, int operator)
{
    if (mp && command != mp_undefined_command) {
        set_cur_sym(mp_id_lookup(mp, (char *) str, strlen(str), 1));
        mp_set_eq_type(cur_sym, command);
        mp_set_eq_property(cur_sym, 0x1); /* todo: enumeration values */
        mp_set_eq_valent(cur_sym, operator);
    }
    if (! primitives_set && n_of_primitives < primitives_max) {
        primitivedata[n_of_primitives++] = (mp_primitive_data) {
            .name      = str,
            .command   = (unsigned char) command,
            .operation = (unsigned char) operator,
            .origin    = (unsigned char) origin,
            .legacy    = (unsigned char) legacy,
        };
    }
}

/*tex

    Some other symbolic tokens only exist for error recovery.

*/

static mp_symbol mp_frozen_primitive(MP mp, const char *str, int c, int o)
{
    mp_symbol s = mp_do_id_lookup(mp, mp->frozen_symbols, (char *) str, strlen(str), 1);
    s->type = c;
    s->property = 0x1; /* todo: enumeration values */
    s->v.data.indep.serial = o;
    return s;
}

/*tex

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| actually is redefinable).

*/

static int mp_is_frozen(MP mp, mp_symbol sym)
{
    mp_symbol temp = mp_do_id_lookup(mp, mp->frozen_symbols, (char *) sym->text->str, sym->text->len, 0);
    return temp == mp->frozen_inaccessible ? 0 : temp == sym;
}

# 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

static inline void mp_do_set_value_sym(MP mp, mp_token_node tok, mp_symbol sym)
{
    (void) mp;
    tok->data.sym = sym;
}

static inline void mp_do_set_value_number(MP mp, mp_token_node tok, const mp_number *num)
{
    (void) mp;
    tok->data.p = NULL;
    tok->data.str = NULL;
    tok->data.node = NULL;
    mp_number_clone(tok->data.n, *num);
}

static inline void mp_do_set_value_str(MP mp, mp_token_node tok, mp_string str)
{
    (void) mp;
    tok->data.p = NULL;
    tok->data.str = str;
    mp_add_string_reference(mp, str);
    tok->data.node = NULL;
    mp_set_number_to_zero(tok->data.n);
}

static inline void mp_do_set_value_node(MP mp, mp_token_node tok, mp_node val)
{
    (void) mp;
    tok->data.p = NULL;
    tok->data.str = NULL;
    tok->data.node = val;
    mp_set_number_to_zero(tok->data.n);
}

static inline void mp_do_set_value_knot(MP mp, mp_token_node tok, mp_knot knt)
{
    (void) mp;
    tok->data.p = knt;
    tok->data.str = NULL;
    tok->data.node = NULL;
    mp_set_number_to_zero(tok->data.n);
}

static mp_node mp_new_token_node(MP mp)
{
    mp_node p = mp->memory_pool[mp_token_pool].list;
    mp->memory_pool[mp_token_pool].used++;
    if (mp->memory_pool[mp_token_pool].used > mp->memory_pool[mp_token_pool].max) {
        mp->memory_pool[mp_token_pool].max++;
    }
    if (p) {
        mp->memory_pool[mp_token_pool].list = p->link;
        mp->memory_pool[mp_token_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_node_data));
    }
    mp_new_number(p->data.n);
    p->link = NULL;
    p->type = mp_token_node_type;
    return (mp_node) p;
}

static void mp_free_token_node(MP mp, mp_node p)
{
    if (p) {
        if (mp->math_mode > mp_math_double_mode) { /* no need when posit so we can change the order */
            mp_free_number(((mp_value_node) p)->data.n);
        }
        mp->memory_pool[mp_token_pool].used--;
        if (mp->memory_pool[mp_token_pool].pool < mp->memory_pool[mp_token_pool].kept) {
            p->link = mp->memory_pool[mp_token_pool].list;
            mp->memory_pool[mp_token_pool].list = p;
            mp->memory_pool[mp_token_pool].pool++;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_token_pool(MP mp)
{
    mp_node p = mp->memory_pool[mp_token_pool].list;
    while (p) {
        mp_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/* A numeric token is created by the following trivial routine. */

static mp_node mp_new_num_tok(MP mp, mp_number *val)
{
    mp_node p = mp_new_token_node(mp);
    mp_set_value_number(p, *val);
    p->type = mp_known_type;
    p->name_type = mp_token_operation;
    return p;
}

/*tex

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.

*/

static void mp_flush_token_list(MP mp, mp_node p)
{
    while (p != NULL) {
        mp_node q = p; /* the node being recycled */
        p = p->link;
        switch (q->type) {
            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:
                mp_delete_string_reference(mp, 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");
        }
        mp_free_token_node(mp, q);
    }
}

/*tex

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.)

Unusual entries are printed in the form of all-caps tokens preceded by a space, e.g., |BAD|. We go
for a spacy layout because we have more screen real estate today.

*/

static 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 (p->type != mp_symbol_node_type) {
            if (p->name_type == mp_token_operation) {
                if (p->type == mp_known_type) {
                    if (cclass == mp_digit_class) {
                        mp_print_char(mp, ' ');
                    }
                    if (mp_number_negative(mp_get_value_number(p))) {
                        if (cclass == mp_left_bracket_class) {
                            mp_print_char(mp, ' ');
                        }
                        mp_print_format(mp, "[%N]", mp_get_value_number(p));
                        c = mp_right_bracket_class;
                    } else {
                        mp_print_number(mp, mp_get_value_number(p));
                        c = mp_digit_class;
                    }
                } else if (p->type == mp_string_type) {
                    mp_print_format(mp, "%Q", mp_get_value_str(p));
                    c = mp_string_class;
                } else {
                    mp_print_string(mp, " BAD");
                }
            } else if ((p->name_type != mp_capsule_operation) || (p->type < mp_vacuous_type) || (p->type > mp_independent_type)) {
                mp_print_string(mp, " BAD");
            } else {
                mp_print_capsule(mp, p);
                c = mp_right_parenthesis_class;
            }
        } else if (p->name_type == mp_expr_operation || p->name_type == mp_suffix_operation || p->name_type == mp_text_operation) {
            int r = mp_get_sym_info(p);
            if (p->name_type == mp_expr_operation) {
                mp_print_format(mp, "(EXPR %i)", r);
            } else if (p->name_type == mp_suffix_operation) {
                mp_print_format(mp, "(SUFFIX %i)", r);
            } else {
                mp_print_format(mp, "(TEXT %i)", r);
            }
            c = mp_right_parenthesis_class;
        } else {
            mp_symbol sr = mp_get_sym_sym(p);
            if (sr == mp_collective_subscript) {
                if (cclass == mp_left_bracket_class) {
                    mp_print_char(mp, ' ');
                }
                mp_print_string(mp, "[]");
                c = mp_right_bracket_class;
            } else {
                mp_string rr = eq_text(sr);
                if (rr == NULL || rr->str == NULL) {
                    mp_print_string(mp, " NONEXISTENT");
                } else {
                    c = mp->char_class[(rr->str[0])];
                    if (c == cclass) {
                        switch (c) {
                            case mp_letter_class:
                                mp_print_char(mp, '.');
                                break;
                            case mp_comma_class:
                            case mp_semicolon_class:
                            case mp_left_parenthesis_class:
                            case mp_right_parenthesis_class:
                                break;
                            default:
                                mp_print_char(mp, ' ');
                                break;
                            }
                    }
                    mp_print_mp_string(mp, rr);
                }
            }
        }
        cclass = c;
        p = p->link;
    }
    return;
}

static void mp_show_token_list_space(MP mp, mp_node p, mp_node q)
{
    (void) q;
    while (p != NULL) {
        if (p->type != mp_symbol_node_type) {
            if (p->name_type == mp_token_operation) {
                if (p->type == mp_known_type) {
                    if (mp_number_negative(mp_get_value_number(p))) {
                        mp_print_format(mp, "[%N]", mp_get_value_number(p));
                    } else {
                        mp_print_number(mp, mp_get_value_number(p));
                    }
                } else if (p->type == mp_string_type) {
                    mp_print_format(mp, "%Q", mp_get_value_str(p));

                } else {
                    mp_print_string(mp, "BAD");
                }
            } else if ((p->name_type != mp_capsule_operation) || (p->type < mp_vacuous_type) || (p->type > mp_independent_type)) {
                mp_print_string(mp, "BAD");
            } else {
                mp_print_capsule(mp, p);
            }
        } else if (p->name_type == mp_expr_operation || p->name_type == mp_suffix_operation || p->name_type == mp_text_operation) {
            int r = mp_get_sym_info(p);
            if (p->name_type == mp_expr_operation) {
                mp_print_format(mp, "(EXPR %i)", r);
            } else if (p->name_type == mp_suffix_operation) {
                mp_print_format(mp, "(SUFFIX %i)", r);
            } else {
                mp_print_format(mp, "(TEXT %i)", r);
            }
        } else {
            mp_symbol sr = mp_get_sym_sym(p);
            if (sr == mp_collective_subscript) {
                mp_print_string(mp, "[]");
            } else {
                mp_string rr = eq_text(sr);
                if (rr == NULL || rr->str == NULL) {
                    mp_print_string(mp, "NONEXISTENT");
                } else {
                    mp_print_mp_string(mp, rr);
                }
            }
        }
        p = p->link;
        if (p) {
            mp_print_char(mp, ' ');
        }
    }
    return;
}

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);
    }
}

/*tex

The following subroutine displays a macro, given a pointer to its reference count.

*/

static void mp_show_macro(MP mp, mp_node p, mp_node q)
{
    p = p->link; /* bypass the reference count */
    while (p->name_type != mp_macro_operation) {
        mp_node r = p->link;
        p->link = NULL;
        mp_show_token_list(mp, p, NULL);
        p->link = r;
        p = r;
    }
    switch (mp_get_sym_info(p)) {
        case mp_general_macro:
            mp_print_string(mp, "-> ");
            break;
        case mp_primary_macro:
        case mp_secondary_macro:
        case mp_tertiary_macro:
            mp_print_format(mp, "<%C> -> ", mp_parameter_commmand, mp_get_sym_info(p));
            break;
        case mp_expr_macro:
            mp_print_string(mp, "<expr> -> ");
            break;
        case mp_of_macro:
            mp_print_string(mp, "<expr> of <primary> -> ");
            break;
        case mp_suffix_macro:
            mp_print_string(mp, "<suffix> -> ");
            break;
        case mp_text_macro:
            mp_print_string(mp, "<text> -> ");
            break;
    }
    mp_show_token_list(mp, p->link, q);
}

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)
{
    (void) mp;
    return A->subscr_head;
}

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;
}

/*tex

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.

*/

static mp_node mp_new_value_node(MP mp)
{
    mp_value_node p = (mp_value_node) mp->memory_pool[mp_value_pool].list;
    mp->memory_pool[mp_value_pool].used++;
    if (mp->memory_pool[mp_value_pool].used > mp->memory_pool[mp_value_pool].max) {
        mp->memory_pool[mp_value_pool].max = mp->memory_pool[mp_value_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_value_pool].list = p->link;
        mp->memory_pool[mp_value_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_value_node_data));
    }
    mp_new_number(p->data.n);
    mp_new_number(p->subscript);
    p->link = NULL;
    p->type = mp_value_node_type;
    return (mp_node) p;
}

static void mp_free_value_node(MP mp, mp_node p)
{
    if (p) {
        mp->memory_pool[mp_value_pool].used--;
        if (mp->math_mode > mp_math_double_mode) {
            mp_free_number(((mp_value_node) p)->data.n);
            mp_free_number(((mp_value_node) p)->subscript);
        }
        if (mp->memory_pool[mp_value_pool].pool < mp->memory_pool[mp_value_pool].kept) {
            mp->memory_pool[mp_value_pool].pool++;
            p->link = mp->memory_pool[mp_value_pool].list;
            mp->memory_pool[mp_value_pool].list = p;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_value_pool(MP mp)
{
    mp_node p = mp->memory_pool[mp_value_pool].list;
    while (p) {
        mp_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex

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.

*/

# 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

static mp_value_node mp_get_attribute_node(MP mp)
{
    mp_value_node p = (mp_value_node) mp_new_value_node(mp);
    p->type = mp_attribute_node_type;
    return p;
}

static mp_value_node mp_get_subscr_node(MP mp)
{
    mp_value_node p = (mp_value_node) mp_new_value_node(mp);
    p->type = mp_subscript_node_type;
    return p;
}

static mp_node mp_get_pair_node(MP mp)
{
    mp_node p = mp->memory_pool[mp_pair_pool].list;
    mp->memory_pool[mp_pair_pool].used++;
    if (mp->memory_pool[mp_pair_pool].used > mp->memory_pool[mp_pair_pool].max) {
        mp->memory_pool[mp_pair_pool].max++;
    }
    if (p) {
        mp->memory_pool[mp_pair_pool].list = p->link;
        mp->memory_pool[mp_pair_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_pair_node_data));
    }
    p->link = NULL;
    p->type = mp_pair_node_type;
    return (mp_node) p;
}

static void mp_free_pair_node(MP mp, mp_node p)
{
    if (p) {
        mp->memory_pool[mp_pair_pool].used--;
        if (mp->memory_pool[mp_pair_pool].pool < mp->memory_pool[mp_pair_pool].kept) {
            p->link = mp->memory_pool[mp_pair_pool].list;
            mp->memory_pool[mp_pair_pool].list = p;
            mp->memory_pool[mp_pair_pool].pool++;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_pair_pool(MP mp)
{
    mp_node p = mp->memory_pool[mp_pair_pool].list;
    while (p) {
        mp_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex

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|.

*/

static void mp_init_pair_node(MP mp, mp_node p)
{
    mp_node q; /* the new node */
    p->type = 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), 1); /* sets |type(q)| and |value(q)| */
    mp_y_part(q)->name_type = mp_y_part_operation;
    mp_y_part(q)->link = p;
    mp_x_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_x_part(q), 2); /* sets |type(q)| and |value(q)| */
    mp_x_part(q)->name_type = mp_x_part_operation;
    mp_x_part(q)->link = p;
    mp_set_value_node(p, q);
}


static mp_node mp_get_transform_node(MP mp)
{
    mp_node p = mp->memory_pool[mp_transform_pool].list;
    mp->memory_pool[mp_transform_pool].used++;
    if (mp->memory_pool[mp_transform_pool].used > mp->memory_pool[mp_transform_pool].max) {
        mp->memory_pool[mp_transform_pool].max++;
    }
    if (p) {
        mp->memory_pool[mp_transform_pool].list = p->link;
        mp->memory_pool[mp_transform_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_transform_node_data));
    }
    p->link = NULL;
    p->type = mp_transform_node_type;
    return (mp_node) p;
}

static void mp_free_transform_node(MP mp, mp_node p)
{
    if (p) {
        mp->memory_pool[mp_transform_pool].used--;
        if (mp->memory_pool[mp_transform_pool].pool < mp->memory_pool[mp_transform_pool].kept) {
            p->link = mp->memory_pool[mp_transform_pool].list;
            mp->memory_pool[mp_transform_pool].list = p;
            mp->memory_pool[mp_transform_pool].pool++;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_transform_pool(MP mp)
{
    mp_node p = mp->memory_pool[mp_transform_pool].list;
    while (p) {
        mp_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static void mp_init_transform_node(MP mp, mp_node p)
{
    mp_node q; /* the new node */
    p->type = 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), 3);  /* sets |type(q)| and |value(q)| */
    mp_yy_part(q)->name_type = mp_yy_part_operation;
    mp_yy_part(q)->link = p;
    mp_yx_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_yx_part(q), 4);  /* sets |type(q)| and |value(q)| */
    mp_yx_part(q)->name_type = mp_yx_part_operation;
    mp_yx_part(q)->link = p;
    mp_xy_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_xy_part(q), 5);  /* sets |type(q)| and |value(q)| */
    mp_xy_part(q)->name_type = mp_xy_part_operation;
    mp_xy_part(q)->link = p;
    mp_xx_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_xx_part(q), 6);  /* sets |type(q)| and |value(q)| */
    mp_xx_part(q)->name_type = mp_xx_part_operation;
    mp_xx_part(q)->link = p;
    mp_ty_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_ty_part(q), 7);  /* sets |type(q)| and |value(q)| */
    mp_ty_part(q)->name_type = mp_y_part_operation;
    mp_ty_part(q)->link = p;
    mp_tx_part(q) = mp_new_value_node(mp);
    mp_new_indep(mp, mp_tx_part(q), 8);  /* sets |type(q)| and |value(q)| */
    mp_tx_part(q)->name_type = mp_x_part_operation;
    mp_tx_part(q)->link = p;
    mp_set_value_node(p, q);
}

static mp_node mp_get_color_node(MP mp)
{
    mp_node p = mp->memory_pool[mp_color_pool].list;
    mp->memory_pool[mp_color_pool].used++;
    if (mp->memory_pool[mp_color_pool].used > mp->memory_pool[mp_color_pool].max) {
        mp->memory_pool[mp_color_pool].max++;
    }
    if (p) {
        mp->memory_pool[mp_color_pool].list = p->link;
        mp->memory_pool[mp_color_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_color_node_data));
    }
    p->link = NULL;
    p->type = mp_color_node_type;
    return (mp_node) p;
}

 static void mp_free_color_node(MP mp, mp_node p)
 {
    if (p) {
        mp->memory_pool[mp_color_pool].used--;
        if (mp->memory_pool[mp_color_pool].pool < mp->memory_pool[mp_color_pool].kept) {
            p->link = mp->memory_pool[mp_color_pool].list;
            mp->memory_pool[mp_color_pool].list = p;
            mp->memory_pool[mp_color_pool].pool++;
        } else {
            mp_memory_free(p);
        }
    }
}

static void mp_flush_color_pool(MP mp)
{
    mp_node p = mp->memory_pool[mp_color_pool].list;
    while (p) {
        mp_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static void mp_init_color_node(MP mp, mp_node p, int type)
{
    mp_node q = mp_get_color_node(mp);
    p->type = type;
    switch (type) {
        case mp_color_type:
            q->type = mp_color_node_type;
            /* */
            mp_red_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_red_part(q), 9);
            mp_red_part(q)->name_type = mp_red_part_operation;
            mp_red_part(q)->link = p;
            /* */
            mp_green_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_green_part(q), 10);
            mp_green_part(q)->name_type = mp_green_part_operation;
            mp_green_part(q)->link = p;
            /* */
            mp_blue_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_blue_part(q), 11);
            mp_blue_part(q)->name_type = mp_blue_part_operation;
            mp_blue_part(q)->link = p;
            break;
        case mp_cmykcolor_type:
            q->type = mp_cmykcolor_node_type;
            /* */
            mp_cyan_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_cyan_part(q), 12);
            mp_cyan_part(q)->name_type = mp_cyan_part_operation;
            mp_cyan_part(q)->link = p;
            /* */
            mp_magenta_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_magenta_part(q), 13);
            mp_magenta_part(q)->name_type = mp_magenta_part_operation;
            mp_magenta_part(q)->link = p;
            /* */
            mp_yellow_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_yellow_part(q), 14);
            mp_yellow_part(q)->name_type = mp_yellow_part_operation;
            mp_yellow_part(q)->link = p;
            /* */
            mp_black_part(q) = mp_new_value_node(mp);
            mp_new_indep(mp, mp_black_part(q), 15);
            mp_black_part(q)->name_type = mp_black_part_operation;
            mp_black_part(q)->link = p;
            break;
    }
    mp_set_value_node(p, q);
}

/*tex

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.

*/

static mp_node mp_id_transform(MP mp)
{
    mp_node q;
    mp_node p = mp_new_value_node(mp);
    p->name_type = mp_capsule_operation;
    mp_set_value_number(p, mp_zero_t); /* todo: this was |null| */
    mp_init_transform_node(mp, p);
    q = mp_get_value_node(p);
    mp_tx_part(q)->type = mp_known_type;
    mp_set_value_number(mp_tx_part(q), mp_zero_t);
    mp_ty_part(q)->type = mp_known_type;
    mp_set_value_number(mp_ty_part(q), mp_zero_t);
    mp_xy_part(q)->type = mp_known_type;
    mp_set_value_number(mp_xy_part(q), mp_zero_t);
    mp_yx_part(q)->type = mp_known_type;
    mp_set_value_number(mp_yx_part(q), mp_zero_t);
    mp_xx_part(q)->type = mp_known_type;
    mp_set_value_number(mp_xx_part(q), mp_unity_t);
    mp_yy_part(q)->type = mp_known_type;
    mp_set_value_number(mp_yy_part(q), mp_unity_t);
    return p;
}

/*tex

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.

*/

static void mp_new_root(MP mp, mp_symbol x)
{
    mp_node p = mp_new_value_node(mp);
    p->type = mp_undefined_type;
    p->name_type = mp_root_operation;
    mp_set_value_sym(p, x);
    mp_set_eq_node(x, p);
}

/*tex

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.

*/

static 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 (1) {
        /* why a loop */
        if (p->name_type == mp_capsule_operation) {
         // mp_print_format(mp, "%%CAPSULE %P", p);
            mp_print_format(mp, "capsule %P", p);
            return;
        } else if (p->name_type >= mp_x_part_operation && p->name_type <= mp_grey_part_operation) {
            mp_print_format(mp, "%s ", mp_op_string_names[p->name_type]);
            p = p->link;
        } else {
            break;
        }
    }
    while (p->name_type > mp_saved_root_operation) {
        /*tex
            Ascend one level, pushing a token onto list |q| and replacing |p| by its parent
        */
        if (p->name_type == mp_subscript_operation) {
            r = mp_new_num_tok(mp, &(mp_subscript(p)));
            do {
                p = p->link;
            } while (p->name_type != mp_attribute_operation);
        } else if (p->name_type == mp_structured_root_operation) {
            p = p->link;
            goto FOUND;
        } else if (p->name_type != mp_attribute_operation) {
            mp_confusion(mp, "variable");
            return;
        } 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);
    }
    /*tex
        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));
    r->link = q;
    if (p->name_type == mp_saved_root_operation) {
        mp_print_string(mp, "(SAVED)");
    }
    mp_show_token_list(mp, r, NULL);
    mp_flush_token_list(mp, r);
}

/*tex

The |interesting| function returns |true| if a given variable is not in a capsule, or if the user
wants to trace capsules.

*/

static int mp_interesting(MP mp, mp_node p)
{
    if (mp_number_positive(internal_value(mp_tracing_capsules_internal))) {
        return 1;
    } else {
        mp_name_type_type t = p->name_type;
        if (t >= mp_x_part_operation && t != mp_capsule_operation) {
            mp_node tt = mp_get_value_node(p->link);
            switch (t) {
# if 0
                case mp_x_part_operation:
                    switch (tt->type) {
                        case mp_color_type:
                            t = mp_red_part(tt)->name_type;
                            break;
                        case mp_cmykcolor_type:
                            t = mp_cyan_part(tt)->name_type;
                            break;
                        default:
                            t = mp_x_part(tt)->name_type;
                            break;
                    }
                    break;
                case mp_y_part_operation:
                    switch (tt->type) {
                        case mp_color_type:
                            t = mp_green_part(tt)->name_type;
                            break;
                        case mp_cmykcolor_type:
                            t = mp_magenta_part(tt)->name_type;
                            break;
                        default:
                            t = mp_y_part(tt)->name_type;
                            break;
                    }
                    break;
                case mp_z_part_operation:
                    switch (tt->type) {
                        case mp_color_type:
                            t = mp_blue_part(tt)->name_type;
                            break;
                        case mp_cmykcolor_type:
                            t = mp_yellow_part(tt)->name_type;
                            break;
                        default:
                            t = mp_capsule_operation; /* error */
                            break;
                    }
                    break;
                case mp_w_part_operation:
                    switch (tt->type) {
                        case mp_cmykcolor_type:
                            t = mp_black_part(tt)->name_type;
                            break;
                        default:
                            t = mp_capsule_operation; /* error */
                            break;
                    }
                    break;
# else
                case mp_x_part_operation:       t = mp_x_part      (tt)->name_type; break;
                case mp_y_part_operation:       t = mp_y_part      (tt)->name_type; break;
# endif
                case mp_xx_part_operation:      t = mp_xx_part     (tt)->name_type; break;
                case mp_xy_part_operation:      t = mp_xy_part     (tt)->name_type; break;
                case mp_yx_part_operation:      t = mp_yx_part     (tt)->name_type; break;
                case mp_yy_part_operation:      t = mp_yy_part     (tt)->name_type; break;
                case mp_red_part_operation:     t = mp_red_part    (tt)->name_type; break;
                case mp_green_part_operation:   t = mp_green_part  (tt)->name_type; break;
                case mp_blue_part_operation:    t = mp_blue_part   (tt)->name_type; break;
                case mp_cyan_part_operation:    t = mp_cyan_part   (tt)->name_type; break;
                case mp_magenta_part_operation: t = mp_magenta_part(tt)->name_type; break;
                case mp_yellow_part_operation:  t = mp_yellow_part (tt)->name_type; break;
                case mp_black_part_operation:   t = mp_black_part  (tt)->name_type; break;
                case mp_grey_part_operation:    t = mp_grey_part   (tt)->name_type; break;
                default:                                                            break;
            }
        }
        return (t != mp_capsule_operation);
    }
}

/*tex

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.

*/

static mp_node mp_new_structure(MP mp, mp_node p)
{
    mp_node r = NULL;
    switch (p->name_type) {
        case mp_root_operation:
            {
                mp_symbol q = mp_get_value_sym(p);
                r = mp_new_value_node(mp);
                mp_set_eq_node(q, r);
            }
            break;
        case mp_subscript_operation:
            /*tex
                Link a new subscript node |r| in place of node |p|.
            */
            {
                mp_node q_new;
                mp_node q = p;
                do {
                    q = q->link;
                } while (q->name_type != 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 = r->link;
                } 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);
                }
                mp_number_clone(mp_subscript(r), mp_subscript(p));
            }
            break;
        case mp_attribute_operation:
            /*tex
                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 = r->link;
                } 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 (q->link != p) {
                        q = q->link;
                    }
                    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, p->link);
        mp_set_value_sym(r, mp_get_value_sym(p));
        r->type = mp_structured_type;
        r->name_type = p->name_type;
        mp_set_attribute_head(r, p);
        p->name_type = 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);
        q->type = mp_undefined_type;
        q->name_type = mp_attribute_operation;
        mp_set_link(q, mp->end_attr);
        mp_set_hashloc(q, mp_collective_subscript);
    }
    return r;
}

/*tex

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|.

*/

static mp_node mp_find_variable(MP mp, mp_node t)
{
    mp_symbol p_sym = mp_get_sym_sym(t);
 // 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 = t->link;
        if (eq_node(p_sym) == NULL) {
            mp_new_root(mp, p_sym);
        }
        p = eq_node(p_sym);
        pp = p;
        while (t != NULL) {
            /*tex
                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 (pp->type != mp_structured_type) {
                if (pp->type > mp_structured_type) {
                    return NULL;
                } else {
                    ss = mp_new_structure(mp, pp);
                    if (p == pp) {
                        p = ss;
                    }
                    pp = ss;
                }
            }
            /*tex Now |type(pp) = mp_structured|. */
            if (p->type != mp_structured_type) {
                /*tex It cannot be |> mp_structured|. */
                p = mp_new_structure(mp, p);
                /*tex Now |type(p) = mp_structured|. */
            }
            if (t->type != mp_symbol_node_type) {
                /*tex
                    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 */
                mp_new_number_clone(nn, mp_get_value_number(t));
                pp = mp_get_attribute_head(pp)->link;
                /* now |mp_get_hashloc(pp)=mp_collective_subscript| */
                q = mp_get_attribute_head(p)->link;
                mp_new_number_clone(save_subscript, mp_subscript(q));
                mp_set_number_to_inf(mp_subscript(q));
                s = mp->temp_head;
                mp_set_link(s, mp_get_subscr_head(p));
                do {
                    r = s;
                    s = s->link;
                } while (mp_number_greater(nn, mp_subscript(s)));
                if (mp_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);
                    mp_number_clone(mp_subscript(p1), nn);
                    p1->name_type = mp_subscript_operation;
                    p1->type = mp_undefined_type;
                    p = (mp_node) p1;
                }
                mp_number_clone(mp_subscript(q), save_subscript);
                mp_free_number(save_subscript);
                mp_free_number(nn);
            } else {
                /* Descend one level for the attribute |mp_get_sym_info(t)| */
                mp_symbol nn1 = mp_get_sym_sym(t);
                ss = mp_get_attribute_head(pp);
                do {
                    rr = ss;
                    ss = ss->link;
                } 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);
                    qq->name_type = mp_attribute_operation;
                    qq->type = 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 = s->link;
                    } 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);
                        q->name_type = mp_attribute_operation;
                        q->type = mp_undefined_type;
                        mp_set_parent((mp_value_node) q, p);
                        p = q;
                    }
                }
            }
            t = t->link;
        }
        if (pp->type >= mp_structured_type) {
            if (pp->type == mp_structured_type) {
                pp = mp_get_attribute_head(pp);
            } else {
                return NULL;
            }
        }
        if (p->type == mp_structured_type) {
            p = mp_get_attribute_head(p);
        }
        if (p->type == mp_undefined_type) {
            if (pp->type == mp_undefined_type) {
                pp->type = mp_numeric_type;
                mp_set_value_number(pp, mp_zero_t);
            }
            p->type = pp->type;
            mp_set_value_number(p, mp_zero_t);
        }
        return p;
    }
}

static void mp_flush_variable(MP mp, mp_node p, mp_node t, int discard_suffixes)
{
    while (t != NULL) {
        if (p->type != mp_structured_type) {
            return;
        } else {
            /*tex Attribute to match: */
            mp_symbol n = mp_get_sym_sym(t);
            t = t->link;
            if (n == mp_collective_subscript) {
                mp_node q = mp_get_subscr_head(p);
                mp_node r = NULL;
                while (q && q->name_type == mp_subscript_operation) { /*tex The q test makes compiler happy. */
                    mp_flush_variable(mp, q, t, discard_suffixes);
                    if (t != NULL) {
                        r = q;
                    } else if (q->type == mp_structured_type) {
                        r = q;
                    } else {
                        if (r == NULL) {
                            mp_set_subscr_head(p, q->link);
                        } else {
                            mp_set_link(r, q->link);
                        }
                        mp_free_value_node(mp, q);
                    }
                    q = r == NULL ? mp_get_subscr_head(p) : r->link;
                }
            }
            p = mp_get_attribute_head(p);
            do {
                p = p->link;
            } while (mp_get_hashloc(p) < n);
            if (mp_get_hashloc(p) != n) {
                return;
            }
        }
    }
    if (discard_suffixes) {
        mp_flush_below_variable(mp, p);
    } else {
        if (p->type == mp_structured_type) {
            p = mp_get_attribute_head(p);
        }
        mp_recycle_value(mp, p);
    }
}

/*tex

The next procedure is simpler; it wipes out everything but |p| itself, which becomes undefined.

*/

static void mp_flush_below_variable(MP mp, mp_node p)
{
    if (p->type != mp_structured_type) {
        /*tex This sets |type(p) = undefined|. */
        mp_recycle_value(mp, p);
    } else {
        mp_node r;
        mp_node q = mp_get_subscr_head(p);
        while (q && q->name_type == mp_subscript_operation) {
            mp_flush_below_variable(mp, q);
            r = q;
            q = q->link;
            mp_free_value_node(mp, r);
        }
        r = mp_get_attribute_head(p);
        q = r->link;
        mp_recycle_value(mp, r);
        mp_free_value_node(mp, r);
        /*tex We know that |q| is okay but the compiler likes a check for |NULL|. */
        if (q) {
            do {
                mp_flush_below_variable(mp, q);
                r = q;
                /*tex Without the |q| checks we get a compiler warning here. */
                q = q->link;
                mp_free_value_node(mp, r);
            } while (q && q != mp->end_attr);
        }
        p->type = mp_undefined_type;
    }
}

/*tex

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.

*/

static int mp_und_type(MP mp, mp_node p)
{
    (void) mp;
    switch (p->type) {
        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 p->type;
        case mp_known_type:
        case mp_dependent_type:
        case mp_proto_dependent_type:
        case mp_independent_type:
            return mp_numeric_type;
        default:
            return 0;
    }
}

/*tex

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.

*/

static void mp_clear_symbol(MP mp, mp_symbol p, int saving)
{
    mp_node q = eq_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) {
                    q->name_type = mp_saved_root_operation;
                } else {
                    mp_flush_below_variable(mp, q);
                    mp_free_value_node(mp, q);
                }
            }
            break;
        default:
            break;
    }
    mp_set_eq_valent(p, mp->frozen_undefined->v.data.indep.serial);
    mp_set_eq_type(p, mp->frozen_undefined->type);
}

/*tex

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

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\stopitemize

The global variable |save_ptr| points to the top item on the save stack.

*/

static mp_save mp_new_save(MP mp)
{
    mp_save p = mp->memory_pool[mp_save_pool].list;
    mp->memory_pool[mp_save_pool].used++;
    if (mp->memory_pool[mp_save_pool].used > mp->memory_pool[mp_save_pool].max) {
        mp->memory_pool[mp_save_pool].max = mp->memory_pool[mp_save_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_save_pool].list = p->link;
        mp->memory_pool[mp_save_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_save_data));
    }
    p->link = NULL;
    return p;
}

static void mp_free_save(MP mp, mp_save p)
{
    mp->memory_pool[mp_save_pool].used--;
    if (mp->memory_pool[mp_save_pool].pool < mp->memory_pool[mp_save_pool].kept) {
        mp->memory_pool[mp_save_pool].pool++;
        p->link = mp->memory_pool[mp_save_pool].list;
        mp->memory_pool[mp_save_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_save_pool(MP mp)
{
    mp_save p = mp->memory_pool[mp_save_pool].list;
    while (p) {
        mp_save n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static void mp_save_boundary(MP mp)
{
    mp_save p = mp_new_save(mp);
    p->type = 0;
    p->link = mp->save_ptr;
    mp->save_ptr = p;
}

/*tex
    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; they to remove things from the
    stack when the program is not inside a group, so there's no point in wasting the space.
*/

static void mp_save_variable(MP mp, mp_symbol q)
{
    if (mp->save_ptr != NULL) {
        mp_save p = mp_new_save(mp);
        p->type = mp_normal_operation;
        p->link = mp->save_ptr;
        p->value.v.data.indep.scale = eq_type(q);
        p->value.v.data.indep.equivalent = eq_valent(q);
        p->value.v.data.node = eq_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_symbol q = (mp_symbol) mp->save_ptr->value.v.data.p;
    if (mp_number_positive(internal_value(mp_tracing_restores_internal))) {
        mp_begin_diagnostic(mp);
        mp_print_format(mp, "%l{restoring %S}", eq_text(q));
        mp_end_diagnostic(mp, 0);
    }
    mp_clear_symbol(mp, q, 0);
    mp_set_eq_type(q, mp->save_ptr->value.v.data.indep.scale);
    mp_set_eq_valent(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) {
            pp->name_type = mp_root_operation;
        }
    }
}

/*tex

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.

*/

static void mp_save_internal(MP mp, int q)
{
    if (mp->save_ptr != NULL) {
        mp_save p = mp_new_save(mp);
        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, mp_save_internal_code, q, internal_type(q), internal_name(q));
        }
        mp_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 (mp_number_positive(internal_value(mp_tracing_restores_internal))) {
        mp_begin_diagnostic(mp);
        mp_print_format(mp, "%l{restoring %s=", internal_name(q));
        switch (internal_type(q)) {
            case mp_known_type:
            case mp_numeric_type:
                mp_print_number(mp, saved.v.data.n);
                break;
            case mp_boolean_type:
                mp_print_string(mp, mp_number_to_boolean(saved.v.data.n) == mp_true_operation ? "true" : "false");
                break;
            case mp_string_type:
                mp_print_mp_string(mp, saved.v.data.str);
                break;
            default:
                mp_confusion(mp, "internal restore");
                break;
        }
        mp_print_char(mp, '}');
        mp_end_diagnostic(mp, 0);
    }
    mp_free_number(mp->internal[q].v.data.n);
    if (internal_run(q) == 1) {
        mp->run_internal(mp, mp_restore_internal_code, q, internal_type(q), internal_name(q));
    }
    mp->internal[q] = saved;
}

/*tex

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.

*/

static void mp_unsave(MP mp)
{
    mp_save p;
    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_free_save(mp, mp->save_ptr);
        mp->save_ptr = p;
    }
    p = mp->save_ptr->link;
 // mp_memory_free(mp->save_ptr);
    mp_free_save(mp, mp->save_ptr);
    mp->save_ptr = p;
}

/*tex

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.

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.

A curl of 1 is shown explicitly, so that the user sees clearly that \MP's default curl is present.

*/

static const char *mp_knot_type_string[] = {
    "endpoint",
    "explicit",
    "given",
    "curl",
    "open",
    "endcycle",
};

static const char *mp_knot_state_string[] = {
    "regular",
    "begin",
    "end",
    "single",
};

static const char * mp_valid_knot_type_string(int n)
{
    return mp_valid_knot_type(n) ? mp_knot_type_string[n] : "invalid";
}
static const char * mp_valid_knot_state_string(int n)
{
    return mp_valid_knot_state(n) ? mp_knot_state_string[n] : "invalid";
}

static void mp_print_path_only(MP mp, mp_knot h) /* todo: spaces, just after things */
{
    mp_knot p = h;
    do {
        mp_knot q = mp_next_knot(p);
        if (p == NULL) {
            mp_print_nl(mp, "[no path, no points]");
            return;
        } else if (q == NULL) {
            mp_print_nl(mp, "[no path, no back link]");
            return;
        } else {
            mp_print_format(mp, "<%s %s : %s> ",
                mp_valid_knot_type_string(mp_left_type(p)),
                mp_valid_knot_type_string(mp_right_type(p)),
                mp_valid_knot_state_string(mp_knotstate(p))
            );
            /*tex print information for adjacent knots |p| and |q| */
            mp_print_format(mp, "(%N,%N) ", p->x_coord, p->y_coord);
            switch (mp_right_type(p)) {
                case mp_endpoint_knot:
                    {
                        if (mp_left_type(p) == mp_open_knot) {
                            mp_print_string(mp, "{open ?} "); /* can't happen */
                        }
                        if ((mp_left_type(q) != mp_endpoint_knot) || (q != h)) {
                            q = NULL; /* force an error */
                        }
                        goto DONE1;
                    }
                 // break;
                case mp_explicit_knot:
                    {
                        /*tex print control points between |p| and |q|, then |goto done1| */
                        if (mp_left_type(q) != mp_explicit_knot) {
                            mp_print_format(mp, ".. controls (%N,%N) and ?? ", p->right_x, p->right_y); /* can't happen */
                        } else {
                            mp_print_format(mp, ".. controls (%N,%N) and (%N,%N) ", p->right_x, p->right_y, q->left_x, q->left_y);
                        }
                        goto DONE1;
                    }
                 // break;
                case mp_open_knot:
                    {
                        /*tex 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_string(mp, "{open ?} "); /* can't happen */
                        }
                    }
                    break;
                case mp_curl_knot:
                case mp_given_knot:
                    {
                        /*tex print information for a curve that begins |curl| or |given| */
                        if (mp_left_type(p) == mp_open_knot) {
                            mp_print_string(mp, "{open ?} "); /* can't happen */
                        }
                        if (mp_right_type(p) == mp_curl_knot) {
                            mp_print_format(mp, "{curl %N} ", p->right_curl);
                        } else {
                            mp_number n_sin, n_cos;
                            mp_new_fraction(n_sin);
                            mp_new_fraction(n_cos);
                            mp_n_sin_cos(p->right_given, n_cos, n_sin);
                            mp_print_format(mp, "{%N,%N} ", n_sin, n_cos);
                            mp_free_number(n_sin);
                            mp_free_number(n_cos);
                        }
                    }
                    break;
                default:
                    {
                        mp_print_string(mp, "??? "); /* can't happen */
                    }
                    break;
            }
            if (mp_left_type(q) <= mp_explicit_knot) {
                mp_print_string(mp, ".. control ? "); /* can't happen */
            } else if ((! mp_number_equal(p->right_tension, mp_unity_t)) || (! mp_number_equal(q->left_tension, mp_unity_t))) {
                /*tex Print tension between |p| and |q| */
                mp_number absval;
                mp_print_string(mp, ".. tension ");
                if (mp_number_negative(p->right_tension)) {
                    mp_print_string(mp, "atleast ");
                }
                mp_new_number_abs(absval, p->right_tension);
                mp_print_number(mp, absval);
                if (! mp_number_equal(p->right_tension, q->left_tension)) {
                    mp_print_string(mp, "and ");
                    if (mp_number_negative(q->left_tension)) {
                        mp_print_string(mp, "atleast ");
                    }
                    mp_number_abs_clone(absval, p->left_tension);
                    mp_print_number(mp, absval);
                }
                mp_free_number(absval);
            }

          DONE1:
            p = q;
            if (p && ((p != h) || (mp_left_type(h) != mp_endpoint_knot))) {
                /*tex  print two dots, followed by |given| or |curl| if present */
                mp_number n_sin, n_cos;
                mp_new_fraction(n_sin);
                mp_new_fraction(n_cos);
                if (mp_left_type(p) == mp_given_knot) {
                    mp_n_sin_cos(p->left_given, n_cos, n_sin);
                    mp_print_format(mp, "%l.. {%N,%N} ", n_cos, n_sin);
                } else if (mp_left_type(p) == mp_curl_knot) {
                    mp_print_format(mp, "%l.. {curl %N} ", p->left_curl);
                } else {
                    mp_print_format(mp, "%l.. ");
                }
                mp_free_number(n_sin);
                mp_free_number(n_cos);
            }
        }
    } while (p != h);
    if (mp_left_type(h) != mp_endpoint_knot) {
        mp_print_string(mp, "cycle");
    }
}

/*tex

It is convenient to have another version of |pr_path| that prints the path as a diagnostic message.

*/

static void mp_print_path(MP mp, mp_knot h, const char *s, int nuline)
{
    mp_begin_diagnostic_print(mp, "Path", s, nuline);
    mp_print_ln(mp);
    mp_print_path_only(mp, h);
    mp_end_diagnostic(mp, 1);
}

 void mp_show_path(MP mp, mp_knot h) /* public */
 {
    mp_print_ln(mp);
    mp_print_path_only(mp, h);
    mp_print_ln(mp);
 }

static inline mp_knot mp_aux_new_knot(MP mp)
{
    mp_knot k = (mp_knot) mp->memory_pool[mp_knot_pool].list;
    mp->memory_pool[mp_knot_pool].used++;
    if (mp->memory_pool[mp_knot_pool].used > mp->memory_pool[mp_knot_pool].max) {
        mp->memory_pool[mp_knot_pool].max = mp->memory_pool[mp_knot_pool].used;
    }
    if (k) {
        mp->memory_pool[mp_knot_pool].list = k->link;
        mp->memory_pool[mp_knot_pool].pool--;
        k->link = NULL;
    } else {
        k = mp_memory_allocate(sizeof(struct mp_knot_data));
    }
    return k;
}

static mp_knot mp_new_knot(MP mp)
{
    mp_knot k = mp_aux_new_knot(mp);
    memset(k, 0, sizeof(struct mp_knot_data));
    mp_new_number(k->x_coord);
    mp_new_number(k->y_coord);
    mp_new_number(k->left_x);
    mp_new_number(k->left_y);
    mp_new_number(k->right_x);
    mp_new_number(k->right_y);
    mp_knotstate(k) = mp_regular_knot;
    mp_originator(k) = mp_regular_knot;
    return k;
}

static void mp_free_knot(MP mp, mp_knot p)
{
    if (mp->math_mode > mp_math_double_mode) {
        mp_free_number(p->x_coord);
        mp_free_number(p->y_coord);
        mp_free_number(p->left_x);
        mp_free_number(p->left_y);
        mp_free_number(p->right_x);
        mp_free_number(p->right_y);
    }
    mp->memory_pool[mp_knot_pool].used--;
    if (mp->memory_pool[mp_knot_pool].pool < mp->memory_pool[mp_knot_pool].kept) {
        mp->memory_pool[mp_knot_pool].pool++;
        p->link = mp->memory_pool[mp_knot_pool].list;
        mp->memory_pool[mp_knot_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_knot_pool(MP mp)
{
    mp_knot p = mp->memory_pool[mp_knot_pool].list;
    while (p) {
        mp_knot n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex
    If we want to duplicate a knot node, we can say |copy_knot|:
*/

static mp_knot mp_copy_knot(MP mp, mp_knot source)
{
    mp_knot k = mp_aux_new_knot(mp); /* maybe avoid clear overhead or do copy in aux */
    memcpy(k, source, sizeof(struct mp_knot_data));
    if (mp->math_mode > mp_math_double_mode) {
        mp_new_number_clone(k->x_coord, source->x_coord);
        mp_new_number_clone(k->y_coord, source->y_coord);
        mp_new_number_clone(k->left_x, source->left_x);
        mp_new_number_clone(k->left_y, source->left_y);
        mp_new_number_clone(k->right_x, source->right_x);
        mp_new_number_clone(k->right_y, source->right_y);
    }
    mp_prev_knot(k) = NULL;
    mp_next_knot(k) = NULL;
    return k;
}

/*tex
    The |copy_path| routine makes a clone of a given path.
*/

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;
    }
}

/*tex

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.

*/

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);
        mp_number_clone(qq->x_coord, pp->x_coord);
        mp_number_clone(qq->y_coord, pp->y_coord);
        mp_number_clone(qq->right_x, pp->left_x);
        mp_number_clone(qq->right_y, pp->left_y);
        mp_number_clone(qq->left_x, pp->right_x);
        mp_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);
        }
    }
}

/*tex

When a cyclic list of knot nodes is no longer needed, it can be recycled by calling the following
subroutine.

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.

*/

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_free_knot(mp, q);
            q = r;
        } while (q != p);
    }
}

/*tex

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).

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.

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]=|. The path angle $\psi_k$ between $z_k-z_{k-1}$ and $z\k-z_k$ will be stored in
|psi[k]|.

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} 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

$$
{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 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}.
$$

*/

static void mp_make_choices(MP mp, mp_knot knots)
{
    mp_knot h;    /* the first breakpoint */
    mp_knot p, q; /* consecutive breakpoints being processed */
    int k, n;     /* current and final knot numbers */
    mp_knot s, t; /* registers for list traversal */
    /*tex
        Make sure that |arithmic_error=false|.
    */
    mp_check_arithmic(mp);
    if (mp_number_positive(internal_value(mp_tracing_choices_internal))) {
        mp_print_path(mp, knots, ", before choices", 1);
    }
    /*tex
        If consecutive knots are equal, join them explicitly. 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.
    */
    p = knots;
    do {
        q = mp_next_knot(p);
        if (mp_number_equal(p->x_coord, q->x_coord) && mp_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;
                mp_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;
                mp_set_number_to_unity(q->right_curl);
            }
            mp_number_clone(p->right_x, p->x_coord);
            mp_number_clone(q->left_x, p->x_coord);
            mp_number_clone(p->right_y, p->y_coord);
            mp_number_clone(q->left_y, p->y_coord);
        }
        p = q;
    } while (p != knots);
    /*tex
        Find the first breakpoint, |h|, on the path; insert an artificial breakpoint if the path is
        an unbroken cycle. 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|.
    */
    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;
            }
        }
    }
    p = h;
    do {
        /*tex
            Fill in the control points between |p| and the next breakpoint, then advance |p| to
            that breakpoint. 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|.
        */
        q = mp_next_knot(p);
        if (mp_right_type(p) >= mp_given_knot) {
            /*tex
                Fill in the control information between consecutive breakpoints |p| and |q|.
            */
            mp_number sine, cosine;       /*tex trig functions of various angles */
            mp_number arg1, arg2, r1, r2;
            mp_number delx, dely;         /*tex directions where |open| meets |explicit| */
            mp_new_fraction(sine);
            mp_new_fraction(cosine);
            mp_new_number(arg1);
            mp_new_number(arg2);
            mp_new_fraction(r1);
            mp_new_fraction(r2);
            mp_new_number(delx);
            mp_new_number(dely);
            /* */
            while ((mp_left_type(q) == mp_open_knot) && (mp_right_type(q) == mp_open_knot)) {
                q = mp_next_knot(q);
            }
            /*tex
                Calculate the turning angles $\psi_k$ and the distances $d_{k,k+1}$; set $n$ to
                the length of the path
            */
            {
              RESTART:
                k = 0;
                s = p;
                n = mp->path_size;
                do {
                    t = mp_next_knot(s);
                    mp_set_number_from_subtraction(mp->delta_x[k], t->x_coord, s->x_coord);
                    mp_set_number_from_subtraction(mp->delta_y[k], t->y_coord, s->y_coord);
                    mp_pyth_add(mp->delta[k], mp->delta_x[k], mp->delta_y[k]);
                    if (k > 0) {
                        mp_make_fraction(r1, mp->delta_y[k - 1], mp->delta[k - 1]);
                        mp_number_clone(sine, r1);
                        mp_make_fraction(r2, mp->delta_x[k - 1], mp->delta[k - 1]);
                        mp_number_clone(cosine, r2);
                        mp_take_fraction(r1, mp->delta_x[k], cosine);
                        mp_take_fraction(r2, mp->delta_y[k], sine);
                        mp_set_number_from_addition(arg1, r1, r2);
                        mp_take_fraction(r1, mp->delta_y[k], cosine);
                        mp_take_fraction(r2, mp->delta_x[k], sine);
                        mp_set_number_from_subtraction(arg2, r1, r2);
                        mp_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));
                        /*tex Retry, loop size has changed. */
                        goto RESTART;
                    } else if (s == q) {
                        n = k;
                    }
                } while (! ((k >= n) && (mp_left_type(s) != mp_end_cycle_knot)));
                if (k == n) {
                    mp_set_number_to_zero(mp->psi[k]);
                } else {
                    mp_number_clone(mp->psi[k], mp->psi[1]);
                }
            }
            /*tex
                Remove |open| types at the breakpoints. 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|.
            */
            if (mp_left_type(q) == mp_open_knot) {
                mp_set_number_from_subtraction(delx, q->right_x, q->x_coord);
                mp_set_number_from_subtraction(dely, q->right_y, q->y_coord);
                if (mp_number_zero(delx) && mp_number_zero(dely)) {
                    mp_left_type(q) = mp_curl_knot;
                    mp_set_number_to_unity(q->left_curl);
                } else {
                    mp_left_type(q) = mp_given_knot;
                    mp_n_arg(q->left_given, delx, dely);
                }
            }
            if ((mp_right_type(p) == mp_open_knot) && (mp_left_type(p) == mp_explicit_knot)) {
                mp_set_number_from_subtraction(delx, p->x_coord, p->left_x);
                mp_set_number_from_subtraction(dely, p->y_coord, p->left_y);
                if (mp_number_zero(delx) && mp_number_zero(dely)) {
                    mp_right_type(p) = mp_curl_knot;
                    mp_set_number_to_unity(p->right_curl);
                } else {
                    mp_right_type(p) = mp_given_knot;
                    mp_n_arg(p->right_given, delx, dely);
                }
            }
            mp_free_number(sine);
            mp_free_number(cosine);
            mp_free_number(arg1);
            mp_free_number(arg2);
            mp_free_number(r1);
            mp_free_number(r2);
            mp_free_number(delx);
            mp_free_number(dely);
            mp_solve_choices(mp, p, q, n);
        } else if (mp_right_type(p) == mp_endpoint_knot) {
            /*tex
                Give reasonable values for the unused control points between |p| and~|q|.
                This step makes it possible to transform an explicitly computed path without
                checking the |mp_left_type| and |mp_right_type| fields.
            */
            mp_number_clone(p->right_x, p->x_coord);
            mp_number_clone(p->right_y, p->y_coord);
            mp_number_clone(q->left_x, q->x_coord);
            mp_number_clone(q->left_y, q->y_coord);
        }
        p = q;
    } while (p != h);
    if (mp_number_positive(internal_value(mp_tracing_choices_internal))) {
        mp_print_path(mp, knots, ", after choices", 1);
    }
    if (mp->arithmic_error) {
        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."
        );
        mp_get_x_next(mp);
        mp->arithmic_error = 0;
    }
}

/*tex

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|.

*/

static 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++) {
        mp_new_number(mp->delta_x[k]);
        mp_new_number(mp->delta_y[k]);
        mp_new_number(mp->delta[k]);
        mp_new_angle(mp->psi[k]);
        mp_new_angle(mp->theta[k]);
        mp_new_fraction(mp->uu[k]);
        mp_new_angle(mp->vv[k]);
        mp_new_fraction(mp->ww[k]);
    }
    mp->path_size = l;
}

/*tex

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.

*/

static void mp_solve_choices(MP mp, mp_knot p, mp_knot q, int n)
{
    int k = 0; /*tex current knot number */
    mp_knot r = 0;
    mp_knot s = p;
    mp_number ff;
    mp_new_fraction(ff);
    while (1) {
        mp_knot t = mp_next_knot(s);
        if (k == 0) {
            /*tex
                Get the linear equations started; or |return| with the control points in place, if
                linear equations needn't be solved.

                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$.
            */
            switch (mp_right_type(s)) {
                case mp_given_knot:
                    if (mp_left_type(t) == mp_given_knot) {
                        /*tex
                            Reduce to simple case of two givens and |return|.
                        */
                        mp_number arg1;
                        mp_number narg;
                        mp_new_angle(narg);
                        mp_n_arg(narg, mp->delta_x[0], mp->delta_y[0]);
                        mp_new_number_from_sub(arg1, p->right_given, narg);
                        mp_n_sin_cos(arg1, mp->ct, mp->st);
                        mp_set_number_from_subtraction(arg1, q->left_given, narg);
                        mp_n_sin_cos(arg1, mp->cf, mp->sf);
                        mp_number_negate(mp->sf);
                        mp_set_controls(mp, p, q, 0);
                        mp_free_number(narg);
                        mp_free_number(arg1);
                        mp_free_number(ff);
                        return;
                    } else {
                        /*tex
                            Set up the equation for a given value of $\theta_0$.
                        */
                        mp_number narg;
                        mp_new_angle(narg);
                        mp_n_arg(narg, mp->delta_x[0], mp->delta_y[0]);
                        mp_set_number_from_subtraction(mp->vv[0], s->right_given, narg);
                        mp_free_number(narg);
                        mp_reduce_angle(mp, &mp->vv[0]);
                        mp_set_number_to_zero(mp->uu[0]);
                        mp_set_number_to_zero(mp->ww[0]);
                        break;
                    }
                case mp_curl_knot:
                    if (mp_left_type(t) == mp_curl_knot) {
                        /*tex
                            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;
                        mp_new_number_abs(lt, q->left_tension);
                        mp_new_number_abs(rt, p->right_tension);
                        if (mp_number_unity(rt)) {
                            mp_number arg2;
                            if (mp_number_nonnegative(mp->delta_x[0])) {
                                mp_new_number_from_add(arg2, mp->delta_x[0], mp_epsilon_t);
                            } else {
                                mp_new_number_from_sub(arg2, mp->delta_x[0], mp_epsilon_t);
                            }
                            mp_set_number_int_div(arg2, 3);
                            mp_set_number_from_addition(p->right_x, p->x_coord, arg2);
                            if (mp_number_nonnegative(mp->delta_y[0])) {
                                mp_set_number_from_addition(arg2, mp->delta_y[0], mp_epsilon_t);
                            } else {
                                mp_set_number_from_subtraction(arg2, mp->delta_y[0], mp_epsilon_t);
                            }
                            mp_set_number_int_div(arg2, 3);
                            mp_set_number_from_addition(p->right_y, p->y_coord, arg2);
                            mp_free_number(arg2);
                        } else {
                            mp_number arg2, r1;
                            mp_new_fraction(r1);
                            mp_new_number_clone(arg2, rt);
                            mp_number_multiply_int(arg2, 3);
                            mp_make_fraction(ff, mp_unity_t, arg2); /* $\alpha/3$ */
                            mp_free_number(arg2);
                            mp_take_fraction(r1, mp->delta_x[0], ff);
                            mp_set_number_from_addition(p->right_x, p->x_coord, r1);
                            mp_take_fraction(r1, mp->delta_y[0], ff);
                            mp_set_number_from_addition(p->right_y, p->y_coord, r1);
                        }
                        if (mp_number_unity(lt)) {
                            mp_number arg2;
                            if (mp_number_nonnegative(mp->delta_x[0])) {
                                mp_new_number_from_add(arg2, mp->delta_x[0], mp_epsilon_t);
                            } else {
                                mp_new_number_from_sub(arg2, mp->delta_x[0], mp_epsilon_t);
                            }
                            mp_set_number_int_div(arg2, 3);
                            mp_set_number_from_subtraction(q->left_x, q->x_coord, arg2);
                            if (mp_number_nonnegative(mp->delta_y[0])) {
                                mp_set_number_from_addition(arg2, mp->delta_y[0], mp_epsilon_t);
                            } else {
                                mp_set_number_from_subtraction(arg2, mp->delta_y[0], mp_epsilon_t);
                            }
                            mp_set_number_int_div(arg2, 3);
                            mp_set_number_from_subtraction(q->left_y, q->y_coord, arg2);
                            mp_free_number(arg2);
                        } else {
                            mp_number arg2, r1;
                            mp_new_fraction(r1);
                            mp_new_number_clone(arg2, lt);
                            mp_number_multiply_int(arg2, 3);
                            mp_make_fraction(ff, mp_unity_t, arg2); /* $\beta/3$ */
                            mp_free_number(arg2);
                            mp_take_fraction(r1, mp->delta_x[0], ff);
                            mp_set_number_from_subtraction(q->left_x, q->x_coord, r1);
                            mp_take_fraction(r1, mp->delta_y[0], ff);
                            mp_set_number_from_subtraction(q->left_y, q->y_coord, r1);
                            mp_free_number(r1);
                        }
                        mp_free_number(ff);
                        mp_free_number(lt);
                        mp_free_number(rt);
                        return;
                    } else {
                        /*tex
                            Set up the equation for a curl at $\theta_0$.
                        */
                        mp_number lt, rt, cc; /* tension values */
                        mp_new_number_clone(cc, s->right_curl);
                        mp_new_number_abs(lt, t->left_tension);
                        mp_new_number_abs(rt, s->right_tension);
                        if (mp_number_unity(rt) && mp_number_unity(lt)) {
                            mp_number arg1, arg2;
                            mp_new_number_clone(arg1, cc);
                            mp_new_number_clone(arg2, cc);
                            mp_number_double(arg1);
                            mp_number_add(arg1, mp_unity_t);
                            mp_number_add(arg2, mp_two_t);
                            mp_make_fraction(mp->uu[0], arg1, arg2);
                            mp_free_number(arg1);
                            mp_free_number(arg2);
                        } else {
                            mp_curl_ratio(mp, &mp->uu[0], &cc, &rt, &lt);
                        }
                        mp_take_fraction(mp->vv[0], mp->psi[1], mp->uu[0]);
                        mp_number_negate(mp->vv[0]);
                        mp_set_number_to_zero(mp->ww[0]);
                        mp_free_number(rt);
                        mp_free_number(lt);
                        mp_free_number(cc);
                    }
                    break;
                case mp_open_knot:
                    mp_set_number_to_zero(mp->uu[0]);
                    mp_set_number_to_zero(mp->vv[0]);
                    mp_number_clone(mp->ww[0], mp_fraction_one_t);
                    /* this begins a cycle */
                    break;
            }
        } else {
            switch (mp_left_type(s)) {
                case mp_end_cycle_knot:
                case mp_open_knot:
                    {
                        /*tex
                            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.

                            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.
                        */
                        mp_number aa, bb, cc, acc; /* temporary registers */
                        mp_number dd, ee;          /* likewise, but |scaled| */
                        mp_new_fraction(aa);
                        mp_new_fraction(bb);
                        mp_new_fraction(cc);
                        mp_new_fraction(acc);
                        mp_new_number(dd);
                        mp_new_number(ee);
                        /*tex
                            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$.

                            Since tension values are never less than 3/4, the values |aa| and |bb|
                            computed here are never more than 4/5.
                        */
                        {
                            mp_number absval;
                            mp_new_number_abs(absval, r->right_tension);
                            if (mp_number_equal(absval, mp_unity_t)) {
                                mp_number_clone(aa, mp_fraction_half_t);
                                mp_number_clone(dd, mp->delta[k]);
                                mp_number_double(dd);
                            } else {
                                mp_number arg1, arg2, ret;
                                mp_new_number_abs(arg2, r->right_tension);
                                mp_number_multiply_int(arg2, 3);
                                mp_number_subtract(arg2, mp_unity_t);
                                mp_make_fraction(aa, mp_unity_t, arg2);
                                mp_number_abs_clone(arg2, r->right_tension);
                                mp_new_fraction(ret);
                                mp_make_fraction(ret, mp_unity_t, arg2);
                                mp_new_number_from_sub(arg1, mp_fraction_three_t, ret);
                                mp_take_fraction(arg2, mp->delta[k], arg1);
                                mp_number_clone(dd, arg2);
                                mp_free_number(ret);
                                mp_free_number(arg1);
                                mp_free_number(arg2);
                            }
                            mp_number_abs_clone(absval, t->left_tension);
                            if (mp_number_equal(absval, mp_unity_t)) {
                                mp_number_clone(bb, mp_fraction_half_t);
                                mp_number_clone(ee, mp->delta[k - 1]);
                                mp_number_double(ee);
                            } else {
                                mp_number arg1, arg2, ret;
                                mp_new_number_abs(arg2, t->left_tension);
                                mp_number_multiply_int(arg2, 3);
                                mp_number_subtract(arg2, mp_unity_t);
                                mp_make_fraction(bb, mp_unity_t, arg2);
                                mp_number_abs_clone(arg2, t->left_tension);
                                mp_new_fraction(ret);
                                mp_make_fraction(ret, mp_unity_t, arg2);
                                mp_new_number_from_sub(arg1, mp_fraction_three_t, ret);
                                mp_take_fraction(ee, mp->delta[k - 1], arg1);
                                mp_free_number(ret);
                                mp_free_number(arg1);
                                mp_free_number(arg2);
                            }
                            mp_free_number(absval);
                        }
                        {
                            mp_number r1;
                            mp_new_number(r1);
                            mp_take_fraction(r1, mp->uu[k - 1], aa);
                            mp_set_number_from_subtraction(cc, mp_fraction_one_t, r1);
                            mp_free_number(r1);
                        }
                        /*tex
                            Calculate the ratio $|ff| = c_k / (c_k + b_k-u_{k - 1}a_k)$. 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.
                        */
                        {
                            mp_number rt, lt;
                            mp_number arg2;
                            mp_new_number_clone(arg2, dd);
                            mp_take_fraction(dd, arg2, cc);
                            mp_new_number_abs(lt, s->left_tension);
                            mp_new_number_abs(rt, s->right_tension);
                            if (! mp_number_equal(lt, rt)) {
                                /*tex $\beta_k^{-1}\ne\alpha_k^{-1}$ */
                                mp_number r1;
                                mp_new_number(r1);
                                if (mp_number_less(lt, rt)) {
                                    /*tex $\alpha_k^2/\beta_k^2$ */
                                    mp_make_fraction(r1, lt, rt);
                                    mp_take_fraction(ff, r1, r1);
                                    mp_number_clone(r1, dd);
                                    mp_take_fraction(dd, r1, ff);
                                } else {
                                    /*tex $\beta_k^2/\alpha_k^2$ */
                                    mp_make_fraction(r1, rt, lt);
                                    mp_take_fraction(ff, r1, r1);
                                    mp_number_clone(r1, ee);
                                    mp_take_fraction(ee, r1, ff);
                                }
                                mp_free_number(r1);
                            }
                            mp_free_number(rt);
                            mp_free_number(lt);
                            mp_set_number_from_addition(arg2, dd, ee);
                            mp_make_fraction(ff, ee, arg2);
                            mp_free_number(arg2);
                        }
                        mp_take_fraction(mp->uu[k], ff, bb);
                        /*tex
                            Calculate the values of $v_k$ and $w_k$. 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$.
                        */
                        mp_take_fraction(acc, mp->psi[k + 1], mp->uu[k]);
                        mp_number_negate(acc);
                        if (mp_right_type(r) == mp_curl_knot) {
                            mp_number r1, arg2;
                            mp_new_fraction(r1);
                            mp_new_number_from_sub(arg2, mp_fraction_one_t, ff);
                            mp_take_fraction(r1, mp->psi[1], arg2);
                            mp_set_number_to_zero(mp->ww[k]);
                            mp_set_number_from_subtraction(mp->vv[k], acc, r1);
                            mp_free_number(r1);
                            mp_free_number(arg2);
                        } else {
                            mp_number arg1, r1;
                            mp_new_fraction(r1);
                            mp_new_number_from_sub(arg1, mp_fraction_one_t, ff);
                            /*tex This is $B_k/(C_k+B_k-u_{k-1}A_k)<5$. */
                            mp_make_fraction(ff, arg1, cc);
                            mp_free_number(arg1);
                            mp_take_fraction(r1, mp->psi[k], ff);
                            mp_number_subtract(acc, r1);
                            mp_number_clone(r1, ff);
                            /*tex This is $A_k/(C_k+B_k-u_{k-1}A_k)$. */
                            mp_take_fraction(ff, r1, aa);
                            mp_take_fraction(r1, mp->vv[k - 1], ff);
                            mp_set_number_from_subtraction(mp->vv[k], acc, r1 );
                            if (mp_number_zero(mp->ww[k - 1])) {
                                mp_set_number_to_zero(mp->ww[k]);
                            } else {
                                mp_take_fraction(mp->ww[k], mp->ww[k - 1], ff);
                                mp_number_negate(mp->ww[k]);
                            }
                            mp_free_number(r1);
                        }
                        if (mp_left_type(s) == mp_end_cycle_knot) {
                            /*tex
                                Adjust $\theta_n$ to equal $\theta_0$ and |goto found|. 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$.
                            */
                            mp_number arg2, r1;
                            mp_new_number(arg2);
                            mp_new_number(r1);
                            mp_set_number_to_zero(aa);
                            mp_number_clone(bb, mp_fraction_one_t);
                            /*tex We have |k=n|. */
                            do {
                                --k;
                                if (k == 0) {
                                    k = n;
                                }
                                mp_take_fraction(r1, aa, mp->uu[k]);
                                mp_set_number_from_subtraction(aa, mp->vv[k], r1);
                                mp_take_fraction(r1, bb, mp->uu[k]);
                                mp_set_number_from_subtraction(bb, mp->ww[k], r1);
                            } while (k != n);
                            /*tex Now $\theta_n=|aa|+|bb|\cdot\theta_n$. */
                            mp_set_number_from_subtraction(arg2, mp_fraction_one_t, bb);
                            mp_make_fraction(r1, aa, arg2);
                            mp_number_clone(aa, r1);
                            mp_number_clone(mp->theta[n], aa);
                            mp_number_clone(mp->vv[0], aa);
                            for (k = 1; k < n; k++) {
                                mp_take_fraction(r1, aa, mp->ww[k]);
                                mp_number_add(mp->vv[k], r1);
                            }
                            mp_free_number(arg2);
                            mp_free_number(r1);
                            mp_free_number(aa);
                            mp_free_number(bb);
                            mp_free_number(cc);
                            mp_free_number(acc);
                            mp_free_number(dd);
                            mp_free_number(ee);
                            goto FOUND;
                        } else {
                            mp_free_number(aa);
                            mp_free_number(bb);
                            mp_free_number(cc);
                            mp_free_number(acc);
                            mp_free_number(dd);
                            mp_free_number(ee);
                            break;
                        }
                    }
                case mp_curl_knot:
                    /*tex
                        Set up the equation for a curl at $\theta_n$ and |goto found|.
                    */
                    {
                        mp_number lt, rt, cc; /* tension values */
                        mp_new_number_clone(cc, s->left_curl);
                        mp_new_number_abs(lt, s->left_tension);
                        mp_new_number_abs(rt, r->right_tension);
                        if (mp_number_unity(rt) && mp_number_unity(lt)) {
                            mp_number arg1, arg2;
                            mp_new_number_clone(arg1, cc);
                            mp_new_number_clone(arg2, cc);
                            mp_number_double(arg1);
                            mp_number_add(arg1, mp_unity_t);
                            mp_number_add(arg2, mp_two_t);
                            mp_make_fraction(ff, arg1, arg2);
                            mp_free_number(arg1);
                            mp_free_number(arg2);
                        } else {
                            mp_curl_ratio(mp, &ff, &cc, &lt, &rt);
                        }
                        {
                            mp_number arg1, arg2, r1;
                            mp_new_fraction(r1);
                            mp_new_fraction(arg1);
                            mp_take_fraction(arg1, mp->vv[n - 1], ff);
                            mp_take_fraction(r1, ff, mp->uu[n - 1]);
                            mp_new_number_from_sub(arg2, mp_fraction_one_t, r1);
                            mp_make_fraction(mp->theta[n], arg1, arg2);
                            mp_number_negate(mp->theta[n]);
                            mp_free_number(r1);
                            mp_free_number(arg1);
                            mp_free_number(arg2);
                        }
                        mp_free_number(rt);
                        mp_free_number(lt);
                        mp_free_number(cc);
                        goto FOUND;
                    }
                case mp_given_knot:
                    /*tex
                        Calculate the given value of $\theta_n$ and |goto found|.
                    */
                    {
                        mp_number narg;
                        mp_new_angle(narg);
                        mp_n_arg(narg, mp->delta_x[n - 1], mp->delta_y[n - 1]);
                        mp_set_number_from_subtraction(mp->theta[n], s->left_given, narg);
                        mp_free_number(narg);
                        mp_reduce_angle(mp, &mp->theta[n]);
                        goto FOUND;
                    }
            }
        }
        r = s;
        s = t;
        ++k;
    }
  FOUND:
    /*tex
        Finish choosing angles and assigning control points.
    */
    {
        mp_number r1;
        mp_new_number(r1);
        for (k = n - 1; k >= 0; k--) {
            mp_take_fraction(r1, mp->theta[k + 1], mp->uu[k]);
            mp_set_number_from_subtraction(mp->theta[k], mp->vv[k], r1);
        }
        mp_free_number(r1);
    }
    s = p;
    k = 0;
    {
        mp_number arg;
        mp_new_number(arg);
        do {
            mp_knot t = mp_next_knot(s);
            mp_n_sin_cos(mp->theta[k], mp->ct, mp->st);
            mp_number_negated_clone(arg, mp->psi[k + 1]);
            mp_number_subtract(arg, mp->theta[k + 1]);
            mp_n_sin_cos(arg, mp->cf, mp->sf);
            mp_set_controls(mp, s, t, k);
            ++k;
            s = t;
        } while (k != n);
        mp_free_number(arg);
    }
    mp_free_number(ff);
}

static void mp_reduce_angle(MP mp, mp_number *a)
{
    mp_number abs_a;
    mp_new_number_abs(abs_a, *a);
    if (mp_number_greater(abs_a, mp_one_eighty_deg_t)) {
        if (mp_number_positive(*a)) {
            mp_number_subtract(*a, mp_three_sixty_deg_t);
        } else {
            mp_number_add(*a, mp_three_sixty_deg_t);
        }
    }
    mp_free_number(abs_a);
}

/*tex

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.

*/

static 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;
    mp_number arg1;
    mp_new_number(arg1);
    mp_new_fraction(alpha);
    mp_new_fraction(beta);
    mp_new_fraction(gamma);
    mp_new_fraction(ff);
    mp_new_fraction(denom);
    mp_new_fraction(num);
    mp_make_fraction(alpha, mp_unity_t, *a_tension);
    mp_make_fraction(beta, mp_unity_t, *b_tension);
    mp_number_clone(gamma, *gamma_orig);
    if (mp_number_lessequal(alpha, beta)) {
        mp_make_fraction(ff, alpha, beta);
        mp_number_clone(arg1, ff);
        mp_take_fraction(ff, arg1, arg1);
        mp_number_clone(arg1, gamma);
        mp_take_fraction(gamma, arg1, ff);
        mp_convert_fraction_to_scaled(beta);
        mp_take_fraction(denom, gamma, alpha);
        mp_number_add(denom, mp_three_t);
    } else {
        mp_make_fraction(ff, beta, alpha);
        mp_number_clone(arg1, ff);
        mp_take_fraction(ff, arg1, arg1);
        mp_take_fraction(arg1, beta, ff);
        mp_convert_fraction_to_scaled(arg1);
        mp_number_clone(beta, arg1);
        mp_take_fraction(denom, gamma, alpha);
        mp_set_number_from_div(arg1, ff, mp_twelvebits_3);
        mp_number_add(denom, arg1);
    }
    mp_number_subtract(denom, beta);
    mp_set_number_from_subtraction(arg1, mp_fraction_three_t, alpha);
    mp_take_fraction(num, gamma, arg1);
    mp_number_add(num, beta);
    mp_number_clone(arg1, denom);
    mp_number_double(arg1);
    mp_number_double(arg1); /* arg1 = 4*denom */
    if (mp_number_greaterequal(num, arg1)) {
        mp_number_clone(*ret, mp_fraction_four_t);
    } else {
        mp_make_fraction(*ret, num, denom);
    }
    mp_free_number(alpha);
    mp_free_number(beta);
    mp_free_number(gamma);
    mp_free_number(num);
    mp_free_number(denom);
    mp_free_number(ff);
    mp_free_number(arg1);
}

/*tex

We're in the home stretch now.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.

*/

static void mp_set_controls(MP mp, mp_knot p, mp_knot q, int k)
{
    mp_number rr, ss; /*tex velocities, divided by thrice the tension */
    mp_number lt, rt; /*tex tensions */
    mp_number sine;   /*tex $\sin(\theta+\phi)$ */
    mp_number tmp;
    mp_number r1, r2;
    mp_new_number(tmp);
    mp_new_number(r1);
    mp_new_number(r2);
    mp_new_number_abs(lt, q->left_tension);
    mp_new_number_abs(rt, p->right_tension);
    mp_new_fraction(sine);
    mp_new_fraction(rr);
    mp_new_fraction(ss);
    mp_velocity(rr, mp->st, mp->ct, mp->sf, mp->cf, rt);
    mp_velocity(ss, mp->sf, mp->cf, mp->st, mp->ct, lt);
    if (mp_number_negative(p->right_tension) || mp_number_negative(q->left_tension)) {
        /*tex
            Decrease the velocities, if necessary, to stay inside the bounding triangle.
        */
        if ((mp_number_nonnegative(mp->st) && mp_number_nonnegative(mp->sf)) || (mp_number_nonpositive(mp->st) && mp_number_nonpositive(mp->sf))) {
            mp_number r1, r2, arg1;
            mp_new_fraction(r1);
            mp_new_fraction(r2);
            mp_new_number_abs(arg1, mp->st);
            mp_take_fraction(r1, arg1, mp->cf);
            mp_number_abs_clone(arg1, mp->sf);
            mp_take_fraction(r2, arg1, mp->ct);
            mp_set_number_from_addition(sine, r1, r2);
            if (mp_number_positive(sine)) {
                mp_set_number_from_addition(arg1, mp_fraction_one_t, mp_unity_t);  /* safety factor */
                mp_number_clone(r1, sine);
                mp_take_fraction(sine, r1, arg1);
                if (mp_number_negative(p->right_tension)) {
                    mp_number_abs_clone(arg1, mp->sf);
                    if (mp_ab_vs_cd(arg1, mp_fraction_one_t, rr, sine) < 0) {
                        mp_number_abs_clone(arg1, mp->sf);
                        mp_make_fraction(rr, arg1, sine);
                    }
                }
                if (mp_number_negative(q->left_tension)) {
                    mp_number_abs_clone(arg1, mp->st);
                    if (mp_ab_vs_cd(arg1, mp_fraction_one_t, ss, sine) < 0) {
                        mp_number_abs_clone(arg1, mp->st);
                        mp_make_fraction(ss, arg1, sine);
                    }
                }
            }
            mp_free_number(arg1);
            mp_free_number(r1);
            mp_free_number(r2);
        }
    }
    mp_take_fraction(r1, mp->delta_x[k], mp->ct);
    mp_take_fraction(r2, mp->delta_y[k], mp->st);
    mp_number_subtract(r1, r2);
    mp_take_fraction(tmp, r1, rr);
    mp_set_number_from_addition(p->right_x, p->x_coord, tmp);
    mp_take_fraction(r1, mp->delta_y[k], mp->ct);
    mp_take_fraction(r2, mp->delta_x[k], mp->st);
    mp_number_add(r1, r2);
    mp_take_fraction(tmp, r1, rr);
    mp_set_number_from_addition(p->right_y, p->y_coord, tmp);
    mp_take_fraction(r1, mp->delta_x[k], mp->cf);
    mp_take_fraction(r2, mp->delta_y[k], mp->sf);
    mp_number_add(r1, r2);
    mp_take_fraction(tmp, r1, ss);
    mp_set_number_from_subtraction(q->left_x, q->x_coord, tmp);
    mp_take_fraction(r1, mp->delta_y[k], mp->cf);
    mp_take_fraction(r2, mp->delta_x[k], mp->sf);
    mp_number_subtract(r1, r2);
    mp_take_fraction(tmp, r1, ss);
    mp_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;
    mp_free_number(tmp);
    mp_free_number(r1);
    mp_free_number(r2);
    mp_free_number(lt);
    mp_free_number(rt);
    mp_free_number(rr);
    mp_free_number(ss);
    mp_free_number(sine);
}

/*tex

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}. Only the simple cases remain
to be handled.

*/

# define TOO_LARGE(a) (fabs((a)) > 4096.0)
# define PI           3.1415926535897932384626433832795028841971

static int out_of_range(MP mp, double a)
{
    mp_number t;
    (void) mp;
    mp_new_number_from_double(mp, t, fabs(a));
    if (mp_number_greaterequal(t, mp_inf_t)) {
        mp_free_number(t);
        return 1;
    } else {
        mp_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;
        mp_set_number_from_double(p->right_tension, 1.0);
        if (mp_right_type(p) == mp_endpoint_knot) {
            mp_right_type(p) = mp_open_knot;
        }
        mp_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;
        mp_set_number_from_double(q->right_tension, 1.0);
        mp_left_type(first) = mp_endpoint_knot;
        mp_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;
}

static int mp_set_knot_xy_double(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 {
        mp_set_number_from_double(p->x_coord, x);
        mp_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_xy_double(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, int type)
{
    mp_knot q = mp_create_knot(mp);
    if (q == NULL) {
        return NULL;
    } else if (! mp_set_knot_xy_double(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)) {
        if (mp_valid_knot_type(type)) {
            mp_right_type(p) = (unsigned char) type;
            mp_left_type(p) = (unsigned char) type;
        }
        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;
        mp_set_number_from_double(q->right_curl, value);
        if (mp_left_type(q) == mp_open_knot) {
            mp_left_type(q) = mp_curl_knot;
            mp_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;
        mp_set_number_from_double(q->left_curl, value);
        if (mp_right_type(q) == mp_open_knot) {
            mp_right_type(q) = mp_curl_knot;
            mp_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;
        mp_set_number_from_double(q->right_curl, value);
        if (mp_left_type(q) == mp_open_knot) {
            mp_left_type(q) = mp_curl_knot;
            mp_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_left_type(q) = mp_curl_knot;
        mp_set_number_from_double(q->left_curl, 1.0);
        mp_right_type(q) = mp_curl_knot;
        mp_set_number_from_double(q->right_curl, 0.0);
        return 1;
    }
}

int mp_set_knot_simple_left_curl(MP mp, mp_knot q)
{
    if (q == NULL) {
        return 0;
    } else {
        mp_left_type(q) = mp_curl_knot;
        mp_set_number_from_double(q->left_curl, 1.0);
        return 1;
    }
}

int mp_set_knot_simple_right_curl(MP mp, mp_knot q)
{
    if (q == NULL) {
        return 0;
    } else {
        mp_right_type(q) = mp_curl_knot;
        mp_set_number_from_double(q->right_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 {
        mp_set_number_from_double(p->right_tension, t1);
        mp_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  {
        mp_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 {
        mp_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;
        mp_set_number_from_double(p->right_x, x1);
        mp_set_number_from_double(p->right_y, y1);
        mp_left_type(q) = mp_explicit_knot;
        mp_set_number_from_double(q->left_x, x2);
        mp_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;
        mp_set_number_from_double(p->left_x, x1);
        mp_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;
        mp_set_number_from_double(p->right_x, x1);
        mp_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;
        mp_set_number_from_double(q->right_curl, value);
        if (mp_left_type(q) == mp_open_knot) {
            mp_left_type(q) = mp_given_knot;
            mp_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;
    }
}

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_arithmic_error = mp->arithmic_error;
        jmp_buf *saved_jump_buffer = mp->jump_buffer;
        mp->jump_buffer = mp_memory_allocate(sizeof(jmp_buf));
        if (mp->jump_buffer == NULL || setjmp(*(mp->jump_buffer)) != 0) {
            return 0;
        } else {
            int retval = 1;
            mp->arithmic_error = 0;
            mp_make_choices(mp, first);
            if (mp->arithmic_error) {
                retval = 0;
            }
            mp->arithmic_error = saved_arithmic_error;
            mp_memory_free(mp->jump_buffer);
            mp->jump_buffer = saved_jump_buffer;
            return retval;
        }
    }
}

void mp_free_path(MP mp, mp_knot p)
{
    mp_toss_knot_list(mp, p);
}

/*tex
    Simple accessors for |mp_knot|.
*/

double mp_number_as_double(MP mp, mp_number n) {
    (void) mp;
    return mp_number_to_double(n);
}

/*tex

\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

$$
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:

\startitemize

\startitem
    Let $z_k^{(0)}=z_k$, for |0<=k<=n|.
\stopitem

\startitem
    Let $z_k^{(j+1)}={1\over2}(z_k^{(j)}+z\k^{(j)})$, for |0<=k<n-j|, for |0<=j<n|.
\stopitem

\stoptitemize

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|.

*/

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 */
    mp_new_number(x1);
    mp_new_number(x2);
    mp_new_number(x3);
    if (c == mp_x_code) {
        mp_set_number_from_of_the_way(x1, *t, p->x_coord, p->right_x);
        mp_set_number_from_of_the_way(x2, *t, p->right_x, q->left_x);
        mp_set_number_from_of_the_way(x3, *t, q->left_x, q->x_coord);
    } else {
        mp_set_number_from_of_the_way(x1, *t, p->y_coord, p->right_y);
        mp_set_number_from_of_the_way(x2, *t, p->right_y, q->left_y);
        mp_set_number_from_of_the_way(x3, *t, q->left_y, q->y_coord);
    }
    mp_set_number_from_of_the_way(x1, *t, x1, x2);
    mp_set_number_from_of_the_way(x2, *t, x2, x3);
    mp_set_number_from_of_the_way(*r, *t, x1, x2);
    mp_free_number(x1);
    mp_free_number(x2);
    mp_free_number(x3);
}

/*tex

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.

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

\starttyping
B(knot_coord(p), right_coord(p), left_coord(q), knot_coord(q); t)
\stoptyping

for $0 < t \le 1$. 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|.

*/

/* Maybe some day, as we do with fonts where we stay within these bounds:

static void mp_bound_stupid(MP mp, mp_knot p)
{
    if (number_less   (q->x_coord, mp->bbmin[mp_x_code])) { mp_number_clone(mp->bbmin[mp_x_code], q->x_coord); }
    if (mp_number_greater(q->x_coord, mp->bbmax[mp_x_code])) { mp_number_clone(mp->bbmax[mp_x_code], q->x_coord); }
    if (number_less   (q->left_x,  mp->bbmin[mp_x_code])) { mp_number_clone(mp->bbmin[mp_x_code], q->left_x);  }
    if (mp_number_greater(q->left_x,  mp->bbmax[mp_x_code])) { mp_number_clone(mp->bbmax[mp_x_code], q->left_x);  }
    if (number_less   (q->right_x, mp->bbmin[mp_x_code])) { mp_number_clone(mp->bbmin[mp_x_code], q->right_x); }
    if (mp_number_greater(q->right_x, mp->bbmax[mp_x_code])) { mp_number_clone(mp->bbmax[mp_x_code], q->right_x); }
    //
    if (number_less   (q->y_coord, mp->bbmin[mp_y_code])) { mp_number_clone(mp->bbmin[mp_y_code], q->y_coord); }
    if (mp_number_greater(q->y_coord, mp->bbmax[mp_y_code])) { mp_number_clone(mp->bbmax[mp_y_code], q->y_coord); }
    if (number_less   (q->left_y,  mp->bbmin[mp_y_code])) { mp_number_clone(mp->bbmin[mp_y_code], q->left_y);  }
    if (mp_number_greater(q->left_y,  mp->bbmax[mp_y_code])) { mp_number_clone(mp->bbmax[mp_y_code], q->left_y);  }
    if (number_less   (q->right_y, mp->bbmin[mp_y_code])) { mp_number_clone(mp->bbmin[mp_y_code], q->right_y); }
    if (mp_number_greater(q->right_y, mp->bbmax[mp_y_code])) { mp_number_clone(mp->bbmax[mp_y_code], q->right_y); }
}

*/

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 */
    mp_new_fraction(t);
    mp_new_fraction(tt);
    if (c == mp_x_code) {
        mp_new_number_clone(x, q->x_coord);
    } else {
        mp_new_number_clone(x, q->y_coord);
    }
    mp_new_number(del1);
    mp_new_number(del2);
    mp_new_number(del3);
    mp_new_number(del);
    mp_new_number(dmax);
    /*tex
        Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|.
    */
    if (mp_number_less(x, mp->bbmin[c])) {
        mp_number_clone(mp->bbmin[c], x);
    }
    if (mp_number_greater(x, mp->bbmax[c])) {
        mp_number_clone(mp->bbmax[c], x);
    }
    /*tex
        Check the control points against the bounding box and set |wavy:=1| if any of them lie
        outside.
    */
    wavy = 1;
    if (c == mp_x_code) {
        if (mp_number_lessequal(mp->bbmin[c], p->right_x) && mp_number_lessequal(p->right_x, mp->bbmax[c])) {
            if (mp_number_lessequal(mp->bbmin[c], q->left_x) && mp_number_lessequal(q->left_x, mp->bbmax[c])) {
                wavy = 0;
            }
        }
    } else {
        if (mp_number_lessequal(mp->bbmin[c], p->right_y) && mp_number_lessequal(p->right_y, mp->bbmax[c])) {
            if (mp_number_lessequal(mp->bbmin[c], q->left_y) && mp_number_lessequal(q->left_y, mp->bbmax[c])) {
                wavy = 0;
            }
        }
    }
    if (wavy) {
        if (c == mp_x_code) {
            mp_set_number_from_subtraction(del1, p->right_x, p->x_coord);
            mp_set_number_from_subtraction(del2, q->left_x, p->right_x);
            mp_set_number_from_subtraction(del3, q->x_coord, q->left_x);
        } else {
            mp_set_number_from_subtraction(del1, p->right_y, p->y_coord);
            mp_set_number_from_subtraction(del2, q->left_y, p->right_y);
            mp_set_number_from_subtraction(del3, q->y_coord, q->left_y);
        }
        /*tex
            Scale up |del1|, |del2|, and |del3| for greater accuracy; also set |del| to the first
            nonzero element of |(del1,del2,del3)|.
        */
        if (mp_number_nonzero(del1)) {
            mp_number_clone(del, del1);
        } else if (mp_number_nonzero(del2)) {
            mp_number_clone(del, del2);
        } else {
            mp_number_clone(del, del3);
        }
        if (mp_number_nonzero(del)) {
            if (mp_number_greater(del1, del2)) {
                if (mp_number_greater(del1, del3)) {
                    mp_number_clone(dmax, del1);
                } else {
                    mp_number_clone(dmax, del3);
                }
            } else {
                if (mp_number_greater(del2, del3)) {
                    mp_number_clone(dmax, del2);
                } else {
                    mp_number_clone(dmax, del3);
                }
            }
            while (mp_number_less(dmax, mp_fraction_half_t)) {
                mp_number_double(dmax);
                mp_number_double(del1);
                mp_number_double(del2);
                mp_number_double(del3);
            }
        }
        if (mp_number_negative(del)) {
            mp_number_negate(del1);
            mp_number_negate(del2);
            mp_number_negate(del3);
        }
        mp_crossing_point(t, del1, del2, del3);
        if (mp_number_less(t, mp_fraction_one_t)) {
            /*tex
                Test the extremes of the cubic against the bounding box.
            */
            mp_eval_cubic(mp, &x, p, q, c, &t);
            /*tex
                Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|.
            */
            if (mp_number_less(x, mp->bbmin[c])) {
                mp_number_clone(mp->bbmin[c], x);
            }
            if (mp_number_greater(x, mp->bbmax[c])) {
                mp_number_clone(mp->bbmax[c], x);
            }
            mp_set_number_from_of_the_way(del2, t, del2, del3);
            /*tex
                Now |0,del2,del3| represent the derivative on the remaining interval.
            */
            if (mp_number_positive(del2)) {
                mp_set_number_to_zero(del2);
            }
            {
                mp_number arg2, arg3;
                mp_new_number(arg2);
                mp_new_number(arg3);
                mp_number_negated_clone(arg2, del2);
                mp_number_negated_clone(arg3, del3);
                mp_crossing_point(tt, mp_zero_t, arg2, arg3);
                mp_free_number(arg2);
                mp_free_number(arg3);
            }
            if (mp_number_less(tt, mp_fraction_one_t)) {
                /*tex
                    Test the second extreme against the bounding box.
                */
                mp_number arg;
                mp_new_number(arg);
                mp_set_number_from_of_the_way(arg, t, tt, mp_fraction_one_t);
                mp_eval_cubic(mp, &x, p, q, c, &arg);
                mp_free_number(arg);
                /*tex
                    Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|.
                */
                if (mp_number_less(x, mp->bbmin[c])) {
                    mp_number_clone(mp->bbmin[c], x);
                }
                if (mp_number_greater(x, mp->bbmax[c])) {
                    mp_number_clone(mp->bbmax[c], x);
                }
            }
        }
    }
    mp_free_number(del3);
    mp_free_number(del2);
    mp_free_number(del1);
    mp_free_number(del);
    mp_free_number(dmax);
    mp_free_number(x);
    mp_free_number(t);
    mp_free_number(tt);
}

/*tex

If |del1 = del2=del3=0|, it's impossible to obey the title of this section. We just set |del = 0| in
that case.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.Finding the bounding box of a path is basically a matter of applying |bound_cubic|
twice for each pair of adjacent knots.

*/

static void mp_path_bbox(MP mp, mp_knot h)
{
    mp_knot p = h;
    mp_number_clone(mp_minx, h->x_coord);
    mp_number_clone(mp_miny, h->y_coord);
    mp_number_clone(mp_maxx, mp_minx);
    mp_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;
    mp_number_clone(mp_minx, h->x_coord);
    mp_number_clone(mp_maxx, mp_minx);
    mp_set_number_to_zero(mp_miny);
    mp_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;
    mp_set_number_to_zero(mp_minx);
    mp_set_number_to_zero(mp_maxx);
    mp_number_clone(mp_miny, h->y_coord);
    mp_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);
}

/*tex

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. 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, 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.

The |arclength| and |arctime| operations are both based on a recursive 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|.

*/

// static int mp_arc_level = 0;

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;
//  mp_arc_level++;
//  if (mp_arc_level > 10) {
//      goto OEPS;
//  }
    mp_new_number(arc );
    mp_new_number(arc1);
    mp_new_number(dx01);
    mp_new_number(dy01);
    mp_new_number(dx12);
    mp_new_number(dy12);
    mp_new_number(dx02);
    mp_new_number(dy02);
    mp_new_number(v002);
    mp_new_number(v022);
    mp_new_number(simply);
    mp_new_number_clone(tol, *tol_orig);
    /*tex
        Bisect the bezier quadratic given by |dx0|, |dy0|, |dx1|, |dy1|, |dx2|, |dy2|.
    */
    mp_set_number_half_from_addition(dx01, *dx0, *dx1);
    mp_set_number_half_from_addition(dx12, *dx1, *dx2);
    mp_set_number_half_from_addition(dx02, dx01, dx12);
    mp_set_number_half_from_addition(dy01, *dy0, *dy1);
    mp_set_number_half_from_addition(dy12, *dy1, *dy2);
    mp_set_number_half_from_addition(dy02, dy01, dy12);
    /*tex
        Initialize |v002|, |v022|, and the arc length estimate |arc|; if it overflows set |arc_test|
        and |return|. 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.
    */
    {
        mp_number tmp, arg1, arg2 ;
        mp_new_number(tmp);
        mp_new_number(arg1);
        mp_new_number(arg2);
        mp_set_number_half_from_addition(arg1, *dx0, dx02);
        mp_number_add(arg1, dx01);
        mp_set_number_half_from_addition(arg2, *dy0, dy02);
        mp_number_add(arg2, dy01);
        mp_pyth_add(v002, arg1, arg2);
        mp_set_number_half_from_addition(arg1, dx02, *dx2);
        mp_number_add(arg1, dx12);
        mp_set_number_half_from_addition(arg2, dy02, *dy2);
        mp_number_add(arg2, dy12);
        mp_pyth_add(v022, arg1, arg2);
        mp_free_number(arg1);
        mp_free_number(arg2);
        mp_number_clone(tmp, *v02);
        mp_number_add_scaled(tmp, 2);
        mp_number_half(tmp);
        mp_set_number_half_from_addition(arc1, *v0, tmp);
        mp_number_subtract(arc1, v002);
        mp_number_half(arc1);
        mp_set_number_from_addition(arc1, v002, arc1);
        mp_set_number_half_from_addition(arc, *v2, tmp);
        mp_number_subtract(arc, v022);
        mp_number_half(arc);
        mp_set_number_from_addition(arc, v022, arc);
        /* reuse |tmp| for the next |if| test: */
        mp_set_number_to_inf(tmp);
        mp_number_subtract(tmp,arc1);
        if (mp_number_less(arc, tmp)) {
            mp_free_number(tmp);
            mp_number_add(arc, arc1);
        } else {
            mp_free_number(tmp);
            mp->arithmic_error = mp_error_code(mp, 1);
            if (mp_number_infinite(*a_goal)) {
                mp_set_number_to_inf(*ret);
            } else {
                mp_set_number_to_unity(*ret);
                mp_number_double(*ret);
                mp_number_negate(*ret); /* -two */
            }
            goto DONE;
        }
    }
    /*tex
        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.
    */
    simple = (mp_number_nonnegative(*dx0) && mp_number_nonnegative(*dx1) && mp_number_nonnegative(*dx2))
          || (mp_number_nonpositive(*dx0) && mp_number_nonpositive(*dx1) && mp_number_nonpositive(*dx2));
    if (simple) {
        simple = (mp_number_nonnegative(*dy0) && mp_number_nonnegative(*dy1) && mp_number_nonnegative(*dy2))
              || (mp_number_nonpositive(*dy0) && mp_number_nonpositive(*dy1) && mp_number_nonpositive(*dy2));
    }
    if (! simple) {
        simple = (mp_number_greaterequal(*dx0, *dy0) && mp_number_greaterequal(*dx1, *dy1) && mp_number_greaterequal(*dx2, *dy2))
              || (mp_number_lessequal   (*dx0, *dy0) && mp_number_lessequal   (*dx1, *dy1) && mp_number_lessequal   (*dx2, *dy2));
        if (simple) {
            mp_number neg_dx0, neg_dx1, neg_dx2;
            mp_new_number(neg_dx0);
            mp_new_number(neg_dx1);
            mp_new_number(neg_dx2);
            mp_number_negated_clone(neg_dx0, *dx0);
            mp_number_negated_clone(neg_dx1, *dx1);
            mp_number_negated_clone(neg_dx2, *dx2);
            simple = (mp_number_greaterequal(neg_dx0, *dy0) && mp_number_greaterequal(neg_dx1, *dy1) && mp_number_greaterequal(neg_dx2, *dy2))
                  || (mp_number_lessequal   (neg_dx0, *dy0) && mp_number_lessequal   (neg_dx1, *dy1) && mp_number_lessequal   (neg_dx2, *dy2));
            mp_free_number(neg_dx0);
            mp_free_number(neg_dx1);
            mp_free_number(neg_dx2);
        }
    }
    mp_set_number_half_from_addition(simply, *v0, *v2);
    mp_number_negate(simply);
    mp_number_add(simply, arc);
    mp_number_subtract(simply, *v02);
    mp_number_abs(simply);
    if (simple && mp_number_lessequal(simply, tol)) {
        if (mp_number_less(arc, *a_goal)) {
            mp_number_clone(*ret, arc);
        } else {
            /*tex
                Estimate when the arc length reaches |a_goal| and set |arc_test| to that time minus
                |two|.

                Since Simpson's rule is based on approximating the integrand by a parabola, 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 $d\tau\over dt$. We
                shall define a function |solve_rising_cubic| that finds $\tau$ given $a$, $b$,
                $c$, and $x$.
            */
            mp_number tmp1, tmp2, tmp3, tmp4;
            mp_new_number_clone(tmp1, *v02);
            mp_number_add_scaled(tmp1, 2);
            mp_number_half(tmp1);
            mp_number_half(tmp1); /* (v02+2) / 4 */
            if (mp_number_lessequal(*a_goal, arc1)) {
                mp_new_number_clone(tmp2, *v0);
                mp_number_half(tmp2);
                mp_new_number_from_sub(tmp3, arc1, tmp2);
                mp_number_subtract(tmp3, tmp1);
                mp_new_number(tmp4);
                mp_solve_rising_cubic(mp, &tmp4, &tmp2, &tmp3, &tmp1, a_goal);
                mp_number_half(tmp4);
                mp_set_number_to_unity(tmp3);
                mp_number_subtract(tmp4, tmp3);
                mp_number_subtract(tmp4, tmp3);
                mp_number_clone(*ret, tmp4);
            } else {
                mp_number tmp5;
                mp_new_number(tmp5);
                mp_new_number_clone(tmp2, *v2);
                mp_number_half(tmp2);
                mp_new_number_from_sub(tmp3, arc, arc1);
                mp_number_subtract(tmp3, tmp1);
                mp_number_subtract(tmp3, tmp2);
                mp_new_number_from_sub(tmp4, *a_goal, arc1);
                mp_solve_rising_cubic(mp, &tmp5, &tmp1, &tmp3, &tmp2, &tmp4);
                mp_number_half(tmp5);
                mp_set_number_to_unity(tmp2);
                mp_set_number_to_unity(tmp3);
                mp_number_half(tmp2);
                mp_number_subtract(tmp2, tmp3);
                mp_number_subtract(tmp2, tmp3);
                mp_set_number_from_addition(*ret, tmp2, tmp5);
                mp_free_number(tmp5);
            }
            mp_free_number(tmp1);
            mp_free_number(tmp2);
            mp_free_number(tmp3);
            mp_free_number(tmp4);
        }
    } else {
        /*tex
            Use one or two recursive calls to compute the |arc_test| function. The |tol| value
            should by multiplied by $\sqrt 2$ before making recursive calls, but $1.5$ is an
            adequate approximation. It is best to avoid using |mp_make_fraction| in this inner loop.
        */
        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 */
        mp_new_number(a_new);
        mp_new_number(a_aux);
        mp_new_number(half_v02);
        /*tex
            Set |a_new| and |a_aux| so their sum is |2*a_goal| and |a_new| is as large as possible.
        */
        mp_set_number_to_inf(a_aux);
        mp_number_subtract(a_aux, *a_goal);
        if (mp_number_greater(*a_goal, a_aux)) {
            mp_set_number_from_subtraction(a_aux, *a_goal, a_aux);
            mp_set_number_to_inf(a_new);
        } else {
            mp_set_number_from_addition(a_new, *a_goal, *a_goal);
            mp_set_number_to_zero(a_aux);
        }
        {
            mp_number half_tol;
            mp_new_number_clone(half_tol, tol);
            mp_number_half(half_tol);
            mp_number_add(tol, half_tol);
            mp_free_number(half_tol);
        }
        mp_number_clone(half_v02, *v02);
        mp_number_half(half_v02);
        mp_new_number(a);
        mp_arc_test(mp, &a, dx0, dy0, &dx01, &dy01, &dx02, &dy02, v0, &v002, &half_v02, &a_new, &tol);
        if (mp_number_negative(a)) {
            mp_set_number_to_unity(*ret);
            mp_number_double(*ret); /* two */
            mp_number_subtract(*ret, a); /* two - a */
            mp_number_half(*ret);
            mp_number_negate(*ret); /* -half(two - a) */
        } else {
            /*tex
                Update |a_new| to reduce |a_new+a_aux| by |a|. 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.
            */
            if (mp_number_greater(a, a_aux)) {
                mp_number_subtract(a_aux, a);
                mp_number_add(a_new, a_aux);
            }
            /*tex
                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.
            */
            mp_new_number(b);
            mp_arc_test(mp, &b, &dx02, &dy02, &dx12, &dy12, dx2, dy2, &half_v02, &v022, v2, &a_new, &tol);
            if (mp_number_negative(b)) {
                mp_number tmp ;
                mp_new_number(tmp);
                mp_number_negated_clone(tmp, b);
                mp_number_half(tmp);
                mp_number_negate(tmp);
                mp_number_clone(*ret, tmp);
                mp_set_number_to_unity(tmp);
                mp_number_half(tmp);
                mp_number_subtract(*ret, tmp); /* (-(half(-b)) - 1/2) */
                mp_free_number(tmp);
            } else {
                mp_set_number_from_subtraction(*ret, b, a);
                mp_number_half(*ret);
                mp_set_number_from_addition(*ret, a, *ret); /* (a + half(b - a)) */
            }
            mp_free_number(b);
        }
        mp_free_number(half_v02);
        mp_free_number(a_aux);
        mp_free_number(a_new);
        mp_free_number(a);
    }
  DONE:
    mp_free_number(arc);
    mp_free_number(arc1);
    mp_free_number(dx01);
    mp_free_number(dy01);
    mp_free_number(dx12);
    mp_free_number(dy12);
    mp_free_number(dx02);
    mp_free_number(dy02);
    mp_free_number(v002);
    mp_free_number(v022);
    mp_free_number(simply);
    mp_free_number(tol);
//OEPS:
//  mp_arc_level--;
}

/*

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)
\ge 0$ 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.

*/

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)
{
    mp_number abc;
    mp_number a, b, c, x; /*tex local versions of arguments */
    mp_number ab, bc, ac; /*tex bisection results */
    mp_number t;          /*tex $2^k+q$ where unscaled answer is in $[q2^{-k},(q+1)2^{-k})$ */
    mp_number xx;         /*tex temporary for updating |x| */
    mp_number neg_x;      /*tex temporary for an |if| */
    if (mp_number_negative(*a_orig) || mp_number_negative(*c_orig)) {
        mp_confusion(mp, "rising cubic");
    }
    mp_new_number(t);
    mp_new_number_clone(a, *a_orig);
    mp_new_number_clone(b, *b_orig);
    mp_new_number_clone(c, *c_orig);
    mp_new_number_clone(x, *x_orig);
    mp_new_number(ab);
    mp_new_number(bc);
    mp_new_number(ac);
    mp_new_number(xx);
    mp_new_number(neg_x);
    mp_new_number_from_add(abc, a, b);
    mp_number_add(abc, c);
    if (mp_number_nonpositive(x)) {
        mp_set_number_to_zero(*ret);
    } else if (mp_number_greaterequal(x, abc)) {
        mp_set_number_to_unity(*ret);
    } else {
        mp_number_clone(t, mp_epsilon_t);
        /*tex
            Rescale if necessary to make sure |a|, |b|, and |c| are all less than |el_gordo div 3|.
        */
        while (mp_number_greater(a, mp_one_third_inf_t) || mp_number_greater(b, mp_one_third_inf_t) || mp_number_greater(c, mp_one_third_inf_t)) {
            mp_number_half(a);
            mp_number_half(b);
            mp_number_half(c);
            mp_number_half(x);
        }
        do {
            mp_number_add(t, t);
            /*tex
                Subdivide the bezier quadratic defined by |a|, |b|, |c|.
            */
            mp_set_number_half_from_addition(ab, a, b);
            mp_set_number_half_from_addition(bc, b, c);
            mp_set_number_half_from_addition(ac, ab, bc);
            mp_number_clone(xx,x);
            mp_number_subtract(xx, a);
            mp_number_subtract(xx, ab);
            mp_number_subtract(xx, ac);
            mp_number_negated_clone(neg_x, x);
            if (mp_number_less(xx, neg_x)) {
                mp_number_double(x);
                mp_number_clone(b, ab);
                mp_number_clone(c, ac);
            } else {
                mp_number_add(x, xx);
                mp_number_clone(a, ac);
                mp_number_clone(b, bc);
                mp_number_add(t, mp_epsilon_t);
            }
        } while (mp_number_less(t, mp_unity_t));
        mp_set_number_from_subtraction(*ret, t, mp_unity_t);
    }
    mp_free_number(abc);
    mp_free_number(t);
    mp_free_number(a);
    mp_free_number(b);
    mp_free_number(c);
    mp_free_number(ab);
    mp_free_number(bc);
    mp_free_number(ac);
    mp_free_number(xx);
    mp_free_number(x);
    mp_free_number(neg_x);
}

/*tex

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|.

*/

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; /*tex length of each $({\it dx},{\it dy})$ pair */
    mp_number v02;        /*tex twice the norm of the quadratic at $t={1\over2}$ */
    mp_new_number(v0);
    mp_new_number(v1);
    mp_new_number(v2);
    mp_pyth_add(v0, *dx0, *dy0);
    mp_pyth_add(v1, *dx1, *dy1);
    mp_pyth_add(v2, *dx2, *dy2);
    if ((mp_number_greaterequal(v0, mp_fraction_four_t)) || (mp_number_greaterequal(v1, mp_fraction_four_t)) || (mp_number_greaterequal(v2, mp_fraction_four_t))) {
        mp->arithmic_error = mp_error_code(mp, 2);
        if (mp_number_infinite(*a_goal)) {
            mp_set_number_to_inf(*ret);
        } else {
            mp_set_number_to_unity(*ret);
            mp_number_double(*ret);
            mp_number_negate(*ret);
        }
    } else {
        mp_number arg1, arg2;
        mp_new_number(v02);
        mp_new_number(arg1);
        mp_new_number(arg2);
        mp_set_number_half_from_addition(arg1, *dx0, *dx2);
        mp_number_add(arg1, *dx1);
        mp_set_number_half_from_addition(arg2, *dy0, *dy2);
        mp_number_add(arg2, *dy1);
        mp_pyth_add(v02, arg1, arg2);
        mp_free_number(arg1);
        mp_free_number(arg2);
        mp_arc_test(mp, ret, dx0, dy0, dx1, dy1, dx2, dy2, &v0, &v02, &v2, a_goal, &mp_arc_tol_k);
        mp_free_number(v02);
    }
    mp_free_number(v0);
    mp_free_number(v1);
    mp_free_number(v2);
}

/*tex
    Now it is easy to find the arc length of an entire path.
*/

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 */
    mp_new_number(a_tot);
    mp_new_number(arg1);
    mp_new_number(arg2);
    mp_new_number(arg3);
    mp_new_number(arg4);
    mp_new_number(arg5);
    mp_new_number(arg6);
    mp_new_number(a);
    mp_new_number(arcgoal);
    mp_set_number_to_inf(arcgoal);
    while (mp_right_type(p) != mp_endpoint_knot) {
        mp_knot q = mp_next_knot(p);
        /*tex add arclength of path segment */
        mp_set_number_from_subtraction(arg1, p->right_x, p->x_coord);
        mp_set_number_from_subtraction(arg2, p->right_y, p->y_coord);
        mp_set_number_from_subtraction(arg3, q->left_x,  p->right_x);
        mp_set_number_from_subtraction(arg4, q->left_y,  p->right_y);
        mp_set_number_from_subtraction(arg5, q->x_coord, q->left_x);
        mp_set_number_from_subtraction(arg6, q->y_coord, q->left_y);
        mp_do_arc_test(mp, &a, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arcgoal);
        mp_slow_add(a_tot, a, a_tot);
        if (q == h) {
            break;
        } else {
            p = q;
        }
    }
    mp_free_number(arcgoal);
    mp_free_number(a);
    mp_free_number(arg1);
    mp_free_number(arg2);
    mp_free_number(arg3);
    mp_free_number(arg4);
    mp_free_number(arg5);
    mp_free_number(arg6);
    mp_check_arithmic(mp);
    mp_number_clone(*ret, a_tot);
    mp_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;
    mp_new_number(a_tot);
    mp_new_number(a_cnt);
    mp_new_number(arg1);
    mp_new_number(arg2);
    mp_new_number(arg3);
    mp_new_number(arg4);
    mp_new_number(arg5);
    mp_new_number(arg6);
    mp_new_number(a);
    mp_new_number(arcgoal);
    mp_set_number_to_inf(arcgoal);
    while (mp_right_type(p) != mp_endpoint_knot) {
        mp_knot q = mp_next_knot(p);
        if (mp_number_greaterequal(a_cnt, *last)) {
            break;
        } else if (mp_number_greaterequal(a_cnt, *first)) {
            /*tex  add arclength of path segment */
            mp_set_number_from_subtraction(arg1, p->right_x, p->x_coord);
            mp_set_number_from_subtraction(arg2, p->right_y, p->y_coord);
            mp_set_number_from_subtraction(arg3, q->left_x,  p->right_x);
            mp_set_number_from_subtraction(arg4, q->left_y,  p->right_y);
            mp_set_number_from_subtraction(arg5, q->x_coord, q->left_x);
            mp_set_number_from_subtraction(arg6, q->y_coord, q->left_y);
            mp_do_arc_test(mp, &a, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arcgoal);
            mp_slow_add(a_tot, a, a_tot);
        }
        if (q == h) {
            break;
        } else {
            p = q;
            mp_number_add(a_cnt, mp_unity_t);
        }
    }
    mp_free_number(arcgoal);
    mp_free_number(a);
    mp_free_number(arg1);
    mp_free_number(arg2);
    mp_free_number(arg3);
    mp_free_number(arg4);
    mp_free_number(arg5);
    mp_free_number(arg6);
    mp_check_arithmic(mp);
    mp_number_clone(*ret, a_tot);
    mp_free_number(a_cnt);
    mp_free_number(a_tot);
}

/*tex

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.

*/

static mp_knot mp_get_arc_time(MP mp, mp_number *ret, mp_knot h, mp_number *arc0_orig, int local)
{
    if (mp_number_negative(*arc0_orig)) {
        /*tex
            Deal with a negative |arc0_orig| value and |return|.
        */
        if (mp_left_type(h) == mp_endpoint_knot) {
            mp_set_number_to_zero(*ret);
        } else {
            mp_number neg_arc0;
            mp_knot p = mp_htap_ypoc(mp, h);
            mp_new_number(neg_arc0);
            mp_number_negated_clone(neg_arc0, *arc0_orig);
            mp_get_arc_time(mp, ret, p, &neg_arc0, 0);
            mp_number_negate(*ret);
            mp_toss_knot_list(mp, p);
            mp_free_number(neg_arc0);
        }
        mp_check_arithmic(mp);
    } else {
        mp_knot p, q, k;                              /*tex for traversing the path */
        mp_number t_tot;                              /*tex accumulator for the result */
        mp_number t;                                  /*tex the result of |do_arc_test| */
        mp_number arc, arc0;                          /*tex portion of |arc0| not used up so far */
        mp_number arg1, arg2, arg3, arg4, arg5, arg6; /*tex |do_arc_test| arguments */
        mp_new_number(t_tot);
        mp_new_number_clone(arc0, *arc0_orig);
        if (mp_number_infinite(arc0)) {
            mp_number_add_scaled(arc0, -1);
        }
        mp_new_number_clone(arc, arc0);
        p = h;
        k = h;
        mp_new_number(arg1);
        mp_new_number(arg2);
        mp_new_number(arg3);
        mp_new_number(arg4);
        mp_new_number(arg5);
        mp_new_number(arg6);
        mp_new_number(t);
        while ((mp_right_type(p) != mp_endpoint_knot) && mp_number_positive(arc)) {
            k = p;
            q = mp_next_knot(p);
            mp_set_number_from_subtraction(arg1, p->right_x, p->x_coord);
            mp_set_number_from_subtraction(arg2, p->right_y, p->y_coord);
            mp_set_number_from_subtraction(arg3, q->left_x,  p->right_x);
            mp_set_number_from_subtraction(arg4, q->left_y,  p->right_y);
            mp_set_number_from_subtraction(arg5, q->x_coord, q->left_x);
            mp_set_number_from_subtraction(arg6, q->y_coord, q->left_y);
            mp_do_arc_test(mp, &t, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arc);
            /*tex
                Update |arc| and |t_tot| after |do_arc_test| has just returned |t|.
            */
            if (mp_number_negative(t)) {
                mp_number_add(t_tot, t);
                mp_number_add(t_tot, mp_two_t);
                mp_set_number_to_zero(arc);
            } else {
                mp_number_add(t_tot, mp_unity_t);
                mp_number_subtract(arc, t);
            }
            if (q == h) {
                /*tex
                    Update |t_tot| and |arc| to avoid going around the cyclic path too many times but
                    set |arithmic_error := 1 | and |goto done| on overflow.
                */
                if (mp_number_positive(arc)) {
                    mp_number n, n1, d1, v1;
                    mp_new_number(n);
                    mp_new_number(v1);
                    mp_new_number_from_sub(d1, arc0, arc); /* d1 = arc0 - arc */
                    mp_new_number_from_div(n1, arc, d1);   /* n1 = (arc / d1) */
                    mp_floor_scaled(n1); /* added */
                    mp_number_clone(n, n1);
                    mp_set_number_from_mul(n1, n1, d1);    /* n1 = (n1 * d1) */
                    mp_number_subtract(arc, n1);           /* arc = arc - n1 */
                    mp_number_clone(d1, mp_inf_t);         /* reuse d1 */
                    mp_number_clone(v1, n);                /* v1 = n */
                    mp_number_add(v1, mp_epsilon_t);       /* v1 = v1 + 1 */
                    mp_set_number_from_div(d1, d1, v1);    /* |d1 = EL_GORDO / v1| */
                    if (mp_number_greater(t_tot, d1)) {
                        mp->arithmic_error = mp_error_code(mp, 3);
                        mp_check_arithmic(mp);
                        mp_set_number_to_inf(*ret);
                        mp_free_number(n);
                        mp_free_number(n1);
                        mp_free_number(d1);
                        mp_free_number(v1);
                        goto RETURN;
                    }
                    mp_set_number_from_mul(t_tot, t_tot, v1);
                    mp_free_number(n);
                    mp_free_number(n1);
                    mp_free_number(d1);
                    mp_free_number(v1);
                }
            }
            p = q;
        }
        mp_check_arithmic(mp);
        if (local) {
            mp_number_add(t, mp_two_t);
            mp_number_clone(*ret, t);
        } else {
            mp_number_clone(*ret, t_tot);
        }
        h = k;
      RETURN:
        mp_free_number(t_tot);
        mp_free_number(t);
        mp_free_number(arc);
        mp_free_number(arc0);
        mp_free_number(arg1);
        mp_free_number(arg2);
        mp_free_number(arg3);
        mp_free_number(arg4);
        mp_free_number(arg5);
        mp_free_number(arg6);
    }
    return h;
}

/*tex

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 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. 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|.

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.

*/

static mp_knot mp_make_pen(MP mp, mp_knot h, int need_hull)
{
    mp_knot q = h;
    /*tex
        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);
        /*tex
            Make sure |h| isn't confused with an elliptical pen.
        */
        if (mp_pen_is_elliptical(h)) {
            mp_number_clone(h->left_x, h->x_coord);
            mp_number_clone(h->left_y, h->y_coord);
            mp_number_clone(h->right_x, h->x_coord);
            mp_number_clone(h->right_y, h->y_coord);
        }
    }
    return h;
}

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;
    mp_set_number_to_zero(h->x_coord);
    mp_set_number_to_zero(h->y_coord);
    mp_number_clone(h->left_x, *diam);
    mp_set_number_to_zero(h->left_y);
    mp_set_number_to_zero(h->right_x);
    mp_number_clone(h->right_y, *diam);
    return h;
}

/*tex

Printing a polygonal pen is very much like printing a path

*/

static void mp_print_pen_only(MP mp, mp_knot h)
{
    if (mp_pen_is_elliptical(h)) {
        /*tex
            Print the elliptical pen |h|.
        */
        mp_number lxx, lyy, rxx, ryy;
        mp_new_number_from_sub(lxx, h->left_x, h->x_coord);
        mp_new_number_from_sub(rxx, h->right_x, h->x_coord);
        mp_new_number_from_sub(lyy, h->left_y, h->y_coord);
        mp_new_number_from_sub(ryy, h->right_y, h->y_coord);
        mp_print_format(mp, "pencircle transformed (%N,%N,%N,%N,%N,%N)", h->x_coord, h->y_coord, lxx, rxx, lyy, ryy);
        mp_free_number(lxx);
        mp_free_number(lyy);
        mp_free_number(rxx);
        mp_free_number(ryy);
    } else {
        mp_knot p = h;
        do {
            /*tex
                Advance |p| making sure the links are OK and |return| if there is a problem.
            */
            mp_knot q = mp_next_knot(p);
            mp_print_format(mp, "(%N,%N) .. ", p->x_coord, p->y_coord);
            if ((q == NULL) || (mp_prev_knot(q) != p)) {
                mp_print_nl(mp, "???");
                return; /* this won't happen */
            }
            p = q;
        } while (p != h);
        mp_print_string(mp, "cycle");
    }
}

/*tex

    Here is another version of |pr_pen| that prints the pen as a diagnostic message.

*/

static void mp_print_pen(MP mp, mp_knot h, const char *s, int nuline) {
    mp_begin_diagnostic_print(mp, "Pen", s, nuline);
    mp_print_ln(mp);
    mp_print_pen_only(mp, h);
    mp_end_diagnostic(mp, 1);
}

/*tex

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.

We need an eight knot path to get a good approximation to an ellipse.

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)$).

*/

static void mp_make_path(MP mp, mp_knot h)
{
    if (mp_pen_is_elliptical(h)) {
        /*tex
            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 */
        mp_new_number(dx);
        mp_new_number(dy);
        mp_new_number_clone(center_x, h->x_coord);
        mp_new_number_clone(center_y, h->y_coord);
        mp_new_number_from_sub(width_x, h->left_x, center_x);
        mp_new_number_from_sub(width_y, h->left_y, center_y);
        mp_new_number_from_sub(height_x, h->right_x, center_x);
        mp_new_number_from_sub(height_y, h->right_y, center_y);
        p = h;
        for (int k = 0; k <= 7; k++) {
            /*tex
                Initialize |p| as the |k|th knot of a circle of unit diameter, transforming it
                appropriately.
            */
            int kk = (k + 6) % 8;
            mp_number r1, r2;
            mp_new_fraction(r1);
            mp_new_fraction(r2);
            mp_take_fraction(r1, mp->half_cos[k], width_x);
            mp_take_fraction(r2, mp->half_cos[kk], height_x);
            mp_number_add(r1, r2);
            mp_set_number_from_addition(p->x_coord, center_x, r1);
            mp_take_fraction(r1, mp->half_cos[k],  width_y);
            mp_take_fraction(r2, mp->half_cos[kk], height_y);
            mp_number_add(r1, r2);
            mp_set_number_from_addition(p->y_coord, center_y, r1);
            mp_take_fraction(r1, mp->d_cos[kk], width_x);
            mp_take_fraction(r2, mp->d_cos[k], height_x);
            mp_number_negated_clone(dx, r1);
            mp_number_add(dx, r2);
            mp_take_fraction(r1, mp->d_cos[kk], width_y);
            mp_take_fraction(r2, mp->d_cos[k], height_y);
            mp_number_negated_clone(dy, r1);
            mp_number_add(dy, r2);
            mp_set_number_from_addition(p->right_x, p->x_coord, dx);
            mp_set_number_from_addition(p->right_y, p->y_coord, dy);
            mp_set_number_from_subtraction(p->left_x, p->x_coord, dx);
            mp_set_number_from_subtraction(p->left_y, p->y_coord, dy);
            mp_free_number(r1);
            mp_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;
            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);
        }
        mp_free_number(dx);
        mp_free_number(dy);
        mp_free_number(center_x);
        mp_free_number(center_y);
        mp_free_number(width_x);
        mp_free_number(width_y);
        mp_free_number(height_x);
        mp_free_number(height_y);
    } else {
        mp_knot p = h;
        do {
            mp_left_type(p) = mp_explicit_knot;
            mp_right_type(p) = mp_explicit_knot;
            mp_number_clone(p->left_x, p->x_coord);
            mp_number_clone(p->left_y, p->y_coord);
            mp_number_clone(p->right_x, p->x_coord);
            mp_number_clone(p->right_y, p->y_coord);
            p = mp_next_knot(p);
        } while (p != h);
    }
}

/*tex

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].

*/

static mp_knot mp_convex_hull(MP mp, mp_knot h)
{
    if (mp_pen_is_elliptical(h)) {
        return h;
    } else {
        /*tex
            Make a polygonal pen convex.
        */
        mp_knot l, r;     /*tex the leftmost and rightmost knots */
        mp_knot p, q;     /*tex knots being scanned */
        mp_knot s;        /*tex the starting point for an upcoming scan */
        mp_number dx, dy; /*tex a temporary pointer */
        mp_new_number(dx);
        mp_new_number(dy);
        /*tex
            Set |l| to the leftmost knot in polygon~|h|.
        */
        l = h;
        p = mp_next_knot(h);
        while (p != h) {
            if (mp_number_lessequal(p->x_coord, l->x_coord) && (mp_number_less(p->x_coord, l->x_coord) || mp_number_less(p->y_coord, l->y_coord))) {
                l = p;
            }
            p = mp_next_knot(p);
        }
        /*tex
            Set |r| to the rightmost knot in polygon~|h|.
        */
        r = h;
        p = mp_next_knot(h);
        while (p != h) {
            if (mp_number_greaterequal(p->x_coord, r->x_coord) && (mp_number_greater(p->x_coord, r->x_coord) || mp_number_greater(p->y_coord, r->y_coord))) {
                r = p;
            }
            p = mp_next_knot(p);
        }
        if (l != r) {
            mp_knot s = mp_next_knot(r);
            /*tex
                Find any knots on the path from |l| to |r| above the |l|-|r| line and move them
                past~|r|.
            */
            {
                mp_number arg1, arg2;
                mp_new_number(arg1);
                mp_new_number(arg2);
                mp_set_number_from_subtraction(dx, r->x_coord, l->x_coord);
                mp_set_number_from_subtraction(dy, r->y_coord, l->y_coord);
                p = mp_next_knot(l);
                while (p != r) {
                    q = mp_next_knot(p);
                    mp_set_number_from_subtraction(arg1, p->y_coord, l->y_coord);
                    mp_set_number_from_subtraction(arg2, p->x_coord, l->x_coord);
                    if (mp_ab_vs_cd(dx, arg1, dy, arg2) > 0) {
                        mp_move_knot(mp, p, r);
                    }
                    p = q;
                }
                mp_free_number(arg1);
                mp_free_number(arg2);
            }
            /*tex
                Find any knots on the path from |s| to |l| below the |l|-|r| line and move them
                past~|l|.
            */
            {
                mp_number arg1, arg2;
                mp_new_number(arg1);
                mp_new_number(arg2);
                p = s;
                while (p != l) {
                    q = mp_next_knot(p);
                    mp_set_number_from_subtraction(arg1, p->y_coord, l->y_coord);
                    mp_set_number_from_subtraction(arg2, p->x_coord, l->x_coord);
                    if (mp_ab_vs_cd(dx, arg1, dy, arg2) < 0) {
                        mp_move_knot(mp, p, l);
                    }
                    p = q;
                }
                mp_free_number(arg1);
                mp_free_number(arg2);
            }
            /*tex
                Sort the path from |l| to |r| by increasing $x$.
            */
            p = mp_next_knot(l);
            while (p != r) {
                q = mp_prev_knot(p);
                while (mp_number_greater(q->x_coord, p->x_coord)) {
                    q = mp_prev_knot(q);
                }
                while (mp_number_equal(q->x_coord, p->x_coord)) {
                    if (mp_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);
                }
            }
            /*tex
                Sort the path from |r| to |l| by decreasing $x$.
            */
            p = mp_next_knot(r);
            while (p != l) {
                q = mp_prev_knot(p);
                while (mp_number_less(q->x_coord, p->x_coord)) {
                    q = mp_prev_knot(q);
                }
                while (mp_number_equal(q->x_coord, p->x_coord)) {
                    if (mp_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);
                }
            }
        }
        if (l != mp_next_knot(l)) {
            /*tex
                Do a gramm scan and remove vertices where there is no left turn.
            */
            mp_number arg1, arg2;
            mp_new_number(arg1);
            mp_new_number(arg2);
            p = l;
            q = mp_next_knot(l);
            while (1) {
                mp_set_number_from_subtraction(dx, q->x_coord, p->x_coord);
                mp_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) {
                    mp_set_number_from_subtraction(arg1, q->y_coord, p->y_coord);
                    mp_set_number_from_subtraction(arg2, q->x_coord, p->x_coord);
                    if (mp_ab_vs_cd(dx, arg1, dy, arg2) <= 0) {
                        /*tex
                            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;
                        }
                    }
                }
            }
            mp_free_number(arg1);
            mp_free_number(arg2);
        }
        mp_free_number(dx);
        mp_free_number(dy);
        return l;
    }
}

// /* cheap solution */
//
// void mp_simplify_path(MP mp, mp_knot h)
// {
//     mp_knot p = h;
//     (void) mp;
//     do {
//         mp_number_clone(p->left_x, p->x_coord);
//         mp_number_clone(p->left_y, p->y_coord);
//         mp_number_clone(p->right_x, p->x_coord);
//         mp_number_clone(p->right_y, p->y_coord);
//         p = mp_next_knot(p);
//     } while (p != h);
// }

/* solution with equaldistant fractional times on path */

void mp_simplify_path(MP mp, mp_knot h)
{
    mp_knot p = h;
    (void) mp;
    do {
        mp_right_type(p) = mp_curl_knot;
        mp_left_type(p) = mp_curl_knot;
        mp_set_number_to_unity(p->right_given);
        mp_set_number_to_unity(p->left_given);
        mp_set_number_to_unity(p->right_tension);
        mp_set_number_to_unity(p->left_tension);
        p = mp_next_knot(p);
    } while (p != h);
    mp_make_choices(mp, h);
}

/*tex

All comparisons are done primarily on $x$ and secondarily on $y$.The |move_knot| procedure removes
|p| from a doubly linked list and inserts it after |q|.

*/

static 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;
}

/*tex

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|.

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.

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.

*/

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_number xx, yy;         /*tex untransformed offset for an elliptical pen */
        mp_number wx, wy, hx, hy; /*tex the transformation matrix for an elliptical pen */
        mp_number d;              /*tex a temporary register */
        mp_new_fraction(xx);
        mp_new_fraction(yy);
        mp_new_number(wx);
        mp_new_number(wy);
        mp_new_number(hx);
        mp_new_number(hy);
        mp_new_fraction(d);
        /*tex
            Find the offset for |(x,y)| on the elliptical pen~|h|
        */
        if (mp_number_zero(*x_orig) && mp_number_zero(*y_orig)) {
            mp_number_clone(mp->cur_x, h->x_coord);
            mp_number_clone(mp->cur_y, h->y_coord);
        } else {
            mp_number x, y, abs_x, abs_y;
            mp_new_number_clone(x, *x_orig);
            mp_new_number_clone(y, *y_orig);
            /*tex
                Find the non-constant part of the transformation for |h|
            */
            mp_set_number_from_subtraction(wx, h->left_x,  h->x_coord);
            mp_set_number_from_subtraction(wy, h->left_y,  h->y_coord);
            mp_set_number_from_subtraction(hx, h->right_x, h->x_coord);
            mp_set_number_from_subtraction(hy, h->right_y, h->y_coord);
            mp_new_number_abs(abs_x, x);
            mp_new_number_abs(abs_y, y);
            while (mp_number_less(abs_x, mp_fraction_half_t) && mp_number_less(abs_y, mp_fraction_half_t)) {
                mp_number_double(x);
                mp_number_double(y);
                mp_number_abs_clone(abs_x, x);
                mp_number_abs_clone(abs_y, y);
            }
            /*tex
                Make |(xx,yy)| the offset on the untransformed |pencircle| for the untransformed
                version of |(x,y)|.
            */
            {
                mp_number r1, r2, arg1;
                mp_new_number(arg1);
                mp_new_fraction(r1);
                mp_new_fraction(r2);
                mp_take_fraction(r1, x, hy);
                mp_number_negated_clone(arg1, hx);
                mp_take_fraction(r2, y, arg1);
                mp_number_add(r1, r2);
                mp_number_negate(r1);
                mp_number_clone(yy, r1);
                mp_number_negated_clone(arg1, wy);
                mp_take_fraction(r1, x, arg1);
                mp_take_fraction(r2, y, wx);
                mp_number_add(r1, r2);
                mp_number_clone(xx, r1);
                mp_free_number(arg1);
                mp_free_number(r1);
                mp_free_number(r2);
            }
            mp_pyth_add(d, xx, yy);
            if (mp_number_positive(d)) {
                mp_number ret;
                mp_new_fraction(ret);
                mp_make_fraction(ret, xx, d);
                mp_number_half(ret);
                mp_number_clone(xx, ret);
                mp_make_fraction(ret, yy, d);
                mp_number_half(ret);
                mp_number_clone(yy, ret);
                mp_free_number(ret);
            }
            {
                mp_number r1, r2;
                mp_new_fraction(r1);
                mp_new_fraction(r2);
                mp_take_fraction(r1, xx, wx);
                mp_take_fraction(r2, yy, hx);
                mp_number_add(r1, r2);
                mp_set_number_from_addition(mp->cur_x, h->x_coord, r1);
                mp_take_fraction(r1, xx, wy);
                mp_take_fraction(r2, yy, hy);
                mp_number_add(r1, r2);
                mp_set_number_from_addition(mp->cur_y, h->y_coord, r1);
                mp_free_number(r1);
                mp_free_number(r2);
            }
            mp_free_number(abs_x);
            mp_free_number(abs_y);
            mp_free_number(x);
            mp_free_number(y);
        }
        mp_free_number(xx);
        mp_free_number(yy);
        mp_free_number(wx);
        mp_free_number(wy);
        mp_free_number(hx);
        mp_free_number(hy);
        mp_free_number(d);
    } else {
        mp_knot p, q; /* consecutive knots */
        mp_number arg1, arg2;
        mp_new_number(arg1);
        mp_new_number(arg2);
        q = h;
        do {
            p = q;
            q = mp_next_knot(q);
            mp_set_number_from_subtraction(arg1, q->x_coord, p->x_coord);
            mp_set_number_from_subtraction(arg2, q->y_coord, p->y_coord);
        } while (mp_ab_vs_cd(arg1, *y_orig, arg2, *x_orig) < 0);
        do {
            p = q;
            q = mp_next_knot(q);
            mp_set_number_from_subtraction(arg1, q->x_coord, p->x_coord);
            mp_set_number_from_subtraction(arg2, q->y_coord, p->y_coord);
        } while (mp_ab_vs_cd(arg1, *y_orig, arg2, *x_orig) > 0);
        mp_number_clone(mp->cur_x, p->x_coord);
        mp_number_clone(mp->cur_y, p->y_coord);
        mp_free_number(arg1);
        mp_free_number(arg2);
    }
}

/*tex

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|.

*/

static void mp_pen_bbox(MP mp, mp_knot h)
{
    if (mp_pen_is_elliptical(h)) {
        mp_number arg1, arg2;
        mp_new_number(arg1);
        mp_new_fraction(arg2);
        mp_number_clone(arg2, mp_fraction_one_t);
        mp_find_offset(mp, &arg1, &arg2, h);
        mp_number_clone(mp_maxx, mp->cur_x);
        mp_number_clone(mp_minx, h->x_coord);
        mp_number_double(mp_minx);
        mp_number_subtract(mp_minx, mp->cur_x);
        mp_number_negate(arg2);
        mp_find_offset(mp, &arg2, &arg1, h);
        mp_number_clone(mp_maxy, mp->cur_y);
        mp_number_clone(mp_miny, h->y_coord);
        mp_number_double(mp_miny);
        mp_number_subtract(mp_miny, mp->cur_y);
        mp_free_number(arg1);
        mp_free_number(arg2);
    } else {
        mp_knot p = mp_next_knot(h);
        mp_number_clone(mp_minx, h->x_coord);
        mp_number_clone(mp_maxx, mp_minx);
        mp_number_clone(mp_miny, h->y_coord);
        mp_number_clone(mp_maxy, mp_miny);
        while (p != h) {
            if (mp_number_less(p->x_coord, mp_minx)) {
                mp_number_clone(mp_minx, p->x_coord);
            }
            if (mp_number_less(p->y_coord, mp_miny)) {
                mp_number_clone(mp_miny, p->y_coord);
            }
            if (mp_number_greater(p->x_coord, mp_maxx)) {
                mp_number_clone(mp_maxx, p->x_coord);
            }
            if (mp_number_greater(p->y_coord, mp_maxy)) {
                mp_number_clone(mp_maxy, p->y_coord);
            }
            p = mp_next_knot(p);
        }
    }
}

/*tex

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.

*/

// TODO: mp_shape_node
// TODO: keep fields
// TODO: use int values of internals

static mp_shape_node mp_aux_new_shape_node(MP mp)
{
    mp_shape_node t = mp->memory_pool[mp_shape_pool].list;
    mp->memory_pool[mp_shape_pool].used++;
    if (mp->memory_pool[mp_shape_pool].used > mp->memory_pool[mp_shape_pool].max) {
        mp->memory_pool[mp_shape_pool].max = mp->memory_pool[mp_shape_pool].used;
    }
    if (t) {
        t = (mp_shape_node) mp->memory_pool[mp_shape_pool].list;
        mp->memory_pool[mp_shape_pool].list = t->link;
        mp->memory_pool[mp_shape_pool].pool--;
    } else {
        t = mp_memory_allocate(sizeof(mp_shape_node_data));
    }
    t->link = NULL;
    return t;
}

static mp_node mp_new_shape_node(MP mp, mp_knot p, int type)
{
    mp_shape_node t = mp_aux_new_shape_node(mp);
    t->type = type;
    mp_path_ptr(t) = p;
    mp_pen_ptr(t) = NULL; /* |NULL| means don't use a pen */
    mp_dash_ptr(t) = NULL;
    mp_new_number(t->red);
    mp_new_number(t->green);
    mp_new_number(t->blue);
    mp_new_number(t->black);
    mp_new_number(t->miterlimit);
    mp_new_number(t->dashscale);
    mp_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;
    /*tex
        Set the |linejoin| and |miterlimit| fields in object |t|.
    */
    if (mp_number_greater(internal_value(mp_linejoin_internal), mp_unity_t)) {
        t->linejoin = mp_beveled_linejoin_code;
    } else if (mp_number_positive(internal_value(mp_linejoin_internal))) {
        t->linejoin = mp_rounded_linejoin_code;
    } else {
        t->linejoin = mp_mitered_linejoin_code;
    }
    t->stacking = (int) mp_round_unscaled(internal_value(mp_stacking_internal));
    if (mp_number_less(internal_value(mp_miterlimit_internal), mp_unity_t)) {
        mp_set_number_to_unity(t->miterlimit);
    } else {
        mp_number_clone(t->miterlimit, internal_value(mp_miterlimit_internal));
    }
    if (mp_number_greater(internal_value(mp_linecap_internal), mp_unity_t)) {
        t->linecap = mp_squared_linecap_code;
    } else if (mp_number_positive(internal_value(mp_linecap_internal))) {
        t->linecap = mp_rounded_linecap_code;
    } else {
        t->linecap = mp_butt_linecap_code;
    }
    /* */
    t->curvature = mp_default_curvature_code;
    t->bytemap = -1; /* mp_no_bytemap_signal */
    /* */
    return (mp_node) t;
}

static mp_shape_node mp_copy_shape_node(MP mp, mp_shape_node source)
{
    mp_shape_node target = mp_aux_new_shape_node(mp);
    memcpy(target, source, (size_t) sizeof(mp_shape_node_data));
    target->link = NULL;
    mp_new_number_clone(target->red, source->red);
    mp_new_number_clone(target->green, source->green);
    mp_new_number_clone(target->blue, source->blue);
    mp_new_number_clone(target->black, source->black);
    mp_new_number_clone(target->miterlimit, source->miterlimit);
    mp_new_number_clone(target->dashscale, source->dashscale);
    mp_path_ptr(target) = mp_copy_path(mp, mp_path_ptr(source));
 // if (mp_pre_script(source) != NULL) {
        mp_add_string_reference(mp, mp_pre_script(source));
 // }
 // if (mp_post_script(source) != NULL) {
        mp_add_string_reference(mp, mp_post_script(source));
 // }
    if (mp_pen_ptr(source) != NULL) {
        mp_pen_ptr(target) = mp_copy_pen(mp, mp_pen_ptr(source));
    }
    if (mp_dash_ptr(source) != NULL) {
        mp_add_edge_ref(mp, mp_dash_ptr(target));
    }
    return target;
}

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) {
        mp_delete_string_reference(mp, mp_pre_script(p));
 // }
 // if (mp_post_script(p) != NULL) {
        mp_delete_string_reference(mp, mp_post_script(p));
 // }
    e = (mp_edge_header_node) mp_dash_ptr(p);
    mp_free_number(p->red);
    mp_free_number(p->green);
    mp_free_number(p->blue);
    mp_free_number(p->black);
    mp_free_number(p->miterlimit);
    mp_free_number(p->dashscale);
    /* */
    mp->memory_pool[mp_shape_pool].used--;
    if (mp->memory_pool[mp_shape_pool].pool < mp->memory_pool[mp_shape_pool].kept) {
        mp->memory_pool[mp_shape_pool].pool++;
        p->link = mp->memory_pool[mp_shape_pool].list;
        mp->memory_pool[mp_shape_pool].list = p;
    } else {
        mp_memory_free(p);
    }
    /* */
    return e ;
}

static void mp_flush_shape_pool(MP mp)
{
    mp_shape_node p = mp->memory_pool[mp_shape_pool].list;
    while (p) {
        mp_shape_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex

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 |mp_take_fraction|.

*/

static void mp_sqrt_det(MP mp, mp_number *ret, const mp_number *a_orig, const mp_number *b_orig, const mp_number *c_orig, const mp_number *d_orig)
{
    mp_number a, b, c, d;
    mp_number maxabs; /*tex $max(|a|,|b|,|c|,|d|)$ */
    unsigned s = 64;  /*tex amount by which the result of |square_rt| needs to be scaled */
    mp_number absval;
    mp_new_number_clone(a, *a_orig);
    mp_new_number_clone(b, *b_orig);
    mp_new_number_clone(c, *c_orig);
    mp_new_number_clone(d, *d_orig);
    mp_new_number_abs(maxabs, a);
    mp_new_number_abs(absval, b);
    if (mp_number_greater(absval, maxabs)) {
        mp_number_clone(maxabs, absval);
    }
    mp_number_abs_clone(absval, c);
    if (mp_number_greater(absval, maxabs)) {
        mp_number_clone(maxabs, absval);
    }
    mp_number_abs_clone(absval, d);
    if (mp_number_greater(absval, maxabs)) {
        mp_number_clone(maxabs, absval);
    }
    mp_free_number(absval);
    while ((mp_number_less(maxabs, mp_fraction_one_t)) && (s > 1)) {
        mp_number_double(a);
        mp_number_double(b);
        mp_number_double(c);
        mp_number_double(d);
        mp_number_double(maxabs);
        s = s/2;
    }
    {
        mp_number r1, r2;
        mp_new_fraction(r1);
        mp_new_fraction(r2);
        mp_take_fraction(r1, a, d);
        mp_take_fraction(r2, b, c);
        mp_number_subtract(r1, r2);
        mp_number_abs(r1);
        mp_square_rt(*ret, r1);
        mp_number_multiply_int(*ret, s);
        mp_free_number(r1);
        mp_free_number(r2);
    }
    mp_free_number(a);
    mp_free_number(b);
    mp_free_number(c);
    mp_free_number(d);
    mp_free_number(maxabs);
}

static void mp_get_pen_scale(MP mp, mp_number *ret, mp_knot p)
{
    if (p == NULL) {
        mp_set_number_to_zero(*ret);
    } else {
        mp_number a, b, c, d;
        mp_new_number_from_sub(a, p->left_x, p->x_coord);
        mp_new_number_from_sub(b, p->right_x, p->x_coord);
        mp_new_number_from_sub(c, p->left_y,    p->y_coord);
        mp_new_number_from_sub(d, p->right_y, p->y_coord);
        mp_sqrt_det(mp, ret, &a, &b, &c, &d);
        mp_free_number(a);
        mp_free_number(b);
        mp_free_number(c);
        mp_free_number(d);
    }
}

/*tex

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 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.

Make a node of type |c| where |p| is the clipping or |setbounds| path.

*/

/* maybe more more here from the caller */

static mp_start_node mp_new_start_node(MP mp)
{
    mp_start_node p = (mp_start_node) mp->memory_pool[mp_start_pool].list;
    mp->memory_pool[mp_start_pool].used++;
    if (mp->memory_pool[mp_start_pool].used > mp->memory_pool[mp_start_pool].max) {
        mp->memory_pool[mp_start_pool].max = mp->memory_pool[mp_start_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_start_pool].list = p->link;
        mp->memory_pool[mp_start_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_start_node_data));
    }
    p->link = NULL;
    return p;
}

static mp_start_node mp_copy_start_node(MP mp, mp_start_node source)
{
    mp_start_node target = mp_new_start_node(mp);
    memcpy(target, source, (size_t) sizeof(mp_start_node_data));
    target->link = NULL;
    mp_path_ptr(target) = mp_copy_path(mp, mp_path_ptr(source));
 // if (mp_pre_script(source) != NULL) {
        mp_add_string_reference(mp, mp_pre_script(source));
 // }
 // if (mp_post_script(source) != NULL) {
        mp_add_string_reference(mp, mp_post_script(source));
 // }
    return target;
}

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) {
        mp_delete_string_reference(mp, mp_pre_script(p));
 // }
 // if (mp_post_script(p) != NULL) {
        mp_delete_string_reference(mp, mp_post_script(p));
 // }
    /* */
    mp->memory_pool[mp_start_pool].used--;
    if (mp->memory_pool[mp_start_pool].pool < mp->memory_pool[mp_start_pool].kept) {
        mp->memory_pool[mp_start_pool].pool++;
        p->link = mp->memory_pool[mp_start_pool].list;
        mp->memory_pool[mp_start_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_start_pool(MP mp)
{
    mp_start_node p = (mp_start_node) mp->memory_pool[mp_start_pool].list;
    while (p) {
        mp_start_node n = (mp_start_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_stop_node mp_new_stop_node(MP mp)
{
    mp_stop_node p = (mp_stop_node) mp->memory_pool[mp_stop_pool].list;
    mp->memory_pool[mp_stop_pool].used++;
    if (mp->memory_pool[mp_stop_pool].used > mp->memory_pool[mp_stop_pool].max) {
        mp->memory_pool[mp_stop_pool].max = mp->memory_pool[mp_stop_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_stop_pool].list = p->link;
        mp->memory_pool[mp_stop_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_stop_node_data));
    }
    p->link = NULL;
    return p;
}

static mp_stop_node mp_copy_stop_node(MP mp, mp_stop_node source)
{
    mp_stop_node target = mp_new_stop_node(mp);
    memcpy(target, source, (size_t) sizeof(mp_stop_node_data));
    target->link = NULL;
    return target;
}

static void mp_free_stop_node(MP mp, mp_stop_node p)
{
    mp->memory_pool[mp_stop_pool].used--;
    if (mp->memory_pool[mp_stop_pool].pool < mp->memory_pool[mp_stop_pool].kept) {
        mp->memory_pool[mp_stop_pool].pool++;
        p->link = mp->memory_pool[mp_stop_pool].list;
        mp->memory_pool[mp_stop_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_stop_pool(MP mp)
{
    mp_stop_node p = (mp_stop_node) mp->memory_pool[mp_stop_pool].list;
    while (p) {
        mp_stop_node n = (mp_stop_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

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_new_start_node(mp);
                t->type = c;
                t->path = p;
                t->stacking = (int) mp_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_new_stop_node(mp);;
                t->type = c;
                t->stacking = (int) mp_round_unscaled(internal_value(mp_stacking_internal));
                return (mp_node) t;
            }
         // break;
        default:
            /* maybe some message */
            break;
    }
    return NULL;
}

static mp_dash_node mp_new_dash_node(MP mp)
{
    mp_dash_node p = mp->memory_pool[mp_dash_pool].list;
    mp->memory_pool[mp_dash_pool].used++;
    if (mp->memory_pool[mp_dash_pool].used > mp->memory_pool[mp_dash_pool].max) {
        mp->memory_pool[mp_dash_pool].max = mp->memory_pool[mp_dash_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_dash_pool].list = p->link;
        mp->memory_pool[mp_dash_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_dash_node_data));
        p->type = mp_dash_node_type;
    }
    mp_new_number(p->start_x);
    mp_new_number(p->stop_x);
    mp_new_number(p->dash_y);
    p->link = NULL;
    return p;
}

static void mp_free_dash_node(MP mp, mp_dash_node p)
{
    mp_free_number(p->start_x);
    mp_free_number(p->stop_x);
    mp_free_number(p->dash_y);
    mp->memory_pool[mp_dash_pool].used--;
    if (mp->memory_pool[mp_dash_pool].pool < mp->memory_pool[mp_dash_pool].kept) {
        mp->memory_pool[mp_dash_pool].pool++;
        p->link = mp->memory_pool[mp_dash_pool].list;
        mp->memory_pool[mp_dash_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_dash_pool(MP mp)
{
    mp_dash_node p = mp->memory_pool[mp_dash_pool].list;
    while (p) {
        mp_dash_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_edge_header_node mp_new_edge_header_node(MP mp)
{
    mp_edge_header_node p = mp->memory_pool[mp_edge_header_pool].list;
    mp->memory_pool[mp_edge_header_pool].used++;
    if (mp->memory_pool[mp_edge_header_pool].used > mp->memory_pool[mp_edge_header_pool].max) {
        mp->memory_pool[mp_edge_header_pool].max = mp->memory_pool[mp_edge_header_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_edge_header_pool].list = p->link;
        mp->memory_pool[mp_edge_header_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_edge_header_node_data));
    }
    p->link = NULL;
    /* */
    p->type = mp_edge_header_node_type;
    mp_new_number(p->start_x);
    mp_new_number(p->stop_x);
    mp_new_number(p->dash_y);
    mp_new_number(p->minx);
    mp_new_number(p->miny);
    mp_new_number(p->maxx);
    mp_new_number(p->maxy);
    p->list = mp_new_token_node(mp); /* or whatever, just a need a link handle */
    /* */
    return p;
}

static void mp_free_edge_header_node(MP mp, mp_edge_header_node p)
{
    mp_free_token_node(mp, p->list);
    mp_free_number(p->start_x);
    mp_free_number(p->stop_x);
    mp_free_number(p->dash_y);
    mp_free_number(p->minx);
    mp_free_number(p->miny);
    mp_free_number(p->maxx);
    mp_free_number(p->maxy);
    /* */
    mp->memory_pool[mp_edge_header_pool].used--;
    if (mp->memory_pool[mp_edge_header_pool].pool < mp->memory_pool[mp_edge_header_pool].kept) {
        mp->memory_pool[mp_edge_header_pool].pool++;
        p->link = mp->memory_pool[mp_edge_header_pool].list;
        mp->memory_pool[mp_edge_header_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_edge_header_pool(MP mp)
{
    mp_edge_header_node p = mp->memory_pool[mp_edge_header_pool].list;
    while (p) {
        mp_edge_header_node n = (mp_edge_header_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static void mp_init_bbox(MP mp, mp_edge_header_node h)
{
    (void) mp;
    mp_bblast(h) = mp_edge_list(h);
    h->bbtype = mp_no_bounds_code;
    mp_set_number_to_inf(h->minx);
    mp_set_number_to_inf(h->miny);
    mp_set_number_to_negative_inf(h->maxx);
    mp_set_number_to_negative_inf(h->maxy);
}

static void mp_init_edges(MP mp, mp_edge_header_node h)
{
  mp_set_dash_list(h, mp->null_dash);
  mp_obj_tail(h) = mp_edge_list(h);
  mp_edge_list(h)->link = NULL;
  mp_edge_ref_count(h) = 0;
  mp_init_bbox(mp, h);
}

/*tex
    Returns an edge structure that needs to be dereferenced.
*/

static mp_edge_header_node mp_toss_graphic_object(MP mp, mp_node p)
{
    switch (p->type) {
        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;
    }
}

static 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_edge_list(h)->link;
    while (q != NULL) {
        mp_node p = q;
        q = q->link;
        r = mp_toss_graphic_object(mp, p);
        if (r != NULL) {
            mp_delete_edge_ref(mp, r);
        }
    }
    mp_free_edge_header_node(mp, h);
}

static 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 = q->link;
        mp_free_dash_node(mp, p);
    }
    mp_set_dash_list(h, mp->null_dash);
}

/*tex

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.

*/

static mp_edge_header_node mp_private_edges(MP mp, mp_edge_header_node h)
{
    /*tex
        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_edge_list(h)->link, NULL);
        /*tex
            Copy the dash list from |h| to |hh|.
        */
        pp = (mp_dash_node) hh;
        p = mp_get_dash_list(h);
        while ((p != mp->null_dash)) {
            pp->link = mp_new_dash_node(mp);
            pp = (mp_dash_node) pp->link;
            mp_number_clone(pp->start_x, p->start_x);
            mp_number_clone(pp->stop_x, p->stop_x);
            p = (mp_dash_node) p->link;
        }
        pp->link = mp->null_dash;
        mp_number_clone(hh->dash_y, h->dash_y);
        /*tex
            Copy the bounding box information from |h| to |hh| and make |mp_bblast(hh)| point into
            the new object list.
        */
        mp_number_clone(hh->minx, h->minx);
        mp_number_clone(hh->miny, h->miny);
        mp_number_clone(hh->maxx, h->maxx);
        mp_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");
            } else {
                p = (mp_dash_node) p->link;
                pp = (mp_dash_node) pp->link;
            }
        }
        mp_bblast(hh) = (mp_node) pp;

        return hh;
    }
}

/*tex

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|.

*/

static mp_edge_header_node mp_copy_objects(MP mp, mp_node p, mp_node q) {
    mp_node pp; /*tex the last newly copied object */
    mp_edge_header_node hh = mp_new_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) {
        /*tex
            Make |mp_link(pp)| point to a copy of object |p|, and update |p| and |pp|. Also
            fix anything in graphical object |pp| that should differ from the corresponding
            field in |p|.
        */
        switch (p->type) {
            case mp_fill_node_type:
            case mp_stroked_node_type:
                pp->link = (mp_node) mp_copy_shape_node(mp, (mp_shape_node) p);
                pp = pp->link;
                break;
            case mp_start_clip_node_type:
            case mp_start_group_node_type:
            case mp_start_bounds_node_type:
                pp->link = (mp_node) mp_copy_start_node(mp, (mp_start_node) p);
                pp = pp->link;
                break;
            case mp_stop_clip_node_type:
            case mp_stop_group_node_type:
            case mp_stop_bounds_node_type:
                pp->link = (mp_node) mp_copy_stop_node(mp, (mp_stop_node) p);
                pp = pp->link;
                break;
            default:
                break;
        }
        p = p->link;
    }
    mp_obj_tail(hh) = pp;
    pp->link = NULL;
    return hh;
}

/*tex

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.

*/

static mp_node mp_skip_one_component(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 = p->link;
    } while (lev != 0);
    return p;
}

/*tex

Here is a diagnostic routine for printing an edge structure in symbolic form.

*/

static void mp_print_edges(MP mp, mp_node h, const char *s, int nuline)
{
    mp_node p = mp_edge_list(h); /*tex a graphical object to be printed */
    mp_number scf;               /*tex a scale factor for the dash pattern */
    mp_new_number(scf);
    mp_begin_diagnostic_print(mp, "Edge structure", s, nuline);
    while (p->link != NULL) {
        p = p->link;
        mp_print_ln(mp);
        switch (p->type) {
            /*tex
                Cases for printing graphical object node |p|.
            */
            case mp_fill_node_type:
                mp_print_string(mp, "Filled contour ");
                mp_print_obj_color (mp, p);
                mp_print_char(mp, ':');
                mp_print_ln(mp);
                mp_print_path_only(mp, mp_path_ptr((mp_shape_node) p));
                mp_print_ln(mp);
                if ((mp_pen_ptr((mp_shape_node) p) != NULL)) {
                    /*tex
                        Print join type for graphical object |p|.
                    */
                    switch (((mp_shape_node) p)->linejoin) {
                        case mp_mitered_linejoin_code:
                            mp_print_string(mp, "mitered joins limited ");
                            mp_print_number(mp, ((mp_shape_node) p)->miterlimit);
                            break;
                        case mp_rounded_linejoin_code:
                            mp_print_string(mp, "round joins");
                            break;
                        case mp_beveled_linejoin_code:
                            mp_print_string(mp, "beveled joins");
                            break;
                        default:
                            mp_print_string(mp, "?? joins");
                            break;
                    }
                    mp_print_string(mp, " with pen");
                    mp_print_ln(mp);
                    mp_print_pen_only(mp, mp_pen_ptr((mp_shape_node) p));
                }
                break;
            case mp_stroked_node_type:
                mp_print_string(mp, "Filled pen stroke ");
                mp_print_obj_color (mp, p);
                mp_print_char(mp, ':');
                mp_print_ln(mp);
                mp_print_path_only(mp, mp_path_ptr((mp_shape_node) p));
                if (mp_dash_ptr(p) != NULL) {
                    /*tex
                        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));
                    if (! ok_to_dash) {
                        mp_set_number_to_unity(scf);
                    } else {
                        mp_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) || mp_number_negative(hhd->dash_y)) {
                        mp_print_string(mp, " dashed ??");
                    } else {
                        mp_number dashoffset;
                        mp_number ret, arg1;
                        mp_new_number(ret);
                        mp_new_number(arg1);
                        mp_new_number(dashoffset);
                        mp_set_number_from_addition(mp->null_dash->start_x, ppd->start_x, hhd->dash_y );
                        mp_print_nl(mp, "dashed (");
                        while (ppd != mp->null_dash) {
                            mp_print_string(mp, "on ");
                            mp_set_number_from_subtraction(arg1, ppd->stop_x, ppd->start_x);
                            mp_take_scaled(ret, arg1, scf);
                            mp_print_number(mp, ret);
                            mp_print_string(mp, " off ");
                            mp_set_number_from_subtraction(arg1, ((mp_dash_node) ppd->link)->start_x, ppd->stop_x);
                            mp_take_scaled(ret, arg1, scf);
                            mp_print_number(mp, ret);
                            ppd = (mp_dash_node) ppd->link;
                            if (ppd != mp->null_dash) {
                                mp_print_char(mp, ' ');
                            }
                        }
                        mp_print_string(mp, ") shifted ");
                        mp_dash_offset(mp, &dashoffset, hhd);
                        mp_take_scaled(ret, dashoffset, scf);
                        mp_number_negate(ret);
                        mp_print_number(mp, ret);
                        mp_free_number(dashoffset);
                        mp_free_number(ret);
                        mp_free_number(arg1);
                        if (!ok_to_dash || mp_number_zero(hhd->dash_y)) {
                            mp_print_string(mp, " (this will be ignored)");
                        }
                    }
                }
                mp_print_ln(mp);
                /*tex
                    For stroked nodes, we need to print |linecap_val(p)| as well.
                */

                /* todo: proper spacing */

                switch (((mp_shape_node) p)->linecap) {
                    case mp_butt_linecap_code:
                        mp_print_string(mp, "butt");
                        break;
                    case mp_rounded_linecap_code:
                        mp_print_string(mp, "round");
                        break;
                    case mp_squared_linecap_code:
                        mp_print_string(mp, "square");
                        break;
                    default:
                        mp_print_string(mp, "??");
                        break;
                }
                mp_print_string(mp, " ends, ");
                /*tex
                    Print join type for graphical object |p|.
                */
                switch (((mp_shape_node) p)->linejoin) {
                    case mp_mitered_linejoin_code:
                        mp_print_string(mp, "mitered joins limited");
                        mp_print_number(mp, ((mp_shape_node) p)->miterlimit);
                        break;
                    case mp_rounded_linejoin_code:
                        mp_print_string(mp, "round joins");
                        break;
                    case mp_beveled_linejoin_code:
                        mp_print_string(mp, "beveled joins");
                        break;
                    default:
                        mp_print_string(mp, "unknown joins");
                        break;
                }
                /*tex
                    Print curvature treatment for graphical object |p|.
                */
                switch (((mp_shape_node) p)->curvature) {
                    case mp_default_curvature_code:
                        mp_print_string(mp, "default curvature");
                        break;
                    case mp_always_curvature_code:
                        mp_print_string(mp, "always curvature");
                        break;
                    case mp_never_curvature_code:
                        mp_print_string(mp, "never curvature");
                        break;
                    case mp_repeat_curvature_code:
                        mp_print_string(mp, "repeat curvature");
                        break;
                    default:
                        mp_print_string(mp, "unknown curvature");
                        break;
                }
                if (((mp_shape_node) p)->bytemap >= 0) {
                    mp_print_format(mp, " with bytemap %i", ((mp_shape_node) p)->bytemap);
                }
                /* */
                mp_print_string(mp, " with pen");
                mp_print_ln(mp);
                if (mp_pen_ptr((mp_shape_node) p) == NULL) {
                    mp_print_string(mp, "???"); /* shouldn't happen */
                } else {
                    mp_print_pen_only(mp, mp_pen_ptr((mp_shape_node) p));
                }
                break;
            case mp_start_clip_node_type:
                mp_print_string(mp, "clipping path:");
                goto COMMONSTART;
            case mp_start_group_node_type:
                mp_print_string(mp, "setgroup path:");
                goto COMMONSTART;
            case mp_start_bounds_node_type:
                mp_print_string(mp, "setbounds path:");
              COMMONSTART:
                mp_print_ln(mp);
                mp_print_path_only(mp, mp_path_ptr((mp_start_node) p));
                break;
            case mp_stop_clip_node_type:
                mp_print_string(mp, "stop clipping");
                break;
            case mp_stop_group_node_type:
                mp_print_string(mp, "stop group");
                break;
            case mp_stop_bounds_node_type:
                mp_print_string(mp, "end of setbounds");
                break;

            default:
                mp_print_string(mp, "[unknown object type!]");
                break;
        }
    }
    mp_print_nl(mp, "End edges");
    if (p != mp_obj_tail(h)) {
        mp_print_string(mp, "?");
    }
    mp_end_diagnostic(mp, 1);
    mp_free_number(scf);
}

/*tex

Here is a routine that prints the color of a graphical object if it isn't black (the default color).

*/

static 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 (mp_number_positive(p0->grey)) {
                mp_print_format(mp,"greyed (%N)", p0->grey);
            }
            break;
        case mp_cmyk_model:
            if (mp_number_positive(p0->cyan)   || mp_number_positive(p0->magenta)
             || mp_number_positive(p0->yellow) || mp_number_positive(p0->black)) {
                mp_print_format(mp,"processcolored (%N,%N,%N,%N)", p0->cyan, p0->magenta, p0->yellow, p0->black);
            }
            break;
        case mp_rgb_model:
            if (mp_number_positive(p0->red) || mp_number_positive(p0->green) || mp_number_positive(p0->blue)) {
                mp_print_format(mp,"colored (%N,%N,%N)", p0->red, p0->green, p0->blue);
            }
            break;
        default:
            break;
    }
}

/*tex

    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.

*/

void mp_dash_offset(MP mp, mp_number *x, mp_dash_node h)
{
    if (mp_get_dash_list(h) == mp->null_dash || mp_number_negative(h->dash_y)) {
        mp_confusion(mp, "dash offset");
    } else if (mp_number_zero(h->dash_y)) {
        mp_set_number_to_zero(*x);
    } else {
        mp_number_clone(*x, (mp_get_dash_list(h))->start_x);
        mp_number_modulo(*x, h->dash_y);
        mp_number_negate(*x);
        if (mp_number_negative(*x)) {
            mp_number_add(*x, h->dash_y);
        }
    }
}

/*tex

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$.

*/

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 {
        mp_node p;              /*tex this scans the stroked nodes in the object list */
        mp_node p0;             /*tex if not |NULL| this points to the first stroked node */
        mp_knot pp, qq, rr;     /*tex pointers into |mp_path_ptr(p)| */
        mp_dash_node d, dd;     /*tex pointers used to create the dash list */
        mp_number y0;
        mp_dash_node dln;       /*tex |mp_link(d)| */
        mp_edge_header_node hh; /*tex an edge header that tells how to break up |dln| */
        mp_node ds;             /*tex the stroked node from which |hh| and |hsf| are derived */
        mp_new_number(y0);      /*tex the initial $y$ coordinate */
        p0 = NULL;
        p = mp_edge_list(h)->link;
        while (p != NULL) {
            if (p->type != mp_stroked_node_type) {
                /*tex
                    Complain that the edge structure contains a node of the wrong type and |goto not_found|.
                */
                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;
            }
            pp = mp_path_ptr((mp_shape_node) p);
            if (p0 == NULL) {
                p0 = p;
                mp_number_clone(y0, pp->y_coord);
            }
            /*tex
                Make |d| point to a new dash node created from stroke |p| and path |pp| or |goto
                not_found| if there is an error. Also make sure |p| and |p0| are the same color and
                |goto not_found| if there is an error
            */
            if (! mp_number_equal(((mp_shape_node) p)->red,   ((mp_shape_node) p0)->red)
             || ! mp_number_equal(((mp_shape_node) p)->black, ((mp_shape_node) p0)->black)
             || ! mp_number_equal(((mp_shape_node) p)->green, ((mp_shape_node) p0)->green)
             || ! mp_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;
            }
            rr = pp;
            if (mp_next_knot(pp) != pp) {
                do {
                    qq = rr;
                    rr = mp_next_knot(rr);
                    /*tex
                        Check for retracing between knots |qq| and |rr| and |goto not_found| if there
                        is a problem.
                    */
                    {
                        mp_number x0, x1, x2, x3; /*tex $x$ coordinates of the segment from |qq| to |rr| */
                        mp_new_number_clone(x0, qq->x_coord);
                        mp_new_number_clone(x1, qq->right_x);
                        mp_new_number_clone(x2, rr->left_x);
                        mp_new_number_clone(x3, rr->x_coord);
                        if (mp_number_greater(x0, x1) || mp_number_greater(x1, x2) || mp_number_greater(x2, x3)) {
                            if (mp_number_less(x0, x1) || mp_number_less(x1, x2) || mp_number_less(x2, x3)) {
                                mp_number a1, a2, a3, a4;
                                int test;
                                mp_new_number_from_sub(a1, x2, x1);
                                mp_new_number_from_sub(a2, x2, x1);
                                mp_new_number_from_sub(a3, x1, x0);
                                mp_new_number_from_sub(a4, x3, x2);
                                test = mp_ab_vs_cd(a1, a2, a3, a4);
                                mp_free_number(a1);
                                mp_free_number(a2);
                                mp_free_number(a3);
                                mp_free_number(a4);
                                if (test > 0) {
                                    mp_x_retrace_error(mp);
                                    mp_free_number(x0);
                                    mp_free_number(x1);
                                    mp_free_number(x2);
                                    mp_free_number(x3);
                                    goto NOT_FOUND;
                                }
                            }
                        }
                        if (mp_number_greater(pp->x_coord, x0) || mp_number_greater(x0, x3)) {
                            if (mp_number_less(pp->x_coord, x0) || mp_number_less(x0, x3)) {
                                mp_x_retrace_error(mp);
                                mp_free_number(x0);
                                mp_free_number(x1);
                                mp_free_number(x2);
                                mp_free_number(x3);
                                goto NOT_FOUND;
                            }
                        }
                        mp_free_number(x0);
                        mp_free_number(x1);
                        mp_free_number(x2);
                        mp_free_number(x3);
                    }
                } while (mp_right_type(rr) != mp_endpoint_knot);
            }
            d = mp_new_dash_node(mp);
            if (mp_dash_ptr(p) == NULL) {
                mp_dash_info(d) = NULL;
            } else {
                mp_dash_info(d) = p;
            }
            if (mp_number_less(pp->x_coord, rr->x_coord)) {
                mp_number_clone(d->start_x, pp->x_coord);
                mp_number_clone(d->stop_x, rr->x_coord);
            } else {
                mp_number_clone(d->start_x, rr->x_coord);
                mp_number_clone(d->stop_x, pp->x_coord);
            }
            /*tex
                Insert |d| into the dash list and |goto not_found| if there is an error.
            */
            mp_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 (mp_number_less(((mp_dash_node) dd->link)->start_x, d->stop_x)) {
                dd = (mp_dash_node) dd->link;
            }
            if ((dd != (mp_dash_node) h) && mp_number_greater(dd->stop_x, d->start_x)) {
                mp_x_retrace_error(mp);
                goto NOT_FOUND;
            }
            d->link = dd->link;
            dd->link = d;
            p = p->link;
        }
        if (mp_get_dash_list(h) == mp->null_dash) {
            /*tex No error message. */
            goto NOT_FOUND;
        } else {
            /*tex
                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 */
            mp_new_number(hsf);
            d = (mp_dash_node) h; /* now |d->link=mp_get_dash_list(h)| */
            while (d->link != mp->null_dash) {
                ds = mp_dash_info(d->link);
                if (ds == NULL) {
                    d = (mp_dash_node) d->link;
                } else {
                    hh = (mp_edge_header_node) mp_dash_ptr(ds);
                    mp_number_clone(hsf, ((mp_shape_node) ds)->dashscale);
                    if (hh == NULL) {
                        mp_confusion(mp, "dash pattern");
                        return NULL;
                    } else if (mp_number_zero(((mp_dash_node) hh)->dash_y )) {
                        d = (mp_dash_node) d->link;
                    } else if (mp_get_dash_list(hh) == NULL) {
                        mp_confusion(mp, "dash list");
                        return NULL;
                    } else {
                        /*tex  replace |mp_link(d)| by a dashed version as determined by edge header |hh| and scale factor |ds| */
                        mp_number xoff; /* added to $x$ values in |mp_get_dash_list(hh)| to match |dln| */
                        mp_number dashoff;
                        mp_number r1, r2;
                        mp_new_number(r1);
                        mp_new_number(r2);
                        dln = (mp_dash_node) d->link;
                        dd = mp_get_dash_list(hh);
                        mp_new_number(dashoff);
                        mp_dash_offset(mp, &dashoff, (mp_dash_node) hh);
                        mp_take_scaled(r1, hsf, dd->start_x);
                        mp_take_scaled(r2, hsf, dashoff);
                        mp_number_add(r1, r2);
                        mp_new_number_from_sub(xoff, dln->start_x, r1);
                        mp_free_number(dashoff);
                        mp_take_scaled(r1, hsf, dd->start_x);
                        mp_take_scaled(r2, hsf, hh->dash_y);
                        mp_set_number_from_addition(mp->null_dash->start_x, r1, r2);
                        mp_number_clone(mp->null_dash->stop_x, mp->null_dash->start_x);
                        /*tex
                            Advance |dd| until finding the first dash that overlaps |dln| when offset by |xoff|.
                        */
                        {
                            mp_number r1;
                            mp_new_number(r1);
                            mp_take_scaled(r1, hsf, dd->stop_x);
                            mp_number_add(r1, xoff);
                            while (mp_number_less(r1, dln->start_x)) {
                                dd = (mp_dash_node) dd->link;
                                mp_take_scaled(r1, hsf, dd->stop_x);
                                mp_number_add(r1, xoff);
                            }
                            mp_free_number(r1);
                        }
                        while (mp_number_lessequal(dln->start_x, dln->stop_x)) {
                            /*tex
                                If |dd| has `fallen off the end', back up to the beginning and fix
                                |xoff|.
                            */
                            if (dd == mp->null_dash) {
                                mp_number ret;
                                mp_new_number(ret);
                                dd = mp_get_dash_list(hh);
                                mp_take_scaled(ret, hsf, hh->dash_y);
                                mp_number_add(xoff, ret);
                                mp_free_number(ret);
                            }
                            /*tex
                                Insert a dash between |d| and |dln| for the overlap with the offset
                                version of |dd|.
                            */
                            {
                                mp_number r1;
                                mp_new_number(r1);
                                mp_take_scaled(r1, hsf, dd->start_x);
                                mp_number_add(r1, xoff);
                                if (mp_number_lessequal(r1, dln->stop_x)) {
                                    d->link = mp_new_dash_node(mp);
                                    d = d->link;
                                    d->link = dln;
                                    mp_take_scaled(r1, hsf, dd->start_x );
                                    mp_number_add(r1, xoff);
                                    if (mp_number_greater(dln->start_x, r1)) {
                                        mp_number_clone(d->start_x, dln->start_x);
                                    } else {
                                        mp_number_clone(d->start_x, r1);
                                    }
                                    mp_take_scaled(r1, hsf, dd->stop_x);
                                    mp_number_add(r1, xoff);
                                    if (mp_number_less(dln->stop_x, r1)) {
                                        mp_number_clone(d->stop_x, dln->stop_x );
                                    } else {
                                        mp_number_clone(d->stop_x, r1);
                                    }
                                }
                                mp_free_number(r1);
                            }
                            dd = (mp_dash_node) dd->link;
                            mp_take_scaled(r1, hsf, dd->start_x);
                            mp_set_number_from_addition(dln->start_x , xoff, r1);
                        }
                        mp_free_number(xoff);
                        mp_free_number(r1);
                        mp_free_number(r2);
                        d->link = dln->link;
                        mp_free_dash_node(mp, dln);
                    }
                }
                mp_free_number(hsf);
            }
            /*tex
                Set |dash_y(h)| and merge the first and last dashes if necessary.
            */
            d = mp_get_dash_list(h);
            while (d->link != mp->null_dash) {
                d = d->link;
            }
            dd = mp_get_dash_list(h);
            mp_set_number_from_subtraction(h->dash_y, d->stop_x, dd->start_x);
            {
                mp_number absval;
                mp_new_number(absval);
                mp_number_abs_clone(absval, y0);
                if (mp_number_greater(absval, h->dash_y) ) {
                    mp_number_clone(h->dash_y, absval);
                } else if (d != dd) {
                    mp_set_dash_list(h, dd->link);
                    mp_set_number_from_addition(d->stop_x, dd->stop_x, h->dash_y);
                    mp_free_dash_node(mp, dd);
                }
                mp_free_number(absval);
            }
            mp_free_number(y0);
            return h;
        }
      NOT_FOUND:
        mp_free_number(y0);
        mp_flush_dash_list(mp, h);
        mp_delete_edge_ref(mp, h);
        return NULL;
    }
}

/*tex
    A similar error occurs when monotonicity fails.
*/

static 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);
}

/*tex

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|.

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.Having carefully
saved the dashed stroked nodes in the corresponding dash nodes, we must be prepared to break up these
dashes into smaller dashes.

The name of this module is a bit of a lie because we just find the first |dd| where |mp_take_scaled(hsf,
stop_x(dd))| is large enough to make an overlap possible. It could be that the un-offset version of
dash |dln| falls in the gap between |dd| and its predecessor.

At this point we already know that |start_x(dln) <= xoff + mp_take_scaled(hsf,stop_x(dd))|.The next
major task is to update the bounding box information in an edge header~|h|. This is done via a
procedure adjust_bbox| that enlarges an edge header's bounding box to accommodate the box computed by
|path_bbox| or |pen_bbox|. (This is stored in global variables |minx|, |miny|, |maxx|, and |maxy|.)

*/

static void mp_adjust_bbox(MP mp, mp_edge_header_node h)
{
    if (mp_number_less(mp_minx, h->minx)) {
        mp_number_clone(h->minx, mp_minx);
    }
    if (mp_number_less(mp_miny, h->miny)) {
        mp_number_clone(h->miny, mp_miny);
    }
    if (mp_number_greater(mp_maxx, h->maxx)) {
        mp_number_clone(h->maxx, mp_maxx);
    }
    if (mp_number_greater(mp_maxy, h->maxy)) {
        mp_number_clone(h->maxy, mp_maxy);
    }
}

/*tex

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|.

*/

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_number dx, dy;            /*tex a unit vector in the direction out of the path at~|p| */
        mp_number d;                 /*tex a factor for adjusting the length of |(dx,dy)| */
        mp_number z;                 /*tex a coordinate being tested against the bounding box */
        mp_number xx, yy;            /*tex the extreme pen vertex in the |(dx,dy)| direction */
        mp_knot q = mp_next_knot(p); /*tex a knot node adjacent to knot |p| */
        mp_new_fraction(dx);
        mp_new_fraction(dy);
        mp_new_number(xx);
        mp_new_number(yy);
        mp_new_number(z);
        mp_new_number(d);
        while (1) {
            /*tex
                Make |(dx,dy)| the final direction for the path segment from |q| to~|p|; set~|d|
            */
            if (q == mp_next_knot(p)) {
                mp_set_number_from_subtraction(dx, p->x_coord, p->right_x);
                mp_set_number_from_subtraction(dy, p->y_coord, p->right_y);
                if (mp_number_zero(dx) && mp_number_zero(dy)) {
                    mp_set_number_from_subtraction(dx, p->x_coord, q->left_x);
                    mp_set_number_from_subtraction(dy, p->y_coord, q->left_y);
                }
            } else {
                mp_set_number_from_subtraction(dx, p->x_coord, p->left_x);
                mp_set_number_from_subtraction(dy, p->y_coord, p->left_y);
                if (mp_number_zero(dx) && mp_number_zero(dy)) {
                    mp_set_number_from_subtraction(dx, p->x_coord, q->right_x);
                    mp_set_number_from_subtraction(dy, p->y_coord, q->right_y);
                }
            }
            mp_set_number_from_subtraction(dx, p->x_coord, q->x_coord);
            mp_set_number_from_subtraction(dy, p->y_coord, q->y_coord);
            mp_pyth_add(d, dx, dy);
            if (mp_number_positive(d)) {
                /*tex
                    Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|.
                */
                mp_number arg1, r;
                mp_new_fraction(r);
                mp_new_number(arg1);
                mp_make_fraction(r, dx, d);
                mp_number_clone(dx, r);
                mp_make_fraction(r, dy, d);
                mp_number_clone(dy, r);
                mp_free_number(r);
                mp_number_negated_clone(arg1, dy);
                mp_find_offset(mp, &arg1, &dx, pp);
                mp_free_number(arg1);
                mp_number_clone(xx, mp->cur_x);
                mp_number_clone(yy, mp->cur_y);
                for (int i = 1; i <= 2; i++) {
                    /*tex
                        Use |(dx,dy)| to generate a vertex of the square end cap and update the
                        bounding box to accommodate it.
                    */
                    mp_number r1, r2, arg1;
                    mp_new_fraction(r1);
                    mp_new_fraction(r2);
                    mp_find_offset(mp, &dx, &dy, pp);
                    mp_new_number_from_sub(arg1, xx, mp->cur_x);
                    mp_take_fraction(r1, arg1, dx);
                    mp_set_number_from_subtraction(arg1, yy, mp->cur_y);
                    mp_take_fraction(r2, arg1, dy);
                    mp_set_number_from_addition(d, r1, r2);
                    if ((mp_number_negative(d) && (i == 1)) || (mp_number_positive(d) && (i == 2))) {
                        mp_confusion(mp, "box ends");
                    }
                    mp_take_fraction(r1, d, dx);
                    mp_set_number_from_addition(z, p->x_coord, mp->cur_x);
                    mp_number_add(z, r1);
                    if (mp_number_less(z, h->minx)) {
                        mp_number_clone(h->minx, z);
                    }
                    if (mp_number_greater(z, h->maxx)) {
                        mp_number_clone(h->maxx, z);
                    }
                    mp_take_fraction(r1, d, dy);
                    mp_set_number_from_addition(z, p->y_coord, mp->cur_y);
                    mp_number_add(z, r1);
                    if (mp_number_less(z, h->miny)) {
                        mp_number_clone(h->miny, z);
                    }
                    if (mp_number_greater(z, h->maxy)) {
                        mp_number_clone(h->maxy, z);
                    }
                    mp_free_number(r1);
                    mp_free_number(r2);
                    mp_free_number(arg1);

                    mp_number_negate(dx);
                    mp_number_negate(dy);
                }
            }
            if (mp_right_type(p) == mp_endpoint_knot) {
                goto DONE;
            } else {
                /*tex
                    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);
            }
        }
      DONE:
        mp_free_number(dx);
        mp_free_number(dy);
        mp_free_number(xx);
        mp_free_number(yy);
        mp_free_number(z);
        mp_free_number(d);
    }
}

/*tex

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 the objects to be
clipped. Such calls are distinguished by the fact that the boolean parameter |top_level| is false.

*/

static void mp_set_bbox(MP mp, mp_edge_header_node h, int top_level)
{
    /*tex
        Wipe out any existing bounding box information if |bbtype(h)| is incompatible with
        |internal[mp_true_corners]|
    */
    mp->bbox_nesting++;
    if (mp->bbox_nesting > 4095) {
        mp_fatal_error(mp, "Boundingbox calculation recurse depth exceeds 4095 ... too much clipping?");
    }
    switch (h->bbtype) {
        case mp_no_bounds_code:
            break;
        case mp_bounds_set_code:
            if (mp_number_positive(internal_value(mp_true_corners_internal))) {
                mp_init_bbox(mp, h);
            }
            break;
        case mp_bounds_unset_code:
            if (mp_number_nonpositive(internal_value(mp_true_corners_internal))) {
                mp_init_bbox(mp, h);
            }
            break;
    }
    while (mp_bblast(h)->link != NULL) {
        mp_node p = mp_bblast(h)->link; /* a graphical object being considered */
        mp_bblast(h) = p;
        switch (p->type) {
            case mp_stop_clip_node_type:
                if (top_level) {
                    mp_confusion(mp, "clip");
                    break;
                } else {
                    goto DONE;
                }
            /*tex
                Other cases for updating the bounding box based on the type of object |p|.
            */
            case mp_start_bounds_node_type:
                if (mp_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);
                    /*tex
                        Scan to the matching |mp_stop_bounds_node| node and update |p| and
                        |mp_bblast(h)|
                    */
                    {
                        int lev = 1;
                        while (lev != 0) {
                            if (p->link == NULL) {
                                mp_confusion(mp, "bounds");
                            } else {
                                p = p->link;
                                if (p->type == mp_start_bounds_node_type) {
                                    ++lev;
                                } else if (p->type == mp_stop_bounds_node_type) {
                                    --lev;
                                }
                            }
                        }
                        mp_bblast(h) = p;
                    }
                }
                break;
            case mp_stop_bounds_node_type:
                if (mp_number_nonpositive(internal_value(mp_true_corners_internal))) {
                    mp_confusion(mp, "bounds");
                }
                break;
            case mp_fill_node_type:
            case mp_stroked_node_type:
                {
                    mp_path_bbox(mp, mp_path_ptr((mp_shape_node) p));
                    /*tex
                        Stroked paths always have a pen. 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.
                    */
                    if (mp_pen_ptr((mp_shape_node) p) != NULL) {
                        mp_number x0a, y0a, x1a, y1a;
                        mp_new_number_clone(x0a, mp_minx);
                        mp_new_number_clone(y0a, mp_miny);
                        mp_new_number_clone(x1a, mp_maxx);
                        mp_new_number_clone(y1a, mp_maxy);
                        mp_pen_bbox(mp, mp_pen_ptr((mp_shape_node) p));
                        mp_number_add(mp_minx, x0a);
                        mp_number_add(mp_miny, y0a);
                        mp_number_add(mp_maxx, x1a);
                        mp_number_add(mp_maxy, y1a);
                        mp_free_number(x0a);
                        mp_free_number(y0a);
                        mp_free_number(x1a);
                        mp_free_number(y1a);
                    }
                    mp_adjust_bbox(mp, h);
                    /*tex
                        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;
            case mp_start_clip_node_type:
                {
                    mp_number sminx, sminy, smaxx, smaxy;
                    mp_number x0a, y0a, x1a, y1a;
                    mp_path_bbox(mp, mp_path_ptr((mp_start_node) p));
                    mp_new_number_clone(x0a, mp_minx);
                    mp_new_number_clone(y0a, mp_miny);
                    mp_new_number_clone(x1a, mp_maxx);
                    mp_new_number_clone(y1a, mp_maxy);
                    mp_new_number_clone(sminx, h->minx);
                    mp_new_number_clone(sminy, h->miny);
                    mp_new_number_clone(smaxx, h->maxx);
                    mp_new_number_clone(smaxy, h->maxy);
                    /*tex
                        Reinitialize the bounding box in header |h| and call |set_bbox| recursively
                        starting at |mp_link(p)|.
                    */
                    mp_set_number_to_inf(h->minx);
                    mp_set_number_to_inf(h->miny);
                    mp_set_number_to_negative_inf(h->maxx);
                    mp_set_number_to_negative_inf(h->maxy);
                    mp_set_bbox(mp, h, 0);
                    /*tex
                        Clip the bounding box in |h| to the rectangle given by |x0a|, |x1a|, |y0a|,
                        |y1a|.
                    */
                    if (mp_number_less(h->minx, x0a)) {
                        mp_number_clone(h->minx, x0a);
                    }
                    if (mp_number_less(h->miny, y0a)) {
                        mp_number_clone(h->miny, y0a);
                    }
                    if (mp_number_greater(h->maxx, x1a)) {
                        mp_number_clone(h->maxx, x1a);
                    }
                    if (mp_number_greater(h->maxy, y1a)) {
                        mp_number_clone(h->maxy, y1a);
                    }
                    mp_number_clone(mp_minx, sminx);
                    mp_number_clone(mp_miny, sminy);
                    mp_number_clone(mp_maxx, smaxx);
                    mp_number_clone(mp_maxy, smaxy);
                    mp_adjust_bbox(mp, h);
                    mp_free_number(sminx);
                    mp_free_number(sminy);
                    mp_free_number(smaxx);
                    mp_free_number(smaxy);
                    mp_free_number(x0a);
                    mp_free_number(y0a);
                    mp_free_number(x1a);
                    mp_free_number(y1a);
                }
                break;
            default:
                break;
        }
    }
    if (! top_level) {
        mp_confusion(mp, "boundingbox");
    }
  DONE:
    --mp->bbox_nesting;
}

/*tex

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.

*/

static mp_knot mp_offset_prep(MP mp, mp_knot c, mp_knot h)
{
    int n;                                 /*tex the number of vertices in the pen polygon */
    mp_knot c0, p, q, q0, r, w, ww;        /*tex for list manipulation */
    int k_needed;                          /*tex amount to be added to |mp_info(p)| when it is computed */
    mp_knot w0;                            /*tex a pointer to pen offset to use just before |p| */
    mp_number dxin, dyin;                  /*tex the direction into knot |p| */
    int turn_amt;                          /*tex change in pen offsets for the current cubic */
    mp_number max_coef;                    /*tex used while scaling */
    mp_number ss;
    mp_number x0, x1, x2, y0, y1, y2;      /*tex representatives of derivatives */
    mp_number t0, t1, t2;                  /*tex coefficients of polynomial for slope testing */
    mp_number du, dv, dx, dy;              /*tex for directions of the pen and the curve */
    mp_number dx0, dy0;                    /*tex initial direction for the first cubic in the curve */
    mp_number x0a, x1a,x2a, y0a, y1a, y2a; /*tex intermediate values */
    mp_number t;                           /*tex where the derivative passes through zero */
    mp_number s;                           /*tex a temporary value */
    mp_number dx_m;                        /*tex signal a pertubation of dx */
    mp_number dy_m;                        /*tex signal a pertubation of dx */
    mp_number dxin_m;                      /*tex signal a pertubation of dxin */
    mp_number u0, u1, v0, v1;              /*tex intermediate values for $d(t)$ calculation */
    int d_sign;                            /*tex sign of overall change in direction for this cubic */
    mp_new_number(max_coef);
    mp_new_number(dxin);
    mp_new_number(dyin);
    mp_new_number(dx0);
    mp_new_number(dy0);
    mp_new_number(x0);
    mp_new_number(y0);
    mp_new_number(x1);
    mp_new_number(y1);
    mp_new_number(x2);
    mp_new_number(y2);
    mp_new_number(du);
    mp_new_number(dv);
    mp_new_number(dx);
    mp_new_number(dy);
    mp_new_number(x0a);
    mp_new_number(y0a);
    mp_new_number(x1a);
    mp_new_number(y1a);
    mp_new_number(x2a);
    mp_new_number(y2a);
    mp_new_number(t0);
    mp_new_number(t1);
    mp_new_number(t2);
    mp_new_number(u0);
    mp_new_number(u1);
    mp_new_number(v0);
    mp_new_number(v1);
    mp_new_number(dx_m);
    mp_new_number(dy_m);
    mp_new_number(dxin_m);
    mp_new_fraction(ss);
    mp_new_fraction(s);
    mp_new_fraction(t);
    /*tex
        Initialize the pen size~|n|. 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
    */
    n = 0;
    p = h;
    do {
        ++n;
        p = mp_next_knot(p);
    } while (p != h);
    /*tex
        Initialize the incoming direction and pen offset at |c|. 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.
    */
    {
        mp_knot hn = mp_next_knot(h);
        mp_knot hp = mp_prev_knot(h);
        mp_set_number_from_subtraction(dxin, hn->x_coord, hp->x_coord);
        mp_set_number_from_subtraction(dyin, hn->y_coord, hp->y_coord);
        if (mp_number_zero(dxin) && mp_number_zero(dyin)) {
            mp_set_number_from_subtraction(dxin, hp->y_coord, h->y_coord);
            mp_set_number_from_subtraction(dyin, h->x_coord, hp->x_coord);
        }
    }
    w0 = h;
    p = c;
    c0 = c;
    k_needed = 0;
     do {
        q = mp_next_knot(p);
        /*tex
            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
        */
        mp_knot_info(p) = zero_offset + k_needed;
        k_needed = 0;
        /*tex
            Prepare for derivative computations; |goto not_found| if the current cubic is dead.
        */
        mp_set_number_from_subtraction(x0, p->right_x, p->x_coord);
        mp_set_number_from_subtraction(x2, q->x_coord, q->left_x);
        mp_set_number_from_subtraction(x1, q->left_x, p->right_x);
        mp_set_number_from_subtraction(y0, p->right_y, p->y_coord);
        mp_set_number_from_subtraction(y2, q->y_coord, q->left_y);
        mp_set_number_from_subtraction(y1, q->left_y, p->right_y);
        {
        	/*tex
                Somewhat weird: these copies to absval.
            */
            mp_number absval;
            mp_new_number_abs(absval, x1);
            mp_number_abs_clone(max_coef, x0);
            if (mp_number_greater(absval, max_coef)) {
                mp_number_clone(max_coef, absval);
            }
            mp_number_abs_clone(absval, x2);
            if (mp_number_greater(absval, max_coef)) {
                mp_number_clone(max_coef, absval);
            }
            mp_number_abs_clone(absval, y0);
            if (mp_number_greater(absval, max_coef)) {
                mp_number_clone(max_coef, absval);
            }
            mp_number_abs_clone(absval, y1);
            if (mp_number_greater(absval, max_coef)) {
                mp_number_clone(max_coef, absval);
            }
            mp_number_abs_clone(absval, y2);
            if (mp_number_greater(absval, max_coef)) {
                mp_number_clone(max_coef, absval);
            }
            mp_free_number(absval);
            if (mp_number_zero(max_coef)) {
                goto NOT_FOUND;
            }
        }
        while (mp_number_less(max_coef, mp_fraction_half_t)) {
            mp_number_double(max_coef);
            mp_number_double(x0);
            mp_number_double(x1);
            mp_number_double(x2);
            mp_number_double(y0);
            mp_number_double(y1);
            mp_number_double(y2);
        }
        /*tex
            Find the initial direction |(dx,dy)|.
        */
        mp_number_clone(dx_m, mp_zero_t);
        mp_number_clone(dy_m, mp_zero_t);
        /* todo: just if else and test before assignment */
        mp_number_clone(dx, x0);
        mp_number_clone(dy, y0);
        if (mp_number_zero(dx) && mp_number_zero(dy)) {
            mp_number_clone(dx, x1);
            mp_number_clone(dy, y1);
            if (mp_number_zero(dx) && mp_number_zero(dy)) {
                mp_number_clone(dx, x2);
                mp_number_clone(dy, y2);
            }
        }
        if (p == c) {
            mp_number_clone(dx0, dx);
            mp_number_clone(dy0, dy);
        }
        /*tex
            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|.
        */
        {
            turn_amt = mp_get_turn_amt(mp, w0, &dx, &dy, mp_ab_vs_cd(dy, dxin, dx, dyin) >= 0);
            w = mp_pen_walk(mp, w0, turn_amt);
            w0 = w;
            mp_knot_info(p) +=  turn_amt;
        }
        /*tex
            Find the final direction |(dxin,dyin)|.
        */
        mp_number_clone(dxin, x2);
        mp_number_clone(dyin, y2);
        if (mp_number_zero(dxin) && mp_number_zero(dyin)) {
            mp_number_clone(dxin, x1);
            mp_number_clone(dyin, y1);
            if (mp_number_zero(dxin) && mp_number_zero(dyin)) {
                mp_number_clone(dxin, x0);
                mp_number_clone(dyin, y0);
            }
        }
        /*tex
            Decide on the net change in pen offsets and set |turn_amt|.
        */
        {
            int sign = mp_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) {
            /*tex
                Check rotation direction based on node position.
            */
            {
                int t;
                mp_set_number_from_subtraction(u0, q->x_coord, p->x_coord);
                mp_set_number_from_subtraction(u1, q->y_coord, p->y_coord);
                t = mp_ab_vs_cd(dx, u1, u0, dy) + mp_ab_vs_cd(u0, dyin, dxin, u1);
             // mp_number_half(t);
                if (t < 0) {
                    d_sign = -1;
                } else if (t == 0) {
                    d_sign = 0;
                } else {
                    d_sign = 1;
                }
            }
        }
        if (d_sign == 0) {
            if (mp_number_zero(dx)) {
                d_sign = mp_number_positive(dy) ? 1 : -1;
            } else {
                d_sign = mp_number_positive(dx) ? 1 : -1;
            }
        }
        /*tex
            Make |ss| negative if and only if the total change in direction is more than $180^\circ$.
        */
        {
            mp_number r1, r2, arg1;
            mp_new_fraction(r1);
            mp_new_fraction(r2);
            mp_take_fraction(r1, x0, y2);
            mp_take_fraction(r2, x2, y0);
            mp_number_half(r1);
            mp_number_half(r2);
            mp_set_number_from_subtraction(t0, r1, r2);
            mp_new_number_from_add(arg1, y0, y2);
            mp_take_fraction(r1, x1, arg1);
            mp_set_number_from_addition(arg1, x0, x2);
            /*|mp_take_fraction(r1, y1, arg1);|*/ /* The old one, is it correct? */
            mp_take_fraction(r2, y1, arg1);
            mp_number_half(r1);
            mp_number_half(r2);
            mp_set_number_from_subtraction(t1, r1, r2);
            mp_free_number(arg1);
            mp_free_number(r1);
            mp_free_number(r2);
        }
        if (mp_number_zero(t0)) {
            /*tex Path reversal always negates |d_sign|. */
            mp_set_number_from_scaled(t0, d_sign);
        }
        if (mp_number_positive(t0)) {
            mp_number arg3;
            mp_new_number(arg3);
            mp_number_negated_clone(arg3, t0);
            mp_crossing_point(t, t0, t1, arg3);
            mp_free_number(arg3);
            mp_set_number_from_of_the_way(u0, t, x0, x1);
            mp_set_number_from_of_the_way(u1, t, x1, x2);
            mp_set_number_from_of_the_way(v0, t, y0, y1);
            mp_set_number_from_of_the_way(v1, t, y1, y2);
        } else {
            mp_number arg1;
            mp_new_number(arg1);
            mp_number_negated_clone(arg1, t0);
            mp_crossing_point(t, arg1, t1, t0);
            mp_free_number(arg1);
            mp_set_number_from_of_the_way(u0, t, x2, x1);
            mp_set_number_from_of_the_way(u1, t, x1, x0);
            mp_set_number_from_of_the_way(v0, t, y2, y1);
            mp_set_number_from_of_the_way(v1, t, y1, y0);
        }
        {
            mp_number tmp1, tmp2, r1, r2, arg1;
            mp_new_fraction(r1);
            mp_new_fraction(r2);
            mp_new_number(tmp1);
            mp_new_number(tmp2);
            mp_set_number_from_of_the_way(tmp1, t, u0, u1);
            mp_set_number_from_of_the_way(tmp2, t, v0, v1);
            mp_new_number_from_add(arg1, x0, x2);
            mp_take_fraction(r1, arg1, tmp1);
            mp_set_number_from_addition(arg1, y0, y2);
            mp_take_fraction(r2, arg1, tmp2);
            mp_set_number_from_addition(ss, r1, r2);
            mp_free_number(arg1);
            mp_free_number(r1);
            mp_free_number(r2);
            mp_free_number(tmp1);
            mp_free_number(tmp2);
        }
        turn_amt = mp_get_turn_amt(mp, w, &dxin, &dyin, (d_sign > 0));
        if (mp_number_negative(ss)) {
            turn_amt = turn_amt - d_sign * n;
        }
        /*tex
            Complete the offset splitting process.
        */
        ww = mp_prev_knot(w);
        /*tex
            Compute (case 2) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$.
        */
        {
            mp_number abs_du, abs_dv;
            mp_new_number(abs_du);
            mp_new_number(abs_dv);
            mp_set_number_from_subtraction(du, ww->x_coord, w->x_coord);
            mp_set_number_from_subtraction(dv, ww->y_coord, w->y_coord);
            mp_number_abs_clone(abs_du, du);
            mp_number_abs_clone(abs_dv, dv);
            if (mp_number_greaterequal(abs_du, abs_dv)) {
                mp_number r1;
                mp_new_fraction(r1);
                mp_make_fraction(s, dv, du);
                mp_take_fraction(r1, x0, s);
                mp_set_number_from_subtraction(t0, r1, y0);
                mp_take_fraction(r1, x1, s);
                mp_set_number_from_subtraction(t1, r1, y1);
                mp_take_fraction(r1, x2, s);
                mp_set_number_from_subtraction(t2, r1, y2);
                if (mp_number_negative(du)) {
                    mp_number_negate(t0);
                    mp_number_negate(t1);
                    mp_number_negate(t2);
                }
                mp_free_number(r1);
            } else {
                mp_number r1;
                mp_new_fraction(r1);
                mp_make_fraction(s, du, dv);
                mp_take_fraction(r1, y0, s);
                mp_set_number_from_subtraction(t0, x0, r1);
                mp_take_fraction(r1, y1, s);
                mp_set_number_from_subtraction(t1, x1, r1);
                mp_take_fraction(r1, y2, s);
                mp_set_number_from_subtraction(t2, x2, r1);
                if (mp_number_negative(dv)) {
                    mp_number_negate(t0);
                    mp_number_negate(t1);
                    mp_number_negate(t2);
                }
                mp_free_number(r1);
            }
            mp_free_number(abs_du);
            mp_free_number(abs_dv);
            if (mp_number_negative(t0)) {
                /*tex Should be positive without rounding error. */
                mp_set_number_to_zero(t0);
            }
        }
        /*tex
            Find the first |t| where $d(t)$ crosses $d_{k-1}$ or set |t := fraction_one + 1|.
        */
        mp_crossing_point(t, t0, t1, t2);
        if (turn_amt >= 0) {
            if (mp_number_negative(t2)) {
                mp_number_clone(t, mp_fraction_one_t);
                mp_number_add_scaled(t, 1);
            } else {
                mp_number tmp, arg1, r1;
                mp_new_fraction(r1);
                mp_new_number(tmp);
                mp_new_number(arg1);
                mp_set_number_from_of_the_way(u0, t, x0, x1);
                mp_set_number_from_of_the_way(u1, t, x1, x2);
                mp_set_number_from_of_the_way(tmp, t, u0, u1);
                mp_number_negated_clone(arg1, du);
                mp_take_fraction(ss, arg1, tmp);
                mp_set_number_from_of_the_way(v0, t, y0, y1);
                mp_set_number_from_of_the_way(v1, t, y1, y2);
                mp_set_number_from_of_the_way(tmp, t, v0, v1);
                mp_number_negated_clone(arg1, dv);
                mp_take_fraction(r1, arg1, tmp);
                mp_number_add(ss, r1);
                mp_free_number(tmp);
                if (mp_number_negative(ss)) {
                    mp_number_clone(t, mp_fraction_one_t);
                    mp_number_add_scaled(t, 1);
                }
                mp_free_number(arg1);
                mp_free_number(r1);
            }
        } else if (mp_number_greater(t, mp_fraction_one_t)) {
            mp_number_clone(t, mp_fraction_one_t);
        }
        if (mp_number_greater(t, mp_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);
            mp_set_number_from_of_the_way(x1a, t, x0, x1);
            mp_set_number_from_of_the_way(x1, t, x1, x2);
            mp_set_number_from_of_the_way(x2a, t, x1a, x1);
            mp_set_number_from_of_the_way(y1a, t, y0, y1);
            mp_set_number_from_of_the_way(y1, t, y1, y2);
            mp_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);
            mp_number_clone(x0, x2a);
            mp_number_clone(y0, y2a);
            mp_knot_info(r) = zero_offset - 1;
            if (turn_amt >= 0) {
                mp_number arg1, arg2, arg3;
                mp_new_number(arg1);
                mp_new_number(arg2);
                mp_new_number(arg3);
                mp_set_number_from_of_the_way(t1, t, t1, t2);
                if (mp_number_positive(t1)) {
                    mp_set_number_to_zero(t1);
                }
                mp_number_negated_clone(arg2, t1);
                mp_number_negated_clone(arg3, t2);
                mp_crossing_point(t, arg1, arg2, arg3);
                mp_free_number(arg1);
                mp_free_number(arg2);
                mp_free_number(arg3);
                if (mp_number_greater(t, mp_fraction_one_t)) {
                    mp_number_clone(t, mp_fraction_one_t);
                }
                /*tex
                    Split off another rising cubic for |fin_offset_prep|.
                */
                mp_split_cubic(mp, r, &t);
                mp_knot_info(mp_next_knot(r)) = zero_offset + 1;
                mp_set_number_from_of_the_way(x1a, t, x1, x2);
                mp_set_number_from_of_the_way(x1,  t, x0, x1);
                mp_set_number_from_of_the_way(x0a, t, x1, x1a);
                mp_set_number_from_of_the_way(y1a, t, y1, y2);
                mp_set_number_from_of_the_way(y1,  t, y0, y1);
                mp_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);
                mp_number_clone(x2, x0a);
                mp_number_clone(y2, y0a);

                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));
            }
        }
        w0 = mp_pen_walk(mp, w0, turn_amt);
      NOT_FOUND:
        /*tex
            Advance |p| to node |q|, removing any \quote {dead} cubics that might have been
            introduced by the splitting process.

            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.
        */
        q0 = q;
        do {
            r = mp_next_knot(p);
            if (r != p && r != q
             && mp_number_equal(p->x_coord, p->right_x)
             && mp_number_equal(p->y_coord, p->right_y)
             && mp_number_equal(p->x_coord, r->left_x)
             && mp_number_equal(p->y_coord, r->left_y)
             && mp_number_equal(p->x_coord, r->x_coord)
             && mp_number_equal(p->y_coord, r->y_coord)) {
                /*tex
                    Remove the cubic following |p| and update the data structures to merge |r| into
                    |p|.
                */
                k_needed = mp_knot_info(p) - zero_offset;
                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);
            }
            p = r;
        } while (p != q);
        /*tex
            Check if we removed too much.
        */
        if ((q != q0) && (q != c || c == c0)) {
            q = mp_next_knot(q);
        }
    } while (q != c);
    /*tex
        Fix the offset change in |mp_knot_info(c)| and set |c| to the return value of |offset_prep|.
    */
    mp->spec_offset = mp_knot_info(c) - zero_offset;
    if (mp_next_knot(c) == c) {
        mp_knot_info(c) = zero_offset + 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_offset - n) {
            mp_knot_info(c) += n;
        }
        while (mp_knot_info(c) > zero_offset) {
            mp_knot_info(c) -= n;
        }
        ;
        if ((mp_knot_info(c) != zero_offset) && mp_ab_vs_cd(dy0, dxin, dx0, dyin) >= 0) {
            mp_knot_info(c) += n;
        }
    }
    mp_free_number(ss);
    mp_free_number(s);
    mp_free_number(dxin);
    mp_free_number(dyin);
    mp_free_number(dx0);
    mp_free_number(dy0);
    mp_free_number(x0);
    mp_free_number(y0);
    mp_free_number(x1);
    mp_free_number(y1);
    mp_free_number(x2);
    mp_free_number(y2);
    mp_free_number(max_coef);
    mp_free_number(du);
    mp_free_number(dv);
    mp_free_number(dx);
    mp_free_number(dy);
    mp_free_number(x0a);
    mp_free_number(y0a);
    mp_free_number(x1a);
    mp_free_number(y1a);
    mp_free_number(x2a);
    mp_free_number(y2a);
    mp_free_number(t0);
    mp_free_number(t1);
    mp_free_number(t2);
    mp_free_number(u0);
    mp_free_number(u1);
    mp_free_number(v0);
    mp_free_number(v1);
    mp_free_number(dx_m);
    mp_free_number(dy_m);
    mp_free_number(dxin_m);
    mp_free_number(t);
    return c;
}

/*tex

Not setting the |info| field of the newly created knot allows the splitting routine to work for
paths.

*/

static void mp_split_cubic(MP mp, mp_knot p, mp_number *t)
{
    mp_number v;    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;
    mp_new_number(v);
    mp_set_number_from_of_the_way(v,          *t, p->right_x, q->left_x);
    mp_set_number_from_of_the_way(p->right_x, *t, p->x_coord, p->right_x);
    mp_set_number_from_of_the_way(q->left_x,  *t, q->left_x, q->x_coord);
    mp_set_number_from_of_the_way(r->left_x,  *t, p->right_x, v);
    mp_set_number_from_of_the_way(r->right_x, *t, v, q->left_x);
    mp_set_number_from_of_the_way(r->x_coord, *t, r->left_x, r->right_x);
    mp_set_number_from_of_the_way(v,          *t, p->right_y, q->left_y);
    mp_set_number_from_of_the_way(p->right_y, *t, p->y_coord, p->right_y);
    mp_set_number_from_of_the_way(q->left_y,  *t, q->left_y, q->y_coord);
    mp_set_number_from_of_the_way(r->left_y,  *t, p->right_y, v);
    mp_set_number_from_of_the_way(r->right_y, *t, v, q->left_y);
    mp_set_number_from_of_the_way(r->y_coord, *t, r->left_y, r->right_y);
    mp_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;
    mp_new_number(v);
    mp_set_number_from_of_the_way(v,          *t, l->right_x, r->left_x);
    mp_set_number_from_of_the_way(l->right_x, *t, l->x_coord, l->right_x);
    mp_set_number_from_of_the_way(r->left_x,  *t, r->left_x, r->x_coord);
    mp_set_number_from_of_the_way(k->left_x,  *t, l->right_x, v);
    mp_set_number_from_of_the_way(k->right_x, *t, v, r->left_x);
    mp_set_number_from_of_the_way(k->x_coord, *t, k->left_x, k->right_x);
    mp_set_number_from_of_the_way(v,          *t, l->right_y, r->left_y);
    mp_set_number_from_of_the_way(l->right_y, *t, l->y_coord, l->right_y);
    mp_set_number_from_of_the_way(r->left_y,  *t, r->left_y, r->y_coord);
    mp_set_number_from_of_the_way(k->left_y,  *t, l->right_y, v);
    mp_set_number_from_of_the_way(k->right_y, *t, v, r->left_y);
    mp_set_number_from_of_the_way(k->y_coord, *t, k->left_y, k->right_y);
    mp_free_number(v);
    mp_free_knot(mp, l);
    mp_free_knot(mp, r);
    return k;
}

/*tex

    Removes the dead cubic following~|p|. This does not set |mp_knot_info(p)| or |mp_right_type(p)|.

*/

static void mp_remove_cubic(MP mp, mp_knot 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);
    mp_number_clone(p->right_x, q->right_x);
    mp_number_clone(p->right_y, q->right_y);
    /* was: mp_memory_free(q); */
    mp_free_knot(mp, q);
}

/*tex

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.

*/

static mp_knot mp_pen_walk(MP mp, mp_knot w, int k)
{
    /*tex
        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;
}

/*tex

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)$.

Let us first solve a special case of the problem: Suppose we know an index~$k$ such that either

\startitemize[r,packed]
\startitem
    $d(t)\succeq d_{k-1}$ for all~$t$ and $d(0)\prec d_k$, or
\stopitem
\startitem
  (ii)~$d(t)\preceq d_k$ for all~$t$
\stopitem
\stopitemize

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.

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.

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 $(*)$.

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.

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 right.) This code
depends on |w0| being the offset for |(dxin,dyin)|.

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.

*/

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)
{
    mp_number du, dv;     /*tex for slope calculation */
    mp_number t0, t1, t2; /*tex test coefficients */
    mp_number t;          /*tex place where the derivative passes a critical slope */
    mp_number s;          /*tex slope or reciprocal slope */
    mp_number v;          /*tex intermediate value for updating |x0..y2| */
    mp_knot q = mp_next_knot(p);
    mp_new_number(du);
    mp_new_number(dv);
    mp_new_number(v);
    mp_new_number(t0);
    mp_new_number(t1);
    mp_new_number(t2);
    mp_new_fraction(s);
    mp_new_fraction(t);
    while (1) {
        mp_knot ww = rise > 0 ? mp_next_knot(w) : mp_prev_knot(w);
        /*tex
            Compute (case 1) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$
        */
        {
            mp_number abs_du, abs_dv;
            mp_new_number(abs_du);
            mp_new_number(abs_dv);
            mp_set_number_from_subtraction(du, ww->x_coord, w->x_coord);
            mp_set_number_from_subtraction(dv, ww->y_coord, w->y_coord);
            mp_number_abs_clone(abs_du, du);
            mp_number_abs_clone(abs_dv, dv);
            if (mp_number_greaterequal(abs_du, abs_dv)) {
                mp_number r1;
                mp_new_fraction(r1);
                mp_make_fraction(s, dv, du);
                mp_take_fraction(r1, *x0, s);
                mp_set_number_from_subtraction(t0, r1, *y0);
                mp_take_fraction(r1, *x1, s);
                mp_set_number_from_subtraction(t1, r1, *y1);
                mp_take_fraction(r1, *x2, s);
                mp_set_number_from_subtraction(t2, r1, *y2);
                if (mp_number_negative(du)) {
                    mp_number_negate(t0);
                    mp_number_negate(t1);
                    mp_number_negate(t2);
                }
                mp_free_number(r1);
            } else {
                mp_number r1;
                mp_new_fraction(r1);
                mp_make_fraction(s, du, dv);
                mp_take_fraction(r1, *y0, s);
                mp_set_number_from_subtraction(t0, *x0, r1);
                mp_take_fraction(r1, *y1, s);
                mp_set_number_from_subtraction(t1, *x1, r1);
                mp_take_fraction(r1, *y2, s);
                mp_set_number_from_subtraction(t2, *x2, r1);
                if (mp_number_negative(dv)) {
                    mp_number_negate(t0);
                    mp_number_negate(t1);
                    mp_number_negate(t2);
                }
                mp_free_number(r1);
            }
            mp_free_number(abs_du);
            mp_free_number(abs_dv);
            if (mp_number_negative(t0)) {
                /*tex Should be positive without rounding error. */
                mp_set_number_to_zero(t0);
            }
        }
        mp_crossing_point(t, t0, t1, t2);
        if (mp_number_greaterequal(t, mp_fraction_one_t)) {
            if (turn_amt > 0) {
                mp_number_clone(t, mp_fraction_one_t);
            } else {
                goto RETURN;
            }
        }
        /*tex
            Split the cubic at $t$, and split off another cubic if the derivative crosses back.
        */
        {
            mp_split_cubic(mp, p, &t);
            p = mp_next_knot(p);
            mp_knot_info(p) = zero_offset + rise;
            --turn_amt;
            mp_set_number_from_of_the_way(v, t, *x0, *x1);
            mp_set_number_from_of_the_way(*x1, t, *x1, *x2);
            mp_set_number_from_of_the_way(*x0, t, v, *x1);
            mp_set_number_from_of_the_way(v, t, *y0, *y1);
            mp_set_number_from_of_the_way(*y1, t, *y1, *y2);
            mp_set_number_from_of_the_way(*y0, t, v, *y1);
            if (turn_amt < 0) {
                mp_number arg1, arg2, arg3;
                mp_new_number(arg1);
                mp_new_number(arg2);
                mp_new_number(arg3);
                mp_set_number_from_of_the_way(t1, t, t1, t2);
                if (mp_number_positive(t1)) {
                    mp_set_number_to_zero(t1); /* without rounding error, |t1| would be |<=0| */
                }
                mp_number_negated_clone(arg2, t1);
                mp_number_negated_clone(arg3, t2);
                mp_crossing_point(t, arg1, arg2, arg3); /* arg1 is zero */
                mp_free_number(arg1);
                mp_free_number(arg2);
                mp_free_number(arg3);
                if (mp_number_greater(t, mp_fraction_one_t)) {
                    mp_number_clone(t, mp_fraction_one_t);
                }
                ++turn_amt;
                if (mp_number_equal(t,mp_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_offset - rise;
                    mp_set_number_from_of_the_way(v, t, *x1, *x2);
                    mp_set_number_from_of_the_way(*x1, t, *x0, *x1);
                    mp_set_number_from_of_the_way(*x2, t, *x1, v);
                    mp_set_number_from_of_the_way(v, t, *y1, *y2);
                    mp_set_number_from_of_the_way(*y1, t, *y0, *y1);
                    mp_set_number_from_of_the_way(*y2, t, *y1, v);
                }
            }
        }
        w = ww;
    }
  RETURN:
    mp_free_number(s);
    mp_free_number(t);
    mp_free_number(du);
    mp_free_number(dv);
    mp_free_number(v);
    mp_free_number(t0);
    mp_free_number(t1);
    mp_free_number(t2);
}

static int mp_get_turn_amt(MP mp, mp_knot w, mp_number *dx, mp_number *dy, int ccw)
{
    int s = 0; /*tex The turn amount so far. */
    mp_number arg1, arg2;
    mp_new_number(arg1);
    mp_new_number(arg2);
    if (ccw) {
        int t;
        mp_knot ww = mp_next_knot(w);
        do {
            mp_set_number_from_subtraction(arg1, ww->x_coord, w->x_coord);
            mp_set_number_from_subtraction(arg2, ww->y_coord, w->y_coord);
            t = mp_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);
        mp_set_number_from_subtraction(arg1, w->x_coord, ww->x_coord);
        mp_set_number_from_subtraction(arg2, w->y_coord, ww->y_coord);
        while (mp_ab_vs_cd(*dy, arg1, *dx, arg2) < 0) {
            --s;
            w = ww;
            ww = mp_prev_knot(ww);
            mp_set_number_from_subtraction(arg1, w->x_coord, ww->x_coord);
            mp_set_number_from_subtraction(arg2, w->y_coord, ww->y_coord);
        }
    }
    mp_free_number(arg1);
    mp_free_number(arg2);
    return s;
}

/*tex

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|.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.

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.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|. 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.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 |mp_take_fraction(-a,-b) = mp_take_fraction(a,b)| and |t_of_the_way(-a,-b) = -
(t_of_the_way(a,b))|.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.

*/

static void mp_print_spec(MP mp, mp_knot cur_spec, mp_knot cur_pen, const char *s)
{
    mp_knot w;            /*tex the current pen offset */
    mp_knot p = cur_spec; /*tex list traversal */
    mp_begin_diagnostic_print(mp, "Envelope spec", s, 1);
    w = mp_pen_walk(mp, cur_pen, mp->spec_offset);
    mp_print_ln(mp);
    mp_print_format(mp, " (%N,%N) %% beginning with offset (%N,%N) ", cur_spec->x_coord, cur_spec->y_coord, w->x_coord, w->y_coord);
    do {
        while (1) {
            mp_knot q = mp_next_knot(p);
            /*tex
                Print the cubic between |p| and |q|.
            */
            mp_print_format(mp, "%l\n .. controls (%N,%N) and (%N,%N) .. (%N,%N)", p->right_x, p->right_y, q->left_x, q->left_y, q->x_coord, q->y_coord);
            p = q;
            if ((p == cur_spec) || (mp_knot_info(p) != zero_offset)) {
                break;
            }
        }
        if (mp_knot_info(p) != zero_offset) {
            /*tex
                Update |w| as indicated by |mp_knot_info(p)| and print an explanation.
            */
            w = mp_pen_walk(mp, w, (mp_knot_info(p) - zero_offset));
            mp_print_string(mp, " % ");
            if (mp_knot_info(p) > zero_offset) {
                mp_print_string(mp, "counter");
            }
            mp_print_format(mp, "clockwise to offset (%N,%N)", w->x_coord, w->y_coord);
        }
    } while (p != cur_spec);
    mp_print_format(mp, "%l\n & cycle");
    mp_end_diagnostic(mp, 1);
}

/*tex

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|.

*/

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;                /*tex for manipulating the path */
    mp_knot w;                          /*tex the pen knot for the current offset */
    mp_number qx, qy;                   /*tex unshifted coordinates of |q| */
    mp_number dxin, dyin, dxout, dyout; /*tex directions at |q| when square or mitered */
    int join_type = 0;                  /*tex codes |0..3| for mitered, round, beveled, or square */
    mp_number tmp;                      /*tex a temporary value */
    mp_number max_ht;                   /*tex maximum height of the pen polygon above the |w0|-|w| line */
    mp_new_number(max_ht);
    mp_new_number(tmp);
    mp_new_fraction(dxin);
    mp_new_fraction(dyin);
    mp_new_fraction(dxout);
    mp_new_fraction(dyout);
    mp->spec_p1 = NULL;
    mp->spec_p2 = NULL;
    mp_new_number(qx);
    mp_new_number(qy);
    /*tex
        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 {
            /*tex
                Make |c| look like a cycle of length one.
            */
            mp_left_type(c) = mp_explicit_knot;
            mp_right_type(c) = mp_explicit_knot;
            mp_number_clone(c->left_x, c->x_coord);
            mp_number_clone(c->left_y, c->y_coord);
            mp_number_clone(c->right_x, c->x_coord);
            mp_number_clone(c->right_y, c->y_coord);
        }
    }
    /*tex
        Use |offset_prep| to compute the envelope spec then walk |h| around to the initial offset.
    */
    c = mp_offset_prep(mp, c, h);
    if (mp_number_positive(internal_value(mp_tracing_specs_internal))) {
        mp_print_spec(mp, c, h, "");
    }
    h = mp_pen_walk(mp, h, mp->spec_offset);
    w = h;
    p = c;
    do {
        mp_knot w0;
        int k, k0; /*tex controls pen edge insertion */
        q = mp_next_knot(p);
        q0 = q;
        mp_number_clone(qx, q->x_coord);
        mp_number_clone(qy, q->y_coord);
        k = mp_knot_info(q);
        k0 = k;
        w0 = w;
        if (k != zero_offset) {
            /*tex
                Set |join_type| to indicate how to handle offset changes at~|q|.
            */
            if (k < zero_offset) {
                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 */
                    /*tex
                        Set the incoming and outgoing directions at |q|; in case of degeneracy set
                        |join_type := 2|.
                    */
                    mp_set_number_from_subtraction(dxin, q->x_coord, q->left_x);
                    mp_set_number_from_subtraction(dyin, q->y_coord, q->left_y);
                    if (mp_number_zero(dxin) && mp_number_zero(dyin)) {
                        mp_set_number_from_subtraction(dxin, q->x_coord, p->right_x);
                        mp_set_number_from_subtraction(dyin, q->y_coord, p->right_y);
                        if (mp_number_zero(dxin) && mp_number_zero(dyin)) {
                            mp_set_number_from_subtraction(dxin, q->x_coord, p->x_coord);
                            mp_set_number_from_subtraction(dyin, q->y_coord, p->y_coord);
                            if (p != c) {
                                /*tex
                                    The coordinates of |p| have been offset by |w|.
                                */
                                mp_number_add(dxin, w->x_coord);
                                mp_number_add(dyin, w->y_coord);
                            }
                        }
                    }
                    mp_pyth_add(tmp, dxin, dyin);
                    if (mp_number_zero(tmp)) {
                        join_type = 2;
                    } else {
                        mp_number r1;
                        mp_new_fraction(r1);
                        mp_make_fraction(r1, dxin, tmp);
                        mp_number_clone(dxin, r1);
                        mp_make_fraction(r1, dyin, tmp);
                        mp_number_clone(dyin, r1);
                        mp_free_number(r1);
                        /*tex
                            Set the outgoing direction at |q|.
                        */
                        mp_set_number_from_subtraction(dxout, q->right_x, q->x_coord);
                        mp_set_number_from_subtraction(dyout, q->right_y, q->y_coord);
                        if (mp_number_zero(dxout) && mp_number_zero(dyout)) {
                            r = mp_next_knot(q);
                            mp_set_number_from_subtraction(dxout, r->left_x, q->x_coord);
                            mp_set_number_from_subtraction(dyout, r->left_y, q->y_coord);
                            if (mp_number_zero(dxout) && mp_number_zero(dyout)) {
                                mp_set_number_from_subtraction(dxout, r->x_coord, q->x_coord);
                                mp_set_number_from_subtraction(dyout, r->y_coord, q->y_coord);
                            }
                        }
                        if (q == c) {
                            mp_number_subtract(dxout, h->x_coord);
                            mp_number_subtract(dyout, h->y_coord);
                        }
                        mp_pyth_add(tmp, dxout, dyout);
                        if (mp_number_zero(tmp)) {
                            /* |mp_confusion(mp, "degenerate spec");| */
                            /*
                                But apparently, it actually can happen. The test case is this:

                                \starttyping
                                path p;
                                linejoin := mitered;
                                p:= (10,0)..(0,10)..(-10,0)..(0,-10)..cycle;
                                addto currentpicture contour p withpen pensquare;
                                \stoptyping

                                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;
                            mp_new_fraction(r1);
                            mp_make_fraction(r1, dxout, tmp);
                            mp_number_clone(dxout, r1);
                            mp_make_fraction(r1, dyout, tmp);
                            mp_number_clone(dyout, r1);
                            mp_free_number(r1);
                        }
                    }
                    if (join_type == 0) { /* mp_mitered_linejoin_code */
                        /*tex
                            If |miterlimit| is less than the secant of half the angle at |q| then set
                            |join_type := 2|.
                        */
                        mp_number r1, r2;
                        mp_new_fraction(r1);
                        mp_new_fraction(r2);
                        mp_take_fraction(r1, dxin, dxout);
                        mp_take_fraction(r2, dyin, dyout);
                        mp_number_add(r1, r2);
                        mp_number_half(r1);
                        mp_number_add(r1, mp_fraction_half_t);
                        mp_take_fraction(tmp, *miterlimit, r1);
                        if (mp_number_less(tmp, mp_unity_t)) {
                            mp_number ret;
                            mp_new_number(ret);
                            mp_take_scaled(ret, *miterlimit, tmp);
                            if (mp_number_less(ret, mp_unity_t)) {
                                join_type = 2;
                            }
                            mp_free_number(ret);
                        }
                        mp_free_number(r1);
                        mp_free_number(r2);
                    }
                }
            }
        }
        /*tex
            Add offset |w| to the cubic from |p| to |q|. The coordinates of |p| have already been
            shifted unless |p| is the first knot in which case they get shifted at the very end.
        */
        mp_number_add(p->right_x, w->x_coord);
        mp_number_add(p->right_y, w->y_coord);
        mp_number_add(q->left_x,  w->x_coord);
        mp_number_add(q->left_y,  w->y_coord);
        mp_number_add(q->x_coord, w->x_coord);
        mp_number_add(q->y_coord, w->y_coord);
        mp_left_type(q) = mp_explicit_knot;
        mp_right_type(q) = mp_explicit_knot;

        while (k != zero_offset) {
            /*tex
                Step |w| and move |k| one step closer to |zero_offset|.
            */
            if (k > zero_offset) {
                w = mp_next_knot(w);
                --k;
            } else {
                w = mp_prev_knot(w);
                ++k;
            }
            if ((join_type == 1) || (k == zero_offset)) {
                mp_number xtot, ytot;
                mp_new_number_from_add(xtot, qx, w->x_coord);
                mp_new_number_from_add(ytot, qy, w->y_coord);
                q = mp_insert_knot(mp, q, &xtot, &ytot);
                mp_free_number(xtot);
                mp_free_number(ytot);
            }
        }
        if (q != mp_next_knot(p)) {
            /*tex
                Set |p=mp_link(p)| and add knots between |p| and |q| as required by |join_type|.
            */
            p = mp_next_knot(p);
            if ((join_type == 0) || (join_type == 3)) {
                if (join_type == 0) {
                    /*tex
                        Insert a new knot |r| between |p| and |q| as required for a mitered join.
                    */
                    mp_number det; /*tex A determinant used for mitered join calculations. */
                    mp_number absdet;
                    mp_number r1, r2;
                    mp_new_fraction(r1);
                    mp_new_fraction(r2);
                    mp_new_fraction(det);
                    mp_new_fraction(absdet);
                    mp_take_fraction(r1, dyout, dxin);
                    mp_take_fraction(r2, dxout, dyin);
                    mp_set_number_from_subtraction(det, r1, r2);
                    mp_number_abs_clone(absdet, det);
                    if (mp_number_less(absdet, mp_near_zero_angle_k)) {
                        r = NULL; /*tex %\sin < 10^{-4}$ */
                    } else {
                        mp_number xtot, ytot, xsub, ysub;
                        mp_new_fraction(xsub);
                        mp_new_fraction(ysub);
                        mp_set_number_from_subtraction(tmp, q->x_coord, p->x_coord);
                        mp_set_number_from_subtraction(tmp, q->y_coord, p->y_coord);
                        mp_take_fraction(r1, tmp, dyout);
                        mp_take_fraction(r2, tmp, dxout);
                        mp_set_number_from_subtraction(tmp, r1, r2);
                        mp_make_fraction(r1, tmp, det);
                        mp_number_clone(tmp, r1);
                        mp_take_fraction(xsub, tmp, dxin);
                        mp_take_fraction(ysub, tmp, dyin);
                        mp_new_number_from_add(xtot, p->x_coord, xsub);
                        mp_new_number_from_add(ytot, p->y_coord, ysub);
                        r = mp_insert_knot(mp, p, &xtot, &ytot);
                        mp_free_number(xtot);
                        mp_free_number(ytot);
                        mp_free_number(xsub);
                        mp_free_number(ysub);
                    }
                    mp_free_number(r1);
                    mp_free_number(r2);
                    mp_free_number(det);
                    mp_free_number(absdet);
                } else {
                    /*tex
                        Make |r| the last of two knots inserted between |p| and |q| to form a squared
                        join.
                    */
                    int kk;               /*tex Keeps track of the pen vertices being scanned. */
                    mp_knot ww;           /*tex The pen vertex being tested. */
                    mp_number ht_x, ht_y; /*tex Perpendicular to the segment from |p| to |q|. */
                    mp_number ht_x_abs, ht_y_abs;
                    mp_number xtot, ytot, xsub, ysub;
                    mp_new_fraction(xsub);
                    mp_new_fraction(ysub);
                    mp_new_number(xtot);
                    mp_new_number(ytot);
                    mp_new_fraction(ht_x);
                    mp_new_fraction(ht_y);
                    mp_new_fraction(ht_x_abs);
                    mp_new_fraction(ht_y_abs);
                    mp_set_number_from_subtraction(ht_x, w->y_coord, w0->y_coord);
                    mp_set_number_from_subtraction(ht_y, w0->x_coord, w->x_coord);
                    mp_number_abs_clone(ht_x_abs, ht_x);
                    mp_number_abs_clone(ht_y_abs, ht_y);
                    while (mp_number_less(ht_x_abs, mp_fraction_half_t) && mp_number_less(ht_y_abs, mp_fraction_half_t)) {
                        mp_number_double(ht_x);
                        mp_number_double(ht_y);
                        mp_number_abs_clone(ht_x_abs, ht_x);
                        mp_number_abs_clone(ht_y_abs, ht_y);
                    }
                    /*tex
                        Scan the pen polygon between |w0| and |w| and make |max_ht| the range dot
                        product with |(ht_x,ht_y)|.
                    */
                    mp_set_number_to_zero(max_ht);
                    kk = zero_offset;
                    ww = w;
                    while (1) {
                        /*tex
                            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 (kk == k0) {
                            break;
                        } else {
                            mp_number r1, r2;
                            mp_new_fraction(r1);
                            mp_new_fraction(r2);
                            mp_set_number_from_subtraction(tmp, ww->x_coord, w0->x_coord);
                            mp_take_fraction(r1, tmp, ht_x);
                            mp_set_number_from_subtraction(tmp, ww->y_coord, w0->y_coord);
                            mp_take_fraction(r2, tmp, ht_y);
                            mp_set_number_from_addition(tmp, r1,    r2);
                            mp_free_number(r1);
                            mp_free_number(r2);
                            if (mp_number_greater(tmp, max_ht)) {
                                mp_number_clone(max_ht, tmp);
                            }
                        }
                    }
                    {
                        mp_number r1 ,r2;
                        mp_new_fraction(r1);
                        mp_new_fraction(r2);
                        mp_take_fraction(r1, dxin, ht_x);
                        mp_take_fraction(r2, dyin, ht_y);
                        mp_number_add(r1, r2);
                        mp_make_fraction(tmp, max_ht, r1);
                        mp_free_number(r1);
                        mp_free_number(r2);
                    }
                    mp_take_fraction(xsub, tmp, dxin);
                    mp_take_fraction(ysub, tmp, dyin);
                    mp_set_number_from_addition(xtot, p->x_coord, xsub);
                    mp_set_number_from_addition(ytot, p->y_coord, ysub);
                    r = mp_insert_knot(mp, p, &xtot, &ytot);
                    {
                        mp_number r1 ,r2;
                        mp_new_fraction(r1);
                        mp_new_fraction(r2);
                        mp_take_fraction(r1, dxout, ht_x);
                        mp_take_fraction(r2, dyout, ht_y);
                        mp_number_add(r1, r2);
                        mp_make_fraction(tmp, max_ht, r1);
                        mp_free_number(r1);
                        mp_free_number(r2);
                    }
                    mp_take_fraction(xsub, tmp, dxout);
                    mp_take_fraction(ysub, tmp, dyout);
                    mp_set_number_from_addition(xtot, q->x_coord, xsub);
                    mp_set_number_from_addition(ytot, q->y_coord, ysub);
                    r = mp_insert_knot(mp, r, &xtot, &ytot);
                    mp_free_number(xsub);
                    mp_free_number(ysub);
                    mp_free_number(xtot);
                    mp_free_number(ytot);
                    mp_free_number(ht_x);
                    mp_free_number(ht_y);
                    mp_free_number(ht_x_abs);
                    mp_free_number(ht_y_abs);
                }
                if (r != NULL) {
                    mp_number_clone(r->right_x, r->x_coord);
                    mp_number_clone(r->right_y, r->y_coord);
                }
            }
        }
        p = q;
    } while (q0 != c);
    mp_free_number(max_ht);
    mp_free_number(tmp);
    mp_free_number(qx);
    mp_free_number(qy);
    mp_free_number(dxin);
    mp_free_number(dyin);
    mp_free_number(dxout);
    mp_free_number(dyout);
    return c;
}

/*tex

    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.

    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.}

*/

static 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;
    mp_number_clone(r->right_x, q->right_x);
    mp_number_clone(r->right_y, q->right_y);
    mp_number_clone(r->x_coord, *x);
    mp_number_clone(r->y_coord, *y);
    mp_number_clone(q->right_x, q->x_coord);
    mp_number_clone(q->right_y, q->y_coord);
    mp_number_clone(r->left_x, r->x_coord);
    mp_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;
}

/*tex

The dot product of the vector from |w0| to |ww| with |(ht_x,ht_y)| ranges from zero to |max_ht|.

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|. If |q = c| then the coordinates of |r| and
the control points between |q| and~|r| have already been offset by |h|.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.}

*/

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;                    /*tex $\max\bigl(\vert x\vert,\vert y\vert\bigr)$ */
    mp_knot p, q;                     /*tex for list traversal */
    mp_number n;                      /*tex the direction time at knot |p| */
    mp_number tt;                     /*tex the direction time within a cubic */
    mp_number abs_x, abs_y;           /*tex Other local variables for |find_direction_time| */
    mp_number x1, x2, x3, y1, y2, y3; /*tex multiples of rotated derivatives */
    mp_number phi;                    /*tex angles of exit and entry at a knot */
    mp_number t;                      /*tex temp storage */
    mp_number x, y;
    mp_new_number(max);
    mp_new_number(x1);
    mp_new_number(x2);
    mp_new_number(x3);
    mp_new_number(y1);
    mp_new_number(y2);
    mp_new_number(y3);
    mp_new_fraction(t);
    mp_new_angle(phi);
    mp_set_number_to_zero(*ret); /* just in case */
    mp_new_number(x);
    mp_new_number(y);
    mp_new_number(abs_x);
    mp_new_number(abs_y);
    mp_new_number(n);
    mp_new_fraction(tt);
    mp_number_clone(x, *x_orig);
    mp_number_clone(y, *y_orig);
    mp_number_abs_clone(abs_x, *x_orig);
    mp_number_abs_clone(abs_y, *y_orig);
    /*tex
        Normalize the given direction for better accuracy; but |return| with zero
        result if it's zero.
    */
    if (mp_number_less(abs_x, abs_y)) {
        mp_number r1;
        mp_new_fraction(r1);
        mp_make_fraction(r1, x, abs_y);
        mp_number_clone(x, r1);
        mp_free_number(r1);
        if (mp_number_positive(y)) {
            mp_number_clone(y, mp_fraction_one_t);
        } else {
            mp_number_negated_clone(y, mp_fraction_one_t);
        }
    } else if (mp_number_zero(x)) {
        goto FREE;
    } else {
        mp_number r1;
        mp_new_fraction(r1);
        mp_make_fraction(r1, y, abs_x);
        mp_number_clone(y, r1);
        mp_free_number(r1);
        if (mp_number_positive(x)) {
            mp_number_clone(x, mp_fraction_one_t);
        } else {
            mp_number_negated_clone(x, mp_fraction_one_t);
        }
    }
    p = h;
    while (1) {
        if (mp_right_type(p) == mp_endpoint_knot) {
            break;
        } else {
            q = mp_next_knot(p);
            /*tex
                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.
            */
            mp_set_number_to_zero(tt);
            /*tex
                Set local variables |x1,x2,x3| and |y1,y2,y3| to multiples of the control
                points of the rotated derivatives.
            */
            {
                mp_number absval;
                mp_new_number(absval);
                mp_set_number_from_subtraction(x1, p->right_x, p->x_coord);
                mp_set_number_from_subtraction(x2, q->left_x,  p->right_x);
                mp_set_number_from_subtraction(x3, q->x_coord, q->left_x);
                mp_set_number_from_subtraction(y1, p->right_y, p->y_coord);
                mp_set_number_from_subtraction(y2, q->left_y,  p->right_y);
                mp_set_number_from_subtraction(y3, q->y_coord, q->left_y);
                mp_number_abs_clone(absval, x2);
                mp_number_abs_clone(max, x1);
                if (mp_number_greater(absval, max)) {
                    mp_number_clone(max, absval);
                }
                mp_number_abs_clone(absval, x3);
                if (mp_number_greater(absval, max)) {
                    mp_number_clone(max, absval);
                }
                mp_number_abs_clone(absval, y1);
                if (mp_number_greater(absval, max)) {
                    mp_number_clone(max, absval);
                }
                mp_number_abs_clone(absval, y2);
                if (mp_number_greater(absval, max)) {
                    mp_number_clone(max, absval);
                }
                mp_number_abs_clone(absval, y3);
                if (mp_number_greater(absval, max)) {
                    mp_number_clone(max, absval);
                }
                mp_free_number(absval);
                if (mp_number_zero(max)) {
                    goto FOUND;
                }
                while (mp_number_less(max, mp_fraction_half_t)) {
                    mp_number_double(max);
                    mp_number_double(x1);
                    mp_number_double(x2);
                    mp_number_double(x3);
                    mp_number_double(y1);
                    mp_number_double(y2);
                    mp_number_double(y3);
                }
                mp_number_clone(t, x1);
                {
                     mp_number r1, r2;
                     mp_new_fraction(r1);
                     mp_new_fraction(r2);
                     mp_take_fraction(r1, x1, x);
                     mp_take_fraction(r2, y1, y);
                     mp_set_number_from_addition(x1, r1, r2);
                     mp_take_fraction(r1, y1, x);
                     mp_take_fraction(r2, t, y);
                     mp_set_number_from_subtraction(y1, r1, r2);
                     mp_number_clone(t, x2);
                     mp_take_fraction(r1, x2, x);
                     mp_take_fraction(r2, y2, y);
                     mp_set_number_from_addition(x2, r1, r2);
                     mp_take_fraction(r1, y2, x);
                     mp_take_fraction(r2, t, y);
                     mp_set_number_from_subtraction(y2, r1, r2);
                     mp_number_clone(t, x3);
                     mp_take_fraction(r1, x3 ,x);
                     mp_take_fraction(r2, y3, y);
                     mp_set_number_from_addition(x3, r1, r2);
                     mp_take_fraction(r1, y3, x);
                     mp_take_fraction(r2, t, y);
                     mp_set_number_from_subtraction(y3, r1, r2);
                     mp_free_number(r1);
                     mp_free_number(r2);
                }
            }
            if (mp_number_zero(y1) && (mp_number_zero(x1) || mp_number_positive(x1))) {
                goto FOUND;
            }
            if (mp_number_positive(n)) {
                /*tex
                    Exit to |found| if an eastward direction occurs at knot |p|.
                */
                mp_number theta;
                mp_number tmp;
                mp_new_angle(theta);
                mp_n_arg(theta, x1, y1);
                mp_new_angle(tmp);
                mp_set_number_from_subtraction(tmp, theta, mp_one_eighty_deg_t);
                if (mp_number_nonnegative(theta) && mp_number_nonpositive(phi) && mp_number_greaterequal(phi, tmp)) {
                    mp_free_number(tmp);
                    mp_free_number(theta);
                    goto FOUND;
                }
                mp_set_number_from_addition(tmp, theta, mp_one_eighty_deg_t);
                if (mp_number_nonpositive(theta) && mp_number_nonnegative(phi) && mp_number_lessequal(phi, tmp)) {
                    mp_free_number(tmp);
                    mp_free_number(theta);
                    goto FOUND;
                }
                mp_free_number(tmp);
                mp_free_number(theta);
                if (p == h) {
                    break;
                }
            }
            if (mp_number_nonzero(x3) || mp_number_nonzero(y3)) {
                mp_n_arg(phi, x3, y3);
            }
            /*tex
                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 (mp_number_negative(x1) && mp_number_negative(x2) && mp_number_negative(x3)) {
                goto DONE;
            }
            {
                if (mp_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 (mp_ab_vs_cd(y1, y2, mp_zero_t, mp_zero_t) < 0) {
                            mp_number tmp, arg2;
                            mp_new_number(tmp);
                            mp_new_number_from_sub(arg2, y1, y2);
                            mp_make_fraction(t, y1, arg2);
                            mp_free_number(arg2);
                            mp_set_number_from_of_the_way(x1, t, x1, x2);
                            mp_set_number_from_of_the_way(x2, t, x2, x3);
                            mp_set_number_from_of_the_way(tmp, t, x1, x2);
                            if (mp_number_zero(tmp) || mp_number_positive(tmp)) {
                                mp_free_number(tmp);
                                mp_number_clone(tt, t);
                                mp_fraction_to_round_scaled(tt);
                                goto FOUND;
                            } else {
                                mp_free_number(tmp);
                            }
                        } else if (mp_number_zero(y3)) {
                            if (mp_number_zero(y1)) {
                                /*tex
                                     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;
                                mp_new_number(arg1);
                                mp_new_number(arg2);
                                mp_new_number(arg3);
                                mp_number_negated_clone(arg1, x1);
                                mp_number_negated_clone(arg2, x2);
                                mp_number_negated_clone(arg3, x3);
                                mp_crossing_point(t, arg1, arg2, arg3);
                                mp_free_number(arg1);
                                mp_free_number(arg2);
                                mp_free_number(arg3);
                                if (mp_number_lessequal(t, mp_fraction_one_t)) {
                                    mp_number_clone(tt, t);
                                    mp_fraction_to_round_scaled(tt);
                                    goto FOUND;
                                } else if (mp_ab_vs_cd(x1, x3, x2, x2) <= 0) {
                                    mp_number arg2;
                                    mp_new_number_from_sub(arg2, x1, x2);
                                    mp_make_fraction(t, x1, arg2);
                                    mp_free_number(arg2);
                                    mp_number_clone(tt, t);
                                    mp_fraction_to_round_scaled(tt);
                                    goto FOUND;
                                }
                            } else if (mp_number_zero(x3) || mp_number_positive(x3)) {
                                mp_set_number_to_unity(tt);
                                goto FOUND;
                            }
                        }
                        goto DONE;
                    }
                }
            }
            if (mp_number_zero(y1) || mp_number_negative(y1)) {
                if (mp_number_negative(y1)) {
                    mp_number_negate(y1);
                    mp_number_negate(y2);
                    mp_number_negate(y3);
                } else if (mp_number_positive(y2)) {
                    mp_number_negate(y2);
                    mp_number_negate(y3);
                }
            }
            /*tex
                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.
            */
            mp_crossing_point(t, y1, y2, y3);
            if (mp_number_greater(t, mp_fraction_one_t)) {
                goto DONE;
            }
            mp_set_number_from_of_the_way(y2, t, y2, y3);
            mp_set_number_from_of_the_way(x1, t, x1, x2);
            mp_set_number_from_of_the_way(x2, t, x2, x3);
            mp_set_number_from_of_the_way(x1, t, x1, x2);
            if (mp_number_zero(x1) || mp_number_positive(x1)) {
                mp_number_clone(tt, t);
                mp_fraction_to_round_scaled(tt);
                goto FOUND;
            }
            if (mp_number_positive(y2)) {
                mp_set_number_to_zero(y2);
            }
            mp_number_clone(tt, t);
            {
                mp_number arg1, arg2, arg3;
                mp_new_number(arg1);
                mp_new_number(arg2);
                mp_new_number(arg3);
                mp_number_negated_clone(arg2, y2);
                mp_number_negated_clone(arg3, y3);
                mp_crossing_point(t, arg1, arg2, arg3);
                mp_free_number(arg1);
                mp_free_number(arg2);
                mp_free_number(arg3);
            }
            if (mp_number_greater(t, mp_fraction_one_t)) {
                goto DONE;
            } else {
                mp_number tmp;
                mp_new_number(tmp);
                mp_set_number_from_of_the_way(x1, t, x1, x2);
                mp_set_number_from_of_the_way(x2, t, x2, x3);
                mp_set_number_from_of_the_way(tmp, t, x1, x2);
                if (mp_number_nonnegative(tmp)) {
                    mp_free_number(tmp);
                    mp_set_number_from_of_the_way(t, t, tt, mp_fraction_one_t);
                    mp_number_clone(tt, t);
                    mp_fraction_to_round_scaled(tt);
                    goto FOUND;
                }
                mp_free_number(tmp);
            }
            DONE:
            p = q;
            mp_number_add(n, mp_unity_t);
        }
    }
    mp_set_number_to_unity(*ret);
    mp_number_negate(*ret);
    goto FREE;
  FOUND:
    mp_set_number_from_addition(*ret, n, tt);
    goto FREE;
  FREE:
    mp_free_number(x);
    mp_free_number(y);
    mp_free_number(abs_x);
    mp_free_number(abs_y);
    /*tex
        Free local variables for |find_direction_time|.
    */
    mp_free_number(x1);
    mp_free_number(x2);
    mp_free_number(x3);
    mp_free_number(y1);
    mp_free_number(y2);
    mp_free_number(y3);
    mp_free_number(t);
    mp_free_number(phi);
    mp_free_number(n);
    mp_free_number(max);
    mp_free_number(tt);
}

/*tex

Computation of the min and max is a tedious but fairly fast sequence of instructions; exactly four
comparisons are made in each branch.This was a macro but a function is way more efficient here.

*/

static void mp_set_min_max(MP mp, int v)
{
    if (mp_number_negative(stack_1(v))) {
        if (mp_number_nonnegative (stack_3(v))) {
            if (mp_number_negative(stack_2(v))) {
                mp_set_number_from_addition(stack_min(v), stack_1(v), stack_2(v));
            } else {
                mp_number_clone(stack_min(v), stack_1(v));
            }
            mp_set_number_from_addition(stack_max(v), stack_1(v), stack_2(v));
            mp_number_add(stack_max(v), stack_3(v));
            if (mp_number_negative(stack_max(v))) {
                mp_set_number_to_zero(stack_max(v));
            }
        } else {
            mp_set_number_from_addition(stack_min(v), stack_1(v), stack_2(v));
            mp_number_add(stack_min(v), stack_3(v));
            if (mp_number_greater(stack_min(v), stack_1(v))) {
                mp_number_clone(stack_min(v), stack_1(v));
            }
            mp_set_number_from_addition(stack_max(v), stack_1(v), stack_2(v));
            if (mp_number_negative(stack_max(v))) {
                mp_set_number_to_zero(stack_max(v));
            }
        }
    } else if (mp_number_nonpositive(stack_3(v))) {
        if (mp_number_positive(stack_2(v))) {
            mp_set_number_from_addition(stack_max(v), stack_1(v), stack_2(v));
        } else {
            mp_number_clone(stack_max(v), stack_1(v));
        }
        mp_set_number_from_addition(stack_min(v), stack_1(v), stack_2(v));
        mp_number_add(stack_min(v), stack_3(v));
        if (mp_number_positive(stack_min(v))) {
            mp_set_number_to_zero(stack_min(v));
        }
    } else  {
        mp_set_number_from_addition(stack_max(v), stack_1(v), stack_2(v));
        mp_number_add(stack_max(v), stack_3(v));
        if (mp_number_less(stack_max(v), stack_1(v))) {
            mp_number_clone(stack_max(v), stack_1(v));
        }
        mp_set_number_from_addition(stack_min(v), stack_1(v), stack_2(v));
        if (mp_number_positive(stack_min(v))) {
            mp_set_number_to_zero(stack_min(v));
        }
    }
}

/*tex
    The |intersectionprecision| internal defaults to 2 so we effective have |1 << 2| as
    in the original, i.e. 2 bits of precision.
*/

static int mp_cubic_intersection(MP mp, mp_knot p, mp_knot pp, int run, int cubic_precision)
{
    mp_knot q, qq;                   /*tex |mp_link(p)|, |mp_link(pp)| */
    mp_number x_two_t;               /*tex increment bit precision */
    mp_number x_two_t_low_precision; /*tex check for low precision */
    mp->time_to_go = mp_max_patience;
    mp_set_number_from_scaled(mp->max_t, 2);
    mp_new_number_clone(x_two_t, mp_two_t);
    mp_number_multiply_int(x_two_t, cubic_precision);
    mp_new_number(x_two_t_low_precision);
    mp_set_number_from_double(x_two_t_low_precision, -0.5);
    mp_number_add(x_two_t_low_precision, x_two_t);
    /*tex Initialize for intersections at level zero: */
    q = mp_next_knot(p);
    qq = mp_next_knot(pp);
    mp->bisect_ptr = bisect_int_packets;
    mp_set_number_from_subtraction(u1r, p->right_x, p->x_coord);
    mp_set_number_from_subtraction(u2r, q->left_x, p->right_x);
    mp_set_number_from_subtraction(u3r, q->x_coord, q->left_x);
    mp_set_min_max(mp, ur_packet);
    mp_set_number_from_subtraction(v1r, p->right_y, p->y_coord);
    mp_set_number_from_subtraction(v2r, q->left_y, p->right_y);
    mp_set_number_from_subtraction(v3r, q->y_coord, q->left_y);
    mp_set_min_max(mp, vr_packet);
    mp_set_number_from_subtraction(x1r, pp->right_x, pp->x_coord);
    mp_set_number_from_subtraction(x2r, qq->left_x, pp->right_x);
    mp_set_number_from_subtraction(x3r, qq->x_coord, qq->left_x);
    mp_set_min_max(mp, xr_packet);
    mp_set_number_from_subtraction(y1r, pp->right_y, pp->y_coord);
    mp_set_number_from_subtraction(y2r, qq->left_y, pp->right_y);
    mp_set_number_from_subtraction(y3r, qq->y_coord, qq->left_y);
    mp_set_min_max(mp, yr_packet);
    mp_set_number_from_subtraction(mp->delx, p->x_coord, pp->x_coord);
    mp_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;
    mp_set_number_from_scaled(mp->cur_t, 1);
    mp_set_number_from_scaled(mp->cur_tt, 1);
  CONTINUE:
    while (1) {
        /*tex
            When we are in arbitrary precision math, low precisions can lead to access locations
            beyond the |stack_size|: in this case we say that there is no intersection.
        */
        if (((x_packet(mp->xy)) + bisect_int_step - 1) > bisect_stack_size || ((u_packet(mp->uv)) + bisect_int_step - 1) > bisect_stack_size
         || ((y_packet(mp->xy)) + bisect_int_step - 1) > bisect_stack_size || ((v_packet(mp->uv)) + bisect_int_step - 1) > bisect_stack_size) {
            mp_set_number_from_scaled(mp->cur_t, 1);
            mp_set_number_from_scaled(mp->cur_tt, 1);
            goto NOT_FOUND;
        }
        /*tex
            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 (mp_number_greater(mp->max_t, x_two_t)) {
            mp_set_number_from_scaled(mp->cur_t, 1);
            mp_set_number_from_scaled(mp->cur_tt, 1);
            goto NOT_FOUND;
        }
        if (mp_number_to_scaled(mp->delx) - mp->tol <= mp_number_to_scaled(stack_max(x_packet(mp->xy))) - mp_number_to_scaled(stack_min(u_packet (mp->uv)))) {
            if (mp_number_to_scaled(mp->delx) + mp->tol >= mp_number_to_scaled(stack_min(x_packet(mp->xy))) - mp_number_to_scaled(stack_max(u_packet (mp->uv)))) {
                if (mp_number_to_scaled(mp->dely) - mp->tol <= mp_number_to_scaled(stack_max(y_packet(mp->xy))) - mp_number_to_scaled(stack_min(v_packet (mp->uv)))) {
                    if (mp_number_to_scaled(mp->dely) + mp->tol >= mp_number_to_scaled(stack_min(y_packet(mp->xy))) - mp_number_to_scaled(stack_max(v_packet (mp->uv)))) {
                        if (mp_number_to_scaled(mp->cur_t) >= mp_number_to_scaled(mp->max_t)) {
                            if (mp_number_equal(mp->max_t, x_two_t) || mp_number_greater(mp->max_t, x_two_t_low_precision)) {
                                if (run == 1) {
                                    /*tex
                                        We've done 17+2 bisections, first restore values to bit precision.
                                    */
                                    mp_number_divide_int(mp->cur_t, cubic_precision);
                                    mp_number_divide_int(mp->cur_tt, cubic_precision);
                                    mp_set_number_from_scaled(mp->cur_t, ((mp_number_to_scaled(mp->cur_t) + 1)/2));
                                    mp_set_number_from_scaled(mp->cur_tt, ((mp_number_to_scaled(mp->cur_tt) + 1)/2));
                                    mp_free_number(x_two_t);
                                    mp_free_number(x_two_t_low_precision);
                                    return 1;
                                } else {
                                    run--;
                                    goto NOT_FOUND;
                                }
                            }
                            mp_number_double(mp->max_t);
                            mp_number_clone(mp->appr_t, mp->cur_t);
                            mp_number_clone(mp->appr_tt, mp->cur_tt);
                        }
                        /*tex
                            Subdivide for a new level of intersection.
                        */
                        mp_number_clone(stack_dx, mp->delx);
                        mp_number_clone(stack_dy, mp->dely);
                        mp_set_number_from_scaled(stack_tol, mp->tol);
                        mp_set_number_from_scaled(stack_uv, mp->uv);
                        mp_set_number_from_scaled(stack_xy, mp->xy);
                        mp->bisect_ptr = mp->bisect_ptr + bisect_int_increment;
                        mp_number_double(mp->cur_t);
                        mp_number_double(mp->cur_tt);
                        mp_number_clone(u1l, stack_1(u_packet (mp->uv)));
                        mp_number_clone(u3r, stack_3(u_packet (mp->uv)));
                        mp_set_number_half_from_addition(u2l, u1l, stack_2(u_packet(mp->uv)));
                        mp_set_number_half_from_addition(u2r, u3r, stack_2(u_packet(mp->uv)));
                        mp_set_number_half_from_addition(u3l, u2l, u2r);
                        mp_number_clone(u1r, u3l);
                        mp_set_min_max(mp, ul_packet);
                        mp_set_min_max(mp, ur_packet);
                        mp_number_clone(v1l, stack_1(v_packet (mp->uv)));
                        mp_number_clone(v3r, stack_3(v_packet (mp->uv)));
                        mp_set_number_half_from_addition(v2l, v1l, stack_2(v_packet(mp->uv)));
                        mp_set_number_half_from_addition(v2r, v3r, stack_2(v_packet(mp->uv)));
                        mp_set_number_half_from_addition(v3l, v2l, v2r);
                        mp_number_clone(v1r, v3l);
                        mp_set_min_max(mp, vl_packet);
                        mp_set_min_max(mp, vr_packet);
                        mp_number_clone(x1l, stack_1(x_packet (mp->xy)));
                        mp_number_clone(x3r, stack_3(x_packet (mp->xy)));
                        mp_set_number_half_from_addition(x2l, x1l, stack_2(x_packet(mp->xy)));
                        mp_set_number_half_from_addition(x2r, x3r, stack_2(x_packet(mp->xy)));
                        mp_set_number_half_from_addition(x3l, x2l, x2r);
                        mp_number_clone(x1r, x3l);
                        mp_set_min_max(mp, xl_packet);
                        mp_set_min_max(mp, xr_packet);
                        mp_number_clone(y1l, stack_1(y_packet (mp->xy)));
                        mp_number_clone(y3r, stack_3(y_packet (mp->xy)));
                        mp_set_number_half_from_addition(y2l, y1l, stack_2(y_packet(mp->xy)));
                        mp_set_number_half_from_addition(y2r, y3r, stack_2(y_packet(mp->xy)));
                        mp_set_number_half_from_addition(y3l, y2l, y2r);
                        mp_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;
                        mp_number_double(mp->delx);
                        mp_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;
                        goto CONTINUE;
                    }
                }
            }
        }
        if (mp->time_to_go > 0) {
            --mp->time_to_go;
        } else {
            /* we have added 2 bit of precision */
            mp_number_divide_int(mp->appr_t, cubic_precision);
            mp_number_divide_int(mp->appr_tt, cubic_precision);
            while (mp_number_less(mp->appr_t, mp_unity_t)) {
                mp_number_double(mp->appr_t);
                mp_number_double(mp->appr_tt);
            }
            mp_number_clone(mp->cur_t, mp->appr_t);
            mp_number_clone(mp->cur_tt, mp->appr_tt);
            mp_free_number(x_two_t);
            mp_free_number(x_two_t_low_precision);
            return 2;
        }
      NOT_FOUND:
        /*tex
            Advance to the next pair |(cur_t,cur_tt)|.
        */
        if (odd_long(mp_number_to_scaled(mp->cur_tt))) {
            if (odd_long(mp_number_to_scaled(mp->cur_t))) {
                /*tex
                    Descend to the previous level and |goto not_found|.
                */
                mp_set_number_from_scaled(mp->cur_t, half(mp_number_to_scaled(mp->cur_t)));
                mp_set_number_from_scaled(mp->cur_tt, half(mp_number_to_scaled(mp->cur_tt)));
                if (mp_number_to_scaled(mp->cur_t) == 0) {
                    mp_free_number(x_two_t);
                    mp_free_number(x_two_t_low_precision);
                    return 3;
                } else {
                    mp->bisect_ptr -= bisect_int_increment;
                    mp->three_l -= (int) mp->tol_step;
                    mp_number_clone(mp->delx, stack_dx);
                    mp_number_clone(mp->dely, stack_dy);
                    mp->tol = (int) mp_number_to_scaled(stack_tol);
                    mp->uv = (int) mp_number_to_scaled(stack_uv);
                    mp->xy = (int) mp_number_to_scaled(stack_xy);
                    goto NOT_FOUND;
                }
            } else {
                mp_set_number_from_scaled(mp->cur_t, mp_number_to_scaled(mp->cur_t) + 1);
                mp_number_add(mp->delx, stack_1(u_packet(mp->uv)));
                mp_number_add(mp->delx, stack_2(u_packet(mp->uv)));
                mp_number_add(mp->delx, stack_3(u_packet(mp->uv)));
                mp_number_add(mp->dely, stack_1(v_packet(mp->uv)));
                mp_number_add(mp->dely, stack_2(v_packet(mp->uv)));
                mp_number_add(mp->dely, stack_3(v_packet(mp->uv)));
                /*tex switch from |l_packets| to |r_packets| */
                mp->uv = mp->uv + bisect_int_packets;
                mp_set_number_from_scaled(mp->cur_tt, mp_number_to_scaled(mp->cur_tt) - 1);
                mp->xy = mp->xy - bisect_int_packets;
                mp_number_add(mp->delx, stack_1(x_packet(mp->xy)));
                mp_number_add(mp->delx, stack_2(x_packet(mp->xy)));
                mp_number_add(mp->delx, stack_3(x_packet(mp->xy)));
                mp_number_add(mp->dely, stack_1(y_packet(mp->xy)));
                mp_number_add(mp->dely, stack_2(y_packet(mp->xy)));
                mp_number_add(mp->dely, stack_3(y_packet(mp->xy)));
            }
        } else {
            mp_set_number_from_scaled(mp->cur_tt, mp_number_to_scaled(mp->cur_tt) + 1);
            mp->tol = mp->tol + mp->three_l;
            mp_number_subtract(mp->delx, stack_1(x_packet(mp->xy)));
            mp_number_subtract(mp->delx, stack_2(x_packet(mp->xy)));
            mp_number_subtract(mp->delx, stack_3(x_packet(mp->xy)));
            mp_number_subtract(mp->dely, stack_1(y_packet(mp->xy)));
            mp_number_subtract(mp->dely, stack_2(y_packet(mp->xy)));
            mp_number_subtract(mp->dely, stack_3(y_packet(mp->xy)));
            /*tex switch from |l_packets| to |r_packets| */
            mp->xy = mp->xy + bisect_int_packets;
        }
    }
    mp_free_number(x_two_t);
    mp_free_number(x_two_t_low_precision);
    return 0;
}

static mp_knot mp_path_intersection_add(MP mp, mp_knot list, mp_knot *last, mp_number *t, mp_number *tt)
{
    int a = (int) mp_number_to_scaled(*t) >> mp_intersection_run_shift;
    int aa = (int) mp_number_to_scaled(*tt) >> mp_intersection_run_shift;
    int b =  (list ? (int) mp_number_to_scaled((*last)->x_coord) : -1) >> mp_intersection_run_shift ;
    int bb = (list ? (int) mp_number_to_scaled((*last)->y_coord) : -1) >> mp_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;
        mp_number_clone(k->x_coord, *t);
        mp_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;
}

static mp_knot mp_path_intersection(MP mp, mp_knot h, mp_knot hh, int path, mp_knot *last)
{
    mp_number n, nn; /*tex Integer parts of intersection times, minus |unity|. */
    int done = 0;
    mp_knot list = NULL;
    mp_knot l = NULL;
    mp_knot ll = NULL;
    int precision = (int) mp_number_to_int(internal_value(mp_intersection_precision_internal));
    if (precision < 1) {
        precision = 1;
    } else if (precision > 4) {
        precision = 4;
    }
    precision = 1 << precision;
    if (last) {
        *last = NULL;
    }
    /*tex
        Change one-point paths into dead cycles.
    */
    if (mp_right_type(h) == mp_endpoint_knot) {
        mp_number_clone(h->right_x, h->x_coord);
        mp_number_clone(h->left_x, h->x_coord);
        mp_number_clone(h->right_y, h->y_coord);
        mp_number_clone(h->left_y, h->y_coord);
        mp_right_type(h) = mp_explicit_knot;
    }
    if (mp_right_type(hh) == mp_endpoint_knot) {
        mp_number_clone(hh->right_x, hh->x_coord);
        mp_number_clone(hh->left_x, hh->x_coord);
        mp_number_clone(hh->right_y, hh->y_coord);
        mp_number_clone(hh->left_y, hh->y_coord);
        mp_right_type(hh) = mp_explicit_knot;
    }
    mp_new_number(n);
    mp_new_number(nn);
    mp->tol_step = 0;
    do {
        mp_knot p, pp; /*tex Link registers that traverse the given paths. */
        int t = -1;
        int tt = -1;
     // mp_set_number_to_unity(n);
     // number_negate(n);
        mp_number_negated_clone(n, mp_unity_t);
        p = h;
        do {
            if (mp_right_type(p) != mp_endpoint_knot) {
             // mp_set_number_to_unity(nn);
             // number_negate(nn);
                mp_number_negated_clone(nn, mp_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, precision);
                        if (mp_number_positive(mp->cur_t)) {
                            mp_number_add(mp->cur_t, n);
                            mp_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 == mp_number_to_scaled(mp->cur_t) && tt == mp_number_to_scaled(mp->cur_tt)) {
                                    if (retrials == 8) { /* is 8 okay? */
                                        break;
                                    } else {
                                        retrials += 1;
                                        goto RETRY;
                                    }
                                } else {
                                    retrials = 0;
                                    t = (int) mp_number_to_scaled(mp->cur_t);
                                    tt = (int) mp_number_to_scaled(mp->cur_tt);
                                    goto RETRY;
                                }
                            } else {
                                goto DONE;
                            }
                        }
                    }
                    mp_number_add(nn, mp_unity_t);
                    ll = pp;
                    pp = mp_next_knot(pp);
                    /* begin experiment HH/MS, maybe a loop  */
                    if (pp != hh && mp_knotstate(pp) == mp_end_knot) {
                        mp_number_add(nn, mp_unity_t);
                        ll = pp;
                        pp = mp_next_knot(pp);
                    }
                    /* end experiment HH/MS */
                } while (pp != hh);
            }
            mp_number_add(n, mp_unity_t);
            l = p;
            p = mp_next_knot(p);
            /* begin experiment HH/MS, maybe a loop  */
            if (p != hh && mp_knotstate(p) == mp_end_knot) {
                mp_number_add(n, mp_unity_t);
                l = p;
                p = mp_next_knot(p);
            }
            /* end experiment HH/MS */
        } while (p != h);
        mp->tol_step = mp->tol_step + 3;
        if (done) {
            /*tex When we do all points: */
            goto DONE;
        }
    } while (mp->tol_step <= 3);
  DONE:
    if (path && l && ll && mp_number_equal(l->x_coord, ll->x_coord) && mp_number_equal(l->y_coord, ll->y_coord)) {
        list = mp_path_intersection_add(mp, list, last, &n, &nn);
    }
    if (! done) {
        mp_number_negated_clone(mp->cur_t, mp_unity_t);
        mp_number_negated_clone(mp->cur_tt, mp_unity_t);
        if (path && ! list) {
            mp_knot k = mp_new_knot(mp);
            mp_number_clone(k->x_coord, mp->cur_t);
            mp_number_clone(k->y_coord, mp->cur_tt);
            mp_prev_knot(k) = k;
            mp_next_knot(k) = k;
            list = k;
            if (last) {
                *last = k;
            }
        }
    }
    mp_free_number(n);
    mp_free_number(nn);
    return list;
}

/*tex

\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:

\startitemize

\startitem
    |type(p) = mp_known| is the nice case, when |value(p)| is the |scaled| value of the variable
    whose address is~|p|.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.)
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |type(p) = undefined| means that variable |p| hasn't appeared
before.
\stopitem

\stopitemize

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|.

*/

static void mp_new_indep(MP mp, mp_node p, int where)
{
    if (++mp->serial_no == mp_max_integer) {
        /*tex We could reset when we have nothing in store. */
        mp_fatal_error(mp, "Variable instance identifiers exhausted");
    } else {
        /*tex Create a new independent variable. */
    }
    p->type = mp_independent_type;
    mp_set_indep_scale(p, 0);
    mp_set_indep_value(p, mp->serial_no);
    if (mp_number_greater(internal_value(mp_tracing_dependencies_internal), mp_unity_t) && where) {
        mp_print_format(mp, "%l[independency: set, node %P, serial %i, location %i]", p, mp->serial_no, where);
    }
}

static inline void do_set_dep_value(MP mp, mp_value_node p, mp_number *q)
{
    /*tex Half of the |value| field in a |dependent| variable. */
    mp_number_clone(p->data.n, *q);
    p->attr_head = NULL;
    p->subscr_head = NULL;
}

static mp_value_node mp_get_dep_node(MP mp, int where)
{
    mp_value_node p = (mp_value_node) mp_new_value_node(mp);
    p->type = mp_dep_node_type;
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal)) && where) {
        mp_print_format(mp, "%l[dependency: new, node %P, location %i]", p, where);
    }
    return p;
}

static void mp_free_dep_node(MP mp, mp_value_node p, int where)
{
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal)) && where) {
        mp_print_format(mp, "%l[dependency: free, node %P, location %i]", p, where);
    }
    mp_free_value_node(mp, (mp_node) p);
}

/*tex

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.

*/

static int mp_count_dependency(MP mp, mp_value_node p)
{
    int n = 0;
    (void) mp;
    while (1) {
        mp_node q = mp_get_dep_info(p);
        if (q == NULL) {
            return n;
        } else {
            ++n;
            if (q->type != mp_independent_type) {
                mp_confusion(mp, "dependency");
            } else {
                p = (mp_value_node) p->link;
            }
        }
    }
}

static void mp_print_dependency(MP mp, mp_value_node p, int t)
{
    mp_number v; /*tex A coefficient. */
    mp_node q;
    mp_value_node pp = p;
    mp_new_number(v);
    while (1) {
        mp_number_abs_clone(v, mp_get_dep_value(p));
        q = mp_get_dep_info(p);
        if (q == NULL) {
            /*tex The constant term. */
            if (mp_number_nonzero(v) || (p == pp)) {
                if (mp_number_positive(mp_get_dep_value(p)) && p != pp) {
                    mp_print_string(mp, " + ");
                }
                mp_print_number(mp, mp_get_dep_value(p));
            }
            return;
        }
        /*tex Print the coefficient, unless it's $\pm1.0$. */
        if (mp_number_negative(mp_get_dep_value(p))) {
            mp_print_string(mp, " - ");
        } else if (p != pp) {
            mp_print_string(mp, " + ");
        }
        if (t == mp_dependent_type) {
            mp_fraction_to_round_scaled(v);
        }
        if (! mp_number_equal(v, mp_unity_t)) {
            mp_print_number(mp, v);
        }
        if (q->type != mp_independent_type) {
            mp_confusion(mp, "dependency");
        } else {
            /* v could be an integer */
            mp_print_variable_name(mp, q);
            mp_set_number_from_scaled(v, mp_get_indep_scale(q));
            while (mp_number_positive(v)) {
                mp_print_string(mp, " * 4 ");
                mp_number_add_scaled(v, -2);
            }
            p = (mp_value_node) p->link;
        }
    }
}

/*tex

The maximum absolute value of a coefficient in a given dependency list is returned by the following
simple function.

*/

static void mp_max_coef(MP mp, mp_number *x, mp_value_node p)
{
    mp_number absval;
    mp_new_number(absval);
    mp_set_number_to_zero(*x);
    while (mp_get_dep_info(p) != NULL) {
        mp_number_abs_clone(absval, mp_get_dep_value(p));
        if (mp_number_greater(absval, *x)) {
            mp_number_clone(*x, absval);
        }
        p = (mp_value_node) p->link;
    }
    mp_free_number(absval);
}

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;           /*tex |mp_get_dep_info(p)| and |mp_get_dep_info(q)|, respectively */
    mp_value_node r, s;       /*tex for list manipulation */
    mp_number threshold;      /*tex defines a neighborhood of zero */
    mp_number half_threshold;
    mp_number v, vv;          /*tex temporary registers */
    mp_new_number(v);
    mp_new_number(vv);
    if (t == mp_dependent_type) {
        mp_new_number_clone(threshold, mp_fraction_threshold_k);
        mp_new_number_clone(half_threshold, mp_half_fraction_threshold_k);
    } else {
        mp_new_number_clone(threshold, mp_scaled_threshold_k);
        mp_new_number_clone(half_threshold, mp_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 {
                /*tex
                    Contribute a term from |p|, plus |f| times the corresponding term from |q|.
                */
                mp_number r1;
                mp_number absval;
                mp_new_fraction(r1);
                mp_new_number(absval);
                if (tt == mp_dependent_type) {
                    mp_take_fraction(r1, *f, mp_get_dep_value(q));
                } else {
                    mp_take_scaled(r1, *f, mp_get_dep_value(q));
                }
                mp_set_number_from_addition(v, mp_get_dep_value(p), r1);
                mp_free_number(r1);
                mp_set_dep_value(p, v);
                s = p;
                p = (mp_value_node) p->link;
                mp_number_abs_clone(absval, v);
                if (mp_number_less(absval, threshold)) {
                    mp_free_dep_node(mp, s, 2);
                } else {
                    if (mp_number_greaterequal(absval, mp_coef_bound_k) && mp->watch_coefs) {
                        qq->type = mp_independent_needing_fix;
                        /*tex
                            If we set this , then we can drop |(mp_type(pp) ==
                            mp_independent_needing_fix && mp->fix_needed)| later
                            |mp_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;
                }
                mp_free_number(absval);
                pp = mp_get_dep_info(p);
                q = (mp_value_node) q->link;
                qq = mp_get_dep_info(q);
            }
        } else {
            /* see the other case for weird */
            if (pp == NULL) {
                mp_set_number_to_negative_inf(v);
            } else if (pp->type == mp_independent_type || (pp->type == mp_independent_needing_fix && mp->fix_needed)) {
                mp_set_number_from_scaled(v, mp_get_indep_value(pp));
            } else {
                mp_number_clone(v, mp_get_value_number(pp));
            }
            if (qq == NULL) {
                mp_set_number_to_negative_inf(vv);
            } else if (qq->type == mp_independent_type || (qq->type == mp_independent_needing_fix && mp->fix_needed)) {
                mp_set_number_from_scaled(vv, mp_get_indep_value(qq));
            } else {
                mp_number_clone(vv, mp_get_value_number(qq));
            }
            if (mp_number_less(v, vv)) {
                /*tex
                    Contribute a term from |q|, multiplied by~|f|.
                */
                mp_number absval;
                {
                    mp_number r1;
                    mp_number arg1, arg2;
                    mp_new_fraction(r1);
                    mp_new_number_clone(arg1, *f);
                    mp_new_number_clone(arg2, mp_get_dep_value(q));
                    if (tt == mp_dependent_type) {
                        mp_take_fraction(r1, arg1, arg2);
                    } else {
                        mp_take_scaled(r1, arg1, arg2);
                    }
                    mp_number_clone(v, r1);
                    mp_free_number(r1);
                    mp_free_number(arg1);
                    mp_free_number(arg2);
                }
                mp_new_number_abs(absval, v);
                if (mp_number_greater(absval, half_threshold)) {
                    s = mp_get_dep_node(mp, 1);
                    mp_set_dep_info(s, qq);
                    mp_set_dep_value(s, v);
                    if (mp_number_greaterequal(absval, mp_coef_bound_k) && mp->watch_coefs) {
                        qq->type = mp_independent_needing_fix;
                        mp->fix_needed = 1;
                    }
                    mp_set_link(r, s);
                    r = s;
                }
                q = (mp_value_node) q->link;
                qq = mp_get_dep_info(q);
                mp_free_number(absval);
            } else {
                mp_set_link(r, p);
                r = p;
                p = (mp_value_node) p->link;
                pp = mp_get_dep_info(p);
            }
        }
    }
    {
        mp_number r1;
        mp_number arg1, arg2;
        mp_new_fraction(r1);
        mp_new_number(arg1);
        mp_new_number(arg2);
        mp_number_clone(arg1, mp_get_dep_value(q));
        mp_number_clone(arg2, *f);
        if (t == mp_dependent_type) {
            mp_take_fraction(r1, arg1, arg2);
        } else {
            mp_take_scaled(r1, arg1, arg2);
        }
        mp_slow_add(arg1, mp_get_dep_value(p), r1);
        mp_set_dep_value(p, arg1);
        mp_free_number(r1);
        mp_free_number(arg1);
        mp_free_number(arg2);
    }
    mp_set_link(r, p);
    mp->dep_final = p;
    mp_free_number(threshold);
    mp_free_number(half_threshold);
    mp_free_number(v);
    mp_free_number(vv);
    return (mp_value_node) mp->temp_head->link;
}

/*tex

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|).

*/

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;      /*tex |mp_get_dep_info(p)| and |mp_get_dep_info(q)|, respectively */
    mp_value_node s;     /*tex for list manipulation */
    mp_value_node r;     /*tex for list manipulation */
    mp_number threshold; /*tex defines a neighborhood of zero */
    mp_number v, vv;     /*tex temporary register */
    mp_new_number(v);
    mp_new_number(vv);
    mp_new_number(threshold);
    if (t == mp_dependent_type) {
        mp_number_clone(threshold, mp_fraction_threshold_k);
    } else {
        mp_number_clone(threshold, mp_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 {
                /*tex
                    Contribute a term from |p|, plus the corresponding term from |q|.
                */
                mp_number absval;
                mp_new_number(absval);
                mp_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) p->link;
                pp = mp_get_dep_info(p);
                mp_number_abs_clone(absval, v);
                if (mp_number_less(absval, threshold)) {
                    mp_free_dep_node(mp, s, 3);
                } else {
                    if (mp_number_greaterequal(absval, mp_coef_bound_k) && mp->watch_coefs) {
                        qq->type = mp_independent_needing_fix;
                        /*tex
                            If we set this, then we can drop |(mp_type(pp) == mp_independent_needing_fix
                            && mp->fix_needed)| later |mp_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;
                }
                mp_free_number(absval);
                q = (mp_value_node) q->link;
                qq = mp_get_dep_info(q);
            }
        } else {
            /* weird: v/vv can be zero, serial or value here: */
            if (pp == NULL) {
                mp_set_number_to_zero(v);
            } else if (pp->type == mp_independent_type || (pp->type == mp_independent_needing_fix && mp->fix_needed)) {
                mp_set_number_from_scaled(v, mp_get_indep_value(pp));
            } else {
                mp_number_clone(v, mp_get_value_number(pp));
            }
            if (qq == NULL) {
                mp_set_number_to_zero(vv);
            } else if (qq->type == mp_independent_type || (qq->type == mp_independent_needing_fix && mp->fix_needed)) {
                mp_set_number_from_scaled(vv, mp_get_indep_value(qq));
            } else {
                mp_number_clone(vv, mp_get_value_number(qq));
            }
            if (mp_number_less(v, vv)) {

         // int ps = pp ? mp_get_indep_value(pp) : 0;
         // int qs = qq ? mp_get_indep_value(qq) : 0;
         // if (ps < qs) {

                s = mp_get_dep_node(mp, 2);
                mp_set_dep_info(s, qq);
                mp_set_dep_value(s, mp_get_dep_value(q));
                q = (mp_value_node) q->link;
                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) p->link;
                pp = mp_get_dep_info(p);
            }
        }
    }
    {
        mp_number r1;
        mp_new_number(r1);
        mp_slow_add(r1, mp_get_dep_value(p), mp_get_dep_value(q));
        mp_set_dep_value(p, r1);
        mp_free_number(r1);
    }
    mp_set_link(r, p);
    mp->dep_final = p;
    mp_free_number(v);
    mp_free_number(vv);
    mp_free_number(threshold);
    return (mp_value_node) mp->temp_head->link;
}

/*tex

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|.

*/

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; /*tex for list manipulation */
    mp_number w;        /*tex tentative coefficient */
    mp_number threshold;
    int scaling_down = (t0 != t1) ? 1 : (! v_is_scaled);
    mp_new_number(threshold);
    mp_new_number(w);
    if (t1 == mp_dependent_type) {
        mp_number_clone(threshold, mp_half_fraction_threshold_k);
    } else {
        mp_number_clone(threshold, mp_half_scaled_threshold_k);
    }
    r = (mp_value_node) mp->temp_head;
    while (mp_get_dep_info(p) != NULL) {
        mp_number absval;
        mp_new_number(absval);
        if (scaling_down) {
            mp_take_fraction(w, *v, mp_get_dep_value(p));
        } else {
            mp_take_scaled(w, *v, mp_get_dep_value(p));
        }
        mp_number_abs_clone(absval, w);
        if (mp_number_lessequal(absval, threshold)) {
            s = (mp_value_node) p->link;
            mp_free_dep_node(mp, p, 4);
            p = s;
        } else {
            if (mp_number_greaterequal(absval, mp_coef_bound_k)) {
                mp->fix_needed = 1;
                mp_get_dep_info(p)->type = mp_independent_needing_fix;
            }
            mp_set_link(r, p);
            r = p;
            mp_set_dep_value(p, w);
            p = (mp_value_node) p->link;
        }
        mp_free_number(absval);
    }
    mp_set_link(r, p);
    {
        mp_number r1;
        mp_new_number(r1);
        if (v_is_scaled) {
            mp_take_scaled(r1, mp_get_dep_value(p), *v);
        } else {
            mp_take_fraction(r1, mp_get_dep_value(p), *v);
        }
        mp_set_dep_value(p, r1);
        mp_free_number(r1);
    }
    mp_free_number(w);
    mp_free_number(threshold);
    return (mp_value_node) mp->temp_head->link;
}

/*tex

Similarly, we sometimes need to divide a dependency list by a given |scaled| constant.

*/

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; /*tex for list manipulation */
    mp_number w;        /*tex tentative coefficient */
    mp_number threshold;
    mp_number v;
    int scaling_down = (t0 != t1);
    mp_new_number(w);
    mp_new_number(threshold);
    mp_new_number_clone(v, *v_orig);
    if (t1 == mp_dependent_type) {
        mp_number_clone(threshold, mp_half_fraction_threshold_k);
    } else {
        mp_number_clone(threshold, mp_half_scaled_threshold_k);
    }
    r = (mp_value_node) mp->temp_head;
    while (mp_get_dep_info(p) != NULL) {
        if (scaling_down) {
            mp_number x, absval;
            mp_new_number_abs(absval, v);
            if (mp_number_less(absval, mp_p_over_v_threshold_k)) {
                mp_new_number_clone(x, v);
                mp_convert_scaled_to_fraction(x);
                mp_make_scaled(w, mp_get_dep_value(p), x);
            } else {
                mp_new_number_clone(x, mp_get_dep_value(p));
                mp_fraction_to_round_scaled(x);
                mp_make_scaled(w, x, v);
            }
            mp_free_number(x);
            mp_free_number(absval);
        } else {
            mp_make_scaled(w, mp_get_dep_value(p), v);
        }
        {
            mp_number absval;
            mp_new_number(absval);
            mp_number_abs_clone(absval, w);
            if (mp_number_lessequal(absval, threshold)) {
                s = (mp_value_node) p->link;
                mp_free_dep_node(mp, p, 5);
                p = s;
            } else {
                if (mp_number_greaterequal(absval, mp_coef_bound_k)) {
                    mp->fix_needed = 1;
                    mp_get_dep_info(p)->type = mp_independent_needing_fix;
                }
                mp_set_link(r, p);
                r = p;
                mp_set_dep_value(p, w);
                p = (mp_value_node) p->link;
            }
            mp_free_number(absval);
        }
    }
    mp_set_link(r, p);
    {
        mp_number ret;
        mp_new_number(ret);
        mp_make_scaled(ret, mp_get_dep_value(p), v);
        mp_set_dep_value(p, ret);
        mp_free_number(ret);
    }
    mp_free_number(v);
    mp_free_number(w);
    mp_free_number(threshold);
    return (mp_value_node) mp->temp_head->link;
}

/*tex

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|.

*/

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| */ /* serial of NULL is 0 */
    while (mp_get_dep_info(s) != NULL && mp_get_indep_value(mp_get_dep_info(s)) > sx) {
        r = s;
        s = (mp_value_node) s->link;
    }
 /* if (mp_get_dep_info(s) != x) { */ /* saveguard: */
    if (mp_get_dep_info(s) == NULL || mp_get_dep_info(s) != x) {
        return p;
    } else {
        mp_value_node ret;
        mp_number v;
        mp_set_link(mp->temp_head, p);
        mp_set_link(r, s->link);
        mp_new_number_clone(v, mp_get_dep_value(s));
        mp_free_dep_node(mp, s, 6);
        ret = mp_p_plus_fq(mp, (mp_value_node) mp->temp_head->link, &v, (mp_value_node) q, t, mp_dependent_type);
        mp_free_number(v);
        return ret;
    }
}

/*tex

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.

*/

static void mp_val_too_big(MP mp, mp_number *x)
{
    if (mp_number_positive(internal_value(mp_warning_check_internal))) {
        char msg[256];
        snprintf(msg, 256, "Value is too large (%s)", mp_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.)"
        );
    }
}

/*tex

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).

*/

static void mp_make_known(MP mp, mp_value_node p, mp_value_node q)
{
    mp_variable_type t = p->type; /* the previous type */
    mp_number absval;
    mp_new_number(absval);
    mp_set_prev_dep(q->link, mp_get_prev_dep(p));
    mp_set_link(mp_get_prev_dep(p), q->link);
    p->type = mp_known_type;
    mp_set_value_number(p, mp_get_dep_value(q));
    mp_free_dep_node(mp, q, 7);
    mp_number_abs_clone(absval, mp_get_value_number(p));
    if (mp_number_greaterequal(absval, mp_warning_limit_t)) {
        mp_val_too_big(mp, &(mp_get_value_number(p)));
    }
    if ((mp_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_char(mp, '=');
        mp_print_number(mp, mp_get_value_number(p));
        mp_end_diagnostic(mp, 0);
    }
    if (cur_exp_node == (mp_node) p && cur_exp_type == t) {
        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);
    }
    mp_free_number(absval);
}

/*

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.

*/

static void mp_fix_dependencies(MP mp)
{
    mp_value_node r = (mp_value_node) mp->dep_head->link;
    mp_value_node s = NULL;
// mp_print_string(mp, "%lFIXING");
    while (r != mp->dep_head) {
        /*tex
            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) r->link;
            }
            x = mp_get_dep_info(q);
            if (x == NULL) {
                break;
            } else if (x->type <= mp_independent_being_fixed) {
                if (x->type < mp_independent_being_fixed) {
                    mp_value_node p = mp_get_dep_node(mp, 3);
                    mp_set_link(p, s);
                    s = p;
                    mp_set_dep_info(s, x);
                    x->type = mp_independent_being_fixed;
                }
                mp_set_dep_value(q, mp_get_dep_value(q));
                mp_number_divide_int(mp_get_dep_value(q), 4);
                if (mp_number_zero(mp_get_dep_value(q))) {
                    mp_set_link(r, q->link);
                    mp_free_dep_node(mp, q, 8);
                    q = r;
                }
            }
            r = q;
        }
        r = (mp_value_node) q->link;
        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) s->link;
        mp_node x = mp_get_dep_info(s);
        mp_free_dep_node(mp, s, 9);
        s = p;
        x->type = mp_independent_type;
        mp_set_indep_scale(x, mp_get_indep_scale(x) + 2);
    }
    mp->fix_needed = 0;
}

/*tex

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|.

*/

static void mp_new_dep(MP mp, mp_node q, mp_variable_type newtype, mp_value_node p, int where)
{
    mp_node r = mp->dep_head->link; /*tex what used to be the first dependency */
    q->type = newtype;
    mp_set_dep_list(q, p);
    mp_set_prev_dep(q, (mp_node) 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);
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal)) && where) {
        if (newtype == mp_dependent_type) {
            mp_print_format(mp, "%l[dependency: set, node %P, type '%s', list %P, location %i]", q, mp_type_string(newtype), p, where);
        } else {
            mp_print_format(mp, "%l[dependency: set, node %P, serial %i, type '%s', list %P, location %i]", q, mp_get_indep_value(q), mp_type_string(newtype), p, where);
        }
    }
}

/*tex

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.

*/

static mp_value_node mp_const_dependency(MP mp, mp_number *v)
{
    mp->dep_final = mp_get_dep_node(mp, 4);
    mp_set_dep_value(mp->dep_final, *v);
    mp_set_dep_info(mp->dep_final, NULL);
    return mp->dep_final;
}

/* Todo: check this hard coded 28 ! */

static mp_value_node mp_single_dependency(MP mp, mp_node p)
{
    mp_value_node q; /*tex the new dependency list */
    int m = mp_get_indep_scale(p); /*tex the number of doublings */
    if (m > 28) {
        q = mp_const_dependency(mp, &mp_zero_t);
    } else {
        q = mp_get_dep_node(mp, 5);
 // cf mfont q->type = 0;
        mp_set_dep_value(q, mp_zero_t);
        mp_set_number_from_scaled(mp_get_dep_value(q), two_to_the(28 - m));
        mp_set_dep_info(q, p);
        {
            /* We append a kind of dummy term: |x = x + 0|. */
            mp_value_node rr = mp_const_dependency(mp, &mp_zero_t);
            mp_set_link(q, rr);
        }
    }
    return q;
}

/*tex
    We sometimes need to make an exact copy of a dependency list.
*/

static mp_value_node mp_copy_dep_list(MP mp, mp_value_node p)
{
    mp_value_node q = mp_get_dep_node(mp, 6); /* the new dependency list */
    mp->dep_final = q;
    if (mp_number_greater(internal_value(mp_tracing_dependencies_internal), mp_unity_t)) {
        mp_print_format(mp, "%l[dependency, copy, node %P]", p);
    }
    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, 7));
            mp->dep_final = (mp_value_node) mp->dep_final->link;
            p = (mp_value_node) p->link;
        }
    }
    return q;
}

/*tex

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.

*/

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 mp_value_node divide_p_by_2_n                    (MP mp, mp_value_node p, 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 void mp_linear_eq(MP mp, mp_value_node p, int t)
{
    mp_value_node r;          /*tex for link manipulation */
    mp_node x;                /*tex the variable that loses its independence */
    int n;                    /*tex the number of times |x| had been halved */
    mp_number v;              /*tex the coefficient of |x| in list |p| */
    mp_value_node prev_r;     /*tex lags one step behind |r| */
    mp_value_node final_node; /*tex the constant term of the new dependency list */
    mp_value_node qq;
    mp_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 (mp_number_positive(internal_value(mp_tracing_equations_internal))) {
    if ((mp_number_positive(internal_value(mp_tracing_equations_internal))) && mp_interesting(mp, (mp_node) x)) {
        display_new_dependency(mp, p, (mp_node) x, n);
    }
    prev_r = (mp_value_node) mp->dep_head;
    r = (mp_value_node) mp->dep_head->link;
    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, r->type);
        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) q->link;
            } while (mp_get_dep_info(q) != NULL);
            prev_r = q;
        }
        r = (mp_value_node) prev_r->link;
    }
    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);
    }
    mp_free_number(v);
}

static mp_value_node find_node_with_largest_coefficient(MP mp, mp_value_node p, mp_number *v)
{
    mp_number vabs; /*tex its absolute value of v*/
    mp_number rabs; /*tex the absolute value of |mp_get_dep_value(r)| */
    mp_value_node q = p;
    mp_value_node r = (mp_value_node) p->link;
    mp_new_number(vabs);
    mp_new_number(rabs);
    mp_number_clone(*v, mp_get_dep_value(q));
    while (mp_get_dep_info(r) != NULL) {
        mp_number_abs_clone(vabs, *v);
        mp_number_abs_clone(rabs, mp_get_dep_value(r));
        if (mp_number_greater(rabs, vabs)) {
            q = r;
            mp_number_clone(*v, mp_get_dep_value(r));
        }
        r = (mp_value_node) r->link;
    }
    mp_free_number(vabs);
    mp_free_number(rabs);
    return q;
}

/*tex

    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|.

*/

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, r->link);
            mp_free_dep_node(mp, r, 10);
        } else {
            mp_number w; /*tex a tentative coefficient */
            mp_number absw;
            mp_new_number(w);
            mp_new_number(absw);
            mp_make_fraction(w, mp_get_dep_value(r), *v);
            mp_number_abs_clone(absw, w);
            if (mp_number_lessequal(absw, mp_half_fraction_threshold_k)) {
                mp_set_link(s, r->link);
                mp_free_dep_node(mp, r, 11);
            } else {
                mp_number_negate(w);
                mp_set_dep_value(r, w);
                s = r;
            }
            mp_free_number(w);
            mp_free_number(absw);
        }
        r = (mp_value_node) s->link;
    } while (mp_get_dep_info(r) != NULL);
    if (t == mp_proto_dependent_type) {
        mp_number ret;
        mp_new_number(ret);
        mp_make_scaled(ret, mp_get_dep_value(r), *v);
        mp_number_negate(ret);
        mp_set_dep_value(r, ret);
        mp_free_number(ret);
    } else if (mp_number_to_scaled(*v) != - mp_number_to_scaled(mp_fraction_one_t)) {
        mp_number ret;
        mp_new_fraction(ret);
        mp_make_fraction(ret, mp_get_dep_value(r), *v);
        mp_number_negate(ret);
        mp_set_dep_value(r, ret);
        mp_free_number(ret);
    }
    *final_node = r;
    return (mp_value_node) mp->temp_head->link;
}

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_string(mp, "*4");
            n = n - 2;
        }
        mp_print_char(mp, '=');
        mp_print_dependency(mp, p, mp_dependent_type);
        mp_end_diagnostic(mp, 0);
 // }
}

/*tex

The |n > 0| test is repeated here because it is of vital importance to the function's functioning.

*/

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) {
        /*tex Divide list |p| by $2^n$: */
        mp_value_node r;
        mp_value_node s;
        mp_number absw;
        mp_number w; /*tex a tentative coefficient */
        mp_new_number(w);
        mp_new_number(absw);
        s = (mp_value_node) mp->temp_head;
        mp_set_link(mp->temp_head, p);
        r = p;
        do {
            if (n > 30) {
                mp_set_number_to_zero(w);
            } else {
                mp_number_clone(w, mp_get_dep_value(r));
                mp_number_divide_int(w, two_to_the(n));
            }
            mp_number_abs_clone(absw, w);
            if (mp_number_lessequal(absw, mp_half_fraction_threshold_k) && (mp_get_dep_info(r) != NULL)) {
                mp_set_link(s, r->link);
                mp_free_dep_node(mp, r, 12);
            } else {
                mp_set_dep_value(r, w);
                s = r;
            }
            r = (mp_value_node) s->link;
        } while (mp_get_dep_info(s) != NULL);
        pp = (mp_value_node) mp->temp_head->link;
        mp_free_number(absw);
        mp_free_number(w);
    }
    return pp;
}

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;
        x->type = mp_known_type;
        mp_set_value_number(x, mp_get_dep_value(p));
        mp_new_number_abs(absx, mp_get_value_number(x));
        if (mp_number_greaterequal(absx, mp_warning_limit_t)) {
            mp_val_too_big(mp, &(mp_get_value_number(x)));
        }
        mp_free_number(absx);
        mp_free_dep_node(mp, p, 13);
        if (cur_exp_node == x && cur_exp_type == mp_independent_type) {
            mp_set_cur_exp_value_number(mp, &(mp_get_value_number(x)));
            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, 1);
        if (cur_exp_node == x && cur_exp_type == mp_independent_type) {
            cur_exp_type = mp_dependent_type;
        }
    }
}

/*tex

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).

*/

static mp_node mp_new_ring_entry(MP mp, mp_node p)
{
    mp_node q = mp_new_value_node(mp); /*tex the new capsule node */
    q->name_type = mp_capsule_operation;
    q->type = p->type;
    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;
}

/*tex

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.

*/

static void mp_ring_delete(MP mp, mp_node p)
{
    mp_node q = mp_get_value_node(p);
    (void) mp;
    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));
    }
}

/*tex

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.

*/

static void mp_nonlinear_eq(MP mp, mp_value v, mp_node p, int flush_p)
{
    mp_variable_type t = p->type - mp_unknown_tag; /*tex the type of ring |p| */
    mp_node q = mp_get_value_node(p);
    if (flush_p) {
        p->type = mp_vacuous_type;
    } else {
        p = q;
    }
    do {
        mp_node r = mp_get_value_node(q);
        q->type = 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);
                mp_add_string_reference(mp, 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;
        }
        /*tex there ain't no more cases */
        q = r;
    } while (q != p);
}

/*tex

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.

*/

static void mp_ring_merge(MP mp, mp_node p, mp_node q)
{
    mp_node r = mp_get_value_node(p); /*tex 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);
}

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);
}

/*tex

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.

\starttabulate
\NC |cur_cmd| \NC denotes a command code from the long list of codes given earlier \NC \NR
\NC |cur_mod| \NC denotes a modifier or operand of the command code \NC \NR
\NC |cur_sym| \NC is the hash address of the symbolic token that was just scanned \NC \NR
\NC zero      \NC in the case of a numeric or string or capsule token \NC \NR
\stoptabulate

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.

*/

/*tex
    Maybe do as with \TEX: collect them in some data structure.
*/

const char *mp_cmd_mod_string(MP mp, int c, int m)
{
    switch (c) {
        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_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_bytemap_command:
            switch (m) {
                case mp_bytemap_set_byte_code   : return "setbyte";
                case mp_bytemap_set_offset_code : return "setbytemapoffset";
                case mp_bytemap_new_code        : return "newbytemap";
                case mp_bytemap_copy_code       : return "copybytemap";
                case mp_bytemap_set_code        : return "setbytemap";
                case mp_bytemap_clip_code       : return "clipbytemap";
                case mp_bytemap_reduce_code     : return "reducebytemap";
                case mp_bytemap_set_options_code: return "setbytemapoptions";
                case mp_bytemap_reset_code      : return "resetbytemap";
                case mp_bytemap_reset_all_code  : return "resetbytemaps";
            }
            break;
        case mp_of_command:            return "of";
        case mp_path_join_command:     return "..";
        case mp_path_connect_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";
        case mp_btex_command:
            switch (m) {
                case mp_btex_code    : return "btex";
                case mp_verbatim_code: return "verbatimtex";
            }
            break;
        case mp_etex_command:
            return "etex";
        case mp_macro_def_command:
            switch (m) {
                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"; /* well ... */
            }
            break;
        case mp_controls_command:
            switch (m) {
                case mp_both_controls_code : return "controls";
                case mp_first_control_code : return "firstcontrol";
                case mp_second_control_code: return "secondcontrol";
            }
            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;
        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;
        case mp_parameter_commmand:
            switch (m) {
             // case mp_general_macro   : return "";
                case mp_primary_macro   : return "primary";
                case mp_secondary_macro : return "secondary";
                case mp_tertiary_macro  : return "tertiary";
             // case mp_expr_macro      : return "";
             // case mp_of_macro        : return "";
             // case mp_suffix_macro    : return "";
             // case mp_text_macro      : return "";
                case mp_expr_parameter  : return "expr";
                case mp_suffix_parameter: return "suffix";
                case mp_text_parameter  : return "text";
                default                 : return "tertiary"; /* really? */
            }
            break;
        case mp_input_command:
            switch (m) {
                case mp_input_code    : return "input";
                case mp_end_input_code: return "endinput";
            }
            break;
        case mp_if_test_command:
            /*tex It shares the codes with: */
        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;
        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(m);
            return mp_operator_string(m);
        case mp_type_name_command:
            return "";
        case mp_stop_command:
            switch (m) {
                case mp_end_code : return "end";
                case mp_dump_code: return "dump";
            }
            break;
        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";
                case mp_silent_mode    : return "silentmode";
            }
            break;
        case mp_protection_command:
            switch (m) {
                case mp_inner_protection_code: return "inner";
                case mp_outer_protection_code: return "outer";
            }
            break;
        case mp_property_command:
            return "setproperty";
        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;
        case mp_left_delimiter_command:
            return "left delimiter";
        case mp_right_delimiter_command:
            return "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);
        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_dashed_code             : return "dashed";
                case mp_with_pre_script_code         : return "withprescript";
                case mp_with_post_script_code        : return "withpostscript";
                case mp_with_nested_pre_script_code  : return "withnestedprescript";
                case mp_with_nested_post_script_code : return "withnestedpostscript";
                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";
                case mp_with_curvature_code          : return "withcurvature";
                case mp_with_mesh_code               : return "withmesh";
                case mp_with_bytemap_code            : return "withbytemap";
                case mp_with_nothing_code            : return "withnothing";
            }
            break;
        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;
        case mp_message_command:
            switch (m) {
                case mp_normal_message_code: return "message";
                case mp_error_message_code : return "errmessage";
                case mp_error_help_code    : return "errhelp";
            }
            break;
    }
    return "[unknown command code!]";
}

/*tex

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.

*/

void mp_print_cmd_mod(MP mp, int c, int m)
{
    mp_print_string(mp, mp_cmd_mod_string(mp, c, m));
}

/*tex

Here is a procedure that displays a given command in braces, in the user's transcript file.

*/

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_format(mp, "%C'd macro:", mp_macro_def_command, c);
            mp_show_token_list(mp, cur_mod_node->link->link, 0);
            break;
        default:
            mp_print_cmd_mod(mp, c, m);
            break;
    }
    mp_print_char(mp, '}');
    mp_end_diagnostic(mp, 0);
}

static void mp_reallocate_input_stack(MP mp, int newsize)
{
    int n = newsize + 1;
    mp->input_files = mp_memory_reallocate(mp->input_files, (size_t) (n + 1) * sizeof(void *));
    mp->input_lines = mp_memory_reallocate(mp->input_lines, (size_t) (n + 1) * sizeof(int));
    for (int k = mp->max_in_open; k <= n; k++) {
        mp->input_files[k] = NULL;
        mp->input_lines[k] = 0;
    }
    mp->max_in_open = newsize;
}

static void mp_check_parameter_size(MP mp, int k)
{
    while (k >= mp->parameter_size) {
        mp->parameter_stack = mp_memory_reallocate(mp->parameter_stack, (size_t) ((k + k / 4) + 1) * sizeof(mp_node));
        mp->parameter_size = k + k / 4;
    }
}

/*tex

Notice that the |line| isn't valid when |token_state| is true because it depends on |mp_input_index|. If we
really need to know the line number for the topmost file in the mp_input_index stack we use the following
function. If a page number or other information is needed, this routine should be modified to compute
it as well.

*/

static int mp_true_line(MP mp)
{
    if (file_state && (mp_input_name > mp_input_last_special)) {
        return mp_input_line;
    } else {
        int 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  <= mp_input_last_special))) {
            --k;
        }
        return (k > 0 ? mp->input_lines[(k - 1) + mp_file_bottom_text] : 0);
    }
}

/*tex

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.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.

*/

void mp_show_context(MP mp)
{
    /*tex prints where the scanner is */
    mp->file_ptr = mp->input_ptr;
    mp->input_stack[mp->file_ptr] = mp->cur_input;
    /*tex store current state */
    while (1) {
        /*tex enter into the context */
        mp->cur_input = mp->input_stack[mp->file_ptr];
        /*tex
            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) {
                /*tex  print location of current line */
                if (mp_input_name > mp_input_last_special) {
                    mp_print_format(mp, "%l<line %i>", mp_true_line(mp));
                } else if (mp_input_name == mp_input_from_terminal) {
                    if (mp->file_ptr == 0) {
                        mp_print_nl(mp, "<direct>");
                    } else {
                        mp_print_nl(mp, "<insert>");
                    }
                } else if (mp_input_name == mp_input_from_tokens) {
                    mp_print_nl(mp, "<scantokens>");
                } else {
                    mp_print_nl(mp, "<read>");
                }
                mp_print_char(mp, ' ');
                if (mp_input_limit > 0) {
                    for (int i = mp_input_start; i <= mp_input_limit - 1; i++) {
                        mp_print_char(mp, mp->buffer[i]);
                    }
                }
            } else {
                /*tex Print type of token list: */
                switch (token_type) {
                    case mp_forever_text:
                        mp_print_nl(mp, "<forever> ");
                        break;
                    case mp_loop_text:
                        /*tex
                            Print the current loop value. 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.
                        */
                        {
                            mp_node pp = mp->parameter_stack[parameter_start];
                            mp_print_nl(mp, "<for(");
                            if (pp != NULL) {
                                if (pp->link == MP_VOID) {
                                    mp_print_exp(mp, pp, 0); /* we're in a |for| loop */
                                } else {
                                    mp_show_token_list(mp, pp, NULL);
                                }
                            }
                            mp_print_string(mp, ")> ");
                        }
                        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> ");
                        if (mp_input_name) {
                            mp_print_mp_string(mp, mp_input_name);
                        } else {
                            /*tex
                                Print the name of a |vardef|'d macro. 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.
                            */
                            mp_node pp = mp->parameter_stack[parameter_start];
                            if (pp == NULL) {
                                mp_show_token_list(mp, mp->parameter_stack[parameter_start + 1], NULL);
                            } else {
                                mp_node qq = pp;
                                while (qq->link != NULL) {
                                    qq = qq->link;
                                }
                                qq->link = mp->parameter_stack[parameter_start + 1];
                                mp_show_token_list(mp, pp, NULL);
                                qq->link = NULL;
                            }
                        }
                        mp_print_string(mp, " -> ");
                        break;
                    default:
                        mp_print_nl(mp, "?"); /* this should never happen */
                        break;
                }
                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);
                }
            }
        }
        if (file_state && (mp_input_name > mp_input_last_special || mp->file_ptr == 0)) {
            break;
        } else {
            --mp->file_ptr;
        }
    }
    /*tex restore original state */
    mp->cur_input = mp->input_stack[mp->input_ptr];
}

/*tex

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. 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.

*/

static void mp_push_input(MP mp)
{
    if (mp->input_ptr > mp->max_input_stack) {
        mp->max_input_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;
}

/*tex

    And of course what goes up must come down.

*/

void mp_pop_input(MP mp)
{
    --mp->input_ptr;
    mp->cur_input = mp->input_stack[mp->input_ptr];
}

/*tex

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.

*/

static void mp_begin_token_list(MP mp, mp_node p, int t)
{
    mp_push_input(mp);
    nstart = p;
    token_type = t;
    parameter_start = mp->parameter_ptr;
    nloc = p;
}

/*tex

When a token list has been fully scanned, the following computations should be done as we leave that
level of input.

*/

static void mp_end_token_list(MP mp)
{
    /*tex 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 {
            /*tex Update the reference count. */
            mp_delete_mac_ref(mp, nstart);
        }
    }
    while (mp->parameter_ptr > parameter_start) {
        /*tex Parameters must be flushed. */
        mp_node p;
        --mp->parameter_ptr;
        p = mp->parameter_stack[mp->parameter_ptr];
        if (p != NULL) {
            if (p->link == MP_VOID) {
                /*tex It's an |expr| parameter. */
                mp_recycle_value(mp, p);
                mp_free_value_node(mp, p);
            } else {
                /*tex It's a |suffix| or |text| parameter. */
                mp_flush_token_list(mp, p);
            }
        }
    }
  DONE:
    mp_pop_input(mp);
}

/*tex

The contents of |cur_cmd, cur_mod, cur_sym| are placed into an equivalent token by the |cur_tok|
routine.

*/

static void mp_encapsulate(MP mp, mp_value_node p, int where)
{
    mp_node q = mp_new_value_node(mp);
    q->name_type = mp_capsule_operation;
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal)) && where) {
        mp_print_format(mp, "%l[dependency: encapsulate, node %P, location %i]", q, where);
    }
    mp_new_dep(mp, q, cur_exp_type, p, 2); /* so no serial */
    mp_set_cur_exp_node(mp, q);
}

static void mp_install(MP mp, mp_node r, mp_node q)
{
    switch (q->type) {
        case mp_known_type:
            {
                r->type = mp_known_type;
                mp_set_value_number(r, mp_get_value_number(q));
                break;
            }
        case mp_independent_type:
            {
                mp_value_node p = mp_single_dependency(mp, q);
                if (p == mp->dep_final) {
                    r->type = mp_known_type;
                    mp_set_value_number(r, mp_zero_t);
                    mp_free_dep_node(mp, p, 14);
                } else {
                    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal))) {
                        mp_print_format(mp, "%l[dependency: install, node %P]", q);
                    }
                    mp_new_dep(mp, r, mp_dependent_type, p, 3);
                }
                break;
            }
        default:
            {
                mp_new_dep(mp, r, q->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) q)), 4);
                break;
            }
    }
}

static void mp_make_exp_copy(MP mp, mp_node p, int where)
{
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal)) && where) {
        mp_print_format(mp, "%l[expression: copy, node %P, location %i]", p, where);
    }
  RESTART:
    cur_exp_type = p->type;
    switch (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:
            {
                /*tex
                    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|.
                */
                if (mp_get_value_node(p) == NULL) {
                    switch (p->type) {
                        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;
                    }
                }
                {
                    mp_value_node q = (mp_value_node) mp_get_value_node(p);
                    mp_node t = mp_new_value_node(mp);
                    t->name_type = mp_capsule_operation;
                    switch (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)), 1);
            break;
        case mp_numeric_type:
            mp_new_indep(mp, p, 16);
            goto RESTART;
        case mp_independent_type:
            {
                mp_value_node q = mp_single_dependency(mp, p);
                if (q == mp->dep_final) {
                    cur_exp_type = mp_known_type;
                    mp_set_cur_exp_value_number(mp, &mp_zero_t);
                    mp_free_dep_node(mp, q, 15);
                } else {
                    cur_exp_type = mp_dependent_type;
                    mp_encapsulate(mp, q, 2);
                }
            }
            break;
        case mp_undefined_type:
            mp_confusion(mp, "undefined copy");
            break;
        default:
            mp_confusion(mp, "copy");
            break;
    }
}

static mp_node mp_cur_tok(MP mp)
{
    mp_node p;
    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;          /*tex possible |cur_exp| numerical to be restored */
            mp_value save_exp = mp->cur_exp; /*tex |cur_exp| to be restored */
            mp_new_number(save_exp_num);
            mp_number_clone(save_exp_num, cur_exp_value_number);
            mp_make_exp_copy(mp, cur_mod_node, 1);
            p = mp_stash_cur_exp(mp);
            p->link = NULL;
            mp->cur_exp = save_exp;
            mp_number_clone(cur_exp_value_number, save_exp_num);
            mp_free_number(save_exp_num);
        } else {
            p = mp_new_token_node(mp);
            p->name_type = mp_token_operation;
            if (cur_cmd == mp_numeric_command) {
                mp_set_value_number(p, cur_mod_number);
                p->type = mp_known_type;
            } else {
                mp_set_value_str(p, cur_mod_str);
                p->type = mp_string_type;
            }
        }
    } else {
        p = mp_new_symbolic_node(mp);
        mp_set_sym_sym(p, cur_sym);
        p->name_type = cur_sym_mod;
    }
    return p;
}

/*tex

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. So, this
one undoes one token of input.

*/

void mp_back_input(MP mp)
{
    /*tex a token list of length one */
    mp_node p = mp_cur_tok(mp);
    /*tex conserve stack space */
    while (token_state && (nloc == NULL)) {
        mp_end_token_list(mp);
    }
    mp_begin_token_list(mp, p, mp_backed_up_text);
}

/*tex

The |back_error| routine is used when we want to restore or replace an offending token just before
issuing an error message.

*/

static void mp_back_error(MP mp, const char *msg, const char *hlp)
{
    mp_back_input(mp);
    mp_error(mp, msg, hlp);
}

/*tex back up one inserted token and call |error|. */

static void mp_ins_error(MP mp, const char *msg, const char *hlp)
{
    mp_back_input(mp);
    token_type = mp_inserted_text;
    mp_error(mp, msg, hlp);
}

/*tex

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 |mp_input_limit| or |line|.

*/

static 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);
    mp_input_index = (int) mp->in_open;
    if (mp->in_open_max < mp->in_open) {
        mp->in_open_max = mp->in_open;
    }
    mp_input_start = (int) mp->first;
    mp_input_name = mp_input_from_terminal; /*tex |terminal_input| is now |true| */
}

/*tex

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.

*/

static void mp_end_file_reading(MP mp)
{
    if (mp->in_open > mp_input_index) {
        if (mp_input_name <= mp_input_last_special) {
            mp_confusion(mp, "endinput");
        } else {
            (mp->close_file) (mp, mp->input_files[mp->in_open]);
            --mp->in_open;
        }
    }
    mp->first = (size_t) mp_input_start;
    if (mp_input_index != mp->in_open) {
        mp_confusion(mp, "endinput");
    } else {
        if (mp_input_name > mp_input_last_special) {
            (mp->close_file) (mp, mp_input_file);
        }
        mp_pop_input(mp);
        --mp->in_open;
    }
}

/*tex

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.

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.

*/

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) {
        /*tex
            Check if the file has ended while flushing \tex\ material and set the result value for
            |check_outer_validity|.
        */
        if (cur_sym != NULL) {
            return 1;
        } else {
            char msg[256];
            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;
        }
    } else {
        /*tex  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);
        //     p->name_type = cur_sym_mod;
        //     /* prepare to read the symbolic token again */
        //     mp_begin_token_list(mp, p, mp_backed_up_text);
        // }
        if (mp->scanner_status > mp_skipping_state) {
            /*tex
                Tell the user what has run away and try to recover.
            */
            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);
            /*tex
                Print the definition-so-far.
            */
            if (cur_sym == NULL) {
                mst = "File ended while scanning";
            } else {
                mst = "Forbidden token found while scanning";
            }
            switch (mp->scanner_status) {
                case mp_flushing_state:
                    {
                        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:
                    {
                        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);
                            /*tex
                                The next line makes sure that the inserted delimiter will match the
                                delimiter that already was read.
                            */
                            mp_set_eq_symbol(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;
                        snprintf(msg, 256, "%s the definition of %s", mst, s->str);
                        mp_delete_string_reference(mp, s);
                        set_cur_sym(mp->frozen_end_def);
                    }
                    break;
                case mp_op_defining_state:
                    {
                        char *s = mp_str(mp, eq_text(mp->warning_info));
                        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, eq_text(mp->warning_info));
                        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);
        } else {
            char msg[256];
            const char *hlp = NULL;
            snprintf(msg, 256, "Incomplete if; all text was ignored after line %d", (int) mp->warning_line);
            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;
    }
}

/*tex

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.

*/

static 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_string(mp, "text?");
                break;
            case mp_var_defining_state:
            case mp_op_defining_state:
                mp_print_string(mp, "definition?");
                break;
            case mp_loop_defining_state:
                mp_print_string(mp, "loop?");
                break;
        }
        mp_print_ln(mp);
        mp_show_token_list(mp, mp->hold_head->link, NULL);
    }
}

/*tex

We need to mention a procedure that may be called by |get_next|.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. The getter sets |cur_cmd|,
|cur_mod|, |cur_sym| to next token.

*/

static void mp_get_next(MP mp)
{
    mp_symbol cur_sym_;
  RESTART:
    set_cur_sym(NULL);
    set_cur_sym_mod(0);
    if (file_state) {
        int k;           /*tex an index into |buffer| */
        unsigned char c; /*tex the current character in the buffer */
        int cclass;      /*tex its class number */
        /*tex
            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[mp_input_limit]|; this makes it unnecessary to
            have a special test for end-of-line.
        */
      SWITCH:
        c = mp->buffer[mp_input_location];
        ++mp_input_location;
        cclass = mp->char_class[c];
        switch (cclass) {
            case mp_digit_class:
                mp_scan_numeric_token((c - '0'));
                return;
            case mp_period_class:
                cclass = mp->char_class[mp->buffer[mp_input_location]];
                if (cclass > mp_period_class) {
                    goto SWITCH;
                } else if (cclass < mp_period_class) {
                    /* |class=digit_class| */
                    mp_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 && mp_input_location < mp_input_limit) {
                    /*tex |btex .. etex|: */
                    goto SWITCH;
                }
                /*tex
                    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 {
                    /* We also accept ASCII BTX ... ETX here. */
                    unsigned char cend = c == '"' ? '"' : c == '\'' ? '\'' : 3 ;
                    if (c == '\'') {
                        if (mp_number_to_boolean(internal_value(mp_single_quote_mode_internal)) == mp_true_operation) {
                            /* okay */
                        } else {
                            break;
                        }
                    }
                    if (mp->buffer[mp_input_location] == cend) {
                        set_cur_mod_str(mp_rts(mp,""));
                    } else {
                        k = mp_input_location;
                        mp->buffer[mp_input_limit + 1] = cend;
                        do {
                            ++mp_input_location;
                        } while (mp->buffer[mp_input_location] != cend);
                        if (mp_input_location > mp_input_limit) {
                            /*tex
                                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.
                            */
                            mp_input_location = mp_input_limit;
                            /*tex
                                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, mp_input_location - k);
                        do {
                            mp_append_char(mp, mp->buffer[k]);
                            ++k;
                        } while (k != mp_input_location);
                        set_cur_mod_str(mp_make_string(mp));
                    }
                    ++mp_input_location;
                    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 = mp_input_location - 1;
                goto FOUND;
            case mp_invalid_class:
                if (mp->scanner_status == mp_tex_flushing_state) {
                    goto SWITCH;
                } else {
                    /*tex
                        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:
                /*tex letters, etc. */
                break;
        }
        k = mp_input_location - 1;
        while (mp->char_class[mp->buffer[mp_input_location]] == cclass) {
            ++mp_input_location;
        }
      FOUND:
        set_cur_sym(mp_id_lookup(mp, (char *) (mp->buffer + k), (size_t) (mp_input_location - k), 1));
    } else {
        /*tex
            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 && nloc->type == mp_symbol_node_type) {
             /*tex symbolic token */
            int cur_sym_mod_ = nloc->name_type;
            int cur_info = mp_get_sym_info(nloc);
            set_cur_sym(mp_get_sym_sym(nloc));
            set_cur_sym_mod(cur_sym_mod_);
            /*tex move to next */
            nloc = nloc->link;
            if (cur_sym_mod_ == mp_expr_operation) {
                set_cur_cmd(mp_capsule_command);
                set_cur_mod_node(mp->parameter_stack[parameter_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->parameter_stack[parameter_start + cur_info], (int) mp_parameter_text);
                goto RESTART;
            }
        } else if (nloc != NULL) {
            /*tex
                Get a stored numeric or string or capsule token and |return|.
            */
            if (nloc->name_type == mp_token_operation) {
                if (nloc->type == 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);
                    mp_add_string_reference(mp, cur_mod_str);
                }
            } else {
                set_cur_mod_node(nloc);
                set_cur_cmd(mp_capsule_command);
            }
            nloc = nloc->link;
            return;
        } else {
            /*tex
                We are done with this token list and resume the previous level.
            */
            mp_end_token_list(mp);
            goto RESTART;
        }
    }
    /*tex
        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(eq_valent(cur_sym_));
    set_cur_mod_node(eq_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;
 //     }
 // }
}

/*tex

The global variable |force_eof| is normally |false|; it is set |true| by an |endinput| command.

*/

static int mp_move_to_next_line(MP mp)
{
    if (mp_input_name > mp_input_last_special) {
        /*tex
            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|.
        */
        ++mp_input_line;
        mp->first = (size_t) mp_input_start;
        if (! mp->force_eof) {
            if (mp_input_ln(mp, mp_input_file)) { /* not end of file */
                mp_firm_up_the_line(mp);  /* this sets |mp_input_limit| */
            } else {
                mp->force_eof = 1;
            }
        }
        if (mp->force_eof) {
            mp->force_eof = 0;
            --mp_input_location;
            if (mp->interaction < mp_silent_mode) {
                --mp->open_parens;
                /*tex Show the user that file has been read. */
                mp_print_char(mp, ')');
                mp_print_flush_line(mp);
            }
            /*tex Resume the previous level. */
            mp_end_file_reading(mp);
            mp_check_outer_validity(mp);
            return 1;
        } else {
            mp->buffer[mp_input_limit] = '%';
            mp->first = (size_t) (mp_input_limit + 1);
            /*tex Get ready to read. */
            mp_input_location = mp_input_start;
        }
    } else if (mp->input_ptr > 0) {
        /*tex
            Text was inserted during error recovery or by |scantokens|.
        */
        mp_end_file_reading(mp);
        /*tex Resume the previous level. */
        return 1;
    } else if (mp->interaction > mp_nonstop_mode) {
        if (mp_input_limit == mp_input_start && mp->interaction < mp_silent_mode) {
            /*tex
                The previous line was empty.
            */
            mp_print_nl(mp, "(Please type a command or say `end')");
        }
        mp_print_ln(mp);
        mp->first = (size_t) mp_input_start;
        /*tex
            Get a line from the terminal, prompt delegated.
        */
        if (! mp_input_ln(mp, mp->term_in)) {
            longjmp(*(mp->jump_buffer), 1);
        }
        mp->buffer[mp->last] = '%';
        mp_input_limit = (int) mp->last;
        mp->buffer[mp_input_limit] = '%';
        mp->first = (size_t) (mp_input_limit + 1);
        mp_input_location = mp_input_start;
    } else {
        mp_fatal_error(mp, "job aborted, no legal end found");
    }
    return 0;
}

/*tex

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.

*/

static void mp_firm_up_the_line(MP mp)
{
    mp_input_limit = (int) mp->last;
}

/*tex

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.

*/

static void mp_get_t_next(MP mp)
{
    mp_get_next(mp);
    /*tex
        What if we just assume that btex ... etex is defined usingf lua and scanners .. then this
        one gets inlined!
    */
    if (cur_cmd == mp_btex_command) {
        /*tex Pass |btex ... etex| to script */
        char *txt = NULL;
        char *ptr = NULL;
        int slin = mp_input_line;
        int size = 0;
        int done = 0;
        int mode = (int) mp_round_unscaled(internal_value(mp_texscriptmode_internal)) ; /* default: 1 */
        int verb = cur_mod == mp_verbatim_code;
        int first;
        /*tex We had a (mandate) trailing space. */
        if (mp_input_location <= mp_input_limit && mp->char_class[mp->buffer[mp_input_location]] == mp_space_class) {
            ++mp_input_location;
        } else {
            /*tex Maybe issue an error message and quit. */
        }
        /* We loop over lines. */
        first = mp_input_location;
        while (1) {
            /*tex
                We don't need to check when we have less than 4 characters left.
            */
            if (mp_input_location < mp_input_limit - 4) {
                if (mp->buffer[mp_input_location] == 'e') {
                    ++mp_input_location;
                    if (mp->buffer[mp_input_location] == 't') {
                        ++mp_input_location;
                        if (mp->buffer[mp_input_location] == 'e') {
                            ++mp_input_location;
                            if (mp->buffer[mp_input_location] == 'x') {
                                /*tex Let's see if we have the right boundary. */
                                if (first == (mp_input_location - 3)) {
                                    /*tex When we're at the start of a line no leading space is required. */
                                    done = 1;
                                } else if (mp->char_class[mp->buffer[mp_input_location - 4]] == mp_space_class) {
                                    /* When we're beyond the start of a line a leading space is required. */
                                    done = 2;
                                }
                                if (done) {
                                    if ((mp_input_location + 1) <= mp_input_limit) {
                                        int c = mp->char_class[mp->buffer[mp_input_location + 1]] ;
                                        if (c != mp_letter_class) {
                                            ++mp_input_location;
                                            /*tex We're past the |x|. */
                                            break;
                                        } else {
                                            /*tex this is no valid |etex|. */
                                            done = 0;
                                        }
                                    } else {
                                        /*tex When we're at the end of a line we're ok. */
                                        ++mp_input_location;
                                        /*tex We're past the |x|. */
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            /*tex No |etex| seen (yet). */
            if (mp_input_location >= mp_input_limit) {
                if (size) {
                    txt = mp_memory_reallocate(txt, (size_t) (size + mp_input_limit - first + 1));
                } else {
                    txt = mp_memory_allocate((size_t) (mp_input_limit - first + 1));
                }
                memcpy(txt + size, mp->buffer + first, mp_input_limit - first);
                size += mp_input_limit - first + 1;
                if (mode <= 0) {
                    txt[size - 1] = ' ';
                } else if (verb) {
                    /*tex Modes $\geq 1$ permit a newline in |verbatimtex|. */
                    txt[size - 1] = '\n';
                } else if (mode >= 2) {
                    /*tex Modes $\geq 2$ permit a newline in |btex|. */
                    txt[size - 1] = '\n';
                } else {
                    txt[size - 1] = ' ';
                }
                if (mp_move_to_next_line(mp)) {
                    /*tex We abort the scanning. */
                    goto FATAL_ERROR;
                }
                first = mp_input_location;
            } else {
                ++mp_input_location;
            }
        }
        if (done) {
            /* We're past the |x|. */
            int l = mp_input_location - 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) {
                /*tex Don't strip |verbatimtex|. */
                txt[size] = '\0';
                ptr = txt;
            } else if (mode >= 4) {
                /*tex Don't strip |btex|. */
                txt[size] = '\0';
                ptr = txt;
            } else {
                /*tex 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;
                }
                /*tex Prune the string. */
                txt[size] = '\0';
                /*tex Strip leading whitespace. */
                ptr = txt;
                while ((size > 1) && (mp->char_class[(unsigned char) ptr[0]] == mp_space_class || ptr[0] == '\n')) {
                    ++ptr;
                    --size;
                }
            }
            /*tex Action. */
            mp_check_script_result(mp, mp->make_text(mp, ptr, size, verb));
            mp_memory_free(txt);
            /*tex Really needed. */
            mp_get_next(mp);
            return;
        }
        /*tex
            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:
        {
            /*tex Line numbers are not always meaningfull so we can get a 0 reported. */
            char msg[256];
            if (slin > 0) {
                snprintf(msg, 256, "No matching 'etex' for '%stex'.", verb ? "verbatim" : "b");
            } else {
                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);
        }
    }
}

/*tex

\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|.

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)|.

*/

static mp_subst_node mp_new_subst_node(MP mp)
{
    mp_subst_node p = mp->memory_pool[mp_subst_pool].list;
    mp->memory_pool[mp_subst_pool].used++;
    if (mp->memory_pool[mp_subst_pool].used > mp->memory_pool[mp_subst_pool].max) {
        mp->memory_pool[mp_subst_pool].max = mp->memory_pool[mp_subst_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_subst_pool].list = p->link;
        mp->memory_pool[mp_subst_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_subst_data));
    }
    p->link = NULL;
    return p;
}

static void mp_free_subst_node(MP mp, mp_subst_node p)
{
    mp->memory_pool[mp_subst_pool].used--;
    if (mp->memory_pool[mp_subst_pool].pool < mp->memory_pool[mp_subst_pool].kept) {
        mp->memory_pool[mp_subst_pool].pool++;
        p->link = mp->memory_pool[mp_subst_pool].list;
        mp->memory_pool[mp_subst_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_subst_pool(MP mp)
{
    mp_subst_node p = mp->memory_pool[mp_subst_pool].list;
    while (p) {
        mp_subst_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_node mp_scan_toks(MP mp, mp_command_code terminator, mp_subst_node subst_list, mp_node tail_end, int suffix_count)
{
    int cur_data_mod = 0;
    mp_node p = mp->hold_head; /*tex tail of the token list being built */
    int balance = 1;           /*tex left delimiters minus right delimiters */
    mp->hold_head->link = NULL;
    while (1) {
        int cur_data;
        mp_get_t_next(mp);
        cur_data = -1;
        if (cur_sym != NULL) {
            /*tex
                Substitute for |cur_sym|, if it's on the |subst_list| .
            */
            mp_subst_node 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;
                } else {
                    q = q->link;
                }
            }
            if (cur_cmd == terminator) {
                /*tex
                    Adjust the balance; |break| if it's zero.
                */
                if (cur_mod > 0) {
                    ++balance;
                } else {
                    --balance;
                    if (balance == 0) {
                        break;
                    }
                }
            } else if (cur_cmd == mp_macro_special_command) {
                /*tex
                    Handle quoted symbols, |\#\AT!|, |\AT!|, or |\AT!\#|.
                */
                if (cur_mod == mp_macro_quote_code) {
                    mp_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);
            pp->name_type = cur_data_mod;
            p->link = pp;
        } else {
            p->link = mp_cur_tok(mp);
        }
        p = p->link;
    }
    p->link = tail_end;
    while (subst_list) {
        mp_subst_node q = subst_list->link;
        mp_free_subst_node(mp, subst_list);
        subst_list = q;
    }
    return mp->hold_head->link;
}

/*tex

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|.

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. This one sets |cur_sym| to a
safe symbol.

*/

static void mp_get_symbol(MP mp)
{
  RESTART:
    mp_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) {
                mp_delete_string_reference(mp, cur_mod_str);
            }
        }
        set_cur_sym(mp->frozen_inaccessible);
        mp_ins_error(mp, "Missing symbolic token inserted", hlp);
        goto RESTART;
    }
}

/*tex

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.

*/

static void mp_get_clear_symbol(MP mp)
{
    mp_get_symbol(mp);
    mp_clear_symbol(mp, cur_sym, 0);
}

/*tex

Here's another little subroutine; it checks that an equals sign or assignment sign comes along at the
proper place in a macro definition.

*/

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."
        );
    }
}

/*tex

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|.

*/

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_node qm = mp_new_subst_node(mp);
    mp_subst_node qn = mp_new_subst_node(mp);
    mp_get_symbol(mp);
    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->link = qm;
    qn->info = cur_sym;
    qn->info_mod = cur_sym_mod;
    qn->value_data = 1;
    qn->value_mod = mp_expr_operation;
    mp_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);
    q->link = r;
    mp_set_sym_info(r, mp_general_macro);
    r->name_type = mp_macro_operation;
    r->link = mp_scan_toks(mp, mp_macro_def_command, qn, NULL, 0);
    mp->scanner_status = mp_normal_state;
    mp_set_eq_type(mp->warning_info, m);
    mp_set_eq_node(mp->warning_info, q);
    mp_get_x_next(mp);
}

/*tex

Parameters to macros are introduced by the keywords |expr|, |suffix|, |text|, |primary|, |secondary|,
and |tertiary|.

Let's turn next to the more complex processing associated with |def| and |vardef|. When the following
procedure is called, |cur_mod| should be either |start_def| or |var_def|.

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.

*/

static void mp_scan_def(MP mp, int code)
{
    int n;                         /*tex the number of special suffix parameters */
    int k;                         /*tex the total number of parameters */
    mp_subst_node r = NULL;        /*tex parameter-substitution list */
    mp_subst_node rp = NULL;       /*tex parameter-substitution list */
    mp_node q;                     /*tex tail of the macro token list */
    mp_node p;                     /*tex temporary storage */
    int sym_type;                  /*tex |expr_sym|, |suffix_sym|, or |text_sym| */
    mp_symbol l_delim, r_delim;    /*tex matching delimiters */
    int c = mp_general_macro;      /*tex the kind of macro we're defining */
    mp->hold_head->link = NULL;
    q = mp_new_symbolic_node(mp);
    mp_set_ref_count(q, 0);
    r = NULL;
    /*tex
        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;
        mp_get_t_next(mp);
        mp->scanner_status = mp_op_defining_state;
        n = 0;
        mp_set_eq_type(mp->warning_info, mp_defined_macro_command);
        mp_set_eq_node(mp->warning_info, q);
    } else {
        /*tex It's a |var_def|. */
        p = mp_scan_declared_variable(mp);
        mp_flush_variable(mp, eq_node(mp_get_sym_sym(p)), p->link, 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) {
            /* We're seeing |\AT!\#|. */
            n = 3;
            mp_get_t_next(mp);
        }
        mp->warning_info_node->type = mp_unsuffixed_macro_type - 2 + n;
        /*tex So a |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) {
        /*tex
            Absorb delimited parameters, putting them into lists |q| and |r|.
        */
        do {
            l_delim = cur_sym;
            r_delim = eq_symbol(cur_sym);
            mp_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:
            /*tex
                Absorb parameter tokens for type |sym_type|.
            */
            do {
                q->link = mp_new_symbolic_node(mp);
                q = q->link;
                q->name_type = sym_type;
                mp_set_sym_info(q, k);
                mp_get_symbol(mp);
                rp = mp_new_subst_node(mp);;
                rp->link = NULL;
                rp->value_data = k;
                rp->value_mod = sym_type;
                rp->info = cur_sym;
                rp->info_mod = cur_sym_mod;
                mp_check_parameter_size(mp, k);
                ++k;
                rp->link = r;
                r = rp;
                mp_get_t_next(mp);
            } while (cur_cmd == mp_comma_command);

            mp_check_delimiter(mp, l_delim, r_delim);
            mp_get_t_next(mp);
        } while (cur_cmd == mp_left_delimiter_command);
    }
    if (cur_cmd == mp_parameter_commmand) {
        /*tex
            Absorb undelimited parameters, putting them into list |r|.
        */
        rp = mp_new_subst_node(mp);
        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_parameter_size(mp, k);
        ++k;
        mp_get_symbol(mp);
        rp->info = cur_sym;
        rp->info_mod = cur_sym_mod;
        rp->link = r;
        r = rp;
        mp_get_t_next(mp);
        if (c == mp_expr_macro && cur_cmd == mp_of_command) {
            c = mp_of_macro;
            rp = mp_new_subst_node(mp);
            rp->link = NULL;
            mp_check_parameter_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;
            mp_get_t_next(mp);
        }
    }
    mp_check_equals(mp);
    p = mp_new_symbolic_node(mp);
    mp_set_sym_info(p, c);
    p->name_type = mp_macro_operation;
    q->link = p;
    /*tex
        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) {
        p->link = 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);
        p->link = qq;
        p = mp_new_symbolic_node(mp);
        mp_set_sym_sym(p, mp->eg_loc);
        qq->link = 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);
}

/*tex

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.

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|.

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.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.Even better would be if the system allows discovery of the
amount of space available on the call stack.

In any case, when the limit is crossed, that is a fatal error.

*/

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);
    }
}

/*tex

An auxiliary subroutine called |expand| is used by |get_x_next| when it has to do exotic expansion
commands.

*/

static void mp_expand(MP mp)
{
    mp_check_expansion_depth(mp);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_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:
            /*tex This procedure is discussed below. */
            mp_conditional(mp);
            break;
        case mp_fi_or_else_command:
            /*tex
                Terminate the current conditional and skip to |fi|.
            */
            if (cur_mod > mp->if_limit) {
                if (mp->if_limit == mp_if_code) {
                    /*tex The condition is 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");
                } 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);
                            break;
                        case mp_else_code:
                            mp_error(mp, "Extra 'else'", hlp);
                            break;
                        default:
                            mp_error(mp, "Extra 'elseif'", hlp);
                            break;
                    }
                }
            } else {
                while (cur_mod != mp_fi_code) {
                    /* skip to |fi| */
                    mp_pass_text(mp);
                }
                mp_pop_condition_stack(mp);
            }
            break;
        case mp_input_command:
            /*tex
                Initiate or terminate input from a file.
            */
            if (cur_mod > 0) {
                mp->force_eof = 1;
            } else {
                mp_start_input(mp);
            }
            break;
        case mp_iteration_command:
            if (cur_mod == mp_end_for_code) {
                /*tex
                    Scold the user for having an extra |endfor|.
                */
                mp_error(
                    mp,
                    "Extra 'endfor'",
                    "I'm not currently working on a for loop, so I had better not try to end anything."
                );
            } else {
                mp_begin_iteration(mp);
            }
            break;
        case mp_repeat_loop_command:
            /*tex
                Repeat a loop. 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.
            */
            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."
                );
            } else {
                mp_resume_iteration(mp);
            }
            break;
        case mp_exit_test_command:
            /*tex
                Exit a loop if the proper time has come.
            */
            mp_get_x_next(mp);
            mp_scan_expression(mp);
            if (cur_exp_type != mp_boolean_type) {
                mp_do_boolean_error(mp);
            }
            if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_unity_t)) {
                mp_show_cmd_mod(mp, mp_nullary_command, (int) cur_exp_value_boolean);
            }
            if (cur_exp_value_boolean == mp_true_operation) {
                if (mp->loop_ptr != NULL) {
                    /*tex
                        Exit prematurely from an iteration. Here we use the fact that |forever_text|
                        is the only |token_type| that is less than |loop_text|.
                    */
                    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)");
                    }
                    mp_stop_iteration(mp);
                } 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?"
                    );
                }
            } 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."
                );
            }
            break;
        case mp_relax_command:
            break;
        case mp_expand_after_command:
            /*tex
                Expand the token after the next token.
            */
            {
                mp_node p;
                mp_get_t_next(mp);
                p = mp_cur_tok(mp);
                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);
            }
            break;
        case mp_scan_tokens_command:
            /*tex
                Put a string into the input buffer.
            */
            mp_get_x_next(mp);
            mp_scan_primary(mp);
            if (cur_exp_type != mp_string_type) {
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_new_number(new_expr.data.n);
                mp_display_error(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."
                );
                mp_get_x_next(mp);
                mp_flush_cur_exp(mp, new_expr);
            } else {
                mp_back_input(mp);
                if (cur_exp_str->len > 0) {
                    /*tex Pretend we're reading a new one-line file. */
                    size_t k; /*tex something that we hope is |<=buf_size| */
                    size_t j; /*tex index into |str_pool| */
                    mp_value new_expr;
                    memset(&new_expr, 0, sizeof(mp_value));
                    mp_new_number(new_expr.data.n);
                    mp_begin_file_reading(mp);
                    mp_input_name = mp_input_from_tokens;
                    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;
                    mp_input_limit = (int) k;
                    while (mp->first < (size_t) mp_input_limit) {
                        mp->buffer[mp->first] = *(cur_exp_str->str + j);
                        j++;
                        ++mp->first;
                    }
                    mp->buffer[mp_input_limit] = '%';
                    mp->first = (size_t) (mp_input_limit + 1);
                    mp_input_location = mp_input_start;
                    mp_flush_cur_exp(mp, new_expr);
                }
            }
            break;
        case mp_runscript_command:
            /*tex
                Put a script result string into the input buffer.
            */
            mp_get_x_next(mp);
            mp_scan_primary(mp);
            switch (cur_exp_type) {
                case mp_string_type:
                    {
                        mp_back_input(mp);
                        if (cur_exp_str->len > 0) {
                            mp_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) mp_number_to_scaled(cur_exp_value_number) / 65536;
                        if (n > 0) {
                            mp_check_script_result(mp, mp->run_script(mp, NULL, 0, n));
                        }
                    }
                    break;
                default:
                    {
                        mp_value new_expr;
                        memset(&new_expr, 0, sizeof(mp_value));
                        mp_new_number(new_expr.data.n);
                        mp_display_error(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."
                        );
                        mp_get_x_next(mp);
                        mp_flush_cur_exp(mp, new_expr);
                    }
                    break;
            }
            break;
        case mp_maketext_command:
            /*tex
                Put a maketext result string into the input buffer.
            */
            mp_get_x_next(mp);
            mp_scan_primary(mp);
            if (cur_exp_type == mp_string_type) {
                mp_back_input(mp);
                if (cur_exp_str->len > 0) {
                    mp_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));
                mp_new_number(new_expr.data.n);
                mp_display_error(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."
                );
                mp_get_x_next(mp);
                mp_flush_cur_exp(mp, new_expr);
            }
            break;
        case mp_defined_macro_command:
            mp_macro_call(mp, cur_mod_node, NULL, cur_sym);
            break;
        default:
            break;
    }
    mp->expand_depth_count--;
}

/*tex

The processing of |input| involves the |start_input| subroutine, which will be declared later; the
processing of |endinput| is trivial.

*/

static void mp_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));
            mp_new_number(new_expr.data.n);
            mp_begin_file_reading(mp);
            mp_input_name = mp_input_from_tokens;
            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;
            }
            mp_input_limit = (int) k;
            memcpy((mp->buffer + mp->first), s, size);
            mp->buffer[mp_input_limit] = '%';
            mp->first = (size_t) (mp_input_limit + 1);
            mp_input_location = mp_input_start;
            mp_flush_cur_exp(mp, new_expr);
        }
        lmt_memory_free(s);
    }
}

/*tex

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.

*/

static void mp_get_x_next(MP mp)
{
    mp_get_t_next(mp);
    if (cur_cmd < mp_min_command) {
        /*tex 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);
            }
            mp_get_t_next(mp);
        } while (cur_cmd < mp_min_command);
        /*tex Restore |cur_type| and |cur_exp|. */
        mp_unstash_cur_exp(mp, save_exp);
    }
}

/*tex

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.

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.This invokes a user-defined control sequence.

*/

static void mp_macro_call(MP mp, mp_node def_ref, mp_node arg_list, mp_symbol macro_name)
{
    int n;                     /*tex the number of arguments */
    mp_node tail = 0;          /*tex tail of the argument list */
    mp_symbol l_delim = NULL;  /*tex a delimiter pair */
    mp_symbol r_delim = NULL;  /*tex a delimiter pair */
    mp_node r = def_ref->link; /*tex current node in the macro's token list */
    mp_add_mac_ref(def_ref);
    if (arg_list == NULL) {
        n = 0;
    } else {
        /*tex
            Determine the number |n| of arguments already supplied, and set |tail| to the tail of
            |arg_list|.
        */
        n = 1;
        tail = arg_list;
        while (tail->link != NULL) {
            ++n;
            tail = tail->link;
        }
    }
    if (mp_number_positive(internal_value(mp_tracing_macros_internal))) {
        /*tex
            Show the text of the macro being expanded, and the existing arguments.
        */
        mp_begin_diagnostic(mp);
        mp_print_ln(mp);
        mp_print_macro_name(mp, arg_list, macro_name);
        if (n == 3) {
            mp_print_string(mp, "@#"); /*tex 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_argument(mp, q, n, 0, 0);
                ++n;
                p = p->link;
            } while (p != NULL);
        }
        mp_end_diagnostic(mp, 0);
    }
    /*tex
        can the remaining arguments, if any; set |r| to the first token of the replacement text.
    */
    set_cur_cmd(mp_comma_command + 1); /*tex anything |<>comma| will do */
    while (r->name_type == mp_expr_operation || r->name_type == mp_suffix_operation || r->name_type == mp_text_operation) {
        /*tex
            Scan the delimited argument represented by |mp_get_sym_info(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.)
        */
        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;
                snprintf(msg, 256, "Missing argument to %s", mp_str(mp, sname));
                mp_delete_string_reference(mp, sname);
                if (r->name_type == mp_suffix_operation || r->name_type == mp_text_operation) {
                    mp_set_cur_exp_value_number(mp, &mp_zero_t); /* todo: this was |null| */
                    cur_exp_type = mp_token_list_type;
                } else {
                    mp_set_cur_exp_value_number(mp, &mp_zero_t);
                    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 = eq_symbol(cur_sym);
        }
        /*tex
            Scan the argument represented by |mp_get_sym_info(r)|.
        */
        if (r->name_type == mp_text_operation) {
            mp_scan_text_arg(mp, l_delim, r_delim);
        } else {
            mp_get_x_next(mp);
            if (r->name_type == mp_suffix_operation) {
                mp_scan_suffix(mp);
            } else {
                mp_scan_expression(mp);
            }
        }
        if ((cur_cmd != mp_comma_command) && ((cur_cmd != mp_right_delimiter_command) || (eq_symbol(cur_sym) != l_delim))) {
            switch (r->link->name_type) {
                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."
                        );
                        set_cur_cmd(mp_comma_command);
                    }
                    break;
                default:
                    {
                        char msg[256];
                        snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, eq_text(r_delim)));
                        mp_back_error(mp, msg, "I've gotten to the end of the macro parameter list.");
                    }
                    break;
            }
        }
      FOUND:
        /*tex
            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|.
        */
        {
            mp_node p = mp_new_symbolic_node(mp);
            if (cur_exp_type == mp_token_list_type) {
                mp_set_sym_sym(p, cur_exp_node);
            } else {
                mp_set_sym_sym(p, mp_stash_cur_exp(mp));
            }
            if (mp_number_positive(internal_value(mp_tracing_macros_internal))) {
                mp_begin_diagnostic(mp);
                mp_print_argument(mp, (mp_node) mp_get_sym_sym(p), n, mp_get_sym_info(r), r->name_type);
                mp_end_diagnostic(mp, 0);
            }
            if (arg_list == NULL) {
                arg_list = p;
            } else {
                tail->link = p;
            }
            tail = p;
            ++n;
        }

        r = r->link;
    }
    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;
        snprintf(msg, 256, "Too many arguments to %s; Missing '%s' has been inserted",
        mp_str(mp, rname), mp_str(mp, eq_text(r_delim)));
        mp_delete_string_reference(mp, rname);
        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) {
        /*tex
            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:
                {
                    /*tex
                        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 (mp_number_positive(internal_value(mp_tracing_macros_internal))) {
                        mp_begin_diagnostic(mp);
                        mp_print_argument(mp, (mp_node) mp_get_sym_sym(p), n, 0, 0);
                        mp_end_diagnostic(mp, 0);
                    }
                    if (arg_list == NULL) {
                        arg_list = p;
                    } else {
                        tail->link = 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;
                        snprintf(msg, 256, "Missing 'of' has been inserted for %s", mp_str(mp, sname));
                        mp_delete_string_reference(mp, sname);
                        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);
                }
                break;
            case mp_suffix_macro:
                {
                    /*tex
                        Scan a suffix with optional delimiters.
                    */
                    if (cur_cmd != mp_left_delimiter_command) {
                        l_delim = NULL;
                    } else {
                        l_delim = cur_sym;
                        r_delim = eq_symbol(cur_sym);
                        mp_get_x_next(mp);
                    }
                    mp_scan_suffix(mp);
                    if (l_delim != NULL) {
                        if ((cur_cmd != mp_right_delimiter_command) || (eq_symbol(cur_sym) != l_delim)) {
                            char msg[256];
                            snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, eq_text(r_delim)));
                            mp_back_error(mp, msg, "I've gotten to the end of the macro parameter list.");
                        }
                        mp_get_x_next(mp);
                    }
                }
                break;
            case mp_text_macro:
                mp_scan_text_arg(mp, NULL, NULL);
                break;
        }
        mp_back_input(mp);
        /*tex
            Append the current expression to |arg_list|.
        */
        {
            mp_node p = mp_new_symbolic_node(mp);
            if (cur_exp_type == mp_token_list_type) {
                mp_set_sym_sym(p, cur_exp_node);
            } else {
                mp_set_sym_sym(p, mp_stash_cur_exp(mp));
            }
            if (mp_number_positive(internal_value(mp_tracing_macros_internal))) {
                mp_begin_diagnostic(mp);
                mp_print_argument(mp, (mp_node) mp_get_sym_sym(p), n, mp_get_sym_info(r), r->name_type);
                mp_end_diagnostic(mp, 0);
            }
            if (arg_list == NULL) {
                arg_list = p;
            } else {
                tail->link = p;
            }
            tail = p;
            ++n;
        }
    }
    r = r->link;
    /*tex
        Feed the arguments and replacement text to the scanner.
    */
    while (token_state && (nloc == NULL)) {
        /*tex conserve stack space */
        mp_end_token_list(mp);
    }
    if (mp->parameter_ptr + n > mp->max_parameter_stack) {
        mp->max_parameter_stack = mp->parameter_ptr + n;
        mp_check_parameter_size(mp, mp->max_parameter_stack);
    }
    mp_begin_token_list(mp, def_ref, mp_macro_text);
    mp_input_name = macro_name ? eq_text(macro_name) : NULL;
    nloc = r;
    if (n > 0) {
        mp_node p = arg_list;
        do {
            mp->parameter_stack[mp->parameter_ptr] = (mp_node) mp_get_sym_sym(p);
            ++mp->parameter_ptr;
            p = p->link;
        } while (p != NULL);
        mp_flush_node_list(mp, arg_list);
    }
}

static void mp_print_macro_name(MP mp, mp_node a, mp_symbol n)
{
    if (n) {
        mp_print_mp_string(mp, eq_text(n));
    } else {
        mp_node p = (mp_node) mp_get_sym_sym(a);
        if (p) {
            /*tex Traverse the first part of |a|. */
            mp_node q = p;
            while (q->link != NULL) {
                q = q->link;
            }
            q->link = (mp_node) mp_get_sym_sym(a->link);
            mp_show_token_list(mp, p, NULL);
            q->link = NULL;
        } else {
            mp_print_mp_string(mp, eq_text(mp_get_sym_sym((mp_node) mp_get_sym_sym(a->link))));
        }
    }
}

static void mp_print_argument(MP mp, mp_node q, int n, int b, int bb)
{
    if (q && q->link == MP_VOID) {
        mp_print_format(mp, "%l(EXPR %i)<-", n);
    } else if (bb < mp_text_operation && b != mp_text_macro) {
        mp_print_format(mp, "%l(SUFFIX %i)<-", n);
    } else {
        mp_print_format(mp, "%l(TEXT %i)<-", n);
    }
    if (q && q->link == MP_VOID) {
        mp_print_exp(mp, q, 1);
    } else {
        mp_show_token_list(mp, q, NULL);
    }
}

/*tex

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.

*/

static void mp_scan_text_arg(MP mp, mp_symbol l_delim, mp_symbol r_delim)
{
    int balance = 1;           /*tex excess of |l_delim| over |r_delim| */
    mp_node p = mp->hold_head; /*tex list tail */
    mp->warning_info = l_delim;
    mp->scanner_status = mp_absorbing_state;
    mp->hold_head->link = NULL;
    while (1) {
        mp_get_t_next(mp);
        if (l_delim == NULL) {
            /*tex
                Adjust the balance for an undelimited argument; |break| if done.
            */
            if (mp_end_of_statement) {
                /*tex
                    We have |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;
            }
        } else {
            /*tex
                Adjust the balance for a delimited argument; |break| if done.
            */
            if (cur_cmd == mp_right_delimiter_command) {
                if (eq_symbol(cur_sym) == l_delim) {
                    --balance;
                    if (balance == 0) {
                        break;
                    }
                }
            } else if (cur_cmd == mp_left_delimiter_command) {
                if (eq_symbol(cur_sym) == r_delim) {
                    ++balance;
                }
            }
        }
        p->link = mp_cur_tok(mp);
        p = p->link;
    }
    mp_set_cur_exp_node(mp, mp->hold_head->link);
    cur_exp_type = mp_token_list_type;
    mp->scanner_status = mp_normal_state;
}

/*tex

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.

It's sometimes necessary to put a single argument onto |param_stack|. The |stack_argument| subroutine
does this.

*/

static void mp_stack_argument(MP mp, mp_node p)
{
    if (mp->parameter_ptr == mp->max_parameter_stack) {
        ++mp->max_parameter_stack;
        mp_check_parameter_size(mp, mp->max_parameter_stack);
    }
    mp->parameter_stack[mp->parameter_ptr] = p;
    ++mp->parameter_ptr;
}

/*

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|.

*/

static mp_if_node mp_new_if_node(MP mp)
{
    mp_if_node p = mp->memory_pool[mp_if_pool].list;
    mp->memory_pool[mp_if_pool].used++;
    if (mp->memory_pool[mp_if_pool].used > mp->memory_pool[mp_if_pool].max) {
        mp->memory_pool[mp_if_pool].max = mp->memory_pool[mp_if_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_if_pool].list = p->link;
        mp->memory_pool[mp_if_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_if_data));
        p->type = mp_if_node_type;
    }
    p->link = NULL;
    return p;
}

static void mp_free_if_node(MP mp, mp_if_node p)
{
    mp->memory_pool[mp_if_pool].used--;
    if (mp->memory_pool[mp_if_pool].pool < mp->memory_pool[mp_if_pool].kept) {
        mp->memory_pool[mp_if_pool].pool++;
        p->link = mp->memory_pool[mp_if_pool].list;
        mp->memory_pool[mp_if_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_if_pool(MP mp)
{
    mp_if_node p = mp->memory_pool[mp_if_pool].list;
    while (p) {
        mp_if_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

/*tex

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.

*/

static void mp_pass_text(MP mp)
{
    int level = 0;
    mp->scanner_status = mp_skipping_state;
    mp->warning_line = mp_true_line(mp);
    while (1) {
        mp_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 {
            /*tex
                Decrease the string reference count, if the current token is a string.
            */
            if (cur_cmd == mp_string_command) {
                mp_delete_string_reference(mp, cur_mod_str);
            }
        }
    }
    mp->scanner_status = mp_normal_state;
}

/*tex

Here's a procedure that changes the |if_limit| code corresponding to a given value of |cond_ptr|.

*/

static void mp_change_if_limit(MP mp, int l, mp_if_node p)
{
    if (p == mp->cond_ptr) {
        mp->if_limit = l;
    } else {
        mp_if_node q = mp->cond_ptr;
        while (1) {
            if (q == NULL) {
                mp_confusion(mp, "if");
                return;
            } else if (q->link == p) {
                q->type = l;
                return;
            } else {
                q = q->link;
            }
        }
    }
}

/*tex

The user is supposed to put colons into the proper parts of conditional statements. Therefore, \MP\
has to check for their presence.

*/

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."
        );
    }
}

/*tex

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.

*/

/*tex

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.

*/

static void mp_push_condition_stack(MP mp)
{
    mp_if_node p = mp_new_if_node(mp);
    p->link = mp->cond_ptr;
    p->type = (int) mp->if_limit;
    p->name_type = mp->cur_if;
    p->if_line_field = 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_if_node p = mp->cond_ptr;
    mp->if_line = p->if_line_field;
    mp->cur_if = p->name_type;
    mp->if_limit = p->type;
    mp->cond_ptr = p->link;
    mp_free_if_node(mp, p);
}

void mp_conditional(MP mp)
{
    mp_if_node save_cond_ptr; /*tex |cond_ptr| corresponding to this conditional */
    int new_if_limit;         /*tex 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 (cur_exp_type != mp_boolean_type) {
        mp_do_boolean_error(mp);
    }
    new_if_limit = mp_else_if_code;
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_unity_t)) {
        /*tex
            Display the boolean value of |cur_exp|
        */
        mp_begin_diagnostic(mp);
        mp_print_string(mp, cur_exp_value_boolean == mp_true_operation ? "{true}" : "{false}");
        mp_end_diagnostic(mp, 0);
    }
  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);
        /*tex
            Wait for |elseif|, |else|, or |fi|.
        */
        return;
    }
    /*tex
        Skip to |elseif| or |else| or |fi|, then |goto done|.
    */
    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);
        }
    }
  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;
    }
}

/*tex

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.

*/

static void mp_bad_for(MP mp, const char *s)
{
    char msg[256];
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    snprintf(msg, 256, "Improper %s has been replaced by 0", s);
    mp_display_error(mp, NULL);
    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);
}

/*tex

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.)

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|).

The last case is when we have just seen |within|, and we need to parse a picture expression and
prepare to iterate over it.

*/

static mp_loop_node mp_new_loop_node(MP mp)
{
    mp_loop_node p = mp->memory_pool[mp_loop_pool].list;
    mp->memory_pool[mp_loop_pool].used++;
    if (mp->memory_pool[mp_loop_pool].used > mp->memory_pool[mp_loop_pool].max) {
        mp->memory_pool[mp_loop_pool].max = mp->memory_pool[mp_loop_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_loop_pool].list = p->link;
        mp->memory_pool[mp_loop_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_loop_data));
    }
    p->link = NULL;
    return p;
}

static void mp_free_loop_node(MP mp, mp_loop_node p)
{
    mp->memory_pool[mp_loop_pool].used--;
    if (mp->memory_pool[mp_loop_pool].pool < mp->memory_pool[mp_loop_pool].kept) {
        mp->memory_pool[mp_loop_pool].pool++;
        p->link = mp->memory_pool[mp_loop_pool].list;
        mp->memory_pool[mp_loop_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_loop_pool(MP mp)
{
    mp_loop_node p = mp->memory_pool[mp_loop_pool].list;
    while (p) {
        mp_loop_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static void mp_begin_iteration(MP mp)
{
    mp_node q;              /*tex link manipulation register */
    mp_symbol n = cur_sym;  /*tex hash address of the current symbol */
    mp_subst_node p = NULL; /*tex substitution list for |scan_toks| */
    int m = cur_mod;        /*tex |start_for| (|for|) or |start_forsuffixes| (|forsuffixes|) */
    mp_loop_node s = mp_new_loop_node(mp);
    s->type = NULL;
    s->list = NULL;
    s->info = NULL;
    s->list_start = NULL;
 // s->link = NULL;
    s->var = NULL;
    s->point = NULL;
    mp_new_number(s->index);
    mp_new_number(s->value);
    mp_new_number(s->old_value);
    mp_new_number(s->step_size);
    mp_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_new_subst_node(mp);;
        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 {
            /*tex |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) {
            /*tex Set up a picture iteration. */
            mp_get_x_next(mp);
            mp_scan_expression(mp);
            if (cur_exp_type == mp_path_type) {
                mp_number_clone(s->value, mp_zero_t);
                mp_number_clone(s->old_value, mp_zero_t);
                mp_number_clone(s->step_size, mp_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) {
                            /*tex So we actually start at the end because we next first. */
                            s->point = p;
                            mp_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 {
                /*tex
                    Make sure the current expression is a known picture.
                */
                if (cur_exp_type != mp_picture_type) {
                    mp_value new_expr;
                    memset(&new_expr, 0, sizeof(mp_value));
                    mp_new_number(new_expr.data.n);
                    new_expr.data.node = (mp_node) mp_new_edge_header_node(mp);
                    mp_display_error(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) cur_exp_node);
                    cur_exp_type = mp_picture_type;
                }
                s->type = cur_exp_node;
                cur_exp_type = mp_vacuous_type;
                q = mp_edge_list(cur_exp_node)->link;
                if (q != NULL && mp_is_start_or_stop(q) && mp_skip_one_component(mp, q) == NULL) {
                    q = q->link;
                }
                s->list = q;
            }
        } else {
            /*tex
                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."
                );
            }
            /*tex
                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) {
                        /*tex
                            Prepare for step-until construction and |break|.
                        */
                        {
                            if (cur_exp_type != mp_known_type) {
                                mp_bad_for(mp, "initial value");
                            }
                            mp_number_clone(s->value, cur_exp_value_number);
                            mp_number_clone(s->old_value, cur_exp_value_number);
                            mp_get_x_next(mp);
                            mp_scan_expression(mp);
                            if (cur_exp_type != mp_known_type) {
                                mp_bad_for(mp, "step size");
                            }
                            mp_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."
                                );
                            }
                            mp_get_x_next(mp);
                            mp_scan_expression(mp);
                            if (cur_exp_type != mp_known_type) {
                                mp_bad_for(mp, "final value");
                            }
                            mp_number_clone(s->final_value, cur_exp_value_number);
                            s->type = MP_PROGRESSION_FLAG;
                            break;
                        }
                    }
                    mp_set_cur_exp_node(mp, mp_stash_cur_exp(mp));
                }
                q->link = mp_new_symbolic_node(mp);
                q = q->link;
                mp_set_sym_sym(q, cur_exp_node);
                if (m == mp_start_for_code) {
                    q->name_type = mp_expr_operation;
                } else if (m == mp_start_forsuffixes_code) {
                    q->name_type = mp_suffix_operation;
                }
                cur_exp_type = mp_vacuous_type;
              CONTINUE:
              ; /* needed */
            } while (cur_cmd == mp_comma_command);
        }
    }
    /*tex
        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."
        );
    }
    /*tex
        Scan the loop text and put it on the loop control stack.
    */
    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;
    mp_resume_iteration(mp);
}

/*tex

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 \quote {|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. The loop text is inserted into \MP's scanning
apparatus by the |resume_iteration| routine.

*/

static void mp_resume_iteration(MP mp)
{
    mp_node p = mp->loop_ptr->type;
    mp_node q; /* link registers */
    mp_number_add(mp->loop_ptr->index, mp_unity_t);
    if (p == MP_PROGRESSION_FLAG) {
        /*
        mp_set_cur_exp_value_number(mp, &(mp->loop_ptr->value));
        if ((mp_number_positive(mp->loop_ptr->step_size) && mp_number_greater(cur_exp_value_number, mp->loop_ptr->final_value))
         || (mp_number_negative(mp->loop_ptr->step_size) && mp_number_less   (cur_exp_value_number, mp->loop_ptr->final_value))) {
            mp_stop_iteration(mp);
            return;
        }
        */
        if ((mp_number_positive(mp->loop_ptr->step_size) && mp_number_greater(mp->loop_ptr->value, mp->loop_ptr->final_value))
         || (mp_number_negative(mp->loop_ptr->step_size) && mp_number_less   (mp->loop_ptr->value, mp->loop_ptr->final_value))) {
            mp_stop_iteration(mp);
            return;
        }
        mp_set_cur_exp_value_number(mp, &(mp->loop_ptr->value));
        cur_exp_type = mp_known_type;
        /* make |q| an |expr| argument */
        q = mp_stash_cur_exp(mp);
        mp_number_clone(mp->loop_ptr->old_value, cur_exp_value_number);
        mp_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 (mp_number_positive(mp->loop_ptr->step_size) && mp_number_less(mp->loop_ptr->value, cur_exp_value_number)) {
            if (mp_number_positive(mp->loop_ptr->final_value)) {
                mp_number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value);
                mp_number_add_scaled(mp->loop_ptr->final_value, -1);
            } else {
                mp_number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value);
                mp_number_add_scaled(mp->loop_ptr->value, 1);
            }
        } else if (mp_number_negative(mp->loop_ptr->step_size) && mp_number_greater(mp->loop_ptr->value, cur_exp_value_number)) {
            if (mp_number_negative(mp->loop_ptr->final_value)) {
                mp_number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value);
                mp_number_add_scaled(mp->loop_ptr->final_value, 1);
            } else {
                mp_number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value);
                mp_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 = p->link;
            mp_free_symbolic_node(mp, q);
            mp->loop_ptr->list = p;
        }
        if (p == NULL) {
            mp_stop_iteration(mp);
            return;
        }
        mp->loop_ptr->list = p->link;
        q = (mp_node) mp_get_sym_sym(p);
        if (q) {
            mp_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 {
        /*tex
            Make |q| a capsule containing the next picture component from |loop_list(loop_ptr)| or
            |goto not_found|.
        */
        q = mp->loop_ptr->list;
        if (q == NULL) {
            goto NOT_FOUND;
        } else if (! mp_is_start_or_stop(q)) {
            q = q->link;
        } else if (! mp_is_stop(q)) {
            q = mp_skip_one_component(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);
        cur_exp_type = mp_picture_type;
        mp->loop_ptr->list = q;
        q = mp_stash_cur_exp(mp);
    }
    mp_begin_token_list(mp, mp->loop_ptr->info, mp_loop_text);
    mp_stack_argument(mp, q);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_unity_t)) {
        /*tex  trace the start of a loop */
        mp_begin_diagnostic(mp);
        mp_print_nl(mp, "{loop value=");
        if ((q != NULL) && (q->link == MP_VOID)) {
            mp_print_exp(mp, q, 1);
        } else {
            mp_show_token_list(mp, q, NULL);
        }
        mp_print_char(mp, '}');
        mp_end_diagnostic(mp, 0);
    }
    return;
  NOT_FOUND:
    mp_stop_iteration(mp);
}

/*tex

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.

*/

static 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 (p->link == 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 = q->link;
            mp_free_symbolic_node(mp, p);
        }
    } else if (p > MP_PROGRESSION_FLAG) {
        mp_delete_edge_ref(mp, p);
    }
    {
        mp_loop_node tmp = mp->loop_ptr;
        mp->loop_ptr = tmp->link;
        mp_flush_token_list(mp, tmp->info);
        mp_free_number(tmp->value);
        mp_free_number(tmp->step_size);
        mp_free_number(tmp->final_value);
        mp_free_loop_node(mp, tmp);
    }
}

/*tex

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.

{\em This section doesn't really apply to the library because files are mostly delegated to the main
program but we keep some comments for historic reasons.}

\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 |.nnn| for some number |nnn| on the \POSTSCRIPT\ 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, 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

\starttyping
|begin_name|
|more_name|(c_1) ... |more_name|(c_n)
|end_name|
\stoptyping

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|.

It is easier to maintain reference counts if we assign initial values.

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.

We can scan such file names easily by using two global variables that keep track of the occurrences
of area and extension delimiters.

Here are the routines for file name scanning.

*/

static 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);
}

static void mp_pack_file_name(MP mp, const char *n)
{
    mp_memory_free(mp->name_of_file);
    mp->name_of_file = mp_strdup(n);
}

/*tex

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.

*/

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);
}

/*tex

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.

*/

static void mp_scan_file_name(MP mp)
{
    mp_begin_name(mp);
    while (mp->buffer[mp_input_location] == ' ') {
        ++mp_input_location;
    }
    while (1) {
        if ((mp->buffer[mp_input_location] == ';') || (mp->buffer[mp_input_location] == '%')) {
            break;
        } else if (! mp_more_name(mp, mp->buffer[mp_input_location])) {
            break;
        } else {
            ++mp_input_location;
        }
    }
    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);
}

/*tex

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.

Initially |job_name = NULL| and when it is not set the initializer will quit. Setting it happens
elsewhere.

Cannot do this earlier because at the |<Allocate or ...>|, the string pool is not yet
initialized.Let's turn now to the procedure that is used to initiate file reading when an |input|
command is being processed.

*/

static void mp_start_input(MP mp)
{
    /*tex
        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."
        );
    }
    if (file_state) {
        mp_scan_file_name(mp);
    } else {
        mp_memory_free(mp->cur_name);
        mp->cur_name = mp_strdup("");
    }
    /*tex
        Set up |input_file| and new level of input.
    */
    mp_begin_file_reading(mp);
    mp_pack_file_name(mp, mp->cur_name);
    if (mp_open_in(mp, &mp_input_file, mp_filetype_program)) {
        char *fname = NULL;
        mp_input_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_char(mp, ' ');
            }
            mp_print_char(mp, '(');
            ++mp->open_parens;
            mp_print_string(mp, fname);
        }
        mp_memory_free(fname);
        mp_print_flush_line(mp);
        /*tex
            Flush |name| and replace it with |cur_name| if it won't be needed.
        */
        mp_flush_string(mp, mp_input_name);
        mp_input_name = mp_rts(mp, mp->cur_name);
        mp_memory_free(mp->cur_name);
        mp->cur_name = NULL;
        /*tex
            Read the first line of the new file.
        */
        mp_input_line = 1;
        mp_input_ln(mp, mp_input_file);
        mp_firm_up_the_line(mp);
        mp->buffer[mp_input_limit] = '%';
        mp->first = (size_t) (mp_input_limit + 1);
        mp_input_location = mp_input_start;
    } else {
        mp_fatal_error(mp, "invalid input file");
        mp_end_file_reading(mp);
    }
}

/*tex

If the file is empty, it is considered to contain a single blank line, so there is no need to test
the return value. The last file-opening commands are for files accessed via the |readfrom| operator
and the |write| command. Such files are stored in separate arrays.

This routine starts reading the file named by string~|s| without setting |loc|, |mp_input_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]|.

*/

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->read_filehandles[n], mp_filetype_text + n)) {
        mp_end_file_reading(mp);
        return 0;
    } else if (! mp_input_ln(mp, mp->read_filehandles[n])) {
        (mp->close_file)(mp, mp->read_filehandles[n]);
        mp_end_file_reading(mp);
        return 0;
    } else {
        mp->read_filenames[n] = mp_strdup(s);
        return 1;
    }
}

/*tex

Open |wr_file[n]| using file name~|s| and update |wr_fname[n]|.

*/

static 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->write_filehandles[n], mp_filetype_text + n)) {
        mp->write_filenames[n] = mp_strdup(s);
    } else {
        mp_fatal_error(mp, "invalid write file");
    }
}

static void mp_set_cur_exp_node(MP mp, mp_node n)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    cur_exp_node = n;
    cur_exp_str = NULL;
    cur_exp_knot = NULL;
    mp_set_number_to_zero(cur_exp_value_number);
}

static void mp_set_cur_exp_knot(MP mp, mp_knot n)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    cur_exp_knot = n;
    cur_exp_node = NULL;
    cur_exp_str = NULL;
    mp_set_number_to_zero(cur_exp_value_number);
}

static void mp_set_cur_exp_value_boolean(MP mp, int b)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    mp_set_number_from_boolean(cur_exp_value_number, b);
    cur_exp_node = NULL;
    cur_exp_str = NULL;
    cur_exp_knot = NULL;
}

static void mp_set_cur_exp_value_scaled(MP mp, int s)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    mp_set_number_from_scaled(cur_exp_value_number, s);
    cur_exp_node = NULL;
    cur_exp_str = NULL;
    cur_exp_knot = NULL;
}

static void mp_set_cur_exp_value_number(MP mp, mp_number *n)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    mp_number_clone(cur_exp_value_number, *n);
    cur_exp_node = NULL;
    cur_exp_str = NULL;
    cur_exp_knot = NULL;
}

static void mp_set_cur_exp_str(MP mp, mp_string s)
{
    if (cur_exp_str) {
        mp_delete_string_reference(mp, cur_exp_str);
    }
    cur_exp_str = s;
    mp_add_string_reference(mp, cur_exp_str);
    cur_exp_node = NULL;
    cur_exp_knot = NULL;
    mp_set_number_to_zero(cur_exp_value_number);
}

/*tex

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:

\startitemize

\startitem
    |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.
\stopitem

\startitem
    |cur_type = mp_boolean_type| means that |cur_exp| is either |true_code| or |false_code|.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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|.
\stopitem

\startitem
    |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|.
\stopitem

\startitem
    |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|.
\stopitem

\startitem
    |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|.
\stopitem

\startitem
    |cur_type = mp_known| means that |cur_exp| is a |scaled| value.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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.
\stopitem

\startitem
    |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|$'.
\stopitem

\startitem
    |cur_type = mp_token_list| means that |cur_exp| points to a linked list of tokens.
\stopitem

\stopitemize

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, cur_exp)|
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.

*/

static mp_node mp_stash_cur_exp(MP mp)
{
    mp_node p; /* the capsule that will be returned */
    mp_variable_type exp_type = 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;
            if (mp_number_greater(internal_value(mp_tracing_dependencies_internal), mp_unity_t)) {
                mp_print_format(mp, "%l[expression: stash direct, node %P, type '%s']", p, mp_type_string(exp_type));
            }
            break;
        default: /* |case mp_path_type: case mp_pen_type: case mp_string_type:| */
            p = mp_new_value_node(mp);
            p->name_type = mp_capsule_operation;
            p->type = 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);
            }
            if (mp_number_greater(internal_value(mp_tracing_dependencies_internal), mp_unity_t)) {
                mp_print_format(mp, "%l[expression: stash capsule, node %P, type '%s']", p, mp_type_string(exp_type));
            }
            break;
    }
    cur_exp_type = mp_vacuous_type;
    p->link = MP_VOID;
    return p;
}

/*tex

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.

*/

static void mp_unstash_cur_exp(MP mp, mp_node p)
{
    cur_exp_type = p->type;
    switch (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;
    }
}

/*tex

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.

*/

static void mp_print_exp(MP mp, mp_node p, int verbosity)
{
    int restore_cur_exp; /*tex should |cur_exp| be restored? */
    mp_variable_type t;  /*tex the type of the expression */
    mp_number vv;        /*tex the value of the expression */
    mp_node v = NULL;
    mp_new_number(vv);
    if (p != NULL) {
        restore_cur_exp = 0;
    } else {
        p = mp_stash_cur_exp(mp);
        restore_cur_exp = 1;
    }
    t = p->type;
    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 {
            mp_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);
    }
    /*tex
        Print an abbreviated value of |v| or |vv| with format depending on |t|.
    */
    switch (t) {
        case mp_vacuous_type:
            mp_print_string(mp, "vacuous");
            break;
        case mp_boolean_type:
            mp_print_string(mp, 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:
            /*tex
                Display a variable that's been declared but not defined. The displayed name of a
                variable in a ring will not be a capsule unless the ring consists entirely of
                capsules.
            */
            mp_print_type(mp, t);
            if (v != NULL) {
                mp_print_char(mp, ' ');
                while ((v->name_type == mp_capsule_operation) && (v != p)) {
                    v = mp_get_value_node(v);
                }
                mp_print_variable_name(mp, v);
            }
            break;
        case mp_string_type:
            mp_print_format(mp, "%Q", mp_get_value_str(p));
            break;
        case mp_pen_type:
        case mp_nep_type:
        case mp_path_type:
        case mp_picture_type:
            if (verbosity <= 1) {
                mp_print_type(mp, t);
            } else {
                if (mp->selector == mp_term_and_log_selector)
                    if (mp_number_nonpositive(internal_value(mp_tracing_online_internal))) {
                        mp->selector = mp_term_only_selector;
                        mp_print_type(mp, t);
                        mp_print_string(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;
                }
            }
            break;
        case mp_transform_type:
            if (mp_number_zero(vv) && v == NULL) {
                mp_print_type(mp, t);
            } else {
                mp_print_char(mp, '(');
                mp_print_big_node(mp, mp_tx_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_ty_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_xx_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_xy_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_yx_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_yy_part(v), verbosity);
                mp_print_char(mp, ')');
            }
            break;
        case mp_color_type:
            if (mp_number_zero(vv) && v == NULL) {
                mp_print_type(mp, t);
            } else {
                mp_print_char(mp, '(');
                mp_print_big_node(mp, mp_red_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_green_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_blue_part(v), verbosity);
                mp_print_char(mp, ')');
            }
            break;
        case mp_pair_type:
            if (mp_number_zero(vv) && v == NULL) {
                mp_print_type(mp, t);
            } else {
                mp_print_char(mp, '(');
                mp_print_big_node(mp, mp_x_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_y_part(v), verbosity);
                mp_print_char(mp, ')');
            }
            break;
        case mp_cmykcolor_type:
            if (mp_number_zero(vv) && v == NULL) {
                mp_print_type(mp, t);
            } else {
                mp_print_char(mp, '(');
                mp_print_big_node(mp, mp_cyan_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_magenta_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_yellow_part(v), verbosity);
                mp_print_char(mp, ',');
                mp_print_big_node(mp, mp_black_part(v), verbosity);
                mp_print_char(mp, ')');
            }
            break;
        case mp_known_type:
            mp_print_number(mp, 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;
    }
    if (restore_cur_exp) {
        mp_unstash_cur_exp(mp, p);
    }
    mp_free_number(vv);
}

static void mp_print_big_node(MP mp, mp_node v, int verbosity)
{
    switch (v->type) {
        case mp_known_type:
            mp_print_number(mp, mp_get_value_number(v));
            break;
        case mp_independent_type:
            mp_print_variable_name(mp, v);
            break;
        default:
            mp_print_dp(mp, v->type, (mp_value_node) mp_get_dep_list((mp_value_node) v), verbosity);
            break;
    }
}

/*tex

In these cases, |v| starts as the big node. 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|.

*/

static void mp_print_dp(MP mp, int t, mp_value_node p, int verbosity)
{
    mp_value_node q = (mp_value_node) p->link; /* the node following |p| */
    if ((mp_get_dep_info(q) == NULL) || (verbosity > 0)) {
        mp_print_dependency(mp, p, t);
    } else {
        mp_print_string(mp, "linearform");
    }
}

/*tex

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|.

*/

static void mp_display_error(MP mp, mp_node p)
{
    if (mp->interaction >= mp_error_stop_mode) {
        mp_print_flush_line(mp);
    }
    mp_print_nl(mp, "<error> ");
    mp_print_exp(mp, p, 1);
}

/*tex

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.

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.

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.

*/

static void mp_flush_cur_exp(MP mp, mp_value v)
{
    if (is_number(cur_exp_value_number)) {
        mp_free_number(cur_exp_value_number);
    }
    switch (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:
            mp_delete_string_reference(mp, 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;
    cur_exp_type = mp_known_type;
}

/*tex

There's a much more general procedure that is capable of releasing the storage associated with any
non-symbolic value packet.

*/

static void mp_recycle_value(MP mp, mp_node p)
{
    if (p != NULL && p != MP_VOID) {
        mp_variable_type t = p->type;
        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:
                mp_delete_string_reference(mp, 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_color_node(mp, mp_get_value_node(p));
                }
                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_color_node(mp, mp_get_value_node(p));
                }
                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_transform_node(mp, mp_get_value_node(p));
                }
                break;
            case mp_dependent_type:
            case mp_proto_dependent_type:
                mp_recycle_dependent_value(mp, 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;
        }
        p->type = mp_undefined_type;
    }
}

/*tex
    Recycle a (proto) dependency list.
*/

static void mp_recycle_dependent_value(MP mp, mp_node p)
{
    mp_value_node q = (mp_value_node) mp_get_dep_list((mp_value_node) p);
    while (mp_get_dep_info(q) != NULL) {
        q = (mp_value_node) q->link;
    }
    mp_set_link(mp_get_prev_dep((mp_value_node) p), q->link);
    mp_set_prev_dep(q->link, mp_get_prev_dep((mp_value_node) p));
    mp_set_link(q, NULL);
    mp_flush_node_list(mp, (mp_node) mp_get_dep_list((mp_value_node) p));
}

/*tex

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).

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 \quote {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.

*/

static void mp_recycle_independent_value(MP mp, mp_node p)
{
    mp_value_node q, r, s;
    mp_node pp;     /*tex link manipulation register */
    mp_number v ;   /*tex a value */
    mp_number absval; /*tex a temporary value */
    mp_variable_type t = p->type;
    mp_new_number(absval);
    mp_new_number(v);
    if (t < mp_dependent_type) {
        mp_number_clone(v, mp_get_value_number(p));
    }
    mp_set_number_to_zero(mp->max_c[mp_dependent_type]);
    mp_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->dep_head->link;
    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) s->link;
            if (mp_get_dep_info(r) == NULL) {
                break;
            } else if (mp_get_dep_info(r) != p) {
                s = r;
            } else {
                t = q->type;
                if (s->link == mp_get_dep_list(q)) {
                    /* reset the |dep_list| */
                    mp_set_dep_list(q, r->link);
                }
                mp_set_link(s, r->link);
                mp_set_dep_info(r, (mp_node) q);
                mp_number_abs_clone(absval, mp_get_dep_value(r));
                if (mp_number_greater(absval, mp->max_c[t])) {
                    /* Record a new maximum coefficient of type |t| */
                    if (mp_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];
                    }
                    mp_number_clone(mp->max_c[t], absval);
                    mp->max_ptr[t] = r;
                } else {
                    mp_set_link(r, mp->max_link[t]);
                    mp->max_link[t] = r;
                }
            }
        }
        q = (mp_value_node) r->link;
    }
    if (mp_number_positive(mp->max_c[mp_dependent_type]) || mp_number_positive(mp->max_c[mp_proto_dependent_type])) {
        /*tex
            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 */
        mp_new_number(ret);
        mp_new_number_clone(test, mp->max_c[mp_dependent_type]);
        mp_number_divide_int(test, 4096);
        if (mp_number_greaterequal(test, mp->max_c[mp_proto_dependent_type])) {
            t = mp_dependent_type;
        } else {
            t = mp_proto_dependent_type;
        }
        /*tex
             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);
        mp_number_clone(v, mp_get_dep_value(s));
        if (t == mp_dependent_type) {
            mp_set_dep_value(s, mp_fraction_one_t);
        } else {
            mp_set_dep_value(s, mp_unity_t);
        }
        mp_number_negate(mp_get_dep_value(s)); /* can be combined above */
        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) r->link;
        }
        q = (mp_value_node) r->link;
        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, 17);
        if (cur_exp_node == pp && cur_exp_type == t) {
            cur_exp_type = mp_independent_type;
        }
        if (mp_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);
        }
        /*tex Complement |t|: */
        t = mp_dependent_type + mp_proto_dependent_type - t;
        if (mp_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];
        }
        /*tex
            Finally, there are dependent and proto-dependent variables whose dependency lists must
            be brought up to date.
        */
        if (t != mp_dependent_type) {
            /*tex
                Substitute new 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);
                    mp_number_negated_clone(test, v);
                    mp_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) r->link;
                    mp_free_dep_node(mp, q, 16);
                }
            }
        } else {
            /*tex
                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) {
                        /*tex
                            For safety's sake, we change |q| to |mp_proto_dependent|
                        */
                        if (cur_exp_node == (mp_node) q && cur_exp_type == mp_dependent_type) {
                            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), &mp_unity_t, mp_dependent_type, mp_proto_dependent_type));
                        q->type = mp_proto_dependent_type;
                        mp_fraction_to_round_scaled(mp_get_dep_value(r));
                    }
                    mp_number_negated_clone(test, v);
                    mp_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) r->link;
                    mp_free_dep_node(mp, q, 17);
                }
            }
        }
        mp_flush_node_list(mp, (mp_node) s);
        if (mp->fix_needed) {
            mp_fix_dependencies(mp);
        }
        mp_check_arithmic(mp);
        mp_free_number(ret);
    }
    mp_free_number(v);
    mp_free_number(absval);
}

static void mp_show_transformed_dependency(MP mp, mp_number *v, mp_variable_type t, mp_node p)
{
    mp_number vv; /* for temp use */
 // int s = mp_get_indep_scale(p);
    mp_print_nl(mp, "### ");
    if (mp_number_positive(*v)) {
        mp_print_char(mp, '-');
    }
    if (t == mp_dependent_type) {
        mp_new_number_clone(vv, mp->max_c[mp_dependent_type]);
        mp_fraction_to_round_scaled(vv);
    } else {
        mp_new_number_clone(vv, mp->max_c[mp_proto_dependent_type]);
    }
    if (! mp_number_equal(vv, mp_unity_t)) {
        mp_print_number(mp, vv);
    }
    mp_print_variable_name(mp, p);
    while (mp_get_indep_scale(p) > 0) {
        mp_print_string(mp, "*4");
        mp_set_indep_scale(p, mp_get_indep_scale(p)-2);
    }
 // while (s > 0) {
 //     mp_print_string(mp, "*4");
 //     s -= 2;
 // }
    if (t == mp_dependent_type) {
        mp_print_char(mp, '=');
    } else {
        mp_print_string(mp, " = ");
    }
    mp_free_number(vv);
}

/*tex

The code for independency removal makes use of three non-symbolic arrays.

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 \quote {location} of a variable at the left of |:=|, not the \quote
{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.

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:

\startitemize

\startitem
    |do_nullary(c)| does primitive operations that have no operands (e.g., |true| or |pencircle|);
\stopitem

\startitem
    |do_unary(c)| applies a primitive operation to the current expression;
\stopitem

\startitem
    |do_binary(p,c)| applies a primitive operation to the capsule~|p| and the current
    expression.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. Errors at the beginning of expressions are
    flagged by |bad_exp|.
\stopitem

\stopitemize

*/

static void mp_bad_exp(MP mp, const char *s)
{
    char msg[256];
    int save_flag;
    {
         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);
         snprintf(msg, 256, "%s expression can't begin with '%s'", s, mp_str(mp, cm));
         mp_delete_string_reference(mp, cm);
    }
    mp_back_input(mp);
    set_cur_sym(NULL);
    set_cur_cmd(mp_numeric_command);
    set_cur_mod_number(mp_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;
}

/*tex

The |stash_in| subroutine puts the current (numeric) expression into a field within a \quote {big
node.}

*/

static void mp_stash_in(MP mp, mp_node p)
{
    p->type = cur_exp_type;
    switch (cur_exp_type) {
        case mp_known_type:
            {
                mp_set_value_number(p, cur_exp_value_number);
                break;
            }
        case mp_independent_type:
            {
                /*tex
                    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) {
                    p->type = mp_known_type;
                    mp_set_value_number(p, mp_zero_t);
                    mp_free_dep_node(mp, q, 18);
                } else {
                    mp_new_dep(mp, p, mp_dependent_type, q, 5);
                }
                mp_recycle_value(mp, cur_exp_node);
                mp_free_value_node(mp, cur_exp_node);
                break;
            }
        default:
            {
                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, 19);
                break;
            }
    }
    cur_exp_type = mp_vacuous_type;
}

/*tex

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.

Here's a routine that puts the current expression back to be read again.

*/

static void mp_back_expr(MP mp)
{
    mp_node p = mp_stash_cur_exp(mp); /* capsule token */
    p->link = NULL;
    mp_begin_token_list(mp, p, mp_backed_up_text);
}

/*tex

Unknown subscripts lead to the following error message.

*/

static void mp_bad_subscript(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    mp_display_error(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."
    );
    mp_flush_cur_exp(mp, new_expr);
}

/*tex

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.

*/

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;
    snprintf(msg, 256, "Variable %s has been obliterated", mp_str(mp, sname));
    mp_delete_string_reference(mp, sname);
    return mp_strdup(msg);
}

/*tex

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.The
|encapsulate| subroutine assumes that |dep_final| is the tail of dependency list~|p|.The |install|
procedure copies a numeric field~|q| into field~|r| of a big node that will be part of a capsule.
Here is a comparatively simple routine that is used to scan the |suffix| parameters of a macro.
Parsing secondary and higher expressions

After the intricacies of |scan_primary|, 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. The following
procedure calls a macro that has two parameters, |p| and |cur_exp|.

*/

static void mp_binary_mac(MP mp, mp_node p, mp_node c, mp_symbol n)
{
    mp_node q = mp_new_symbolic_node(mp);
    mp_node r = mp_new_symbolic_node(mp);
    q->link = r;
    mp_set_sym_sym(q, p);
    mp_set_sym_sym(r, mp_stash_cur_exp(mp));
    mp_macro_call(mp, c, q, n);
}

/*tex

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. Here we convert a pair to a knot with two endpoints.

*/

/* todo : mp->last_x and mp->last_y */

static mp_knot mp_pair_to_knot(MP mp)
{
    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);
    mp_number_clone(q->x_coord, mp->cur_x);
    mp_number_clone(q->y_coord, mp->cur_y);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

static mp_knot mp_numeric_to_knot_no(MP mp, mp_number x, mp_number y)
{
    mp_knot q = mp_new_knot(mp);
    mp_left_type(q) = mp_open_knot;
    mp_right_type(q) = mp_open_knot;
    mp_originator(q) = mp_metapost_user;
    mp_knotstate(q) = mp_regular_knot;
    mp_prev_knot(q) = q;
    mp_next_knot(q) = q;
    mp_number_clone(q->x_coord, x);
    mp_number_clone(q->y_coord, y);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

static mp_knot mp_pair_to_knot_xy(MP mp, mp_number x, mp_number y)
{
    mp_knot q = mp_numeric_to_knot_no(mp, x, y);
    mp_known_pair(mp);
    mp_number_add(q->x_coord, mp->cur_x);
    mp_number_add(q->y_coord, mp->cur_y);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

static mp_knot mp_numeric_to_knot_xy(MP mp, mp_number x, mp_number y)
{
    mp_knot q = mp_numeric_to_knot_no(mp, x, y);
    mp_number_add(q->x_coord, cur_exp_value_number);
    mp_number_add(q->y_coord, cur_exp_value_number);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

static mp_knot mp_numeric_to_knot_x(MP mp, mp_number x, mp_number y)
{
    mp_knot q = mp_numeric_to_knot_no(mp, x, y);
    mp_number_add(q->x_coord, cur_exp_value_number);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

static mp_knot mp_numeric_to_knot_y(MP mp, mp_number x, mp_number y)
{
    mp_knot q = mp_numeric_to_knot_no(mp, x, y);
    mp_number_add(q->y_coord, cur_exp_value_number);
    mp_number_clone(mp->previous_x, mp->last_x);
    mp_number_clone(mp->previous_y, mp->last_y);
    mp_number_clone(mp->last_x, q->x_coord);
    mp_number_clone(mp->last_y, q->y_coord);
    return q;
}

/*tex

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.

*/

static void mp_known_pair(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    if (cur_exp_type != mp_pair_type) {
        mp_display_error(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);
        mp_set_number_to_zero(mp->cur_x);
        mp_set_number_to_zero(mp->cur_y);
    } else {
        mp_node p = mp_get_value_node(cur_exp_node);
        /*tex
            Make sure that both |x| and |y| parts of |p| are known; copy them into |cur_x| and
            |cur_y|.
        */
        if (mp_x_part(p)->type == mp_known_type) {
            mp_number_clone(mp->cur_x, mp_get_value_number(mp_x_part(p)));
        } else {
            mp_display_error(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));
            mp_set_number_to_zero(mp->cur_x);
        }
        if (mp_y_part(p)->type == mp_known_type) {
            mp_number_clone(mp->cur_y, mp_get_value_number(mp_y_part(p)));
        } else {
            mp_display_error(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));
            mp_set_number_to_zero(mp->cur_y);
        }
        mp_flush_cur_exp(mp, new_expr);
    }
}

/*tex

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.

*/

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) {
        /*tex Scan a curl specification */
        mp_get_x_next(mp);
        mp_scan_expression(mp);
        if ((cur_exp_type != mp_known_type) || (mp_number_negative(cur_exp_value_number))) {
            mp_value new_expr;
            memset(&new_expr, 0, sizeof(mp_value));
            mp_new_number(new_expr.data.n);
            mp_set_number_to_unity(new_expr.data.n);
            mp_display_error(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 {
        /*tex Scan a given direction */
        mp_scan_expression(mp);
        if (cur_exp_type > mp_pair_type) {
            /*tex Get given directions separated by commas */
            mp_number xx;
            mp_new_number(xx);
            if (cur_exp_type != mp_known_type) {
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_new_number(new_expr.data.n);
                mp_display_error(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);
            }
            mp_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 (cur_exp_type != mp_known_type) {
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_new_number(new_expr.data.n);
                mp_display_error(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);
            }
            mp_number_clone(mp->cur_y, cur_exp_value_number);
            mp_number_clone(mp->cur_x, xx);
            mp_free_number(xx);
        } else {
            mp_known_pair(mp);
        }
        if (mp_number_zero(mp->cur_x) && mp_number_zero(mp->cur_y)) {
            t = mp_open_knot;
        } else {
            mp_number narg;
            mp_new_angle(narg);
            mp_n_arg(narg, mp->cur_x, mp->cur_y);
            t = mp_given_knot;
            mp_set_cur_exp_value_number(mp, &narg);
            mp_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;
}

/*tex

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.

*/

static void mp_push_of_path_result(MP mp, int what, mp_knot p, mp_number i, mp_number n)
{
    switch (what) {
        case 0: /* point */
            mp_pair_value(mp, &(p->x_coord), &(p->y_coord));
            break;
        case 1: /* precontrol */
            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: /* postcontrol */
            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: /* direction */
        case 4: /* unitdirection */
            {
                mp_number x, y;
                if (mp_right_type(p) == mp_endpoint_knot) {
                    mp_new_number_clone(x, p->x_coord);
                    mp_new_number_clone(y, p->y_coord);
                } else {
                    mp_new_number_clone(x, p->right_x);
                    mp_new_number_clone(y, p->right_y);
                }
                if (mp_left_type(p) == mp_endpoint_knot) {
                    mp_number_subtract(x, p->x_coord);
                    mp_number_subtract(y, p->y_coord);
                } else {
                    mp_number_subtract(x, p->left_x);
                    mp_number_subtract(y, p->left_y);
                }
                if (what == 4) {
                    mp_number n;
                    mp_pyth_add(n, x, y);
                    if (mp_number_zero(n)) {
                        mp_number_clone(x, mp_zero_t);
                        mp_number_clone(y, mp_zero_t);
                    } else {
                        mp_set_number_from_div(x, x, n);
                        mp_set_number_from_div(y, y, n);
                    }
                    mp_free_number(n);
                }
                mp_pair_value(mp, &x, &y);
                mp_free_number(x);
                mp_free_number(y);
            }
            break;
        case 5: /* state */
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_set_number_from_int(expr.data.n, mp_knotstate(p));
                mp_flush_cur_exp(mp, expr);
            }
            break;
        case 6: /* index */
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_set_number_from_subtraction(expr.data.n, i, mp_unity_t);
                mp_flush_cur_exp(mp, expr);
            }
            break;
        case 7: /* lastindex */
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_number_clone(expr.data.n, n);
                mp_flush_cur_exp(mp, expr);
            }
            break;
        case 8: /* length */
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_set_number_from_addition(expr.data.n, n, mp_unity_t);
                mp_flush_cur_exp(mp, expr);
            }
            break;
        case 9: /* first */
            {
                cur_exp_type = mp_boolean_type;
                mp_set_cur_exp_value_boolean(mp, mp_number_equal(i, mp_unity_t) ? mp_true_operation : mp_false_operation);
            }
            break;
        case 10: /* last */
            {
                cur_exp_type = mp_boolean_type;
                mp_set_cur_exp_value_boolean(mp, mp_number_greater(i, n) ? mp_true_operation : mp_false_operation);
            }
            break;
        case 11: /* xpart */
            {
                cur_exp_type = mp_known_type;
                mp_set_cur_exp_value_number(mp,  &(p->x_coord));
            }
            break;
        case 12: /* ypart */
            {
                cur_exp_type = mp_known_type;
                mp_set_cur_exp_value_number(mp,  &(p->y_coord));
            }
            break;
    }
}

/*tex

A few helpers that safe typing:

*/

static inline int mp_pair_is_known(mp_node n)
{
    return
        (mp_x_part(n)->type == mp_known_type) &&
        (mp_y_part(n)->type == mp_known_type);
}

static inline int mp_transform_is_known(mp_node n)
{
    return
        (mp_tx_part(n)->type == mp_known_type) &&
        (mp_ty_part(n)->type == mp_known_type) &&
        (mp_xx_part(n)->type == mp_known_type) &&
        (mp_xy_part(n)->type == mp_known_type) &&
        (mp_yx_part(n)->type == mp_known_type) &&
        (mp_yy_part(n)->type == mp_known_type);
}

static inline int mp_rgb_color_is_known(mp_node n)
{
    return
        (mp_red_part  (n)->type == mp_known_type) &&
        (mp_green_part(n)->type == mp_known_type) &&
        (mp_blue_part (n)->type == mp_known_type);
}

static inline int mp_cmyk_color_is_known(mp_node n)
{
    return
        (mp_cyan_part   (n)->type == mp_known_type) &&
        (mp_magenta_part(n)->type == mp_known_type) &&
        (mp_yellow_part (n)->type == mp_known_type) &&
        (mp_black_part  (n)->type == mp_known_type);
}

/*tex

    Copy |buffer| line to |cur_exp|.

*/

static void mp_finish_read(MP mp)
{
    mp_str_room(mp, (int) mp->last - (int) mp_input_start);
    for (size_t k = (size_t) mp_input_start; k < mp->last; k++) {
        mp_append_char(mp, mp->buffer[k]);
    }
    mp_end_file_reading(mp);
    cur_exp_type = mp_string_type;
    mp_set_cur_exp_str(mp, mp_make_string(mp));
}

/*tex
    Scan a nullary operation.
*/

static void mp_do_command_nullary(MP mp, int c)
{
    mp_check_arithmic(mp);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
        mp_show_cmd_mod(mp, mp_nullary_command, c);
    }
    switch (c) {
        case mp_true_operation:
        case mp_false_operation:
            cur_exp_type = mp_boolean_type;
            mp_set_cur_exp_value_boolean(mp, c);
            break;
        case mp_null_picture_operation:
            cur_exp_type = mp_picture_type;
            mp_set_cur_exp_node(mp, (mp_node) mp_new_edge_header_node(mp));
            mp_init_edges(mp, (mp_edge_header_node) cur_exp_node);
            break;
        case mp_null_pen_operation:
            cur_exp_type = mp_pen_type;
            mp_set_cur_exp_knot(mp, mp_get_pen_circle(mp, &mp_zero_t));
            break;
        case mp_normal_deviate_operation:
            {
                mp_number r;
                mp_new_number(r);
                /*|mp_norm_rand (mp, &r)|;*/
                mp_m_norm_rand(r);
                cur_exp_type = mp_known_type;
                mp_set_cur_exp_value_number(mp, &r);
                mp_free_number(r);
            }
            break;
        case mp_pen_circle_operation:
            cur_exp_type = mp_pen_type;
            mp_set_cur_exp_knot(mp, mp_get_pen_circle(mp, &mp_unity_t));
            break;
        case mp_version_operation:
            cur_exp_type = mp_string_type;
            mp_set_cur_exp_str(mp, mp_intern(mp, metapost_version));
            break;
        case mp_iterator_index_operation:
            cur_exp_type = mp_known_type;
            mp_set_cur_exp_value_number(mp, mp->loop_ptr ? &(mp->loop_ptr->index) : &mp_zero_t);
            break;
        case mp_path_point_operation:
        case mp_path_precontrol_operation:
        case mp_path_postcontrol_operation:
        case mp_path_direction_operation:
        case mp_path_unit_direction_operation:
        case mp_path_state_operation:
        case mp_path_index_operation:
        case mp_path_lastindex_operation:
        case mp_path_length_operation:
        case mp_path_first_operation:
        case mp_path_last_operation:
        case mp_path_xpart_operation:
        case mp_path_ypart_operation:
            cur_exp_type = mp_undefined_type;
            if (mp->loop_ptr && mp->loop_ptr->point != NULL) {
                mp_push_of_path_result(mp, c - mp_path_point_operation, mp->loop_ptr->point, mp->loop_ptr->value, mp->loop_ptr->final_value);
            } else {
                mp_pair_value(mp, &mp_zero_t, &mp_zero_t);
            }
            break;
        case mp_last_xy_operation:
            cur_exp_type = mp_undefined_type;
            mp_pair_value(mp, &mp->last_x, &mp->last_y);
            break;
        case mp_last_x_operation:
            cur_exp_type = mp_known_type;
            mp_set_cur_exp_value_number(mp, &mp->last_x);
            break;
        case mp_last_y_operation:
            cur_exp_type = mp_known_type;
            mp_set_cur_exp_value_number(mp, &mp->last_y);
            break;
        case mp_previous_xy_operation:
            cur_exp_type = mp_undefined_type;
            mp_pair_value(mp, &mp->previous_x, &mp->previous_y);
            break;
        case mp_previous_x_operation:
            cur_exp_type = mp_known_type;
            mp_set_cur_exp_value_number(mp, &mp->previous_x);
            break;
        case mp_previous_y_operation:
            cur_exp_type = mp_known_type;
            mp_set_cur_exp_value_number(mp, &mp->previous_y);
            break;
    }
    mp_check_arithmic(mp);
}

/*tex

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) .

*/

static int mp_pict_color_type(MP mp, int c)
{
    /* cur_pic_item = mp_edge_list(cur_exp_node)->link */
    return (
        (mp_edge_list(cur_exp_node)->link != NULL)
        &&
        (
            (! mp_has_color(mp_edge_list(cur_exp_node)->link))
            ||
            ((
                (mp_color_model(mp_edge_list(cur_exp_node)->link) == c)
                ||
                (
                    (mp_color_model(mp_edge_list(cur_exp_node)->link) == mp_uninitialized_model)
                    &&
                    (mp_number_to_scaled(internal_value(mp_default_color_model_internal))/mp_number_to_scaled(mp_unity_t)) == 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;
    mp_number_clone(k->x_coord, *x);
    mp_number_clone(k->y_coord, *y);
    mp_number_clone(k->left_x, *x);
    mp_number_clone(k->left_y, *y);
    mp_number_clone(k->right_x, *x);
    mp_number_clone(k->right_y, *y);
    return k;
}

static mp_knot mp_simple_int_knot(MP mp, int x,int 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;
    mp_set_number_from_int(k->x_coord, x);
    mp_set_number_from_int(k->y_coord, y);
    mp_set_number_from_int(k->left_x, x);
    mp_set_number_from_int(k->left_y, y);
    mp_set_number_from_int(k->right_x, x);
    mp_set_number_from_int(k->right_y, y);
    return k;
}

static void mp_make_bounding_box(MP mp)
{
    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_number dx;
    mp_number dy;
    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_new_number(dx);
    mp_number_clone(dx, lr->x_coord);
    mp_number_subtract(dx, ll->x_coord);
    mp_number_abs(dx);
    mp_number_divide_int(dx, 3);
    mp_new_number(dy);
    mp_number_clone(dy, ul->y_coord);
    mp_number_subtract(dy, ll->y_coord);
    mp_number_abs(dy);
    mp_number_divide_int(dy, 3);
    /* */
    mp_number_add(ll->right_x, dx);
    mp_number_add(ul->left_x, dx);
    mp_number_subtract(lr->left_x, dx);
    mp_number_subtract(ur->right_x, dx);
    mp_number_add(ll->left_y, dy);
    mp_number_add(lr->right_y, dy);
    mp_number_subtract(ur->left_y, dy);
    mp_number_subtract(ul->right_y, dy);
    /* */
    mp_free_number(dx);
    mp_free_number(dy);
    /* */
 // mp_solve_path(mp, ll);
    cur_exp_type = mp_path_type;
    mp_set_cur_exp_knot(mp, ll);
}

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;
    mp_number_clone(k->x_coord, o->x_coord);
    mp_number_clone(k->y_coord, o->y_coord);
    mp_number_clone(k->left_x, o->left_x);
    mp_number_clone(k->left_y, o->left_y);
    mp_number_clone(k->right_x, o->right_x);
    mp_number_clone(k->right_y, o->right_y);
    return k;
}

static int mp_nice_pair(MP mp, mp_node p, int t)
{
    (void) mp;
    return (t == mp_pair_type) && mp_pair_is_known(mp_get_value_node(p));
}

static int mp_nice_color(MP mp, mp_node p, int t)
{
    (void) mp;
    return (t == mp_color_type) && mp_rgb_color_is_known(mp_get_value_node(p));
}

static int mp_nice_color_or_pair(MP mp, mp_node p, int t)
{
    (void) mp;
    switch (t) {
        case mp_pair_type:
            return mp_pair_is_known(mp_get_value_node(p));
        case mp_color_type:
            return mp_rgb_color_is_known(mp_get_value_node(p));
        case mp_cmykcolor_type:
            return mp_cmyk_color_is_known(mp_get_value_node(p));
        default:
            return 0;
    }
}

static void mp_print_known_or_unknown_type(MP mp, int t, mp_node v)
{
    mp_print_char(mp, '(');
    if (t > mp_known_type) {
        mp_print_string(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_string(mp, "unknown ");
                }
                break;
        }
        mp_print_type(mp, t);
    }
    mp_print_char(mp, ')');
}

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_operator(mp, c);
    mp_print_known_or_unknown_type(mp, cur_exp_type, cur_exp_node);
    sname = mp_make_string(mp);
    mp->selector = selector;
    snprintf(msg, 256, "Not implemented: %s", mp_str(mp, sname));
    mp_delete_string_reference(mp, sname);
    mp_display_error(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."
    );
    mp_get_x_next(mp);
}

static void mp_negate_dep_list(MP mp, mp_value_node p)
{
    (void) mp;
    while (1) {
        mp_number_negate(mp_get_dep_value(p));
        if (mp_get_dep_info(p) == NULL) {
            return;
        } else {
            p = (mp_value_node) p->link;
        }
    }
}

static void mp_negate_value(MP mp, mp_node r)
{
    if (r->type == mp_known_type) {
        mp_set_value_number(r, mp_get_value_number(r)); /* to clear the rest */
        mp_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 mp_pair_to_path(MP mp) {
    mp_set_cur_exp_knot(mp, mp_pair_to_knot(mp));
    cur_exp_type = mp_path_type;
}

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->temp_val->type = cur_exp_type;
    p->link= mp->temp_val;
    mp_free_value_node(mp, cur_exp_node);
    switch (c) {
        case mp_x_part_operation:
            switch (cur_exp_type) {
                case mp_pair_type:
                    mp_make_exp_copy(mp, mp_x_part(p), c);
                    break;
                case mp_color_type:
                    mp_make_exp_copy(mp, mp_red_part(p), c);
                    break;
                case mp_cmykcolor_type:
                    mp_make_exp_copy(mp, mp_cyan_part(p), c);
                    break;
                default:
                    mp_make_exp_copy(mp, mp_tx_part(p), c);
                    break;
            }
            break;
        case mp_y_part_operation:
            switch (cur_exp_type) {
                case mp_pair_type:
                    mp_make_exp_copy(mp, mp_y_part(p), c);
                    break;
                case mp_color_type:
                    mp_make_exp_copy(mp, mp_green_part(p), c);
                    break;
                case mp_cmykcolor_type:
                    mp_make_exp_copy(mp, mp_magenta_part(p), c);
                    break;
                default:
                    mp_make_exp_copy(mp, mp_ty_part(p), c);
                    break;
            }
            break;
        case mp_z_part_operation:
            switch (cur_exp_type) {
                case mp_color_type:
                    mp_make_exp_copy(mp, mp_blue_part(p), c);
                    break;
                case mp_cmykcolor_type:
                    mp_make_exp_copy(mp, mp_yellow_part(p), c);
                    break;
                default:
                    mp_confusion(mp, "invalid zpart");
                    break;
            }
            break;
        case mp_w_part_operation:
            switch (cur_exp_type) {
                case mp_cmykcolor_type:
                    mp_make_exp_copy(mp, mp_black_part(p), c);
                    break;
                default:
                    mp_confusion(mp, "invalid wpart");
                    break;
            }
            break;
        case mp_xx_part_operation:
            mp_make_exp_copy(mp, mp_xx_part(p), c);
            break;
        case mp_xy_part_operation:
            mp_make_exp_copy(mp, mp_xy_part(p), c);
            break;
        case mp_yx_part_operation:
            mp_make_exp_copy(mp, mp_yx_part(p), c);
            break;
        case mp_yy_part_operation:
            mp_make_exp_copy(mp, mp_yy_part(p), c);
            break;
        case mp_red_part_operation:
            mp_make_exp_copy(mp, mp_red_part(p), c);
            break;
        case mp_green_part_operation:
            mp_make_exp_copy(mp, mp_green_part(p), c);
            break;
        case mp_blue_part_operation:
            mp_make_exp_copy(mp, mp_blue_part(p), c);
            break;
        case mp_cyan_part_operation:
            mp_make_exp_copy(mp, mp_cyan_part(p), c);
            break;
        case mp_magenta_part_operation:
            mp_make_exp_copy(mp, mp_magenta_part(p), c);
            break;
        case mp_yellow_part_operation:
            mp_make_exp_copy(mp, mp_yellow_part(p), c);
            break;
        case mp_black_part_operation:
            mp_make_exp_copy(mp, mp_black_part(p), c);
            break;
        case mp_grey_part_operation:
            mp_make_exp_copy(mp, mp_grey_part(p), c);
            break;
    }
    mp_recycle_value(mp, mp->temp_val);
}

static void mp_take_picture_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));
    mp_new_number(new_expr.data.n);
    p = mp_edge_list(cur_exp_node)->link;
    if (p != NULL) {
        switch (c) {
            case mp_x_part_operation:
            case mp_y_part_operation:
            case mp_z_part_operation:
            case mp_w_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:
                            mp_number_clone(new_expr.data.n, ((mp_shape_node) p)->red);
                            break;
                        case mp_green_part_operation:
                            mp_number_clone(new_expr.data.n, ((mp_shape_node) p)->green);
                            break;
                        case mp_blue_part_operation:
                            mp_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) {
                        mp_set_number_to_unity(new_expr.data.n);
                    } else {
                        switch (c) {
                            case mp_cyan_part_operation:
                                mp_number_clone(new_expr.data.n, ((mp_shape_node) p)->cyan);
                                break;
                            case mp_magenta_part_operation:
                                mp_number_clone(new_expr.data.n, ((mp_shape_node) p)->magenta);
                                break;
                            case mp_yellow_part_operation:
                                mp_number_clone(new_expr.data.n, ((mp_shape_node) p)->yellow);
                                break;
                            case mp_black_part_operation:
                                mp_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)) {
                    mp_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 */
                        mp_number_clone(new_expr.data.n, internal_value(mp_default_color_model_internal));
                    } else {
                        mp_number_clone(new_expr.data.n, mp_unity_t);
                        mp_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);
                        mp_add_string_reference(mp, new_expr.data.str);
                    } else {
                        new_expr.data.str = mp_rts(mp,"");
                    }
                    mp_flush_cur_exp(mp, new_expr);
                    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);
                        mp_add_string_reference(mp, new_expr.data.str);
                    } else {
                        new_expr.data.str = mp_rts(mp,"");
                    }
                    mp_flush_cur_exp(mp, new_expr);
                    cur_exp_type = mp_string_type;
                }
                break;
            case mp_stacking_part_operation:
                mp_number_clone(new_expr.data.n, mp_unity_t);
                mp_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 (p->type) {
                        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);
                    cur_exp_type = mp_path_type;
                }
                break;
            case mp_pen_part_operation:
                if (! mp_has_pen(p)) {
                    goto NOT_FOUND;
                } else {
                    switch (p->type) {
                        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);
                                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 (p->type != 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);
                    cur_exp_type = mp_picture_type;
                }
                break;
        }
        return;
    }
  NOT_FOUND:
    /*tex
        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);
            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;
            mp_set_number_to_zero(cur_exp_knot->x_coord);
            mp_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;
            cur_exp_type = mp_path_type;
            break;
        case mp_pen_part_operation:
            new_expr.data.p = mp_get_pen_circle(mp, &mp_zero_t);
            mp_flush_cur_exp(mp, new_expr);
            cur_exp_type = mp_pen_type; /* todo: mp_nep_type */
            break;
        case mp_dash_part_operation:
            new_expr.data.node = (mp_node) mp_new_edge_header_node(mp);
            mp_flush_cur_exp(mp, new_expr);
            mp_init_edges(mp, (mp_edge_header_node) cur_exp_node);
            cur_exp_type = mp_picture_type;
            break;
        default:
            mp_set_number_to_zero(new_expr.data.n);
            mp_flush_cur_exp(mp, new_expr);
            break;
    }
}

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);
    mp_set_number_from_int(*n, l);
}

static void mp_path_segments(MP mp, mp_number *n)
{
    mp_knot p = cur_exp_knot;
    int l = 1;
    do {
        p = mp_next_knot(p);
        if (mp_knotstate(p) == mp_begin_knot || mp_knotstate(p) == mp_single_knot) {
            ++l;
        }
    } while (p != cur_exp_knot);
    mp_set_number_from_int(*n, l);
}

static int mp_path_segment(MP mp, int n, int *first, int *last)
{
    if (n > 0) {
        mp_knot p = cur_exp_knot;
        int f = -1;
        int c = 0;
        /* mp_begin_knot */
        --n;
        if (! n) {
            f = 0;
        }
        do {
            p = mp_next_knot(p);
            switch (mp_knotstate(p)) {
                case mp_begin_knot:
                    if (f >= 0) {
                        goto CHECK;
                    } else {
                        if (! n) {
                            f = c;
                        }
                        break;
                    }
                case mp_end_knot:
                    if (f >= 0) {
                        goto CHECK;
                    } else {
                        --n;
                        break;
                    }
                case mp_single_knot:
                    {
                        *first = f;
                        *last = f;
                        return 1;
                    }
            }
            ++c;
        } while (p != cur_exp_knot);
        --c;
        /* mp_end_knot */
      CHECK:
        if (f >= 0) {
            *first = f;
            *last = c + 1;
            return 1;
        }
    }
    return 0;
}

static void mp_path_no_length(MP mp, mp_number *n)
{
    mp_set_number_from_boolean(*n, mp_next_knot(cur_exp_knot) == cur_exp_knot ? mp_true_operation : mp_false_operation);
}

/*tex

    Counts interior components in picture |cur_exp|.

*/

static void mp_picture_length(MP mp, mp_number *n)
{
    mp_node p = mp_edge_list(cur_exp_node)->link;
    int l = 0;
    if (p != NULL) {
        if (mp_is_start_or_stop(p) && mp_skip_one_component(mp, p) == NULL) {
            p = p->link;
        }
        while (p != NULL) {
            if (! mp_is_start_or_stop(p)) {
                p = p->link;
            } else if (! mp_is_stop(p)) {
                p = mp_skip_one_component(mp, p);
            } else {
                break;
            }
            ++l;
        }
    }
    mp_set_number_from_int(*n, l);
}

static void mp_an_angle(MP mp, mp_number *ret, mp_number *xpar, mp_number *ypar)
{
    mp_set_number_to_zero(*ret);
    if (! (mp_number_zero(*xpar) && mp_number_zero(*ypar))) {
        mp_n_arg(*ret, *xpar, *ypar);
    }
}

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
);

static void mp_turn_cycles(MP mp, mp_number *turns, mp_knot c)
{
    int selector;                  /*tex saved |selector| setting */
    mp_number res, ang;            /*tex the angles of intermediate results */
    mp_knot p;                     /*tex for running around the path */
    mp_number xp, yp;              /*tex coordinates of next point */
    mp_number x, y;                /*tex helper coordinates */
    mp_number arg1, arg2;
    mp_number in_angle, out_angle; /*tex helper angles */
    mp_number seven_twenty_deg_t;
    mp_set_number_to_zero(*turns);
    mp_new_number(arg1);
    mp_new_number(arg2);
    mp_new_number(xp);
    mp_new_number(yp);
    mp_new_number(x);
    mp_new_number(y);
    mp_new_angle(in_angle);
    mp_new_angle(out_angle);
    mp_new_angle(ang);
    mp_new_angle(res);
    mp_new_angle(seven_twenty_deg_t);
    mp_number_clone(seven_twenty_deg_t, mp_three_sixty_deg_t);
    mp_number_double(seven_twenty_deg_t);
    p = c;
    selector = mp->selector;
    mp->selector = mp_term_only_selector;
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_unity_t)) {
        mp_begin_diagnostic(mp);
        mp_print_nl(mp, "");
        mp_end_diagnostic(mp, 0);
    }
    do {
        mp_number_clone(xp, p_next->x_coord);
        mp_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 (mp_number_greater(ang, seven_twenty_deg_t)) {
            mp_error(mp, "Strange path", NULL);
            mp->selector = selector;
            mp_set_number_to_zero(*turns);
            goto DONE;
        }
        mp_number_add(res, ang);
        if (mp_number_greater(res, mp_one_eighty_deg_t)) {
            mp_number_subtract(res, mp_three_sixty_deg_t);
            mp_number_add(*turns, mp_unity_t);
        }
        if (mp_number_lessequal(res, mp_negative_one_eighty_deg_t)) {
            mp_number_add(res, mp_three_sixty_deg_t);
            mp_number_subtract(*turns, mp_unity_t);
        }
        /*tex Incoming angle at next point: */
        mp_number_clone(x, p_next->left_x);
        mp_number_clone(y, p_next->left_y);
        if (mp_number_equal(xp, x) && mp_number_equal(yp, y)) {
            mp_number_clone(x, p->right_x);
            mp_number_clone(y, p->right_y);
        }
        if (mp_number_equal(xp, x) && mp_number_equal(yp, y)) {
            mp_number_clone(x, p->x_coord);
            mp_number_clone(y, p->y_coord);
        }
        mp_set_number_from_subtraction(arg1, xp, x);
        mp_set_number_from_subtraction(arg2, yp, y);
        mp_an_angle(mp, &in_angle, &arg1, &arg2);
        /*tex Outgoing angle at next point: */
        mp_number_clone(x, p_next->right_x);
        mp_number_clone(y, p_next->right_y);
        if (mp_number_equal(xp, x) && mp_number_equal(yp, y)) {
            mp_number_clone(x, p_nextnext->left_x);
            mp_number_clone(y, p_nextnext->left_y);
        }
        if (mp_number_equal(xp, x) && mp_number_equal(yp, y)) {
            mp_number_clone(x, p_nextnext->x_coord);
            mp_number_clone(y, p_nextnext->y_coord);
        }
        mp_set_number_from_subtraction(arg1, x, xp);
        mp_set_number_from_subtraction(arg2, y, yp);
        mp_an_angle(mp, &out_angle, &arg1, &arg2);
        mp_set_number_from_subtraction(ang, out_angle, in_angle);
        mp_reduce_angle(mp, &ang);
        if (mp_number_nonzero(ang)) {
            mp_number_add(res, ang);
            if (mp_number_greaterequal(res, mp_one_eighty_deg_t)) {
                mp_number_subtract(res, mp_three_sixty_deg_t);
                mp_number_add(*turns, mp_unity_t);
            }
            if (mp_number_lessequal(res, mp_negative_one_eighty_deg_t)) {
                mp_number_add(res, mp_three_sixty_deg_t);
                mp_number_subtract(*turns, mp_unity_t);
            }
        }
        p = mp_next_knot(p);
    } while (p != c);
    mp->selector = selector;
  DONE:
    mp_free_number(xp);
    mp_free_number(yp);
    mp_free_number(x);
    mp_free_number(y);
    mp_free_number(seven_twenty_deg_t);
    mp_free_number(in_angle);
    mp_free_number(out_angle);
    mp_free_number(ang);
    mp_free_number(res);
    mp_free_number(arg1);
    mp_free_number(arg2);
}

static void mp_turn_cycles_wrapper(MP mp, mp_number *ret, mp_knot c)
{
    if (mp_next_knot(c) == c) {
        /*tex
            One-knot paths always have a turning number of 1.
        */
        mp_set_number_to_unity(*ret);
    } else {
        mp_turn_cycles(mp, ret, c);
    }
}

static int mp_test_known(MP mp, int c)
{
    int b = mp_false_operation; /* is the current expression known? */
    switch (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:
            if (mp_transform_is_known(mp_get_value_node(cur_exp_node))) {
                b = mp_true_operation;
            }
            break;
        case mp_color_type:
            if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                b = mp_true_operation;
            }
            break;
        case mp_cmykcolor_type:
            if (mp_cmyk_color_is_known(mp_get_value_node(cur_exp_node))) {
                b = mp_true_operation;
            }
            break;
        case mp_pair_type:
            if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                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;
    }
}

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;
    mp_new_number_clone(x1, *x);
    mp_new_number_clone(y1, *y);
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    p = mp_new_value_node(mp);
    new_expr.type = p->type;
    new_expr.data.node = p;
    mp_flush_cur_exp(mp, new_expr);
    cur_exp_type = mp_pair_type;
    p->name_type = mp_capsule_operation;
    mp_init_pair_node(mp, p);
    p = mp_get_value_node(p);
    mp_x_part(p)->type = mp_known_type;
    mp_set_value_number(mp_x_part(p), x1);
    mp_y_part(p)->type = mp_known_type;
    mp_set_value_number(mp_y_part(p), y1);
    mp_free_number(x1);
    mp_free_number(y1);
}

static int mp_get_cur_bbox(MP mp)
{
    switch (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 (mp_number_greater(p->minx, p->maxx)) {
                    mp_set_number_to_zero(mp_minx);
                    mp_set_number_to_zero(mp_maxx);
                    mp_set_number_to_zero(mp_miny);
                    mp_set_number_to_zero(mp_maxy);
                } else {
                    mp_number_clone(mp_minx, p->minx);
                    mp_number_clone(mp_maxx, p->maxx);
                    mp_number_clone(mp_miny, p->miny);
                    mp_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;
        case mp_pair_type:
            mp_set_number_to_zero(mp_minx);
            mp_set_number_to_zero(mp_maxx);
            mp_set_number_to_zero(mp_miny);
            mp_set_number_to_zero(mp_maxy);
            break;
        default:
            return 0;
    }
    return 1;
}

static int mp_get_cur_xbox(MP mp)
{
    if (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 (cur_exp_type == mp_path_type) {
        mp_path_ybox(mp, cur_exp_knot);
        return 1;
    } else {
        return mp_get_cur_bbox(mp);
    }
}

static void mp_do_read_or_close(MP mp, int c)
{
    int n = mp->n_of_read_files;
    int n0 = mp->n_of_read_files;
    char *fn = mp_strdup(mp_str(mp, cur_exp_str));
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    /*tex
        Find the |n| where |read_filenames[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
        |read_filenames| arrays are marked with |NULL|'s.
    */
    while (mp_strcmp(fn, mp->read_filenames[n]) != 0) {
        if (n > 0) {
            --n;
        } else if (c == mp_close_from_operation) {
            goto CLOSE_FILE;
        } else {
            if (n0 == mp->n_of_read_files) {
                if (mp->n_of_read_files < mp->max_read_files) {
                    ++mp->n_of_read_files;
                } else {
                    int max = mp->max_read_files + (mp->max_read_files / 4);
                    void **filehandles = mp_memory_allocate((size_t) (max + 1) * sizeof(void *));
                    char **filenames = mp_memory_allocate((size_t) (max + 1) * sizeof(char *));
                    for (int i = 0; i <= max; i++) {
                        if (i <= mp->max_read_files) {
                            filehandles[i] = mp->read_filehandles[i];
                            filenames[i] = mp->read_filenames[i];
                        } else {
                            filehandles[i] = 0;
                            filenames[i] = NULL;
                        }
                    }
                    mp_memory_free(mp->read_filehandles);
                    mp_memory_free(mp->read_filenames);
                    mp->max_read_files = max;
                    mp->read_filehandles = filehandles;
                    mp->read_filenames = filenames;
                }
            }
            n = n0;
            if (mp_start_read_input(mp, fn, n)) {
                goto FOUND;
            } else {
                goto NOT_FOUND;
            }
        }
        if (mp->read_filenames[n] == NULL) {
            n0 = n;
        }
    }
    if (c == mp_close_from_operation) {
        (mp->close_file)(mp, mp->read_filehandles[n]);
        goto NOT_FOUND;
    }
    mp_begin_file_reading(mp);
    mp_input_name = mp_input_from_file;
    if (mp_input_ln(mp, mp->read_filehandles[n])) {
        goto FOUND;
    }
    mp_end_file_reading(mp);
  NOT_FOUND:
    /*tex
        Record the end of file and set |cur_exp| to a dummy value.
    */
    mp_memory_free(mp->read_filenames[n]);
    mp->read_filenames[n] = NULL;
    if (n == mp->n_of_read_files - 1) {
        mp->n_of_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;
    mp_add_string_reference(mp, new_expr.data.str);
    mp_flush_cur_exp(mp, new_expr);
    cur_exp_type = mp_string_type;
    return;
  CLOSE_FILE:
    mp_flush_cur_exp(mp, new_expr);
    cur_exp_type = mp_vacuous_type;
    return;
  FOUND:
    mp_flush_cur_exp(mp, new_expr);
    mp_finish_read(mp);
}

static void mp_set_up_not(MP mp, int c)
{
    if (cur_exp_type != mp_boolean_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_set_cur_exp_value_boolean(mp, (cur_exp_value_boolean == mp_true_operation) ? mp_false_operation : mp_true_operation);
    }
}

static void mp_set_up_sqrt(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n;
        mp_new_number(n);
        mp_square_rt(n, cur_exp_value_number);
        mp_set_cur_exp_value_number(mp, &n);
        mp_free_number(n);
    }
}

static void mp_set_up_norm(MP mp, int c) /* for 3D experiments */
{
    mp_number r;
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            mp_new_number_from_mul(r, cur_exp_value_number, cur_exp_value_number);
            goto OKAY;
        case mp_pair_type:
            if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                /* a = sqrt(a dotprod a) */
                mp_number x, y;
                mp_new_number_from_mul(x, mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_add(r, x, y);
                mp_free_number(x);
                mp_free_number(y);
                goto OKAY;
            } else {
                break;
            }
       case mp_color_type:
            if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                mp_number x, y, z;
                mp_new_number_from_mul(x, mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(z, mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_add(r, x, y);
                mp_number_add(r, z);
                mp_free_number(x);
                mp_free_number(y);
                mp_free_number(z);
                goto OKAY;
            } else {
                break;
            }
        case mp_cmykcolor_type:
            if (mp_cmyk_color_is_known(mp_get_value_node(cur_exp_node))) {
                mp_number x, y, z, w;
                mp_new_number_from_mul(x, mp_get_value_number(mp_cyan_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_cyan_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_magenta_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_magenta_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(z, mp_get_value_number(mp_yellow_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_yellow_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(w, mp_get_value_number(mp_black_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_black_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_add(r, x, y);
                mp_number_add(r, z);
                mp_number_add(r, w);
                mp_free_number(x);
                mp_free_number(y);
                mp_free_number(z);
                mp_free_number(w);
                goto OKAY;
            } else {
                break;
            }
        default:
            break;
    }
    mp_bad_unary(mp, c);
    return;
  OKAY:
    mp_square_rt(r, r);
    {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        new_expr.data.n = r;
        mp_flush_cur_exp(mp, new_expr);
    }
}

static void mp_set_up_m_exp(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n;
        mp_new_number(n);
        mp_m_exp(n, cur_exp_value_number);
        mp_set_cur_exp_value_number(mp, &n);
        mp_free_number(n);
    }
}

static void mp_set_up_m_log(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n;
        mp_new_number(n);
        mp_m_log(n, cur_exp_value_number);
        mp_set_cur_exp_value_number(mp, &n);
        mp_free_number(n);
    }
}

static void mp_set_up_sin_cos_d(MP mp, int c)
{
    /*tex
        This is rather inefficient, esp decimal, to calculate both each time. We could pass
        NULL as signal to do only one, or just have n_sin and n_cos.
    */
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n_sin, n_cos, arg1, arg2;
        mp_new_number(arg1);
        mp_new_number(arg2);
        mp_new_fraction(n_sin);
        mp_new_fraction(n_cos);
        mp_number_clone(arg1, cur_exp_value_number);
        mp_number_clone(arg2, mp_unity_t); /* maybe dp360 */
        mp_number_multiply_int(arg2, 360);
        mp_number_modulo(arg1, arg2);
        mp_convert_scaled_to_angle(arg1);
        mp_n_sin_cos(arg1, n_cos, n_sin);
        if (c == mp_sin_d_operation) {
            mp_fraction_to_round_scaled(n_sin);
            mp_set_cur_exp_value_number(mp, &n_sin);
        } else {
            mp_fraction_to_round_scaled(n_cos);
            mp_set_cur_exp_value_number(mp, &n_cos);
        }
        mp_free_number(arg1);
        mp_free_number(arg2);
        mp_free_number(n_sin);
        mp_free_number(n_cos);
    }
}

static void mp_set_up_floor(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n;
        mp_new_number(n);
        mp_number_clone(n, cur_exp_value_number);
        mp_floor_scaled(n);
        mp_set_cur_exp_value_number(mp, &n);
        mp_free_number(n);
    }
}

static void mp_set_up_normalize(MP mp, int c)
{
    if (mp_nice_color(mp, cur_exp_node, cur_exp_type)) {
        mp_number n;
        mp_node q = cur_exp_node;
        mp_make_exp_copy(mp, q, 2);
        mp_node p = mp_get_value_node(cur_exp_node);
        mp_pyth_add3(n,
            mp_get_value_number(mp_red_part(p)),
            mp_get_value_number(mp_green_part(p)),
            mp_get_value_number(mp_blue_part(p))
        );
        if (mp_number_zero(n)) {
            mp_set_value_number(mp_red_part(p), mp_zero_t);
            mp_set_value_number(mp_green_part(p), mp_zero_t);
            mp_set_value_number(mp_blue_part(p), mp_zero_t);
        } else {
            mp_number x, y, z;
            mp_new_number_from_div(x, mp_get_value_number(mp_red_part(p)), n);
            mp_new_number_from_div(y, mp_get_value_number(mp_green_part(p)), n);
            mp_new_number_from_div(z, mp_get_value_number(mp_blue_part(p)), n);
            mp_set_value_number(mp_red_part(p), x);
            mp_set_value_number(mp_green_part(p), y);
            mp_set_value_number(mp_blue_part(p), z);
        }
        mp_free_number(n);
        mp_recycle_value(mp, q);
        mp_free_value_node(mp, q);
    } else {
        mp_bad_unary(mp, c);
    }
}


static void mp_set_up_uniform_deviate(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_number n;
        mp_new_number(n);
        mp_m_unif_rand(n, cur_exp_value_number);
        mp_set_cur_exp_value_number(mp, &n);
        mp_free_number(n);
    }
}

static void mp_set_up_odd(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_set_cur_exp_value_boolean(mp, mp_number_odd(cur_exp_value_number) ? mp_true_operation : mp_false_operation);
        cur_exp_type = mp_boolean_type;
    }
}

static void mp_set_up_angle(MP mp, int c)
{
    if (mp_nice_pair(mp, cur_exp_node, cur_exp_type)) {
        mp_value expr;
        mp_node p; /* for list manipulation */
        mp_number narg;
        memset(&expr, 0, sizeof(mp_value));
        mp_new_number(expr.data.n);
        mp_new_angle(narg);
        p = mp_get_value_node(cur_exp_node);
        mp_n_arg(narg, mp_get_value_number(mp_x_part(p)), mp_get_value_number(mp_y_part(p)));
        mp_number_clone(expr.data.n, narg);
        mp_convert_angle_to_scaled(expr.data.n);
        mp_free_number(narg);
        mp_flush_cur_exp(mp, expr);
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_char(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        int n = (int) mp_round_unscaled(cur_exp_value_number) % 256;
        unsigned char s[2];
        mp_set_cur_exp_value_scaled(mp, n);
        cur_exp_type = mp_string_type;
        if (mp_number_negative(cur_exp_value_number)) {
            n = (int) mp_number_to_scaled(cur_exp_value_number) + 256;
            mp_set_cur_exp_value_scaled(mp, n);
        }
        s[0] = (unsigned char) mp_number_to_scaled(cur_exp_value_number);
        s[1] = '\0';
        mp_set_cur_exp_str(mp, mp_rtsl (mp, (char *) s, 1));
    }
}

static void mp_set_up_decimal(MP mp, int c)
{
    if (cur_exp_type != mp_known_type) {
        mp_bad_unary(mp, c);
    } else {
        int selector = mp->selector;
        mp->selector = mp_new_string_selector;
        mp_print_number(mp, cur_exp_value_number);
        mp_set_cur_exp_str(mp, mp_make_string(mp));
        mp->selector = selector;
        cur_exp_type = mp_string_type;
    }
}

static void mp_set_up_to_string(MP mp, int c)
{
    if (cur_exp_type != mp_string_type) {
        mp_bad_unary(mp, c);
    } else {
        int n;
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        if (c == mp_ASCII_operation) {
            n = cur_exp_str->len == 0 ? -1 : cur_exp_str->str[0];
        } else {
            int invalid = 0; /* did the string contain an invalid digit? */
            int radix = c == mp_oct_operation ? 8 : 16;
            n = 0;
            for (size_t k = 0; k < cur_exp_str->len; k++) {
            //unsigned char m = (unsigned char) (*(cur_exp_str->str + k));
                unsigned char m = (unsigned char) cur_exp_str->str[k];
                if ((m >= '0') && (m <= '9'))
                    m = (unsigned char) (m - '0');
                else if ((m >= 'A') && (m <= 'F'))
                    m = (unsigned char) (m - 'A' + 10);
                else if ((m >= 'a') && (m <= 'f'))
                    m = (unsigned char) (m - 'a' + 10);
                else {
                    invalid = 1;
                    m = 0;
                }
                if ((int) m >= radix) {
                    invalid = 1;
                    m = 0;
                }
                if (n < 32768 / radix) {
                    n = n * radix + m;
                } else {
                    n = 32767;
                }
            }
            /*tex
                Give error messages if |invalid| or |n >= 4096| (so this code is rather
                scaled specific.
            */
            if (invalid) {
                mp_display_error(mp, NULL);
                mp_back_error(
                    mp,
                    "String contains illegal digits",
                    c == mp_oct_operation ?
                        "I zeroed out characters that weren't in the range 0..7."
                    :
                        "I zeroed out characters that weren't hex digits."
                );
                mp_get_x_next(mp);
            }
            if (n > 4095 && mp_number_positive(internal_value(mp_warning_check_internal))) {
                char msg[256];
                snprintf(msg, 256, "Number too large (%d)", (int) n);
                mp_back_error(
                    mp,
                    msg,
                    "I have trouble with numbers greater than 4095; watch out.\n"
                    "Set warningcheck := 0 to suppress this message."
                );
                mp_get_x_next(mp);
            }
        }
        mp_number_clone(new_expr.data.n, mp_unity_t);
        mp_number_multiply_int(new_expr.data.n, n);
        mp_flush_cur_exp(mp, new_expr);
    }
}

static void mp_set_up_segments(MP mp, int c)
{
    switch (cur_exp_type) {
        case mp_path_type:
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_path_segments(mp, &expr.data.n);
                mp_flush_cur_exp(mp, expr);
                break;
            }
        default:
            mp_bad_unary(mp, c);
            break;
    }
}

static void mp_set_up_length(MP mp, int c)
{
    /*tex
        The length operation is somewhat unusual in that it applies to a variety of different
        types of operands. Plain \METAPOST\ lets |abs| to |length|.
    */
    switch (cur_exp_type) {
        case mp_string_type:
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_number_clone(expr.data.n, mp_unity_t);
                /* Kind of weird, this multiply: */
                mp_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));
                mp_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);
                mp_number_abs(cur_exp_value_number); /* well ... */
                break;
            }
        case mp_picture_type:
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_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, cur_exp_type)) {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_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 if (mp_nice_color(mp, cur_exp_node, cur_exp_type)) {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_pyth_add3(expr.data.n,
                    mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))),
                    mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))),
                    mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node)))
                );
                mp_flush_cur_exp(mp, expr);
            } else {
                mp_bad_unary(mp, c);
            }
            break;
    }
}

static void mp_set_up_some_point(MP mp, int c)
{
    if (cur_exp_type == mp_path_type) {
        mp_knot p = cur_exp_knot;
        switch (c) {
            case mp_first_point_operation:
            case mp_first_pre_control_operation:
            case mp_first_post_control_operation:
            case mp_first_direction_operation:
            case mp_first_unit_direction_operation:
                c -= mp_first_point_operation;
                break;
            case mp_last_point_operation:
            case mp_last_pre_control_operation:
            case mp_last_post_control_operation:
            case mp_last_direction_operation:
            case mp_last_unit_direction_operation:
                c -= mp_last_point_operation;
                p = mp_prev_knot(p);
                break;
        }
        mp_push_of_path_result(mp, c, p, mp_zero_t, mp_zero_t);
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_no_length(MP mp, int c)
{
    /*tex
        For now we only support paths.
    */
    switch (cur_exp_type) {
        case mp_path_type:
            {
                mp_value expr;
                memset(&expr, 0, sizeof(mp_value));
                mp_new_number(expr.data.n);
                mp_path_no_length(mp, &expr.data.n);
                mp_flush_cur_exp(mp, expr);
                cur_exp_type = mp_boolean_type;
                break;
            }
        case mp_string_type:
        case mp_known_type:
        case mp_picture_type:
        default:
            mp_bad_unary(mp, c);
            break;
    }
}

static void mp_set_up_turning(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_value expr;
        memset(&expr, 0, sizeof(mp_value));
        mp_new_number(expr.data.n);
        mp_flush_cur_exp(mp, expr);
    } else if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) {
        mp_value expr;
        memset(&expr, 0, sizeof(mp_value));
        mp_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));
        mp_new_number(expr.data.n);
        mp_turn_cycles_wrapper(mp, &expr.data.n, cur_exp_knot);
        mp_flush_cur_exp(mp, expr);
    }
}

static void mp_set_up_type_1(MP mp, int c)
{
    mp_value expr;
    /*tex 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));
    mp_new_number(expr.data.n);
    mp_set_number_from_boolean(expr.data.n, (cur_exp_type == type || cur_exp_type == (type + 1)) ? mp_true_operation : mp_false_operation);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

static void mp_set_up_type_2(MP mp, int c)
{
    mp_value expr;
    /*tex They are parallel: */
    int type = (c - mp_transform_type_operation) + mp_transform_type;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    mp_set_number_from_boolean(expr.data.n, cur_exp_type == type ? mp_true_operation : mp_false_operation);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

static void mp_set_up_type_3(MP mp, int c)
{
    (void) c;
    mp_value expr;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    mp_set_number_from_boolean(expr.data.n, (cur_exp_type >= mp_known_type && cur_exp_type <= mp_independent_type) ? mp_true_operation : mp_false_operation);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

static int mp_path_is_singularity(MP mp, mp_knot first)
{
    mp_knot current = first;
    while (1) {
        mp_knot next = mp_next_knot(current);
        if (next == first) {
            break;
        } else if (! mp_number_equal(current->x_coord, next->x_coord) || ! mp_number_equal(current->y_coord, next->y_coord)) {
            return 0;
        } else {
            current = next;
        }
    }
    return 1;
}

static void mp_set_up_singularity(MP mp, int c)
{
    mp_value expr;
    int b = mp_false_operation;
    (void) c;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    switch (cur_exp_type) {
        case mp_path_type:
            b = mp_path_is_singularity(mp, cur_exp_knot) ? mp_true_operation : mp_false_operation;
            break;
        case mp_pair_type:
            b = mp_true_operation;
            break;
        default:
            b = mp_false_operation;
            break;
    }
    mp_set_number_from_boolean(expr.data.n, b);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

/*tex

    Splitting the pruning is easier to trace. Later we can merge. Although pruning takes time,
    think of a 3000 point path, we eventually gain back on less overhead in the backend and smaller
    results.

    \starttyping
    (1,1) -- (2,2) -- (3,3) && % regular regular end      implicit begin
    (1,1) -- (2,2) -- (3,3) && % begin regular end
    (1,1)                   && % single
    (1,1) -- (1,1)          && % begin end
    (1,1) -- (1,1) -- (1,1) ;  % begin regular regular    implicit end
    \stoptyping

*/

static inline int mp_same_point(MP mp, mp_knot left, mp_knot right)
{
    return mp_number_equal(left->x_coord, right->x_coord) && mp_number_equal(left->y_coord, right->y_coord);
}

static void mp_set_up_prune_singularities_r_r(MP mp, mp_knot first)
{
    mp_knot current = first;
    mp_knot previous = NULL;
    int state = mp_begin_knot;
    while (1) {
        mp_knot next = mp_next_knot(current);
        if (previous && state == mp_regular_knot && next != first && current->state == mp_regular_knot && mp_same_point(mp, previous, current)) {
            mp_knot wiped = current;
            mp_next_knot(previous) = next;
            mp_prev_knot(next) = previous;
            mp_number_clone(previous->right_x, current->right_x);
            mp_number_clone(previous->right_y, current->right_y);
            current = next;
            mp_free_knot(mp, wiped);
            goto PICKUP;
        }
        state = current->state;
        previous = current;
        current = next;
      PICKUP:
        if (current == first) {
            break;
        }
    }
}

static void mp_set_up_prune_singularities_b_r(MP mp, mp_knot first)
{
    mp_knot previous = first;
    mp_knot current = mp_next_knot(first);
    int state = mp_begin_knot;
    while (current != first) {
        mp_knot next = mp_next_knot(current);
        if (state == mp_begin_knot && next != first && current->state == mp_regular_knot && mp_same_point(mp, previous, current)) {
            mp_knot wiped = current;
            mp_next_knot(previous) = next;
            mp_prev_knot(next) = previous;
            mp_number_clone(previous->right_x, current->right_x);
            mp_number_clone(previous->right_y, current->right_y);
            current = next;
            state = mp_regular_knot;
            mp_free_knot(mp, wiped);
            goto PICKUP;
        }
        state = current->state;
        previous = current;
        current = next;
      PICKUP:
        if (current == first) {
            break;
        }
    }
}

static void mp_set_up_prune_singularities_r_e(MP mp, mp_knot first) /* todo : last is also end */
{
     mp_knot current = first;
     mp_knot previous = NULL;
     int state = mp_begin_knot;
     while (current != first) {
        mp_knot next = mp_next_knot(current);
         if (previous && state == mp_regular_knot && (current->state == mp_end_knot || next == first ) && mp_same_point(mp, previous, current)) {
             mp_knot wiped = previous;
             mp_knot prev = mp_prev_knot(previous);
             mp_number_clone(current->left_x, previous->left_x);
             mp_number_clone(current->left_y, previous->left_y);
             mp_next_knot(prev) = current;
             mp_prev_knot(current) = prev;
             mp_free_knot(mp, wiped);
         }
         state = current->state;
         previous = current;
         current = next;
         if (current == first) {
             break;
         }
     }
}

static void mp_set_up_prune_singularities_b_e(MP mp, mp_knot first)
{
    mp_knot previous = first;
    mp_knot current = mp_next_knot(first);
    int state = mp_begin_knot;
    while (current != first) {
        mp_knot next = mp_next_knot(current);
        if (state == mp_begin_knot && (current->state == mp_end_knot || next == first) && mp_same_point(mp, previous, current)) {
            mp_knot wiped = current;
            mp_next_knot(previous) = next;
            mp_prev_knot(next) = previous;
            mp_number_clone(previous->right_x, previous->x_coord);
            mp_number_clone(previous->right_y, previous->y_coord);
            mp_number_clone(previous->left_x, previous->x_coord);
            mp_number_clone(previous->left_y, previous->y_coord);
            previous->state = mp_single_knot;
            current = next;
            state = mp_single_knot;
            mp_free_knot(mp, wiped);
            goto PICKUP;
        }
        state = current->state;
        previous = current;
        current = next;
      PICKUP:
        if (current == first) {
            break;
        }
    }
}

static void mp_set_up_prune_singularities_w_s(MP mp, mp_knot first)
{
    mp_knot previous = first;
    mp_knot current = mp_next_knot(first);
    while (current != first) {
        mp_knot next = mp_next_knot(current);
        if (current->state == mp_single_knot) {
            mp_knot wiped = current;
            mp_next_knot(previous) = next;
            mp_prev_knot(next) = previous;
            mp_number_clone(previous->right_x, previous->x_coord);
            mp_number_clone(previous->right_y, previous->y_coord);
            mp_number_clone(next->left_x, next->x_coord);
            mp_number_clone(next->left_y, next->y_coord);
            mp_free_knot(mp, wiped);
            current = next;
            goto PICKUP;
        } else {
            previous = current;
            current = next;
        }
      PICKUP:
        if (current == first) {
            break;
        }
    }
}

static void mp_set_up_prune_singularities_c_s(MP mp, mp_knot first)
{
    mp_knot previous = first;
    mp_knot current = mp_next_knot(first);
    int state = mp_begin_knot;
    while (current != first) {
        mp_knot next = mp_next_knot(current);
        if (state == mp_end_knot && current->state == mp_begin_knot && mp_same_point(mp, previous, current)) {
            mp_knot wiped = current;
            mp_next_knot(previous) = next;
            mp_prev_knot(next) = previous;
            mp_number_clone(previous->right_x, current->right_x);
            mp_number_clone(previous->right_y, current->right_y);
            previous->state = mp_regular_knot;
            current = next;
            state = mp_regular_knot;
            mp_free_knot(mp, wiped);
            goto PICKUP;
        }
        state = current->state;
        previous = current;
        current = next;
      PICKUP:
        if (current == first) {
            break;
        }
    }
}

static void mp_set_up_prune_singularities(MP mp, int c)
{
    switch (cur_exp_type) {
        case mp_path_type: /* we can use a interternal as bitset */
            {
                int prune = (int) mp_round_unscaled(internal_value(mp_prune_options_internal));
                if (prune & collapse_regular_regular_prune) {
                    mp_set_up_prune_singularities_r_r(mp, cur_exp_knot);
                }
                if (prune & collapse_begin_regular_prune) {
                    mp_set_up_prune_singularities_b_r(mp, cur_exp_knot);
                }
                if (prune & collapse_regular_end_prune) {
                    mp_set_up_prune_singularities_r_e(mp, cur_exp_knot);
                }
                if (prune & collapse_begin_end_prune) {
                    mp_set_up_prune_singularities_b_e(mp, cur_exp_knot);
                }
                if (prune & wipe_single_prune) {
                    mp_set_up_prune_singularities_w_s(mp, cur_exp_knot);
                }
                if (prune & connect_segments_prune) {
                    mp_set_up_prune_singularities_c_s(mp, cur_exp_knot);
                }
                break;
            }
        case mp_picture_type:
            {
                mp_node current  = mp_edge_list(cur_exp_node)->link;
                mp_node previous = NULL;
                while (current) {
                    if (mp_is_start_or_stop(current) && mp_skip_one_component(mp, current) == NULL) {
                        /* skip over it */
                    } else if (current->type == mp_fill_node_type || current->type == mp_stroked_node_type) {
                        if (mp_path_is_singularity(mp, mp_path_ptr((mp_shape_node) current))) {
                            if (previous) {
                                previous->link = current->link;
                            } else {
                                mp_edge_list(cur_exp_node)->link = current->link;
                            }
                            mp_free_shape_node(mp, (mp_shape_node) current);
                            current = previous ? previous->link : NULL;
                            continue;
                        }
                    }
                    previous = current;
                    current = current->link;
                }
             // mp_set_cur_exp_node(mp, mp_get_value_node(p));
             // mp_add_edge_ref(mp, cur_exp_node);
                break;
            }
        default:
            mp_bad_unary(mp, c);
            break;
    }
}

static void mp_set_up_known(MP mp, int c)
{
    mp_value expr;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    mp_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;
    cur_exp_type = mp_boolean_type;
}

static void mp_set_up_cycle(MP mp, int c)
{
    mp_value expr;
    int b = 0;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    if (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;
    }
    mp_set_number_from_boolean(expr.data.n, b);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

static void mp_set_up_arc_length(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_value expr;
        memset(&expr, 0, sizeof(mp_value));
        mp_new_number(expr.data.n);
        mp_get_arc_length(mp, &expr.data.n, cur_exp_knot);
        mp_flush_cur_exp(mp, expr);
    }
}

static void mp_set_up_group(MP mp, int c)
{
    mp_value expr;
    int b;
    (void) c;
    memset(&expr, 0, sizeof(mp_value));
    mp_new_number(expr.data.n);
    if (cur_exp_type != mp_picture_type) {
        b = mp_false_operation;
    } else if (mp_edge_list(cur_exp_node)->link == NULL) {
        b = mp_false_operation;
    } else {
        /* they are parallel: */
        int type = c - mp_filled_operation + mp_fill_node_type;
        b = mp_edge_list(cur_exp_node)->link->type == type ? mp_true_operation: mp_false_operation;
    }
    mp_set_number_from_boolean(expr.data.n, b);
    mp_flush_cur_exp(mp, expr);
    cur_exp_type = mp_boolean_type;
}

static void mp_set_up_reverse(MP mp, int c)
{
    switch (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, c);
            break;
    }
}

static void mp_set_up_uncycle(MP mp, int c)
{
    switch (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, c);
            break;
    }
}

static void mp_set_up_center_of(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        /*tex We keep the pair. */
    } else if (mp_get_cur_bbox(mp)) {
        /* todo: make this a function call */
        mp_number x, y;
        mp_new_number(x);
        mp_new_number(y);
        mp_set_number_half_from_subtraction(x, mp_maxx, mp_minx);
        mp_set_number_half_from_subtraction(y, mp_maxy, mp_miny);
        mp_number_add(x, mp_minx);
        mp_number_add(y, mp_miny);
        mp_pair_value(mp, &x, &y);
        mp_free_number(x);
        mp_free_number(y);
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_center_of_mass(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        /*tex We keep the pair. */
    } else if (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;
        mp_new_number(x);
        mp_new_number(y);
        do {
            ++l;
            p = mp_next_knot(p);
            mp_number_add(x, p->x_coord);
            mp_number_add(y, p->y_coord);
        } while (p != cur_exp_knot);
        mp_number_divide_int(x, l);
        mp_number_divide_int(y, l);
        mp_pair_value(mp, &x, &y);
        mp_free_number(x);
        mp_free_number(y);
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_delta(MP mp, int c)
{
    if (c == mp_delta_arclength_operation) {
        mp_known_pair(mp);
        if (mp->loop_ptr && mp->loop_ptr->point != NULL) {
            mp_value new_expr;
            mp_number count;
            mp_knot p = mp->loop_ptr->point;
            int f = (int) mp_round_unscaled(mp->cur_x);
            int l = (int) mp_round_unscaled(mp->cur_y) - f;
            if (f > 0) { while (f--) { p = mp_next_knot(p); } } else
            if (f < 0) { while (f++) { p = mp_prev_knot(p); } }
            memset(&new_expr, 0, sizeof(mp_value));
            mp_set_number_from_int(count, l);
            mp_new_number(new_expr.data.n);
            mp_get_subarc_length(mp, &new_expr.data.n, p, &mp_zero_t, &count);
            mp_flush_cur_exp(mp, new_expr);
            mp_free_number(count);
        } else {
            mp_bad_unary(mp, c);
        }
    } else if (c == mp_delta_iterator_index_operation) {
        mp_loop_node p = mp->loop_ptr->link;
        mp_set_cur_exp_value_number(mp, &cur_exp_value_number);
        int n = (int) mp_round_unscaled(cur_exp_value_number);
        if (n > 0) {
            while (p && --n > 0) {
                p = p->link;
            }
        } else {
            p = NULL;
        }
        mp_set_cur_exp_value_number(mp, p ? &p->index : &mp_zero_t);
    } else if (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 = (int) mp_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);
                }
            }
            mp_push_of_path_result(mp, c - mp_delta_point_operation, p, mp->loop_ptr->value, mp->loop_ptr->final_value);
        }
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_pen(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else {
        cur_exp_type = mp_pen_type;
        mp_set_cur_exp_knot(mp, mp_make_pen(mp, cur_exp_knot, 1));
    }
}

static void mp_set_up_nep(MP mp, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else {
        cur_exp_type = mp_nep_type;
        mp_set_cur_exp_knot(mp, cur_exp_knot);
    }
}

static void mp_set_up_convexed(MP mp, int c)
{
    if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else {
        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);
    }
}

static void mp_set_up_uncontrolled(MP mp, int c)
{
    if (cur_exp_type != mp_path_type) {
        mp_bad_unary(mp, c);
    } else {
        cur_exp_type = mp_path_type;
        mp_simplify_path(mp, cur_exp_knot);
    }
}

static void mp_set_up_make_path(MP mp, int c)
{
    if (cur_exp_type != mp_pen_type && cur_exp_type != mp_nep_type) {
        mp_bad_unary(mp, c);
    } else {
        cur_exp_type = mp_path_type;
        mp_make_path(mp, cur_exp_knot);
    }
}

static void mp_set_up_corner(MP mp, int c)
{
    if (mp_get_cur_bbox(mp)) {
        switch (c) {
            case mp_ll_corner_operation:
                mp_pair_value(mp, &mp_minx, &mp_miny);
                break;
            case mp_lr_corner_operation:
                mp_pair_value(mp, &mp_maxx, &mp_miny);
                break;
            case mp_ul_corner_operation:
                mp_pair_value(mp, &mp_minx, &mp_maxy);
                break;
            case mp_ur_corner_operation:
                mp_pair_value(mp, &mp_maxx, &mp_maxy);
                break;
            case mp_corners_operation:
                mp_make_bounding_box(mp);
                break;
        }
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_range(MP mp, int c)
{
    switch (c) {
        case mp_x_range_operation:
            if (mp_get_cur_xbox(mp)) {
                mp_pair_value(mp, &mp_minx, &mp_maxx);
                return;
            } else {
                break;
            }
        case mp_y_range_operation:
            if (mp_get_cur_ybox(mp)) {
                mp_pair_value(mp, &mp_miny, &mp_maxy);
                return;
            } else {
                break;
            }
        default:
            break;
    }
    mp_bad_unary(mp, c);
}

static void mp_set_up_from(MP mp, int c)
{
    if (cur_exp_type != mp_string_type) {
        mp_bad_unary(mp, c);
    } else {
        mp_do_read_or_close(mp, c);
    }
}

static void mp_set_up_plus(MP mp, int c)
{
    if (cur_exp_type < mp_color_type) {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_minus(MP mp, int c)
{
    switch (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, 2);
                if (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 (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 (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)) {
                mp_number_negate(cur_exp_value_number);
            }
            break;
        default:
            mp_bad_unary(mp, c);
            break;
    }
}

static void mp_set_up_part(MP mp, int c)
{
    switch (cur_exp_type) {
        case mp_known_type:
            switch (c) {
                case mp_grey_part_operation:
                    /* self */
                    return;
                default:
                    goto BAD;
            }
        case mp_pair_type:
            switch (c) {
                case mp_x_part_operation:
                case mp_y_part_operation:
                    /* take */
                    break;
                default:
                    goto BAD;
            }
            break;
        case mp_transform_type:
            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:
                    /* take */
                    break;
                default:
                    goto BAD;
            }
            break;
        case mp_picture_type:
            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:
                    break;
                case mp_red_part_operation:
                case mp_green_part_operation:
                case mp_blue_part_operation:
                    if (mp_pict_color_type(mp, mp_rgb_model)) {
                        break;
                    } else {
                        mp_bad_color_part(mp, c);
                        return;
                    }
                case mp_cyan_part_operation:
                case mp_magenta_part_operation:
                case mp_yellow_part_operation:
                case mp_black_part_operation:
                    if (mp_pict_color_type(mp, mp_cmyk_model)) {
                        break;
                    } else {
                        mp_bad_color_part(mp, c);
                        return;
                    }
                case mp_grey_part_operation:
                    if (mp_pict_color_type(mp, mp_grey_model)) {
                        break;
                    } else {
                        mp_bad_color_part(mp, c);
                        return;
                    }
                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:
                    break;
                default:
                    goto BAD;
            }
            mp_take_picture_part(mp, c);
            return;
        case mp_color_type:
            switch (c) {
                case mp_red_part_operation:
                case mp_green_part_operation:
                case mp_blue_part_operation:
                    /* take */
                case mp_x_part_operation:
                case mp_y_part_operation:
                case mp_z_part_operation:
                    /* also */
                    break;
                default:
                    goto BAD;
            }
            break;
        case mp_cmykcolor_type:
            switch (c) {
                case mp_cyan_part_operation:
                case mp_magenta_part_operation:
                case mp_yellow_part_operation:
                case mp_black_part_operation:
                    /* take */
                case mp_x_part_operation:
                case mp_y_part_operation:
                case mp_z_part_operation:
                case mp_w_part_operation:
                    /* also */
                    break;
                default:
                    goto BAD;
            }
            break;
        default:
            goto BAD;
    }
    mp_take_part(mp, c);
    return;
  BAD:
    mp_bad_unary(mp, c);
}

/* Trace the current unary operation */

static void mp_trace_unary(MP mp, int c)
{
    mp_begin_diagnostic(mp);
    mp_print_nl(mp, "{");
    mp_print_operator(mp, c);
    mp_print_char(mp, '(');
    mp_print_exp(mp, NULL, 0); /* show the operand, but not verbosely */
    mp_print_string(mp, ")}");
    mp_end_diagnostic(mp, 0);
}

static void mp_do_unary(MP mp, int c)
{
    mp_check_arithmic(mp);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
        mp_trace_unary(mp, c);
    }
    switch (c) {
        case mp_plus_operation:
            mp_set_up_plus(mp, c);
            break;
        case mp_minus_operation:
            mp_set_up_minus(mp, c);
            break;
        case mp_not_operation:
            mp_set_up_not(mp, c);
            break;
        case mp_sqrt_operation:
            mp_set_up_sqrt(mp, c);
            break;
        case mp_norm_operation:
            mp_set_up_norm(mp, c);
            break;
        case mp_m_exp_operation:
            mp_set_up_m_exp(mp, c);
            break;
        case mp_m_log_operation:
            mp_set_up_m_log(mp, c);
            break;
        case mp_sin_d_operation:
        case mp_cos_d_operation:
            mp_set_up_sin_cos_d(mp, c);
            break;
        case mp_floor_operation:
            mp_set_up_floor(mp, c);
            break;
        case mp_normalize_operation:
            mp_set_up_normalize(mp, c);
            break;
        case mp_uniform_deviate_operation:
            mp_set_up_uniform_deviate(mp, c);
            break;
        case mp_odd_operation:
            mp_set_up_odd(mp, c);
            break;
        case mp_angle_operation:
            mp_set_up_angle(mp, c);
            break;
        case mp_x_part_operation:
        case mp_y_part_operation:
        case mp_z_part_operation:
        case mp_w_part_operation:
        case mp_xx_part_operation:
        case mp_xy_part_operation:
        case mp_yx_part_operation:
        case mp_yy_part_operation:
        case mp_red_part_operation:
        case mp_green_part_operation:
        case mp_blue_part_operation:
        case mp_cyan_part_operation:
        case mp_magenta_part_operation:
        case mp_yellow_part_operation:
        case mp_black_part_operation:
        case mp_grey_part_operation:
        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:
            mp_set_up_part(mp, c);
            break;
        case mp_char_operation:
            mp_set_up_char(mp, c);
            break;
        case mp_decimal_operation:
            mp_set_up_decimal(mp, c);
            break;
        case mp_oct_operation:
        case mp_hex_operation:
        case mp_ASCII_operation:
            mp_set_up_to_string(mp, c);
            break;
        case mp_segments_operation:
            mp_set_up_segments(mp, c);
            break;
        case mp_prune_singularities_operation:
            mp_set_up_prune_singularities(mp, c);
            break;
        case mp_length_operation:
            mp_set_up_length(mp, c);
            break;
        case mp_no_length_operation:
            mp_set_up_no_length(mp, c);
            break;
        case mp_first_point_operation:
        case mp_first_pre_control_operation:
        case mp_first_post_control_operation:
        case mp_first_direction_operation:
        case mp_first_unit_direction_operation:
        case mp_last_point_operation:
        case mp_last_pre_control_operation:
        case mp_last_post_control_operation:
        case mp_last_direction_operation:
        case mp_last_unit_direction_operation:
            mp_set_up_some_point(mp, c);
            break;
        case mp_turning_operation:
            mp_set_up_turning(mp, c);
            break;
        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_set_up_type_1(mp, c);
            break;
        case mp_transform_type_operation:
        case mp_color_type_operation:
        case mp_cmykcolor_type_operation:
        case mp_pair_type_operation:
            mp_set_up_type_2(mp, c);
            break;
        case mp_numeric_type_operation:
            mp_set_up_type_3(mp, c);
            break;
        case mp_known_operation:
        case mp_unknown_operation:
            mp_set_up_known(mp, c);
            break;
        case mp_cycle_operation:
        case mp_recycle_operation:
        case mp_no_cycle_operation:
            mp_set_up_cycle(mp, c);
            break;
        case mp_arc_length_operation:
            mp_set_up_arc_length(mp, c);
            break;
        case mp_filled_operation:
        case mp_stroked_operation:
        case mp_clipped_operation:
        case mp_grouped_operation:
        case mp_bounded_operation:
            mp_set_up_group(mp, c);
            break;
        case mp_make_pen_operation:
            mp_set_up_pen(mp, c);
            break;
        case mp_make_nep_operation:
            mp_set_up_nep(mp, c);
            break;
        case mp_convexed_operation:
            mp_set_up_convexed(mp, c);
            break;
        case mp_uncontrolled_operation:
            mp_set_up_uncontrolled(mp, c);
            break;
        case mp_make_path_operation:
            mp_set_up_make_path(mp, c);
            break;
        case mp_reverse_operation:
            mp_set_up_reverse(mp, c);
            break;
        case mp_uncycle_operation:
            mp_set_up_uncycle(mp, c);
            break;
        case mp_singularity_operation:
            mp_set_up_singularity(mp, c);
            break;
        case mp_ll_corner_operation:
        case mp_lr_corner_operation:
        case mp_ul_corner_operation:
        case mp_ur_corner_operation:
        case mp_corners_operation:
            mp_set_up_corner(mp, c);
            break;
        case mp_center_of_operation:
            mp_set_up_center_of(mp, c);
            break;
        case mp_center_of_mass_operation:
            mp_set_up_center_of_mass(mp, c);
            break;
        case mp_x_range_operation:
        case mp_y_range_operation:
            mp_set_up_range(mp, c);
            break;
        case mp_delta_point_operation:
        case mp_delta_precontrol_operation:
        case mp_delta_postcontrol_operation:
        case mp_delta_direction_operation:
        case mp_delta_unit_direction_operation:
        case mp_delta_arclength_operation:
        case mp_delta_iterator_index_operation:
            mp_set_up_delta(mp, c);
            break;
        case mp_read_from_operation:
        case mp_close_from_operation:
            mp_set_up_from(mp, c);
            break;
    }
    mp_check_arithmic(mp);
}

/*tex

The |nice_pair| function returns |true| if both components of a pair are known.The
|nice_color_or_pair| function is analogous except that it also accepts fully known colors.

Negation is easy except when the current expression is of type |independent|, or when it is a pair
with one or more |independent| components.It is tempting to argue that the negative of an independent
variable is an independent variable, hence we don't have to do anything when negating it. The fallacy
is that other dependent variables pointing to the current expression must change the sign of their
coefficients if we make no change to the current expression.

Instead, we work around the problem by copying the current expression and recycling it afterwards
(cf.~the |stash_in| routine). If the current expression is a pair, but the context wants it to be a
path, we call |pair_to_path|.

*/

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));
    mp_new_number(new_expr.data.n);
    p = mp_edge_list(cur_exp_node)->link;
 // mp_display_error(mp, NULL);
    selector = mp->selector;
    mp->selector = mp_new_string_selector;
    mp_print_operator(mp, c);
    sname = mp_make_string(mp);
    mp->selector = selector;
    switch (mp_color_model(p)) {
        case mp_grey_model: snprintf(msg, 256, "Wrong picture color model: %s of grey object",      mp_str(mp, sname)); break;
        case mp_cmyk_model: snprintf(msg, 256, "Wrong picture color model: %s of cmyk object",      mp_str(mp, sname)); break;
        case mp_rgb_model:  snprintf(msg, 256, "Wrong picture color model: %s of rgb object",       mp_str(mp, sname)); break;
        case mp_no_model:   snprintf(msg, 256, "Wrong picture color model: %s of marking object",   mp_str(mp, sname)); break;
        default:            snprintf(msg, 256, "Wrong picture color model: %s of defaulted object", mp_str(mp, sname)); break;
    }
    mp_delete_string_reference(mp, sname);
    mp_display_error(mp, NULL);
    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) {
        mp_number_clone(new_expr.data.n, mp_unity_t);
    } else {
        mp_set_number_to_zero(new_expr.data.n);
    }
    mp_flush_cur_exp(mp, new_expr);
}

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 = mp_number_to_double(*AX);
    double ay = mp_number_to_double(*AY);
    double bx = mp_number_to_double(*BX);
    double by = mp_number_to_double(*BY);
    double cx = mp_number_to_double(*CX);
    double cy = mp_number_to_double(*CY);
    double dx = mp_number_to_double(*DX);
    double dy = mp_number_to_double(*DY);
    mp_new_number_from_sub(deltax, *BX, *AX);
    mp_new_number_from_sub(deltay, *BY, *AY);
    if (mp_number_zero(deltax) && mp_number_zero(deltay)) {
        mp_set_number_from_subtraction(deltax, *CX, *AX);
        mp_set_number_from_subtraction(deltay, *CY, *AY);
    }
    if (mp_number_zero(deltax) && mp_number_zero(deltay)) {
        mp_set_number_from_subtraction(deltax, *DX, *AX);
        mp_set_number_from_subtraction(deltay, *DY, *AY);
    }
    mp_new_number(xi);
    mp_new_number(xm);
    mp_new_number(xo);
    mp_an_angle(mp, &xi, &deltax, &deltay);
    mp_set_number_from_subtraction(deltax, *CX, *BX);
    mp_set_number_from_subtraction(deltay, *CY, *BY);
    mp_an_angle(mp, &xm, &deltax, &deltay); /* !!! never used? */
    mp_set_number_from_subtraction(deltax, *DX, *CX);
    mp_set_number_from_subtraction(deltay, *DY, *CY);
    if (mp_number_zero(deltax) && mp_number_zero(deltay)) {
        mp_set_number_from_subtraction(deltax, *DX, *BX);
        mp_set_number_from_subtraction(deltay, *DY, *BY);
    }
    if (mp_number_zero(deltax) && mp_number_zero(deltay)) {
        mp_set_number_from_subtraction(deltax, *DX, *AX);
        mp_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(mp_number_to_double(xo)) - mp_out(mp_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(mp_number_to_double(xo)) - mp_out(mp_number_to_double(xi)); /* ? */
            if (res < -180.0) {
                res += 360.0;
            } else if (res > 180.0) {
                res -= 360.0;
            }
        } else {
            res = mp_out(mp_number_to_double(xo)) - mp_out(mp_number_to_double(xi)); /* ? */
        }
    } else if ((mp_sign (a) * mp_sign (c)) < 0.0) {
        res = mp_out(mp_number_to_double(xo)) - mp_out(mp_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(mp_number_to_double(xo)) - mp_out(mp_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) mp_bezier_error;
    } else if ((b * b) < (4.0 * a * c)) {
        res = mp_out(mp_number_to_double(xo)) - mp_out(mp_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(mp_number_to_double(xo)) - mp_out(mp_number_to_double(xi));
        if (res < -180.0) {
            res += 360.0;
        } else if (res > 180.0) {
            res -= 360.0;
        }
    }
    mp_free_number(deltax);
    mp_free_number(deltay);
    mp_free_number(xi);
    mp_free_number(xo);
    mp_free_number(xm);
    mp_set_number_from_double(*ret, res);
    mp_convert_scaled_to_angle(*ret);
}

/*tex

The |pair_value| routine changes the current expression to a given ordered pair of values.

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.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. The string denoting end-of-file is a one-byte string at position zero, by definition. I have
to cheat a little here because .... CHECK

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.

*/

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_operator(mp, c);
    }
    mp_print_known_or_unknown_type(mp, p->type, p);
    if (c >= mp_min_of_operation) {
        mp_print_string(mp, "of");
    } else {
        mp_print_operator(mp, c);
    }
    mp_print_known_or_unknown_type(mp, cur_exp_type, cur_exp_node);
    sname = mp_make_string(mp);
    mp->selector = selector;
    snprintf(msg, 256, "Not implemented: %s", mp_str(mp, sname));
    mp_delete_string_reference(mp, sname);
    mp_display_error(mp, p);
 // mp_display_error(mp, NULL); /* weird */
    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\n"
        "operation."
    );
    mp_get_x_next(mp);
}

static void mp_bad_envelope_pen(MP mp)
{
    mp_display_error(mp, NULL);
 // mp_display_error(mp, NULL); /* weird */
    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."
    );
    mp_get_x_next(mp);
}

static mp_node mp_tarnished(MP mp, mp_node p)
{
    mp_node q = mp_get_value_node(p);
    (void) mp;
    switch (p->type) {
        case mp_pair_type:
            return (
                (mp_x_part(q)->type == mp_independent_type) ||
                (mp_y_part(q)->type == mp_independent_type)
            ) ? MP_VOID : NULL;
        case mp_color_type:
            return (
                (mp_red_part  (q)->type == mp_independent_type) ||
                (mp_green_part(q)->type == mp_independent_type) ||
                (mp_blue_part (q)->type == mp_independent_type)
            ) ? MP_VOID : NULL;
        case mp_cmykcolor_type:
            return (
                (mp_cyan_part   (q)->type == mp_independent_type) ||
                (mp_magenta_part(q)->type == mp_independent_type) ||
                (mp_yellow_part (q)->type == mp_independent_type) ||
                (mp_black_part  (q)->type == mp_independent_type)
            ) ? MP_VOID : NULL;
        case mp_transform_type:
            return (
                (mp_tx_part(q)->type == mp_independent_type) ||
                (mp_ty_part(q)->type == mp_independent_type) ||
                (mp_xx_part(q)->type == mp_independent_type) ||
                (mp_xy_part(q)->type == mp_independent_type) ||
                (mp_yx_part(q)->type == mp_independent_type) ||
                (mp_yy_part(q)->type == mp_independent_type)
            ) ? MP_VOID : NULL;
        default:
            return NULL;
    }
}

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; /*tex The destination. */
    mp_set_dep_list(p, v);
    p->type = t;
    if (mp_get_dep_info(v) == NULL) {
        mp_number vv; /*tex The value, if it is |known|. */
        mp_new_number_clone(vv, mp_get_value_number(v));
        if (q == NULL) {
            mp_value new_expr;
            memset(&new_expr, 0, sizeof(mp_value));
            mp_new_number_clone(new_expr.data.n, vv);
            mp_flush_cur_exp(mp, new_expr);
        } else {
            mp_recycle_value(mp, (mp_node) p);
            q->type = mp_known_type;
            mp_set_value_number(q, vv);
        }
        mp_free_number(vv);
    } else if (q == NULL) {
        cur_exp_type = t;
    }
    if (mp->fix_needed) {
        mp_fix_dependencies(mp);
    }
}

static void mp_add_or_subtract(MP mp, mp_node p, mp_node q, int c)
{
    mp_variable_type s, t;  /*tex operand types */
    mp_value_node r;        /*tex dependency list traverser */
    mp_value_node v = NULL; /*tex second operand value for dep lists */
    mp_number vv;           /*tex second operand value for known values */
    mp_new_number(vv);
    if (q == NULL) {
        t = cur_exp_type;
        if (t < mp_dependent_type) {
            mp_number_clone(vv, cur_exp_value_number);
        } else {
            v = (mp_value_node) mp_get_dep_list((mp_value_node) cur_exp_node);
        }
    } else {
        t = q->type;
        if (t < mp_dependent_type) {
            mp_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 (p->type == mp_known_type) {
            if (c == mp_minus_operation) {
                mp_slow_sub(vv, mp_get_value_number(p), vv);
            } else {
                mp_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);
            }
            mp_free_number(vv);
            return;
        } else {
            /*tex
                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) r->link;
            }
            if (c == mp_minus_operation) {
                mp_slow_sub(vv, mp_get_dep_value(r), vv);
            } else {
                mp_slow_add(vv, mp_get_dep_value(r), vv);
            }
            mp_set_dep_value(r, vv);
            if (qq == NULL) {
                qq = mp_get_dep_node(mp, 8);
                mp_set_cur_exp_node(mp, (mp_node) qq);
                cur_exp_type = p->type;
                qq->name_type = mp_capsule_operation;
                /* clang: never read: |q = (mp_node) qq;| */
            }
            mp_set_dep_list(qq, mp_get_dep_list((mp_value_node) p));
            qq->type = p->type;
            mp_set_prev_dep(qq, mp_get_prev_dep((mp_value_node) p));
            mp_get_prev_dep((mp_value_node) p)->link = (mp_node) qq;
            p->type = mp_known_type; /* this will keep the recycler from collecting non-garbage */
        }
    } else {
        if (c == mp_minus_operation) {
            mp_negate_dep_list(mp, v);
        }
        /*tex
            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 (p->type == 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) v->link;
            }
            mp_slow_add(vv, mp_get_value_number(p), mp_get_dep_value(v));
            mp_set_dep_value(v, vv);
        } else {
            s = p->type;
            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;
                    mp_new_fraction(ret1);
                    mp_new_fraction(ret2);
                    mp_max_coef(mp, &ret1, r);
                    mp_max_coef(mp, &ret2, v);
                    mp_number_add(ret1, ret2);
                    b = mp_number_less(ret1, mp_coef_bound_k);
                    mp_free_number(ret1);
                    mp_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, &mp_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, &mp_unity_t, r, mp_proto_dependent_type, mp_dependent_type);
            }
          DONE:
            /*tex
                Output the answer, |v| (which might have become |known|).
            */
            if (q != NULL) {
                mp_dep_finish(mp, v, (mp_value_node) q, t);
            } else {
                cur_exp_type = t;
                mp_dep_finish(mp, v, NULL, t);
            }
        }
    }
    mp_free_number(vv);
}

static void mp_dep_mult(MP mp, mp_value_node p, mp_number *v, int v_is_scaled)
{
    mp_value_node q; /*tex the dependency list being multiplied by |v| */
    int s, t;        /*tex its type, before and after */
    if (p == NULL) {
        q = (mp_value_node) cur_exp_node;
    } else if (p->type != mp_known_type) {
        q = p;
    } else {
        mp_number r1, arg1;
        mp_new_number_clone(arg1, mp_get_dep_value(p));
        if (v_is_scaled) {
            mp_new_number(r1);
            mp_take_scaled(r1, arg1, *v);
        } else {
            mp_new_fraction(r1);
            mp_take_fraction(r1, arg1, *v);
        }
        mp_set_dep_value(p, r1);
        mp_free_number(r1);
        mp_free_number(arg1);
        return;
    }
    t = q->type;
    q = (mp_value_node) mp_get_dep_list(q);
    s = t;
    if (t == mp_dependent_type && v_is_scaled) {
        mp_number arg1, arg2;
        mp_new_fraction(arg1);
        mp_max_coef(mp, &arg1, q);
        mp_new_number_abs(arg2, *v);
        if (mp_ab_vs_cd(arg1, arg2, mp_coef_bound_minus_1, mp_unity_t) >= 0) {
            t = mp_proto_dependent_type;
        }
        mp_free_number(arg1);
        mp_free_number(arg2);
    }
    q = mp_p_times_v(mp, q, v, s, t, v_is_scaled);
    mp_dep_finish(mp, q, p, t);
}

static void mp_hard_times(MP mp, mp_node p)
{
    if (p->type <= mp_pair_type) {
        mp_value_node q = (mp_value_node) mp_stash_cur_exp(mp);
        mp_unstash_cur_exp(mp, p);
        p = (mp_node) q;
    }
    /*tex

        Now |cur_type| is |mp_pair_type|, |mp_color_type| or |mp_cmykcolor_type|. The next code is
        kind of nasty. In version 3.15.01 a long standing dependencies bug was solved: the last
        (or first) of a set of fields is treated differently from the rest, so there are \im {n
        - 1} calls to |mp_new_dep| while the left over field is hooked into the main dependency
        list. This could lead to a performance drop which was noticed by DEK in 2025.

    */
    switch (cur_exp_type) {
        case mp_pair_type:
            {
                mp_node e = mp_get_value_node(cur_exp_node);
                mp_number x, y;
                mp_new_number_clone(x, mp_get_value_number(mp_x_part(e)));
                mp_new_number_clone(y, mp_get_value_number(mp_y_part(e)));
                mp_new_dep(mp, mp_y_part(e), p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 6);
                mp_free_value_node(mp, mp_x_part(e));
                mp_x_part(e) = p;
                mp_set_link(mp_get_prev_dep(p), mp_x_part(e));
                mp_dep_mult(mp, (mp_value_node) mp_x_part(e), &x, 1);
                mp_dep_mult(mp, (mp_value_node) mp_y_part(e), &y, 1);
                mp_free_number(x);
                mp_free_number(y);
            }
            break;
        case mp_color_type:
            {
                mp_node e = mp_get_value_node(cur_exp_node);
                mp_number r, g, b;
                mp_new_number_clone(r, mp_get_value_number(mp_red_part(e)));
                mp_new_number_clone(g, mp_get_value_number(mp_green_part(e)));
                mp_new_number_clone(b, mp_get_value_number(mp_blue_part(e)));
                mp_new_dep(mp, mp_blue_part(e),  p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 9);
                mp_new_dep(mp, mp_green_part(e), p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 8);
                mp_free_value_node(mp, mp_red_part(e));
                mp_red_part(e) = p;
                mp_set_link(mp_get_prev_dep(p), mp_red_part(e));
                mp_dep_mult(mp, (mp_value_node) mp_red_part(e),   &r, 1);
                mp_dep_mult(mp, (mp_value_node) mp_green_part(e), &g, 1);
                mp_dep_mult(mp, (mp_value_node) mp_blue_part(e),  &b, 1);
                mp_free_number(r);
                mp_free_number(g);
                mp_free_number(b);
            }
            break;
        case mp_cmykcolor_type:
            {
                mp_node e = mp_get_value_node(cur_exp_node);
                mp_number c, m, y, k;
                mp_new_number_clone(c, mp_get_value_number(mp_cyan_part(e)));
                mp_new_number_clone(m, mp_get_value_number(mp_magenta_part(e)));
                mp_new_number_clone(y, mp_get_value_number(mp_yellow_part(e)));
                mp_new_number_clone(k, mp_get_value_number(mp_black_part(e)));
                mp_new_dep(mp, mp_black_part(e),   p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 11);
                mp_new_dep(mp, mp_yellow_part(e),  p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 12);
                mp_new_dep(mp, mp_magenta_part(e), p->type, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p)), 13);
                mp_free_value_node(mp, mp_cyan_part(e));
                mp_cyan_part(e) = p;
                mp_set_link(mp_get_prev_dep(p), mp_cyan_part(e));
                mp_dep_mult(mp, (mp_value_node) mp_cyan_part(e),    &c, 1);
                mp_dep_mult(mp, (mp_value_node) mp_magenta_part(e), &m, 1);
                mp_dep_mult(mp, (mp_value_node) mp_yellow_part(e),  &y, 1);
                mp_dep_mult(mp, (mp_value_node) mp_black_part(e),   &k, 1);
                mp_free_number(c);
                mp_free_number(m);
                mp_free_number(y);
                mp_free_number(k);
            }
            break;
        default:
            break;
    }
}

static void mp_harder_times(MP mp, mp_node p)
{
    if (p->type <= mp_pair_type) {
        mp_value_node q = (mp_value_node) mp_stash_cur_exp(mp);
        mp_unstash_cur_exp(mp, p);
        p = (mp_node) q;
    }
    {
        mp_node e = mp_get_value_node(cur_exp_node);
        mp_number x, y, z;
        mp_new_number_from_mul(x, mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_red_part(mp_get_value_node(p))));
        mp_new_number_from_mul(y, mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_green_part(mp_get_value_node(p))));
        mp_new_number_from_mul(z, mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_blue_part(mp_get_value_node(p))));
        mp_set_value_number(mp_red_part(e), x);
        mp_set_value_number(mp_green_part(e), y);
        mp_set_value_number(mp_blue_part(e), z);
        mp_free_number(x);
        mp_free_number(y);
        mp_free_number(z);
    }
}

static void mp_dep_div(MP mp, mp_value_node p, mp_number *v)
{
    mp_value_node q; /*tex the dependency list being divided by |v| */
    int s, t;        /*tex its type, before and after */
    if (p == NULL) {
        q = (mp_value_node) cur_exp_node;
    } else if (p->type != mp_known_type) {
        q = p;
    } else {
        mp_number ret;
        mp_new_number(ret);
        mp_make_scaled(ret, mp_get_value_number(p), *v);
        mp_set_value_number(p, ret);
        mp_free_number(ret);
        return;
    }
    t = q->type;
    q = (mp_value_node) mp_get_dep_list(q);
    s = t;
    if (t == mp_dependent_type) {
        mp_number arg1, arg2;
        mp_new_number(arg2);
        mp_new_fraction(arg1);
        mp_max_coef(mp, &arg1, q);
        mp_number_abs_clone(arg2, *v);
        if (mp_ab_vs_cd(arg1, mp_unity_t, mp_coef_bound_minus_1, arg2) >= 0) {
            t = mp_proto_dependent_type;
        }
        mp_free_number(arg1);
        mp_free_number(arg2);
    }
    q = mp_p_over_v(mp, q, v, s, t);
    mp_dep_finish(mp, q, p, t);
}

static void mp_set_up_trans(MP mp, int c)
{
    mp_node p, q, r;
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    if ((c != mp_transformed_operation) || (cur_exp_type != mp_transform_type)) {
        /*tex
            Put the current transform into |cur_exp|.
        */
        p = mp_stash_cur_exp(mp);
        mp_set_cur_exp_node(mp, mp_id_transform(mp));
        cur_exp_type = mp_transform_type;
        q = mp_get_value_node(cur_exp_node);
        /*tex
            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.
        */
        switch (c) {
            case mp_rotated_operation:
                if (p->type == mp_known_type) {
                    /*tex
                        Install sines and cosines, then |goto done|.
                    */
                    mp_number n_sin, n_cos, arg1, arg2;
                    mp_new_fraction(n_sin);
                    mp_new_fraction(n_cos);
                    mp_new_number_clone(arg2, mp_unity_t);
                    mp_new_number_clone(arg1, mp_get_value_number(p));
                    mp_number_multiply_int(arg2, 360);
                    mp_number_modulo(arg1, arg2);
                    mp_convert_scaled_to_angle(arg1);
                    mp_n_sin_cos(arg1, n_cos, n_sin);
                    mp_fraction_to_round_scaled(n_sin);
                    mp_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)));
                    mp_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)));
                    mp_free_number(arg1);
                    mp_free_number(arg2);
                    mp_free_number(n_sin);
                    mp_free_number(n_cos);
                    goto DONE;
                }
                break;
            case mp_slanted_operation:
                if (p->type > mp_pair_type) {
                    mp_install(mp, mp_xy_part(q), p);
                    goto DONE;
                }
                break;
            case mp_scaled_operation:
                if (p->type > 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 (p->type == 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 (p->type > mp_pair_type) {
                    mp_install(mp, mp_xx_part(q), p);
                    goto DONE;
                }
                break;
            case mp_y_scaled_operation:
                if (p->type > mp_pair_type) {
                    mp_install(mp, mp_yy_part(q), p);
                    goto DONE;
                }
                break;
            case mp_z_scaled_operation:
                if (p->type == mp_pair_type) {
                    /*tex
                        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_y_part(r)->type == mp_known_type) {
                            mp_set_value_number(mp_y_part(r), mp_get_value_number(mp_y_part(r)));
                            mp_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;
                    }
                }
                break;
            case mp_xy_scaled_operation:
                if (p->type == mp_pair_type) {
                    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_y_part(r));
                    goto DONE;
                } else if (p->type > mp_pair_type) {
                    mp_install(mp, mp_xx_part(q), p);
                    mp_install(mp, mp_yy_part(q), p);
                    goto DONE;
                }
                break;
            case mp_x_shifted_operation:
                if (p->type == mp_pair_type) {
                    r = mp_get_value_node(p);
                    mp_install(mp, mp_tx_part(q), mp_x_part(r));
                    goto DONE;
                }
                break;
            case mp_y_shifted_operation:
                if (p->type == mp_pair_type) {
                    r = mp_get_value_node(p);
                    mp_install(mp, mp_ty_part(q), mp_y_part(r));
                    goto DONE;
                }
                break;
            case mp_bytemap_scaled_operation:
                if (p->type == mp_known_type) {
                    int index = (int) mp_round_unscaled(mp_get_value_number(p));
                    int nx = 1;
                    int ny = 1;
                    mp_number sx, sy;
                    if (mp_bytemap_valid_data(mp, index)) {
                        bytemap_data b = mp->bytemaps[index];
                        nx = b.nx;
                        ny = b.ny;
                    }
                    mp_new_number(sx);
                    mp_new_number(sy);
                    mp_set_number_from_int(sx, nx);
                    mp_set_number_from_int(sy, ny);
                    mp_set_value_number(mp_xx_part(q), sx);
                    mp_set_value_number(mp_yy_part(q), sy);
                    mp_free_number(sx);
                    mp_free_number(sy);
                    goto DONE;
                }
                break;
            case mp_transformed_operation:
                break;
        }
        mp_display_error(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);
    }
    /*tex
        If the current transform is entirely known, stash it in global variables; otherwise |return|
    */
    q = mp_get_value_node(cur_exp_node);
    if (mp_transform_is_known(q)) {
        mp_number_clone(mp->txx, mp_get_value_number(mp_xx_part(q)));
        mp_number_clone(mp->txy, mp_get_value_number(mp_xy_part(q)));
        mp_number_clone(mp->tyx, mp_get_value_number(mp_yx_part(q)));
        mp_number_clone(mp->tyy, mp_get_value_number(mp_yy_part(q)));
        mp_number_clone(mp->tx,  mp_get_value_number(mp_tx_part(q)));
        mp_number_clone(mp->ty,  mp_get_value_number(mp_ty_part(q)));
        mp_new_number(new_expr.data.n);
        mp_flush_cur_exp(mp, new_expr);
    }
}

static void mp_set_up_known_trans(MP mp, int c)
{
    mp_set_up_trans(mp, c);
    if (cur_exp_type != mp_known_type) {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        mp_display_error(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);
        mp_set_number_to_unity(mp->txx);
        mp_set_number_to_zero(mp->txy);
        mp_set_number_to_zero(mp->tyx);
        mp_set_number_to_unity(mp->tyy);
        mp_set_number_to_zero(mp->tx);
        mp_set_number_to_zero(mp->ty);
    }
}

static void mp_number_trans(MP mp, mp_number *p, mp_number *q)
{
    mp_number r1, r2, v;
    mp_new_number(r1);
    mp_new_number(r2);
    mp_take_scaled(r1, *p, mp->txx);
    mp_take_scaled(r2, *q, mp->txy);
    mp_number_add(r1, r2);
    mp_new_number_from_add(v, r1, mp->tx);
    mp_take_scaled(r1, *p, mp->tyx);
    mp_take_scaled(r2, *q, mp->tyy);
    mp_number_add(r1, r2);
    mp_set_number_from_addition(*q, r1, mp->ty);
    mp_number_clone(*p,v);
    mp_free_number(r1);
    mp_free_number(r2);
    mp_free_number(v);
}

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);
}

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);
}

static void mp_do_path_pen_trans(MP mp, mp_shape_node p, mp_number *sqdet, int sgndet)
{
    if (mp_pen_ptr(p) != NULL) {
        mp_number sx, sy;
        mp_new_number_clone(sx, mp->tx);
        mp_new_number_clone(sy, mp->ty);
        mp_set_number_to_zero(mp->tx);
        mp_set_number_to_zero(mp->ty);
        mp_do_pen_trans(mp, mp_pen_ptr(p));
        if (mp_number_nonzero(*sqdet) && ((p->type == mp_stroked_node_type) && (mp_dash_ptr(p) != NULL))) {
            mp_number ret;
            mp_new_number(ret);
            mp_take_scaled(ret, ((mp_shape_node) p)->dashscale, *sqdet);
            mp_number_clone(((mp_shape_node) p)->dashscale, ret);
            mp_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);
        }
        mp_number_clone(mp->tx, sx);
        mp_number_clone(mp->ty, sy);
        mp_free_number(sx);
        mp_free_number(sy);
    }
}

static mp_edge_header_node mp_edges_trans(MP mp, mp_edge_header_node h)
{
    mp_node q;         /*tex the object being transformed */
    mp_dash_node r, s; /*tex for list manipulation */
    mp_number sqdet;   /*tex square root of determinant for |dashscale| */
    int sgndet;        /*tex sign of the determinant */
    h = mp_private_edges(mp, h);
    mp_new_number(sqdet);
    mp_sqrt_det(mp, &sqdet, &(mp->txx), &(mp->txy), &(mp->tyx), &(mp->tyy));
    sgndet = mp_ab_vs_cd(mp->txx, mp->tyy, mp->txy, mp->tyx);
    if (mp_get_dash_list(h) != mp->null_dash) {
        /*tex
            Try to transform the dash list of |h|.
        */
        if (mp_number_nonzero(mp->txy) || mp_number_nonzero(mp->tyx) || mp_number_nonzero(mp->ty) || mp_number_non_equal_abs(mp->txx, mp->tyy)) {
            mp_flush_dash_list(mp, h);
        } else {
            mp_number abs_tyy, ret;
            mp_new_number(abs_tyy);
            if (mp_number_negative(mp->txx)) {
                /*tex
                    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 = r->link;
                    mp_number_swap(s->start_x, s->stop_x );
                    s->link = mp_get_dash_list(h);
                    mp_set_dash_list(h, s);
                }
            }
            /*tex
                Scale the dash list by |txx| and shift it by |tx|.
            */
            r = mp_get_dash_list(h);
            {
                mp_number arg1;
                mp_new_number(arg1);
                while (r != mp->null_dash) {
                    mp_take_scaled(arg1, r->start_x, mp->txx);
                    mp_set_number_from_addition(r->start_x, arg1, mp->tx);
                    mp_take_scaled(arg1, r->stop_x, mp->txx);
                    mp_set_number_from_addition(r->stop_x, arg1, mp->tx);
                    r = r->link;
                }
                mp_free_number(arg1);
            }
            mp_number_abs_clone(abs_tyy, mp->tyy);
            mp_new_number(ret);
            mp_take_scaled(ret, h->dash_y, abs_tyy);
            mp_number_clone(h->dash_y, ret);
            mp_free_number(ret);
            mp_free_number(abs_tyy);
        }
    }
    /*tex
        Make the bounding box of |h| unknown if it can't be updated properly without scanning the
        whole structure.
    */
    if (mp_number_zero(mp->txx) && mp_number_zero(mp->tyy)) {
        /*tex
            Swap the $x$ and $y$ parameters in the bounding box of |h|.
        */
        mp_number_swap(h->minx, h->miny);
        mp_number_swap(h->maxx, h->maxy);
    } else if (mp_number_nonzero(mp->txy) || mp_number_nonzero(mp->tyx)) {
        mp_init_bbox(mp, h);
        goto DONE1;
    }
    if (mp_number_lessequal(h->minx, h->maxx)) {
        /*tex
            Scale the bounding box by |txx+txy| and |tyx+tyy|; then shift by |(tx,ty)|.
        */
        mp_number tot, ret;
        mp_new_number(ret);
        mp_new_number_from_add(tot,mp->txx,mp->txy);
        mp_take_scaled(ret, h->minx, tot);
        mp_set_number_from_addition(h->minx,ret, mp->tx);
        mp_take_scaled(ret, h->maxx, tot);
        mp_set_number_from_addition(h->maxx,ret, mp->tx);
        mp_set_number_from_addition(tot,mp->tyx,mp->tyy);
        mp_take_scaled(ret, h->miny, tot);
        mp_set_number_from_addition(h->miny, ret, mp->ty);
        mp_take_scaled(ret, h->maxy, tot);
        mp_set_number_from_addition(h->maxy, ret, mp->ty);
        mp_set_number_from_addition(tot, mp->txx, mp->txy);
        if (mp_number_negative(tot)) {
            mp_number_swap(h->minx, h->maxx);
        }
        mp_set_number_from_addition(tot, mp->tyx, mp->tyy);
        if (mp_number_negative(tot)) {
            mp_number_swap(h->miny, h->maxy);
        }
        mp_free_number(ret);
        mp_free_number(tot);
    }
    DONE1:
    q = mp_edge_list(h)->link;
    while (q != NULL) {
        /*tex
            Transform graphical object |q|.
        */
        switch (q->type) {
            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;
        }
        q = q->link;
    }
    mp_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)
{
    mp_number_clone(mp->txx, *se_sf);
    mp_number_clone(mp->tyy, *se_sf);
    mp_set_number_to_zero(mp->txy);
    mp_set_number_to_zero(mp->tyx);
    mp_set_number_to_zero(mp->tx);
    mp_set_number_to_zero(mp->ty);
    return mp_edges_trans(mp, se_pic);
}

/*tex
    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;
    mp_new_number_clone(delta, *delta_orig);
    if (! mp_number_equal(*t, mp_unity_t)) {
        mp_dep_mult(mp, (mp_value_node) p, t, 1);
    }
    if (mp_number_nonzero(*u)) {
        if (q->type == mp_known_type) {
            mp_number tmp;
            mp_new_number(tmp);
            mp_take_scaled(tmp, mp_get_value_number(q), *u);
            mp_number_add(delta, tmp);
            mp_free_number(tmp);
        } else {
            /*tex
                Ensure that |type(p) = mp_proto_dependent|.
            */
            if (p->type != mp_proto_dependent_type) {
                if (p->type == mp_known_type) {
                    mp_new_dep(mp, p, p->type, mp_const_dependency(mp, &(mp_get_value_number(p))), 15);
                } 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), &mp_unity_t,
                            mp_dependent_type, mp_proto_dependent_type, 1));
                }
                p->type = 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, q->type));
        }
    }
    if (p->type == mp_known_type) {
        mp_set_value_number(p, mp_get_value_number(p));
        mp_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) r->link;
        }
        mp_new_number_clone(tmp, mp_get_value_number(r));
        mp_number_add(delta, tmp);
     // mp_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);
            p->type = mp_known_type;
            mp_set_value_number(p, delta);
        }
        mp_free_number(tmp);
    }
    if (mp->fix_needed) {
        mp_fix_dependencies(mp);
    }
    mp_free_number(delta);
}

static void mp_add_mult_dep(MP mp, mp_value_node p, mp_number *v, mp_node r)
{
    if (r->type == mp_known_type) {
        mp_number ret;
        mp_new_number(ret);
        mp_take_scaled(ret, mp_get_value_number(r), *v);
        mp_set_dep_value(mp->dep_final, mp_get_dep_value(mp->dep_final));
        mp_number_add(mp_get_dep_value(mp->dep_final), ret);
        mp_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, r->type));
        if (mp->fix_needed) {
            mp_fix_dependencies(mp);
        }
    }
}

static void mp_bilin2(MP mp, mp_node p, mp_node t, mp_number *v, mp_node u, mp_node q)
{
    mp_number vv; /*tex temporary storage for |value(p)| */
    mp_new_number_clone(vv, mp_get_value_number(p));
    /*tex This sets |dep_final|: */
    mp_new_dep(mp, p, mp_proto_dependent_type, mp_const_dependency(mp, &mp_zero_t), 16);
    if (mp_number_nonzero(vv)) {
         /*tex |dep_final| doesn't change. */
        mp_add_mult_dep(mp, (mp_value_node) p, &vv, t);
    }
    if (mp_number_nonzero(*v)) {
        mp_number arg1;
        mp_new_number_clone(arg1, *v);
        mp_add_mult_dep(mp, (mp_value_node) p, &arg1, u);
        mp_free_number(arg1);
    }
    if (q != NULL) {
        mp_add_mult_dep(mp, (mp_value_node) p, &mp_unity_t, q);
    }
    if (mp_get_dep_list((mp_value_node) p) == (mp_node) mp->dep_final) {
        mp_number_clone(vv, mp_get_dep_value(mp->dep_final));
        mp_recycle_value(mp, p);
        p->type = mp_known_type;
        mp_set_value_number(p, vv);
    }
    mp_free_number(vv);
}

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;
    mp_new_number(tmp);
    mp_new_number_clone(delta, *delta_orig);
    if (! mp_number_equal(*t, mp_unity_t)) {
        mp_take_scaled(tmp, mp_get_value_number(p), *t);
    } else {
        mp_number_clone(tmp, mp_get_value_number(p));
    }
    mp_number_add(delta, tmp);
    if (mp_number_nonzero(*u)) {
        mp_number ret;
        mp_new_number(ret);
        mp_take_scaled(ret, *v, *u);
        mp_set_value_number(p, delta);
        mp_number_add(mp_get_value_number(p), ret);
        mp_free_number(ret);
    } else {
        mp_set_value_number(p, delta);
    }
    mp_free_number(tmp);
    mp_free_number(delta);
}

static void mp_big_trans(MP mp, mp_node p, int c)
{
    mp_node q = mp_get_value_node(p);
    if (q->type == mp_pair_node_type) {
        if (mp_x_part(q)->type != mp_known_type || mp_y_part(q)->type != mp_known_type) {
            goto UNKNOWN;
        }
    } else if (! mp_transform_is_known(q)) {
        goto UNKNOWN;
    }
    {
        /*tex Transform a known big node. */
        mp_node r, pp, qq;
        mp_set_up_trans(mp, c);
        if (cur_exp_type == mp_known_type) {
            /*tex Transform known by known. */
            mp_make_exp_copy(mp, p, 19);
            r = mp_get_value_node(cur_exp_node);
            if (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), &mp_zero_t);
                mp_bilin3(mp, mp_yx_part(r), &(mp->tyy), &(mp_get_value_number(mp_xx_part(q))), &(mp->tyx), &mp_zero_t);
                mp_bilin3(mp, mp_xy_part(r), &(mp->txx), &(mp_get_value_number(mp_yy_part(q))), &(mp->txy), &mp_zero_t);
                mp_bilin3(mp, mp_xx_part(r), &(mp->txx), &(mp_get_value_number(mp_yx_part(q))), &(mp->txy), &mp_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, 20);
            r = mp_get_value_node(cur_exp_node);
            if (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);
        }
        return;
    }
  UNKNOWN:
    {
        /*tex Transform an unknown big node and |return|. */
        mp_node r;
        mp_set_up_known_trans(mp, c);
        mp_make_exp_copy(mp, p, 21);
        r = mp_get_value_node(cur_exp_node);
        if (cur_exp_type == mp_transform_type) {
            mp_bilin1(mp, mp_yy_part(r), &(mp->tyy), mp_xy_part(q), &(mp->tyx), &mp_zero_t);
            mp_bilin1(mp, mp_yx_part(r), &(mp->tyy), mp_xx_part(q), &(mp->tyx), &mp_zero_t);
            mp_bilin1(mp, mp_xy_part(r), &(mp->txx), mp_yy_part(q), &(mp->txy), &mp_zero_t);
            mp_bilin1(mp, mp_xx_part(r), &(mp->txx), mp_yx_part(q), &(mp->txy), &mp_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));
        return;
    }
}

static void mp_set_up_offset(MP mp, mp_node p, int c)
{
    if ((cur_exp_type == mp_pen_type || cur_exp_type == mp_nep_type) && mp_nice_pair(mp, p, p->type)) {
        mp_find_offset(mp, &(mp_get_value_number(mp_x_part(mp_get_value_node(p)))), &(mp_get_value_number(mp_y_part(mp_get_value_node(p)))), cur_exp_knot);
        mp_pair_value(mp, &(mp->cur_x), &(mp->cur_y));
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_direction_time(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && mp_nice_pair(mp, p, p->type)) {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        mp_find_direction_time(mp, &new_expr.data.n, &(mp_get_value_number(mp_x_part(mp_get_value_node(p)))), &(mp_get_value_number(mp_y_part(mp_get_value_node(p)))), cur_exp_knot);
        mp_flush_cur_exp(mp, new_expr);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_envelope(MP mp, mp_node p, int c)
{
    if ((p->type != mp_pen_type && p->type != mp_nep_type) || (cur_exp_type != mp_path_type)) {
        mp_bad_binary(mp, p, c);
    } else {
        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);
            cur_exp_type = mp_path_type;
        } else {
            int linejoin, linecap;
            mp_number miterlimit;
            mp_new_number(miterlimit);
            if (mp_number_greater(internal_value(mp_linejoin_internal), mp_unity_t)) {
                linejoin = mp_beveled_linejoin_code;
            } else if (mp_number_positive(internal_value(mp_linejoin_internal))) {
                linejoin = mp_rounded_linejoin_code;
            } else {
                linejoin = mp_mitered_linejoin_code;
            }
            if (mp_number_greater(internal_value(mp_linecap_internal), mp_unity_t)) {
                linecap = mp_squared_linecap_code;
            } else if (mp_number_positive(internal_value(mp_linecap_internal))) {
                linecap = mp_rounded_linecap_code;
            } else {
                linecap = mp_butt_linecap_code;
            }
            if (mp_number_less(internal_value(mp_miterlimit_internal), mp_unity_t)) {
                mp_set_number_to_unity(miterlimit);
            } else {
                mp_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));
            cur_exp_type = mp_path_type;
        }
    }
}

static void mp_set_up_boundingpath(MP mp, mp_node p, int c)
{
    if ((p->type != mp_pen_type && p->type != mp_nep_type) || (cur_exp_type != mp_path_type)) {
        mp_bad_binary(mp, p, c);
    } else {
        int linejoin, linecap;
        mp_number miterlimit;
        mp_knot q = mp_copy_path(mp, cur_exp_knot); /* the original path */
        mp_knot qq;
        mp_knot pen = mp_get_value_knot(p);
        mp_new_number(miterlimit);
        /*tex
            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 (mp_number_greater(internal_value(mp_linejoin_internal), mp_unity_t)) {
            linejoin = mp_beveled_linejoin_code;
        } else if (mp_number_positive(internal_value(mp_linejoin_internal))) {
            linejoin = mp_rounded_linejoin_code;
        } else {
            linejoin = mp_mitered_linejoin_code;
        }
        if (mp_number_greater(internal_value(mp_linecap_internal), mp_unity_t)) {
            linecap = mp_squared_linecap_code;
        } else if (mp_number_positive(internal_value(mp_linecap_internal))) {
            linecap = mp_rounded_linecap_code;
        } else {
            linecap = mp_butt_linecap_code;
        }
        if (mp_number_less(internal_value(mp_miterlimit_internal), mp_unity_t)) {
            mp_set_number_to_unity(miterlimit);
        } else {
            mp_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);
        cur_exp_type = mp_path_type;
        if (! mp_get_cur_bbox(mp)) {
            mp_bad_binary(mp, p, c);
            mp_set_cur_exp_knot(mp, q);
            cur_exp_type = mp_path_type;
        } else {
            mp_make_bounding_box(mp);
        }
    }
}

static void mp_find_point(MP mp, mp_number *v_orig, int c)
{
    mp_knot p;   /*tex the path */
    mp_number n; /*tex its length */
    mp_number v;
    (void) c;
    mp_new_number(n);
    mp_new_number_clone(v, *v_orig);
    p = cur_exp_knot;
    if (mp_left_type(p) == mp_endpoint_knot) {
        mp_set_number_to_unity(n);
        mp_number_negate(n);
    }
    do {
        p = mp_next_knot(p);
        mp_number_add(n, mp_unity_t);
    } while (p != cur_exp_knot);
    if (mp_number_zero(n)) {
        mp_set_number_to_zero(v);
    } else if (mp_number_negative(v)) {
        if (mp_left_type(p) == mp_endpoint_knot) {
            mp_set_number_to_zero(v);
        } else    {
            /*tex |v = n - 1 - ((-v - 1) % n) == - ((-v - 1) % n) - 1 + n| */
            mp_number_negate(v);
            mp_number_add_scaled(v, -1);
            mp_number_modulo(v, n);
            mp_number_negate(v);
            mp_number_add_scaled(v, -1);
            mp_number_add(v, n);
        }
    } else if (mp_number_greater(v, n)) {
        if (mp_left_type(p) == mp_endpoint_knot) {
            mp_number_clone(v, n);
        } else {
            mp_number_modulo(v, n);
        }
    }
    p = cur_exp_knot;
    while (mp_number_greaterequal(v, mp_unity_t)) {
        p = mp_next_knot(p);
        mp_number_subtract(v, mp_unity_t);
    }
    if (mp_number_nonzero(v)) {
        /*tex
            Insert a fractional node by splitting the cubic.
        */
        mp_convert_scaled_to_fraction(v);
        mp_split_cubic(mp, p, &v);
        p = mp_next_knot(p);
    }
    /*tex
        Set the current expression to the desired path coordinates.
    */
    mp_push_of_path_result(mp, c - mp_point_operation, p, mp_zero_t, mp_zero_t);
    mp_free_number(v);
    mp_free_number(n);
}

static void mp_finish_binary(MP mp, mp_node old_p, mp_node old_exp)
{
    mp_check_arithmic(mp);
    /* 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);
    }
}

/* way to big the next one */

static void mp_set_up_plus_minus(MP mp, mp_node p, int c)
{
    /* Add or subtract the current expression from |p| */
    if ((cur_exp_type < mp_color_type) || (p->type < mp_color_type)) {
        mp_bad_binary(mp, p, c);
    } else {
        if ((cur_exp_type > mp_pair_type) && (p->type > mp_pair_type)) {
            mp_add_or_subtract(mp, p, NULL, c);
        } else if (cur_exp_type != p->type) {
            /*tex
                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 (cur_exp_type == mp_color_type && p->type == mp_cmykcolor_type) {
                mp_node q = mp_get_value_node(p);
                mp_node r = mp_get_value_node(cur_exp_node);
                mp_number_negate((mp_cyan_part(q))->data.n);
                mp_number_negate((mp_magenta_part(q))->data.n);
                mp_number_negate((mp_yellow_part(q))->data.n);
                mp_number_add((mp_cyan_part(q))->data.n, mp_unity_t);
                mp_number_add((mp_magenta_part(q))->data.n, mp_unity_t);
                mp_number_add((mp_yellow_part(q))->data.n, mp_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 (cur_exp_type == mp_cmykcolor_type && p->type == mp_color_type) {
                mp_node q = mp_get_value_node(p);
                mp_node r = mp_get_value_node(cur_exp_node);
                mp_number_negate((mp_red_part(q))->data.n);
                mp_number_negate((mp_green_part(q))->data.n);
                mp_number_negate((mp_blue_part(q))->data.n);
                mp_number_add((mp_red_part(q))->data.n, mp_unity_t);
                mp_number_add((mp_green_part(q))->data.n, mp_unity_t);
                mp_number_add((mp_blue_part(q))->data.n, mp_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 (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;
            }
        }
    }
}

static void mp_set_up_compare(MP mp, mp_node p, int c)
{
    mp_check_arithmic(mp);
    /*tex
        At this point |arithmic_error| should be |false|?
    */
    if ((cur_exp_type > mp_pair_type) && (p->type > mp_pair_type)) {
        /*tex |cur_exp := (p) - cur_exp| */
        mp_add_or_subtract(mp, p, NULL, mp_minus_operation);
    } else if (cur_exp_type != p->type) {
        mp_bad_binary(mp, p, (int) c);
        goto DONE;
    } else {
        /*tex
            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 (cur_exp_type) {
            case mp_string_type:
                {
                    mp_value new_expr;
                    memset(&new_expr, 0, sizeof(mp_value));
                    mp_new_number(new_expr.data.n);
                    mp_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:
                {
                    /*tex
                        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) {
                        mp_value new_expr;
                        memset(&new_expr, 0, sizeof(mp_value));
                        mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_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 (rr->type != mp_known_type || ! mp_number_zero(mp_get_value_number(rr))) {
                            break;
                        }
                    }
                    mp_take_part(mp, part_type);
                }
                break;
            case mp_boolean_type:
                {
                    mp_value new_expr;
                    memset(&new_expr, 0, sizeof(mp_value));
                    mp_new_number(new_expr.data.n);
                    mp_set_number_from_boolean(new_expr.data.n, mp_number_to_scaled(cur_exp_value_number) - mp_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;
        }
    }
    /*tex
        Compare the current expression with zero.
    */
    if (cur_exp_type != mp_known_type) {
        if (cur_exp_type < mp_known_type) {
            mp_display_error(mp, p);
            mp_back_error(mp,
                "Unknown relation will be considered false",
                "The quantities shown above have not been equated."
            );
        } else {
            mp_display_error(mp, NULL);
            mp_back_error(mp,
                "Unknown relation will be considered false",
                "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_value new_expr;
            memset(&new_expr, 0, sizeof(mp_value));
            mp_new_number(new_expr.data.n);
            mp_set_number_from_boolean(new_expr.data.n, mp_false_operation);
            mp_get_x_next(mp);
            mp_flush_cur_exp(mp, new_expr);
        }
    } else {
        int b = 0;
        switch (c) {
            case mp_less_than_operation:
                b = mp_number_negative(cur_exp_value_number);
                break;
            case mp_less_or_equal_operation:
                b = mp_number_nonpositive(cur_exp_value_number);
                break;
            case mp_greater_than_operation:
                b = mp_number_positive(cur_exp_value_number);
                break;
            case mp_greater_or_equal_operation:
                b = mp_number_nonnegative(cur_exp_value_number);
                break;
            case mp_equal_operation:
                b = mp_number_zero(cur_exp_value_number);
                break;
            case mp_unequal_operation:
                b = mp_number_nonzero(cur_exp_value_number);
                break;
        }
        mp_set_cur_exp_value_boolean(mp, b ? mp_true_operation : mp_false_operation);
    }
    cur_exp_type = mp_boolean_type;
  DONE:
    /*tex
        Ignore overflow in comparisons.
    */
    mp->arithmic_error = 0;
}

static void mp_set_up_and_or(MP mp, mp_node p, int c)
{
    /*tex
        Here we use the sneaky fact that |and_op-false_code=or_op-true_code|.
    */
    if ((p->type != mp_boolean_type) || (cur_exp_type != mp_boolean_type)) {
        mp_bad_binary(mp, p, (int) c);
    } else if (mp_number_to_boolean(p->data.n) == c + mp_false_operation - mp_and_operation) {
        mp_set_cur_exp_value_boolean(mp, mp_number_to_boolean(p->data.n));
    }
}

static int mp_set_up_over(MP mp, mp_node p, int c, mp_node old_p, mp_node old_exp)
{
    if ((cur_exp_type != mp_known_type) || (p->type < mp_color_type)) {
        mp_bad_binary(mp, p, c);
        return 0;
    } else {
        mp_number v_n;
        mp_new_number_clone(v_n, cur_exp_value_number);
        mp_unstash_cur_exp(mp, p);
        if (mp_number_zero(v_n)) {
            mp_display_error(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 (cur_exp_type) {
                case mp_known_type:
                    {
                        mp_number ret;
                        mp_new_number(ret);
                        mp_make_scaled(ret, cur_exp_value_number, v_n);
                        mp_set_cur_exp_value_number(mp, &ret);
                        mp_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;
            }
        }
        mp_free_number(v_n);
        mp_finish_binary(mp, old_p, old_exp);
        return 1;
    }
}

static void mp_set_up_power(MP mp, mp_node p, int c)
{
    if ((cur_exp_type == mp_known_type) && (p->type == mp_known_type)) {
        mp_number r;
        mp_new_number(r);
        mp_power_of(r, mp_get_value_number(p), cur_exp_value_number);
        mp_check_arithmic(mp);
        mp_set_cur_exp_value_number(mp, &r);
        mp_free_number(r);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_pythag(MP mp, mp_node p, int c)
{
    if ((cur_exp_type == mp_known_type) && (p->type == mp_known_type)) {
        mp_number r;
        mp_new_number(r);
        if (c == mp_pythag_add_operation) {
            mp_pyth_add(r, mp_get_value_number(p), cur_exp_value_number);
        } else {
            mp_pyth_sub(r, mp_get_value_number(p), cur_exp_value_number);
        }
        mp_set_cur_exp_value_number(mp, &r);
        mp_free_number(r);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_dotprod(MP mp, mp_node p, int c) /* for 3D experiments */
{
    mp_number r;
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (p->type == mp_numeric_type || p->type == mp_known_type) {
                mp_new_number_from_mul(r, cur_exp_value_number, mp_get_value_number(p));
                goto OKAY;
            } else {
                break;
            }
        case mp_pair_type:
            if (mp_pair_is_known(mp_get_value_node(cur_exp_node)) && mp_nice_pair(mp, p, p->type)) {
                mp_number x, y;
                mp_new_number_from_mul(x, mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_x_part(mp_get_value_node(p))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_y_part(mp_get_value_node(p))));
                mp_new_number_from_add(r, x, y);
                mp_free_number(x);
                mp_free_number(y);
                goto OKAY;
            } else {
                break;
            }
        case mp_color_type:
            if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node)) && p->type == mp_color_type && mp_rgb_color_is_known(mp_get_value_node(p))) {
                mp_number x, y, z;
                mp_new_number_from_mul(x, mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_red_part(mp_get_value_node(p))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_green_part(mp_get_value_node(p))));
                mp_new_number_from_mul(z, mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_blue_part(mp_get_value_node(p))));
                mp_new_number_from_add(r, x, y);
                mp_number_add(r, z);
                mp_free_number(x);
                mp_free_number(y);
                mp_free_number(z);
                goto OKAY;
            } else {
                break;
            }
        case mp_cmykcolor_type:
            if (mp_cmyk_color_is_known(mp_get_value_node(cur_exp_node)) && p->type == mp_cmykcolor_type && mp_cmyk_color_is_known(mp_get_value_node(p))) {
                mp_number x, y, z, w;
                mp_new_number_from_mul(x, mp_get_value_number(mp_cyan_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_cyan_part(mp_get_value_node(p))));
                mp_new_number_from_mul(y, mp_get_value_number(mp_magenta_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_magenta_part(mp_get_value_node(p))));
                mp_new_number_from_mul(z, mp_get_value_number(mp_yellow_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_yellow_part(mp_get_value_node(p))));
                mp_new_number_from_mul(w, mp_get_value_number(mp_black_part(mp_get_value_node(cur_exp_node))), mp_get_value_number(mp_black_part(mp_get_value_node(p))));
                mp_new_number_from_add(r, x, y);
                mp_number_add(r, z);
                mp_number_add(r, w);
                mp_free_number(x);
                mp_free_number(y);
                mp_free_number(z);
                mp_free_number(w);
                goto OKAY;
            } else {
                break;
            }
        default:
            break;
    }
    mp_bad_binary(mp, p, c);
    return;
  OKAY:
    {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        new_expr.data.n = r;
        mp_flush_cur_exp(mp, new_expr);
    }
}

static void mp_set_up_crossprod(MP mp, mp_node p, int c) /* for 3D experiments */
{
    switch (cur_exp_type) {
        case mp_pair_type:
            if (mp_pair_is_known(mp_get_value_node(cur_exp_node)) && mp_nice_pair(mp, p, p->type)) {
                mp_number xy, yx, r;
                mp_new_number_from_mul(xy, mp_get_value_number(mp_x_part(mp_get_value_node(p))), mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(yx, mp_get_value_number(mp_y_part(mp_get_value_node(p))), mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                mp_set_number_from_subtraction(r, xy, yx);
                mp_free_number(xy);
                mp_free_number(yx);
                {
                    mp_value new_expr;
                    memset(&new_expr, 0, sizeof(mp_value));
                    new_expr.data.n = r;
                    mp_flush_cur_exp(mp, new_expr);
                }
                return;
            } else {
                break;
            }
        case mp_color_type:
            if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node)) && p->type == mp_color_type && mp_rgb_color_is_known(mp_get_value_node(p))) {
                /* (greenpart a * bluepart  b  - bluepart  a * greenpart b) */
                /* (bluepart  a * redpart   b  - redpart   a * bluepart  b) */
                /* (redpart   a * greenpart b  - greenpart a * redpart   b) */
                mp_number gb, bg, br, rb, rg, gr;
                mp_new_number_from_mul(gb, mp_get_value_number(mp_green_part(mp_get_value_node(p))), mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(bg, mp_get_value_number(mp_blue_part(mp_get_value_node(p))), mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(br, mp_get_value_number(mp_blue_part(mp_get_value_node(p))), mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(rb, mp_get_value_number(mp_red_part(mp_get_value_node(p))), mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(rg, mp_get_value_number(mp_red_part(mp_get_value_node(p))), mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))));
                mp_new_number_from_mul(gr, mp_get_value_number(mp_green_part(mp_get_value_node(p))), mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))));
                mp_set_number_from_subtraction(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))), gb, bg);
                mp_set_number_from_subtraction(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))), br, rb);
                mp_set_number_from_subtraction(mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))), rg, gr);
                mp_free_number(gb);
                mp_free_number(bg);
                mp_free_number(br);
                mp_free_number(rb);
                mp_free_number(rg);
                mp_free_number(gr);
                return;
            } else {
                break;
            }
     // case mp_cmykcolor_type:
            /* we could return the cmy one */
        default:
            break;
    }
    mp_bad_binary(mp, p, c);
}

static void mp_set_up_div(MP mp, mp_node p, int c) /* for 3D experiments */
{
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (p->type == mp_numeric_type || p->type == mp_known_type) {
                /* x div y = floor(x/y) */
                mp_number r;
                mp_new_number_from_div(r, mp_get_value_number(p), cur_exp_value_number);
                mp_floor_scaled(r);
                mp_set_cur_exp_value_number(mp, &r);
                mp_free_number(r);
                return;
            } else {
                break;
            }
        default:
            break;
    }
    mp_bad_binary(mp, p, c);
}

static void mp_set_up_mod(MP mp, mp_node p, int c) /* for 3D experiments */
{
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (p->type == mp_numeric_type || p->type == mp_known_type) {
                /* x mod y = x - y*floor(x/y) */
                mp_number r;
                mp_new_number_from_div(r, mp_get_value_number(p), cur_exp_value_number);
                mp_floor_scaled(r);
                mp_set_number_from_mul(r, r, cur_exp_value_number);
                mp_set_number_from_subtraction(r, mp_get_value_number(p), r);
                mp_set_cur_exp_value_number(mp, &r);
                mp_free_number(r);
                return;
            } else {
                break;
            }
        default:
            break;
    }
    mp_bad_binary(mp, p, c);
}

static int mp_set_up_times(MP mp, mp_node p, int c, mp_node old_p, mp_node old_exp)
{
    if ((cur_exp_type < mp_color_type) || (p->type < mp_color_type)) {
        mp_bad_binary(mp, p, mp_times_operation);
        return 0;
    } else if ((cur_exp_type == mp_known_type) || (p->type == mp_known_type)) {
        /*tex
            Multiply when at least one operand is known.

            pair * number or number * pair or number * number
        */
        mp_number vv;
        mp_new_fraction(vv);
        if (p->type == mp_known_type) {
            mp_number_clone(vv, mp_get_value_number(p));
            mp_free_value_node(mp, p);
        } else {
            mp_number_clone(vv, cur_exp_value_number);
            mp_unstash_cur_exp(mp, p);
        }
        switch (cur_exp_type) {
            case mp_known_type:
                {
                    mp_number ret;
                    mp_new_number(ret);
                    mp_take_scaled(ret, cur_exp_value_number, vv);
                    mp_set_cur_exp_value_number(mp, &ret);
                    mp_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;
        }
        mp_free_number(vv);
        mp_finish_binary(mp, old_p, old_exp);
        return 1;
    } else if ((mp_nice_color_or_pair(mp, p, p->type) && (cur_exp_type > mp_pair_type))
            || (mp_nice_color_or_pair(mp, cur_exp_node, cur_exp_type) && (p->type > mp_pair_type))) {
        mp_hard_times(mp, p);
        mp_finish_binary(mp, old_p, old_exp);
        return 1;
    } else if (mp_nice_color(mp, p, p->type) && mp_nice_color(mp, cur_exp_node, cur_exp_type)) {
        mp_harder_times(mp, p);
        mp_finish_binary(mp, old_p, old_exp);
        return 1;
    } else {
        mp_bad_binary(mp, p, c);
        return 0;
    }
}

static void mp_set_up_intertimes(MP mp, mp_node p, int c)
{
    if (p->type == 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 (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && (p->type == 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);
            // mp_new_number_clone(arg1, mp->cur_t);
            // mp_new_number_clone(arg2, mp->cur_tt);
            // mp_pair_value(mp, &arg1, &arg2);
            // mp_free_number(arg1);
            // mp_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;
            cur_exp_type = mp_path_type;
            mp_set_cur_exp_knot(mp, list);
        }
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_subarc_length(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && p->type == mp_pair_type) {
        mp_value new_expr;
        mp_node q = mp_get_value_node(p);
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        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, c);
    }
}

static void mp_set_up_arc_point_list(MP mp, mp_node p, int c)
{
    /*
        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 (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && p->type == mp_known_type) {
        // we can consider using ints as we have discrete points
        mp_value new_expr;
        mp_knot cur = cur_exp_knot;
        mp_number len, aln, seg, tot, tim, stp, acc, tmp, idx, cnt;
        mp_knot last = NULL;
        mp_knot list = NULL;
        int iscycle = mp_left_type(cur_exp_knot) == mp_explicit_knot;
        mp_new_number(len);
        mp_path_length(mp, &len);
        mp_new_number(aln);
        mp_get_arc_length(mp, &aln, cur_exp_knot);
        mp_new_number(seg);
        mp_new_number(tot);
        mp_new_number(tim);
        mp_number_clone(idx, mp_get_value_number(p));
        mp_new_number_from_div(stp, aln, idx);
        mp_new_number(cnt);
        mp_new_number(acc);
        mp_get_subarc_length(mp, &acc, cur_exp_knot, &mp_zero_t, &mp_unity_t);
        /* */
        mp_new_number(tmp);
        memset(&new_expr, 0, sizeof(mp_value));
        mp_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 */
        mp_number_clone(tot, stp);
        /* checking the index is more robust than checking on accumulated length */
        while (mp_number_greater(idx, mp_zero_t)) {
            int toss = 0;
            mp_knot k;
            while (1) {
                if (mp_number_lessequal(tot, acc)) {
                    break;
                } else {
                    mp_number_add(seg, mp_unity_t);
                    mp_number_clone(tim, acc);
                    cur = mp_next_knot(cur);
                    mp_get_subarc_length(mp, &tmp, cur, &mp_zero_t, &mp_unity_t);
                    mp_number_add(acc, tmp) ;
                    mp_number_add(cnt, mp_unity_t);
                    if (mp_number_greater(cnt, len)) {
                        /* we went over the end so we need to backtrack to the last knot */
                        k = mp_prev_knot(cur_exp_knot);
                        goto OVERSHOOT;
                    }
                }
            }
            /* still from the start, can be improved with offset */
            mp_number_clone(tmp, tot);
            mp_number_subtract(tmp, tim);
            k = mp_get_arc_time(mp, &new_expr.data.n, cur, &tmp, 1);
            /* */
            if (k) {
                mp_knot kk;
                /* somehow we can get numbers way larger than 1 */
                if (mp_number_greaterequal(new_expr.data.n, mp_unity_t)) {
                    k = mp_next_knot(k);
                } else if (! mp_number_equal(new_expr.data.n, mp_zero_t)) {
                    mp_convert_scaled_to_fraction(new_expr.data.n);
                    k = mp_split_cubic_knot(mp, k, &new_expr.data.n);
                    toss = 1;
                }
                OVERSHOOT:
                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_free_knot(mp, k);
                }
                mp_number_add(tot, stp);
            } else {
                break;
            }
            mp_number_subtract(idx, mp_unity_t);
        }
        mp_free_number(len);
        mp_free_number(aln);
        mp_free_number(seg);
        mp_free_number(tot);
        mp_free_number(tim);
        mp_free_number(stp);
        mp_free_number(acc);
        mp_free_number(tmp);
        mp_free_number(idx);
        mp_free_number(cnt);
        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;
            }
            cur_exp_type = mp_path_type;
            mp_set_cur_exp_knot(mp, list);
        } else {
            mp_bad_unary(mp, c);
        }
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_arc_point(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && (p->type == mp_known_type || p->type == mp_pair_type)) {
        mp_value new_expr;
        mp_knot k;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        if (p->type == mp_pair_type) {
            mp_knot f = cur_exp_knot;
            mp_node q = mp_get_value_node(p);
            mp_number x;
            mp_new_number_clone(x, mp_get_value_number(mp_x_part(q)));
            if (mp_number_greater(x, mp_zero_t)) {
                while (mp_number_greater(x, mp_zero_t)) {
                    f = mp_next_knot(f);
                    mp_number_subtract(x, mp_unity_t);
                }
            } else {
                while (mp_number_less(x, mp_zero_t)) {
                    f = mp_next_knot(f);
                    mp_number_add(x, mp_unity_t);
                }
            }
            k = mp_get_arc_time(mp, &new_expr.data.n, f, &(mp_get_value_number(mp_y_part(q))), 1);
            mp_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 (mp_number_equal(new_expr.data.n, mp_unity_t)) {
            if (! mp_number_less(new_expr.data.n, mp_unity_t)) {
                k = mp_next_knot(k);
            } else if (! mp_number_equal(new_expr.data.n, mp_zero_t)) {
         // } else if (mp_number_non_equal_abs(new_expr.data.n, mp_zero_t)) {
                mp_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_free_knot(mp, k);
            }
        } else {
            mp_bad_unary(mp, c);
        }
    } else {
        mp_bad_unary(mp, c);
    }
}

static void mp_set_up_arc_time(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && (p->type == mp_known_type)) {
        mp_value new_expr;
# if 0
        mp_number len;
        mp_new_number(len);
        mp_get_arc_length(mp, &len, cur_exp_knot);
        mp_number_modulo(mp_get_value_number(p), len);
        mp_free_number(len);
# endif
        memset(&new_expr, 0, sizeof(mp_value));
        mp_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, c);
    }
}

static int mp_set_up_transform(MP mp, mp_node p, int c, mp_node old_p, mp_node old_exp)
{
    /*tex
        The next few sections of the program deal with affine transformations
        of coordinate data.
    */
    switch (p->type) {
        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 1;
        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 1;
        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 1;
        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 1;
        case mp_color_type:
        case mp_cmykcolor_type:
            if (c == mp_scaled_operation) {
                return mp_set_up_times(mp, p, c, old_p, old_exp);
            } else {
                /* fall through */
            }
        default:
            mp_bad_binary(mp, p, c);
            break;
    }
    return 0;
}

static void mp_set_up_combine(MP mp, mp_node p, int c)
{
    if ((cur_exp_type == mp_string_type) && (p->type == mp_string_type)) {
        mp_string str = mp_cat(mp, mp_get_value_str(p), cur_exp_str);
        mp_delete_string_reference(mp, cur_exp_str) ;
        mp_set_cur_exp_str(mp, str);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_substring(MP mp, mp_node p, int c)
{
    if (mp_nice_pair(mp, p, p->type) && (cur_exp_type == mp_string_type)) {
        mp_string str = mp_chop_string (mp,
            cur_exp_str,
            (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))),
            (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(p))))
        );
        mp_delete_string_reference(mp, cur_exp_str) ;
        mp_set_cur_exp_str(mp, str);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_set_up_subpath(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if (cur_exp_type == mp_path_type) {
        mp_knot q;      /*tex a knot in the original path */
        mp_knot pp, qq; /*tex link variables for copies of path nodes */
        mp_number a, b; /*tex indices for chopping */
        mp_number l;
        int reversed;   /*tex was |a > b|? */
        switch (p->type) {
            case mp_numeric_type:
            case mp_known_type:
                {
                    int first = -1;
                    int last = -1;
                    if (mp_path_segment(mp, (int) mp_round_unscaled(mp_get_value_number(p)), &first, &last)) {
                        mp_new_number(a);
                        mp_new_number(b);
                        mp_set_number_from_int(a, first);
                        mp_set_number_from_int(b, last);
                        break;
                    } else {
                        goto BAD;
                    }
                }
            case mp_pair_type:
                if (mp_nice_pair(mp, p, p->type)) {
                    mp_new_number_clone(a, mp_get_value_number(mp_x_part(mp_get_value_node(p))));
                    mp_new_number_clone(b, mp_get_value_number(mp_y_part(mp_get_value_node(p))));
                } else {
                   goto BAD;
                }
                break;
            default:
                goto BAD;
        }
        mp_new_number(l);
        mp_path_length(mp, &l);
        if (mp_number_lessequal(a, b)) {
            reversed = 0;
        } else {
            reversed = 1;
            mp_number_swap(a, b);
        }
        /*tex
            Dispense with the cases |a < 0| and/or |b > l|.
        */
        if (mp_number_negative(a)) {
            if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) {
                mp_set_number_to_zero(a);
                if (mp_number_negative(b)) {
                    mp_set_number_to_zero(b);
                }
            } else {
                /*tex
                    A cycle always has length |l > 0|.
                */
                do {
                    mp_number_add(a, l);
                    mp_number_add(b, l);
                } while (mp_number_negative(a));
            }
        }
        if (mp_number_greater(b, l)) {
            if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) {
                mp_number_clone(b, l);
                if (mp_number_greater(a, l)) {
                    mp_number_clone(a, l);
                }
            } else {
                while (mp_number_greaterequal(a, l)) {
                    mp_number_subtract(a, l);
                    mp_number_subtract(b, l);
                }
            }
        }
        q = cur_exp_knot;
        while (mp_number_greaterequal(a, mp_unity_t)) {
            q = mp_next_knot(q);
            mp_number_subtract(a, mp_unity_t);
            mp_number_subtract(b, mp_unity_t);
        }
        if (mp_number_equal(b, a)) {
            /*tex
                Construct a path from |pp| to |qq| of length zero.
            */
            if (mp_number_positive(a)) {
                mp_number arg1;
                mp_new_number_clone(arg1, a);
                mp_convert_scaled_to_fraction(arg1);
                mp_split_cubic(mp, q, &arg1);
                mp_free_number(arg1);
                q = mp_next_knot(q);
            }
            pp = mp_copy_knot(mp, q);
            qq = pp;
        } else {
            /*tex
                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;
                mp_number_subtract(b, mp_unity_t);
            } while (mp_number_positive(b));
            if (mp_number_positive(a)) {
                mp_knot ss = pp;
                mp_number arg1;
                mp_new_number_clone(arg1, a);
                mp_convert_scaled_to_fraction(arg1);
                mp_split_cubic(mp, ss, &arg1);
                mp_free_number(arg1);
                pp = mp_next_knot(ss);
                mp_free_knot(mp, ss);
                if (rr == ss) {
                    mp_number arg1, arg2;
                    mp_new_number_from_sub(arg1, mp_unity_t, a);
                    mp_new_number_clone(arg2, b);
                    mp_make_scaled(b, arg2, arg1);
                    mp_free_number(arg1);
                    mp_free_number(arg2);
                    rr = pp;
                }
            }
            if (mp_number_negative(b)) {
                mp_number arg1;
                mp_new_number_from_add(arg1, b, mp_unity_t);
                mp_convert_scaled_to_fraction(arg1);
                mp_split_cubic(mp, rr, &arg1);
                mp_free_number(arg1);
                mp_free_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);
        }
        mp_free_number(l);
        mp_free_number(a);
        mp_free_number(b);
        return;
    }
  BAD:
    mp_bad_binary(mp, p, c);
}

static void mp_set_up_segment(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if (cur_exp_type == mp_path_type) {
        switch (p->type) {
            case mp_numeric_type:
            case mp_known_type:
                {
                    int first = -1;
                    int last = -1;
                    if (mp_path_segment(mp, (int) mp_round_unscaled(mp_get_value_number(p)), &first, &last)) {
                        mp_number a, b;
                        mp_new_number(a);
                        mp_new_number(b);
                        mp_set_number_from_int(a, first);
                        mp_set_number_from_int(b, last);
                        mp_pair_value(mp, &a, &b);
                        mp_free_number(a);
                        mp_free_number(b);
                        return;
                    } else {
                        goto BAD;
                    }
                }
            default:
                goto BAD;
        }
        return;
    }
  BAD:
    mp_bad_binary(mp, p, c);
}

static void mp_set_up_direction(MP mp, mp_node p, int c)
{
    if (cur_exp_type == mp_pair_type) {
        mp_pair_to_path(mp);
    }
    if ((cur_exp_type == mp_path_type) && (p->type == mp_known_type)) {
        mp_find_point(mp, &(mp_get_value_number(p)), (int) c);
    } else {
        mp_bad_binary(mp, p, c);
    }
}

static void mp_do_binary(MP mp, mp_node p, int c)
{
    mp_node old_p, old_exp; /* capsules to recycle */
    mp_check_arithmic(mp);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
        /*tex
            Trace the current binary operation.
        */
        mp_begin_diagnostic(mp);
        mp_print_nl(mp, "{(");
        /*tex
            Show the operand, but not verbosely.
        */
        mp_print_exp(mp, p, 0);
        mp_print_char(mp, ')');
        mp_print_operator(mp, (int) c);
        mp_print_char(mp, '(');
        mp_print_exp(mp, NULL, 0);
        mp_print_string(mp, ")}");
        mp_end_diagnostic(mp, 0);
    }
    /*tex
        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 (p->type) {
        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, 22);
        p = mp_stash_cur_exp(mp);
        mp_unstash_cur_exp(mp, q);
    }
    /*
        Sidestep |independent| cases in the current expression.
    */
    switch (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, 23);
    }
    switch (c) {
        case mp_plus_operation:
        case mp_minus_operation:
            mp_set_up_plus_minus(mp, p, c);
            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:
            mp_set_up_compare(mp, p, c);
            break;
        case mp_and_operation:
        case mp_or_operation:
            mp_set_up_and_or(mp, p, c);
            break;
        case mp_times_operation:
            if (mp_set_up_times(mp, p, c, old_p, old_exp)) {
                return;
            } else {
                break;
            }
        case mp_over_operation:
            if (mp_set_up_over(mp, p, c, old_p, old_exp)) {
                return;
            } else {
                break;
            }
        case mp_power_operation:
            mp_set_up_power(mp, p, c);
            break;
        case mp_pythag_add_operation:
        case mp_pythag_sub_operation:
            mp_set_up_pythag(mp, p, c);
            break;
        case mp_dotprod_operation:
            mp_set_up_dotprod(mp, p, c);
            break;
        case mp_crossprod_operation:
            mp_set_up_crossprod(mp, p, c);
            break;
        case mp_div_operation:
            mp_set_up_div(mp, p, c);
            break;
        case mp_mod_operation:
            mp_set_up_mod(mp, p, 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:
        case mp_xy_scaled_operation:
        case mp_x_shifted_operation:
        case mp_y_shifted_operation:
        case mp_bytemap_scaled_operation:
            if (mp_set_up_transform(mp, p, c, old_p, old_exp)) {
                return;
            } else {
                break;
            }
        case mp_concat_operation:
        case mp_just_append_operation:
        case mp_tolerant_concat_operation:
        case mp_tolerant_append_operation:
            mp_set_up_combine(mp, p, c);
            break;
        case mp_substring_operation:
            mp_set_up_substring(mp, p, c);
            break;
        case mp_subpath_operation:
            mp_set_up_subpath(mp, p, c);
            break;
        case mp_segment_operation:
            mp_set_up_segment(mp, p, c);
            break;
        case mp_point_operation:
        case mp_precontrol_operation:
        case mp_postcontrol_operation:
        case mp_direction_operation:
        case mp_unit_direction_operation:
            mp_set_up_direction(mp, p, c);
            break;
        case mp_pen_offset_operation:
            mp_set_up_offset(mp, p, c);
            break;
        case mp_direction_time_operation:
            mp_set_up_direction_time(mp, p, c);
            break;
        case mp_envelope_operation:
            mp_set_up_envelope(mp, p, c);
            break;
        case mp_boundingpath_operation:
            mp_set_up_boundingpath(mp, p, c);
            break;
        case mp_arc_time_operation:
            mp_set_up_arc_time(mp, p, c);
            break;
        case mp_arc_point_operation:
            mp_set_up_arc_point(mp, p, c);
            break;
        case mp_arc_point_list_operation:
            mp_set_up_arc_point_list(mp, p, c);
            break;
        case mp_subarc_length_operation:
            mp_set_up_subarc_length(mp, p, c);
            break;
        case mp_intertimes_operation:
        case mp_intertimes_list_operation:
            mp_set_up_intertimes(mp, p, c);
            break;
        case mp_bytemap_value_operation:
            mp_bytemap_value(mp, p, c);
            break;
        case mp_bytemap_found_operation:
            mp_bytemap_found(mp, p, c);
            break;
        case mp_bytemap_path_operation:
            mp_bytemap_path(mp, p, c);
            break;
        case mp_bytemap_bounds_operation:
            mp_bytemap_bounds(mp, p, c, 0);
            break;
        default:
            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);
}

/*tex

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. 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|.

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|.

*/

static void mp_frac_mult(MP mp, mp_number *n, mp_number *d)
{
    /*tex Multiplies |cur_exp| by |n/d|. */
    mp_node old_exp; /*tex a capsule to recycle */
    mp_number v;     /*tex |n/d| */
    mp_new_fraction(v);
    if (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
        mp_begin_diagnostic(mp);
        mp_print_format(mp, "%l{(%N/%N) * (", *n, *d);
        mp_print_exp(mp, NULL, 0);
        mp_print_string(mp, ")}");
        mp_end_diagnostic(mp, 0);
    }
    switch (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, 24);
    }
    mp_make_fraction(v, *n, *d);
    switch (cur_exp_type) {
        case mp_known_type:
            {
                mp_number r1, arg1;
                mp_new_fraction(r1);
                mp_new_number_clone(arg1, cur_exp_value_number);
                mp_take_fraction(r1, arg1, v);
                mp_set_cur_exp_value_number(mp, &r1);
                mp_free_number(r1);
                mp_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);
    }
    mp_free_number(v);
}

/*tex

The |hard_times| routine multiplies a nice color or pair by a dependency list. TODO

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.

Procedure |set_up_known_trans| is like |set_up_trans|, but it insists that the transformation be
entirely known.Here's a procedure that applies the transform |txx..ty| to a pair of coordinates in
locations |p| and~|q|.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.Transforming a pen is
very similar, except that there are no |mp_left_type| and |mp_right_type| fields.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.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|.

The sum |txx + txy| is whichever of |txx| or |txy| is nonzero. The other sum is similar.Now we ready
for the main task of transforming the graphical objects in edge structure~|h|.The hard cases of
transformation occur when big nodes are involved, and when some of their components are unknown.

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$.

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|.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 $.Finally, in |bilin3| everything is |known|.

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 |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.

*/

static void worry_about_bad_statement            (MP mp);
static void flush_unparsable_junk_after_statement(MP mp);

static void mp_do_statement(MP 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) {
        /*tex
            Do an equation, assignment, title, or |<expression> 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 (cur_exp_type == mp_string_type) {
                /* Do a title */
                if (mp_number_positive(internal_value(mp_tracing_titles_internal))) {
                    mp_print_format(mp, "%l%S\n", cur_exp_str);
                }
            } else if (cur_exp_type != mp_vacuous_type) {
                mp_display_error(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));
            mp_new_number(new_expr.data.n);
            mp_flush_cur_exp(mp, new_expr);
            cur_exp_type = mp_vacuous_type;
        }
    } else {
        /*tex
            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 (mp_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_do_delimiters(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_bytemap_command:
                switch (cur_mod) {
                    case mp_bytemap_set_byte_code:
                        mp_bytemap_set_byte(mp);
                        break;
                    case mp_bytemap_set_offset_code:
                        mp_bytemap_set_offset(mp);
                        break;
                    case mp_bytemap_new_code:
                        mp_bytemap_new(mp);
                        break;
                    case mp_bytemap_copy_code:
                        mp_bytemap_copy(mp);
                        break;
                    case mp_bytemap_set_code:
                        mp_bytemap_set(mp);
                        break;
                    case mp_bytemap_reduce_code:
                        mp_bytemap_reduce(mp);
                        break;
                    case mp_bytemap_clip_code:
                        mp_bytemap_clip(mp);
                        break;
                    case mp_bytemap_set_options_code:
                        mp_bytemap_set_options(mp);
                        break;
                    case mp_bytemap_reset_code:
                        mp_bytemap_reset(mp);
                        break;
                    case mp_bytemap_reset_all_code:
                        mp_bytemap_reset_all(mp);
                        break;
                }
                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;
        }
        cur_exp_type = mp_vacuous_type;
    }
    if (cur_cmd < mp_semicolon_command) {
        flush_unparsable_junk_after_statement(mp);
    }
    mp->error_count = 0;
}

/*tex

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.

*/

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;
        snprintf(msg, 256, "A statement can't begin with '%s'", mp_str(mp, sname));
        mp_delete_string_reference(mp, 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);
    }
}

/*tex

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.

*/

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 {
        mp_get_t_next(mp);
        if (cur_cmd == mp_string_command) {
            mp_delete_string_reference(mp, cur_mod_str);
        }
    } while (! mp_end_of_statement); /*tex |cur_cmd = semicolon|, |end_group|, or |stop| */
    mp->scanner_status = mp_normal_state;
}

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_string(mp, ")=(");
    mp_print_exp(mp, NULL, 0);
    mp_print_string(mp, ")}");
    mp_end_diagnostic(mp, 0);
}

/*tex

Equations and assignments are performed by the pair of mutually recursive 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).

*/

static 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 (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
        trace_equation(mp, lhs);
    }
    if (cur_exp_type == mp_unknown_path_type) {
        /*tex
            In this case |make_eq| will change the pair to a path.
        */
        if (lhs->type == mp_pair_type) {
            mp_node p; /* temporary register */
            p = mp_stash_cur_exp(mp);
            mp_unstash_cur_exp(mp, lhs);
            lhs = p;
        }
    }
    /*tex Equate |lhs| to |(cur_type,cur_exp)|. */
    mp_make_eq(mp, lhs);
}

static void mp_bad_lhs(MP mp)
{
    mp_display_error(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 mp_bad_internal_assignment(MP mp, mp_node lhs)
{
    if (internal_type(mp_get_sym_info(lhs)) == mp_known_type) {
        char msg[256];
        snprintf(msg, 256,
            "Internal quantity '%s' must receive a known numeric value",
            internal_name(mp_get_sym_info(lhs))
        );
        mp_display_error(mp, NULL);
        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) {
        char msg[256];
        snprintf(msg, 256,
            "Internal quantity '%s' must receive a known boolean value",
            internal_name(mp_get_sym_info(lhs))
        );
        mp_display_error(mp, NULL);
        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 {
        char msg[256];
        snprintf(msg, 256,
            "Internal quantity '%s' must receive a known string",
            internal_name(mp_get_sym_info(lhs))
        );
        mp_display_error(mp, NULL);
        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];
    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 mp_bad_internal_assignment_precision(MP mp, mp_node lhs, mp_number *min, mp_number *max)
{
    char msg[256];
    char hlp[256];
    snprintf(msg, 256,
        "Bad '%s' has been ignored",
        internal_name(mp_get_sym_info(lhs)));
    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)), mp_number_tostring(*min), mp_number_tostring(*max));
    mp_back_error(mp, msg, hlp);
    mp_get_x_next(mp);
}

static void mp_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 (lhs->name_type == mp_internal_operation) {
        mp_print_string(mp, internal_name(mp_get_sym_info(lhs)));
    } else {
        mp_show_token_list(mp, lhs, NULL);
    }
    mp_print_string(mp, ":=");
    mp_print_exp(mp, NULL, 0);
    mp_print_char(mp, '}');
    mp_end_diagnostic(mp, 0);
}

/*tex

And |do_assignment| is similar to |do_equation|:

*/

static void mp_do_assignment(MP mp)
{
    if (cur_exp_type != mp_token_list_type) {
        mp_bad_lhs(mp);
        mp_do_equation(mp);
    } else {
        mp_node lhs = cur_exp_node; /* token list for the left-hand side */
        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 (mp_number_greater(internal_value(mp_tracing_commands_internal), mp_two_t)) {
            trace_assignment(mp, lhs);
        }
        if (lhs->name_type == mp_internal_operation) {
            /*tex Assign the current expression to an internal variable. */
            switch (cur_exp_type) {
                case mp_known_type:
                case mp_string_type:
                case mp_boolean_type:
                    if (internal_type(mp_get_sym_info(lhs)) == 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:
                         //     mp_number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number);
                         //     mp->run_internal(mp, mp_tracing_internal_code, mp->int_ptr, mp_number_to_int(internal_value(mp_get_sym_info(lhs))), internal_name(mp_get_sym_info(lhs)));
                         //     break;
                            case mp_number_precision_internal:
                                if (cur_exp_type == mp_known_type
                                      && (! mp_number_less   (cur_exp_value_number, mp_precision_min))
                                      && (! mp_number_greater(cur_exp_value_number, mp_precision_max))
                                    ) {
                                     if (internal_type(mp_get_sym_info(lhs)) == mp_string_type) {
                                         mp_add_string_reference(mp, cur_exp_str);
                                         set_internal_string(mp_get_sym_info(lhs), cur_exp_str);
                                     } else {
                                         mp_number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number);
                                     }
                                     mp_set_precision();
                                } else {
                                    mp_bad_internal_assignment_precision(mp, lhs, &mp_precision_min, &mp_precision_max);
                                }
                                break;
                            case mp_less_digits_internal:
                                if (cur_exp_type == mp_boolean_type) {
                                     mp_number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number);
                                     mp->less_digits = cur_exp_value_boolean == mp_true_operation ? 1 : 0;
                                } else {
                                    mp_bad_internal_assignment(mp, lhs);
                                }
                                break;
                            default:
                                if (internal_type(mp_get_sym_info(lhs)) == mp_string_type) {
                                    mp_add_string_reference(mp, cur_exp_str);
                                    set_internal_string(mp_get_sym_info(lhs), cur_exp_str);
                                } else {
                                    mp_number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number);
                                }
                                break;
                        }
                    } else {
                        mp_bad_internal_assignment(mp, lhs);
                    }
                    break;
                default:
                    mp_bad_internal_assignment(mp, lhs);
                }
        } else {
            /*tex Assign the current expression to the variable |lhs|. */
            mp_node p = mp_find_variable(mp, lhs); /*tex Where the left-hand value is stored. */
            if (p != NULL) {
                mp_node q = mp_stash_cur_exp(mp); /*tex Temporary capsule for the right-hand value. */
                cur_exp_type = mp_und_type(mp, p);
                mp_recycle_value(mp, p);
                p->type = cur_exp_type;
                mp_set_value_number(p, mp_zero_t);
                mp_make_exp_copy(mp, p, 25);
                p = mp_stash_cur_exp(mp);
                mp_unstash_cur_exp(mp, q);
                mp_make_eq(mp, p);
            } else {
                mp_bad_expression_assignment(mp, lhs);
            }
        }
        mp_flush_node_list(mp, lhs);
    }
}

static void announce_bad_equation(MP mp, mp_node lhs)
{
    char msg[256];
    snprintf(msg, 256,
        "Equation cannot be performed (%s=%s)",
        (lhs->type <= mp_pair_type ? mp_type_string(lhs->type) : "numeric"),
        (cur_exp_type <= mp_pair_type ? mp_type_string(cur_exp_type) : "numeric"));
    mp_display_error(mp, lhs);
 // mp_display_error(mp, NULL); /* weird */
    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 (cur_exp_type <= mp_string_type) {
        if (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 (mp_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);
    }
}

/*tex

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.

*/

static void mp_make_eq(MP mp, mp_node lhs)
{
    mp_value new_expr;
    mp_variable_type t; /*tex type of the left-hand side */
    mp_number v;        /*tex value of the left-hand side */
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(v);
  RESTART:
    t = lhs->type;
    if (t <= mp_pair_type) {
        mp_number_clone(v, mp_get_value_number(lhs));
    }
    /*tex
        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 (cur_exp_type == t + mp_unknown_tag) {
                mp_new_number(new_expr.data.n);
                switch (t) {
                    case mp_boolean_type:
                        mp_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 (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 (cur_exp_type == t - mp_unknown_tag) {
                mp_nonlinear_eq(mp, mp->cur_exp, lhs, 1);
            } else if (cur_exp_type == t) {
                mp_ring_merge (mp, lhs, cur_exp_node);
            } else if (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 (cur_exp_type == t) {
                /*tex 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 (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;
    }
    mp_check_arithmic(mp);
    mp_recycle_value(mp, lhs);
    mp_free_number(v);
    mp_free_value_node(mp, lhs);
}

static void deal_with_redundant_or_inconsistent_equation(MP mp, mp_value_node p, mp_node r)
{
    mp_number absp;
    mp_new_number_abs(absp, mp_get_value_number(p));
    if (mp_number_greater(absp, mp_equation_threshold_t)) {   /* off by .001 or more */
        char msg[256];
        snprintf(msg, 256, "Inconsistent equation (off by %s)", mp_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);
    }
    mp_free_number(absp);
    mp_free_dep_node(mp, p, 20);
}

static void mp_try_eq(MP mp, mp_node l, mp_node r)
{
    mp_value_node p;     /*tex dependency list for right operand minus left operand */
    mp_value_node q;     /*tex the constant term of |p| is here */
    mp_value_node pp;    /*tex dependency list for right operand */
    mp_variable_type tt; /*tex the type of list |pp| */
    int copied;          /*tex have we copied a list that ought to be recycled? */
    /*tex
        Remove the left operand from its container, negate it, and put it into dependency list~|p|
        with constant term~|q|
    */
    mp_variable_type t = l->type; /*tex The type of list |p|. */
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal))) {
        if (r) {
            mp_print_format(mp, "%l[equation: try, left %s %P, right %s %P]", mp_type_string(l->type), l, mp_type_string(r->type), r);
        } else {
            mp_print_format(mp, "%l[equation: try, left %i %P]", mp_type_string(l->type), l);
        }
    }
    switch (t) {
        case mp_known_type:
            {
                mp_number arg1;
                mp_new_number(arg1);
                mp_number_negated_clone(arg1, mp_get_value_number(l));
                t = mp_dependent_type;
                p = mp_const_dependency(mp, &arg1);
                q = p;
                mp_free_number(arg1);
            }
            break;
        case mp_independent_type:
            {
                t = mp_dependent_type;
                p = mp_single_dependency(mp, l);
                mp_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) {
                    mp_number_negate(mp_get_dep_value(q));
                    if (mp_get_dep_info(q) == NULL) {
                        break;
                    } else {
                        q = (mp_value_node) q->link;
                    }
                }
                mp_get_prev_dep(ll)->link = q->link;
                mp_set_prev_dep((mp_value_node) q->link, mp_get_prev_dep(ll));
                ll->type = mp_known_type;
            }
            break;
    }
    /*tex
        Add the right operand to list |p|.
    */
    if (r == NULL) {
        if (cur_exp_type == mp_known_type) {
            mp_number_add(mp_get_value_number(q), cur_exp_value_number);
            goto DONE1;
        } else {
            tt = 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 (r->type == mp_known_type) {
        mp_number_add(mp_get_dep_value(q), mp_get_value_number(r));
        goto DONE1;
    } else {
        tt = r->type;
        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;
    }
    /*tex
        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, &mp_unity_t, pp, mp_proto_dependent_type, mp_dependent_type);
    } else {
        mp_number x;
        mp_new_number(x);
        q = p;
        while (mp_get_dep_info(q) != NULL) {
            mp_number_clone(x, mp_get_dep_value(q));
            mp_fraction_to_round_scaled(x); /* so always scaled */
            mp_set_dep_value(q, x);
            q = (mp_value_node) q->link;
        }
        mp_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 && cur_exp_type != mp_known_type && cur_exp_node->type == mp_known_type) {
            mp_node pp = cur_exp_node;
            mp_set_cur_exp_value_number(mp, &(mp_get_value_number(pp)));
            cur_exp_type = mp_known_type;
            mp_free_value_node(mp, pp);
        }
    }
}

/*tex

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|.

*/

static mp_node mp_scan_declared_variable(MP mp)
{
    mp_symbol x;  /*tex hash address of the variable's root */
    mp_node h, t; /*tex 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) {
                    /*tex
                        Descend past a collective subscript If the subscript isn't collective, we
                        don't accept it as part of the declared variable.
                    */
                    mp_symbol ll = cur_sym; /*tex 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;
                }
            }
        }
        t->link = mp_new_symbolic_node(mp);
        t = t->link;
        mp_set_sym_sym(t, cur_sym);
        t->name_type = 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 (eq_node(x) == NULL) {
        mp_new_root(mp, x);
    }
    return h;
}

/*tex

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.

*/

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 {
        mp_get_t_next(mp);
        /*tex
            Decrease the string reference count, if the current token is a string.
        */
        if (cur_cmd == mp_string_command) {
            mp_delete_string_reference(mp, cur_mod_str);
        }
        /*tex Break on either |end_of_statement| or |comma|. */
    } while (cur_cmd < mp_comma_command);
    mp->scanner_status = mp_normal_state;
}

static void mp_do_type_declaration(MP mp)
{
    int t = mp_numeric_type; /* cur_mod >= mp_transform_type ? cur_mod : cur_mod + mp_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); /*tex token list for a declared variable */
        mp_node q;                                 /*tex value node for the variable */
        mp_flush_variable(mp, eq_node(mp_get_sym_sym(p)), p->link, 0);
        q = mp_find_variable(mp, p);
        if (q != NULL) {
            q->type = t;
            mp_set_value_number(q, mp_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);
}

/*tex

\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|.

*/

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));
            mp_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_buffer);
        mp->jump_buffer = mp_memory_allocate(sizeof(jmp_buf));
        if (mp->jump_buffer == NULL || setjmp (*(mp->jump_buffer)) != 0) {
            return mp->history;
        }
        mp_main_control(mp);  /*tex come to life */
        mp_final_cleanup(mp); /*tex prepare for death */
        mp_close_files_and_terminate(mp);
    }
    return mp->history;
}

/*tex

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.

*/

void mp_set_internal(MP mp, char *n, char *v, int isstring)
{
    size_t l = strlen(n);
    const char *errid = NULL;
    if (l > 0) {
        mp_symbol 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(eq_valent(p)) == mp_string_type) && (isstring)) {
            set_internal_string(eq_valent(p), mp_rts(mp, v));
        } else if ((internal_type(eq_valent(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 {
                mp_number_clone(internal_value(eq_valent(p)), mp_unity_t);
                mp_number_multiply_int(internal_value(eq_valent(p)), test);
            }
        } else {
            errid = "value has the wrong type";
        }
    }
    if (errid != NULL) {
        char err[256];
        if (isstring) {
            snprintf(err, 256, "%s=\"%s\": %s, assignment ignored.", n, v, errid);
        } else {
            snprintf(err, 256, "%s=%d: %s, assignment ignored.", n, atoi (v), errid);
        }
        mp_warn(mp, err);
    }
}

/*tex

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.

\startitemize
\startitem |fptr| is a virtual file pointer \stopitem
\startitem |data| is the data this stream holds \stopitem
\startitem |cur|  is a cursor pointing into |data| \stopitem
\startitem |size| is the allocated length of the data stream \stopitem
\startitem |used| is the actual length of the data stream \stopitem
\stopitemize

There are small differences between input and output: |term_in| never uses |used|, whereas the other
four never use |cur|.

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.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 *|.Here are all of the functions that need to
be overloaded for |mp_execute|.This is where we fill them all in.This might change too.

*/

static mp_edge_object_node mp_graphic_export (MP mp, mp_edge_header_node p);

void mplib_shipout_backend(MP mp, void *voidh)
{
    mp_edge_header_node h = (mp_edge_header_node) voidh;
    mp_edge_object_node hh = mp_graphic_export(mp, h);
    if (hh) {
        mp_run_data *run = mp_rundata(mp);
        if (run->edges == NULL) {
            run->edges = hh;
        } else {
            mp_edge_object_node p = run->edges;
            while (p->next != NULL) {
                p = p->next;
            }
            p->next = hh;
        }
    }
}

/*tex

Perhaps this is the most important API function in the library.

*/

mp_run_data *mp_rundata(MP mp) {
    return &(mp->run_data);
}

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_buffer);
        mp->jump_buffer = mp_memory_allocate(sizeof(jmp_buf));
        if (mp->jump_buffer == NULL || setjmp (*(mp->jump_buffer)) != 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; */
                /*tex  start non-interactive work */
                /*tex  initialize the output routines */
                mp->term_offset = 0;
                mp->file_offset = 0;
                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);
                mp_print_flush_line(mp);
                mp->input_ptr = 0;
                mp->max_input_stack = mp_file_bottom_text;
                mp->in_open = mp_file_bottom_text;
                mp->open_parens = 0;
                mp->max_buf_stack = 0;
                mp->parameter_ptr = 0;
                mp->max_parameter_stack = 0;
                mp_input_start = mp_input_location = 0;
                mp_input_index = mp_file_bottom_text;
                nloc = nstart = NULL;
                mp->first = 0;
                mp_input_line = 0;
                mp_input_name = mp_input_from_terminal;
                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 = (
                        (int) mp_number_to_scaled(internal_value(mp_time_internal)) / (int) mp_number_to_scaled(mp_unity_t))
                      + (int) mp_number_to_scaled(internal_value(mp_day_internal)
                    );
                }
                mp_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);
                }
            }
            mp->run_state = 1;
            /* we grab one line */
            mp_input_ln(mp, mp->term_in);
            mp_firm_up_the_line(mp);
            mp->buffer[mp_input_limit] = '%';
            mp->first = (size_t) (mp_input_limit + 1);
            mp_input_location = mp_input_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;
}

int mp_finish(MP mp)
{
    int history = 0;
    if (mp->finished || mp->history >= mp_fatal_error_stop) {
        history = mp->history;
        mp_free_instance(mp);
    } else {
        mp_memory_free(mp->jump_buffer);
        mp->jump_buffer = mp_memory_allocate(sizeof(jmp_buf));
        if (mp->jump_buffer == NULL || setjmp (*(mp->jump_buffer)) != 0) {
            history = mp->history;
        } else {
            history = mp->history;
            mp_final_cleanup(mp); /* prepare for death */
        }
        mp_close_files_and_terminate(mp);
        mp_free_instance(mp);
    }
    return history;
}

/*tex
    People may want to know the library version.
*/

char *mp_metapost_version(void) {
    return mp_strdup(metapost_version);
}

/*tex

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).

*/

static void mp_do_max_knot_pool(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_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>'."
        );
    }
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if (cur_exp_type != mp_known_type) {
        mp_display_error(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."
        );
        mp_get_x_next(mp);
        mp_flush_cur_exp(mp, new_expr);
    } else {
        /* the action */
        int p = (int) mp_number_to_scaled (cur_exp_value_number) / 65536;
        if (p > mp->memory_pool[mp_knot_pool].max) {
            mp->memory_pool[mp_knot_pool].max = p;
        } else {
            /*tex Not now: flush excess nodes. */
            /*tex We always keep the minimum. */
        }
    }
}

static void mp_do_random_seed(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_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>'."
        );
    }
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if (cur_exp_type != mp_known_type) {
        mp_display_error(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."
        );
        mp_get_x_next(mp);
        mp_flush_cur_exp(mp, new_expr);
    } else {
        /*tex
            Initialize the random seed to |cur_exp|.
        */
        mp_init_randoms((int) mp_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_format(mp, "%l{randomseed := %N}\n", cur_exp_value_number);
            mp->selector = selector;
        }
    }
}

/*tex

The |inner| and |outer| commands are only slightly harder.

*/

static 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) {
//                    mp_set_eq_type(cur_sym, (t - mp_outer_tag_command));
//                }
//                break;
//            case 1:
//                if (t < mp_outer_tag_command) {
//                    mp_set_eq_type(cur_sym, (t + mp_outer_tag_command));
//                }
//                break;
//        }
        mp_get_x_next(mp);
    } while (cur_cmd == mp_comma_command);
}

/*tex

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.

*/

static void mp_do_property(MP mp)
{
   int p = 0;
   mp_get_x_next(mp);
   mp_scan_primary(mp);
   switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            mp_back_input(mp);
            p = (int) mp_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);
            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);
    }
}

/*tex

\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.

*/

static void mp_do_delimiters(MP mp)
{
    mp_symbol 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;
    mp_set_eq_type(l_delim, mp_left_delimiter_command);
    mp_set_eq_symbol(l_delim, r_delim);
    mp_set_eq_type(r_delim, mp_right_delimiter_command);
    mp_set_eq_symbol(r_delim, l_delim);
    mp_get_x_next(mp);
}

/*tex

Here is a procedure that is called when \MP\ has reached a point where some right delimiter is
mandatory.

*/

static void mp_check_delimiter(MP mp, mp_symbol l_delim, mp_symbol r_delim)
{
    if (cur_cmd == mp_right_delimiter_command && eq_symbol(cur_sym) == l_delim) {
        return;
    } else if (cur_sym != r_delim) {
        char msg[256];
        snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, eq_text(r_delim)));
        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];
        snprintf(msg, 256, "The token '%s' is no longer a right delimiter", mp_str(mp, eq_text(r_delim)));
        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."
        );
    }
}

/*tex

The next four commands save or change the values associated with tokens.

*/

static void mp_do_interim(MP mp) {
    mp_get_x_next(mp);
    if (cur_cmd != mp_internal_command) {
        char msg[256];
        snprintf(msg, 256,
            "The token '%s' isn't an internal quantity",
            (cur_sym == NULL ? "(%CAPSULE)" : mp_str(mp, eq_text(cur_sym)))
        );
        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);
}

/*tex

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.

*/

static void mp_do_let(MP mp)
{
    mp_symbol l; /*tex 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'."
        );
    }
    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);
    mp_set_eq_type(l, cur_cmd);
    switch (cur_cmd) {
        case mp_tag_command:
            mp_set_eq_valent(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:
            mp_set_eq_node(l, cur_mod_node);
            break;
        case mp_left_delimiter_command:
        case mp_right_delimiter_command:
            mp_set_eq_symbol(l, eq_symbol(cur_sym));
            break;
        default:
            mp_set_eq_valent(l, cur_mod);
            break;
    }
    mp_get_x_next(mp);
}

static void mp_grow_internals(MP mp)
{
    int oldmax = mp->memory_pool[mp_internals_pool].max;
    int newmax = oldmax + mp->memory_pool[mp_internals_pool].step;
    if (newmax > mp_max_halfword) {
        mp_confusion(mp, "out of memory"); /* can't be reached */
    } else {
        size_t oldsize = (size_t) (oldmax + 1) * sizeof(mp_internal);
        size_t newsize = (size_t) (newmax + 1) * sizeof(mp_internal);
        mp_internal *internal = mp_memory_allocate(newsize);
        for (int k = 0; k <= newmax; k++) {
            if (k <= oldmax) {
                memcpy(internal + k, mp->internal + k, sizeof(mp_internal));
            } else {
                memset(internal + k, 0, sizeof(mp_internal));
                mp_new_number(((mp_internal *)(internal + k))->v.data.n);
            }
        }
        mp_memory_free(mp->internal);
        mp->internal = internal;
        mp->memory_pool[mp_internals_pool].max = newmax;
        mp->memory_pool[mp_internals_pool].count -= oldsize;
        mp->memory_pool[mp_internals_pool].count += newsize;
    }
}

/* newinternal [numeric|string|boolean] [runscript] | [runscript] */

/* 0:allocate 1:push 2:pop 3:pushlogging 4:poplogging */

static 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 (the_type == mp_known_type) {
        /*tex
            We do as traditional MP does.
        */
    } else {
        /*tex
            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 {
        int used = mp->memory_pool[mp_internals_pool].used;
        if (used >= mp->memory_pool[mp_internals_pool].max) {
            mp_grow_internals(mp);
        }
        ++used;
        mp->memory_pool[mp_internals_pool].used = used;
        mp_get_clear_symbol(mp);
        mp_set_eq_type(cur_sym, mp_internal_command);
        mp_set_eq_valent(cur_sym, used);
        mp_memory_free(internal_name(used));
        set_internal_name(used, mp_strdup(mp_str(mp, eq_text(cur_sym))));
        if (the_type == mp_string_type) {
            set_internal_string(used, mp_rts(mp,""));
        } else {
            mp_set_number_to_zero(internal_value(used));
        }
        set_internal_type(used, the_type);
        set_internal_run(used, run_script);
        if (run_script) {
           mp->run_internal(mp, mp_initialize_internal_code, used, the_type, internal_name(used));
        }
        mp_get_x_next(mp);
    } while (cur_cmd == mp_comma_command);
}

/*tex

    Experimental feature:

    newbytemap 1 of (100,100) ;
    newbytemap 2 of (100,100) ;
    setbytemap 1 to 100 ;
    setbytemap 2 to 200 ;
    setbyte (3,4) of 1 to 123 ;
    setbyte (3,4,2,2) of 1 to 50 ;
    setbyte (3,4) of (1,2) to 150 ;

*/

/*
    We will move much of the bytemap code to an auxiliary module and eventually have an 'internal' that hold a bytemap
    reference.
*/

# define max_bytemaps 1024

static int mp_bytemap_grow(MP mp) /* todo */
{
    int oldmax = mp->memory_pool[mp_bytemaps_pool].max;
    int newmax = oldmax + mp->memory_pool[mp_bytemaps_pool].step;
    if (newmax > max_bytemaps) {
        newmax = max_bytemaps;
    }
    if (newmax <= oldmax) {
        return 0;
    } else {
        size_t newsize = (size_t) (newmax + 1) * sizeof(bytemap_data);
        mp->bytemaps = mp_memory_reallocate(mp->bytemaps, newsize);
        for (int index = oldmax; index < newmax; index++) {
            mp->bytemaps[index] = (bytemap_data) {
                .data    = NULL,
                .nx      = 0,
                .ny      = 0,
                .nz      = 0,
                .ox      = 0,
                .oy      = 0,
                .options = 0,
                .model   = bytemap_gray,
            };
        }
        mp->memory_pool[mp_bytemaps_pool].max = newmax;
    }
    return 1;
}

static int mp_bytemap_valid(MP mp, int index)
{
    return index >= 0 && index < mp->memory_pool[mp_bytemaps_pool].max;
}

static int mp_bytemap_ensure(MP mp, int index)
{
    if (index < 0 || index >= max_bytemaps) {
        return 0;
    }
    while (index >= mp->memory_pool[mp_bytemaps_pool].max) {
        if (! mp_bytemap_grow(mp)) {
            return 0;
        }
    }
    return 1;
}

static int mp_bytemap_valid_data(MP mp, int index)
{
    return mp_bytemap_valid(mp, index) && mp->bytemaps[index].data;
}

static char *mp_bytemap_get_value(MP mp, int index, int *nx, int *ny, int *nz)
{
    if (index >= 0 && index < mp->memory_pool[mp_bytemaps_pool].max && mp->bytemaps[index].data) {
        return bytemap_get_value(&(mp->bytemaps[index]), nx, ny, nz);
    } else {
        return NULL;
    }
}

static inline unsigned char mp_valid_byte(int value) /* todo */
{
    if (value < 0) {
        return 0;
    } else if (value > 255) {
        return 255;
    } else {
        return (unsigned char) value;
    }
}

// integer approximation (299*256/1000 ≈ 77, 587*256/1000 ≈ 150, 114*256/1000 ≈ 29)

static inline int mp_aux_weighted(int r, int g, int b) /* todo */
{
 // return round(0.299 * r + 0.587 * g + 0.114 * b);
    return (77 * r + 150 * g + 29 * b + 128) >> 8;
}

static inline int mp_aux_bytemap_get_byte(MP mp, bytemap_data *bytemap, mp_number *source) /* todo */
{
    if (bytemap->options & bytemap_option_posit) {
        posit8_t p = convertDoubleToP8(mp_number_to_double(*source));
        return (int) p.v;
    } else {
        return (int) mp_round_unscaled(*source);
    }
}

static inline void mp_aux_bytemap_set_byte(MP mp, bytemap_data *bytemap, mp_number *target, int value)
{
    if (bytemap->options & bytemap_option_posit) {
        posit8_t p = { .v = (uint8_t) value };
        mp_set_number_from_double(*target, convertP8ToDouble(p));
    } else {
        mp_set_number_from_int(*target, value);
    }
}

static inline void mp_aux_set_bytemap_channel(bytemap_data *bytemap, int x, int y, int z, int v)
{
    if (x >= 0 && y >= 0 && x < bytemap->nx && y < bytemap->ny && z >= 0 && z < bytemap->nz) {
        switch (bytemap->nz) {
            case 1:
                bytemap->data[bm_current_y(bytemap->ny,y) * bytemap->nx + x] = mp_valid_byte(v);
                break;
            case 3:
                /* todo: check overflow */
                bytemap->data[(bm_current_y(bytemap->ny,y) * bytemap->nx + x) * 3 + z] = mp_valid_byte(v);
                break;
        }
    }
}

static int mp_aux_bytemap_allocate(MP mp, int index, int nx, int ny, int nz, unsigned char *data)
{
 // if (nx > 0 && ny > 0 && nx <= 0x4FFF && ny <= 0x4FFF) {
    if (bytemap_okay(nx, ny, nz)) {
        if (mp->bytemaps[index].data) {
            mp_memory_free(mp->bytemaps[index].data);
            mp->memory_pool[mp_bytemaps_pool].used--;
            mp->memory_pool[mp_bytemap_data_pool].count -=
                mp->bytemaps[index].nx * mp->bytemaps[index].ny * mp->bytemaps[index].nz;
        }
        mp->bytemaps[index] = (bytemap_data) {
            /* There is no checking in valid data here! */
            .data    = data ? data : mp_memory_clear_allocate(nx * ny * nz),
            .nx      = nx,
            .ny      = ny,
            .nz      = nz,
            .ox      = 0,
            .oy      = 0,
            .options = 0,
            .model   = nz == 3 ? bytemap_rgb : bytemap_gray,
        };
        mp->memory_pool[mp_bytemap_data_pool].count += nx * ny * nz;
        mp->memory_pool[mp_bytemaps_pool].used++;
        return 1;
    } else {
        return 0;
    }
}

/* begin of public */

bytemap_data *mp_bytemap_get_by_index(MP mp, int index)
{
    if (index >= 0 && index < mp->memory_pool[mp_bytemaps_pool].max) {
        if (mp->bytemaps[index].data) {
            return &(mp->bytemaps[index]);
        }
    }
    /*tex
        We don't need to allocate as this is catched at the \LUA\ end and
        only access to valid bytemaps make sense.
    */
    return NULL;
}

int mp_bytemap_new_by_index(MP mp, int index, int nx, int ny, int nz, unsigned char *data)
{
    return mp_bytemap_ensure(mp, index) && mp_aux_bytemap_allocate(mp, index, nx, ny, nz, data);
}

/* end of public */

static void mp_bytemap_copy(MP mp) /* done */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (cur_cmd == mp_to_command) {
                int oldindex = (int) mp_round_unscaled(cur_exp_value_number);
                mp_get_x_next(mp);
                mp_scan_primary(mp);
                switch (cur_exp_type) {
                    case mp_numeric_type:
                    case mp_known_type:
                        {
                            int newindex = (int) mp_round_unscaled(cur_exp_value_number);
                            if (mp_bytemap_valid(mp, oldindex) && mp_bytemap_ensure(mp, newindex)) {
                                bytemap_data *source = &(mp->bytemaps[oldindex]);
                                bytemap_data *target = &(mp->bytemaps[newindex]);
                                int target_had_data = target->data != NULL;
                                bytemap_copy(source, target, &(mp->memory_pool[mp_bytemap_data_pool].count));
                                if (! target_had_data && target->data) {
                                    mp->memory_pool[mp_bytemaps_pool].used++;
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_clip(MP mp) /* done */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (cur_cmd == mp_to_command) {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                mp_get_x_next(mp);
                mp_scan_primary(mp);
                switch (cur_exp_type) {
                    case mp_numeric_type:
                    case mp_known_type:
                        {
                         // int value = (int) mp_round_unscaled(cur_exp_value_number);
                            if (mp_bytemap_valid_data(mp, index)) {
                                bytemap_data *b = &mp->bytemaps[index];
                                int value = mp_aux_bytemap_get_byte(mp, b, &(cur_exp_value_number));
                                bytemap_clip(b, value, &(mp->memory_pool[mp_bytemap_data_pool].count));
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_new(MP mp) /* todo */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (cur_cmd == mp_of_command) {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                int nx = 0;
                int ny = 0;
                int nz = 1;
                mp_get_x_next(mp);
                mp_scan_primary(mp);
                switch (cur_exp_type) {
                    case mp_numeric_type:
                    case mp_known_type:
                        if (mp_bytemap_ensure(mp, index)) {
                            nx = (int) mp_round_unscaled(cur_exp_value_number);
                            ny = 1;
                        }
                        break;
                    case mp_pair_type:
                        if (mp_bytemap_ensure(mp, index)) {
                            if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                                nx = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                                ny = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                            }
                        }
                        break;
                    case mp_color_type:
                        if (mp_bytemap_ensure(mp, index)) {
                            if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                                nx = (int) mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))));
                                ny = (int) mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))));
                                nz = (int) mp_round_unscaled(mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))));
                                if (nz < 0) {
                                    nz = 1;
                                } else if (nz > 3) {
                                    nz = 3;
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
                if (mp_aux_bytemap_allocate(mp, index, nx, ny, nz, NULL)) {
                 // mp_print_format(mp, "new bytemap %i: nx %i, ny %i, nz %i", nx, ny, nz);
                } else {
                    mp_warn(mp, "invalid bytemap specification");
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_reduce(MP mp) /* done */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                int method = 0;
                if (cur_cmd == mp_to_command) {
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    method = (int) mp_round_unscaled(cur_exp_value_number);
                }
                if (mp_bytemap_valid_data(mp, index)) {
                    bytemap_reduce(&(mp->bytemaps[index]), method, &(mp->memory_pool[mp_bytemap_data_pool].count));
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_value(MP mp, mp_node p, int c) /* done */
{
    switch (cur_exp_type) {
        case mp_numeric_type:  /* needed ? */
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                int x = 0;
                int y = 0;
                int z = 0;
                mp_number r;
                switch (p->type) {
                    case mp_pair_type:
                        if (mp_pair_is_known(mp_get_value_node(p))) {
                            x = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p))));
                            y = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(p))));
                        }
                        break;
                    case mp_color_type:
                        if (mp_rgb_color_is_known(mp_get_value_node(p))) {
                            x = (int) mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(p))));
                            y = (int) mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(p))));
                            z = (int) mp_round_unscaled(mp_get_value_number(mp_blue_part(mp_get_value_node(p))));
                        }
                        break;
                    default:
                        mp_bad_unary(mp, c);
                        break;
                }
                mp_new_number(r);
                if (mp_bytemap_valid_data(mp, index)) {
                    mp_aux_bytemap_set_byte(mp, &(mp->bytemaps[index]), &r, bytemap_get_byte(&(mp->bytemaps[index]), x, y, z));
                }
                mp_set_cur_exp_value_number(mp, &r);
                mp_free_number(r);
            }
            break;
        default:
            mp_bad_binary(mp, p, c);
            break;
    }
}

static void mp_bytemap_found(MP mp, mp_node p, int c) /* done */ /* todo */
{
    switch (cur_exp_type) {
        case mp_numeric_type:  /* needed ? */
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                int found = 0;
                switch (p->type) {
                    case mp_numeric_type:  /* needed ? */
                    case mp_known_type:
                         /* here posit */
                         if (mp_bytemap_valid_data(mp, index)) {
                             found = bytemap_has_byte_gray(&mp->bytemaps[index],
                                // (int) mp_round_unscaled(mp_get_value_number(mp_get_value_node(p)))
                                // (int) mp_round_unscaled(mp_get_value_number(p))
                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(p)))
                            );
                         }
                        break;
                    case mp_pair_type:
                        if (mp_pair_is_known(mp_get_value_node(p))) {
                            /* here posit */
                            if (mp_bytemap_valid_data(mp, index)) {
                                found = bytemap_has_byte_range(&mp->bytemaps[index],
                                // (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))),
                                // (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(p))))
                                   mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_x_part(mp_get_value_node(p))))),
                                   mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_y_part(mp_get_value_node(p)))))
                                );
                            }
                        }
                        break;
                    case mp_color_type:
                        if (mp_rgb_color_is_known(mp_get_value_node(p))) {
                            /* here posit */
                            if (mp_bytemap_valid_data(mp, index)) {
                                found = bytemap_has_byte_rgb(&mp->bytemaps[index],
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part(mp_get_value_node(p))))),
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(p))))),
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part(mp_get_value_node(p)))))
                                );
                            }
                        }
                        break;
                    default:
                        mp_bad_unary(mp, mp_bytemap_found_operation);
                        break;
                }
                mp_set_cur_exp_value_boolean(mp, found ? mp_true_operation : mp_false_operation);
                cur_exp_type = mp_boolean_type;
            }
            break;
        default:
            mp_bad_binary(mp, p, c);
            break;
    }
}

static void mp_bytemap_path(MP mp, mp_node p, int c) /* no need */
{
    (void) c;
    switch (cur_exp_type) {
        case mp_numeric_type:  /* needed ? */
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                mp_knot head = NULL;
                mp_knot tail = NULL;
                if (mp_bytemap_valid_data(mp, index)) {
                    int nx = mp->bytemaps[index].nx;
                    int ny = mp->bytemaps[index].ny;
                    int nz = mp->bytemaps[index].nz;
                    switch (nz) {
                        case 1:
                            {
                                int value = -1;
                                int range = -1;
                                switch (p->type) {
                                    case mp_numeric_type:  /* needed ? */
                                    case mp_known_type:
                                     // value = (int) mp_round_unscaled(mp_get_value_number(p));
                                        value = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(p)));
                                        break;
                                    case mp_pair_type:
                                        if (mp_pair_is_known(mp_get_value_node(p))) {
                                         // value = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p))));
                                         // range = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(p))));
                                            value = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_x_part(mp_get_value_node(p)))));
                                            range = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_y_part(mp_get_value_node(p)))));
                                        }
                                        break;
                                    case mp_color_type:
                                        if (mp_rgb_color_is_known(mp_get_value_node(p))) {
                                            /* here posit */
                                            value = mp_aux_weighted(
                                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part(mp_get_value_node(p))))),
                                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(p))))),
                                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part(mp_get_value_node(p)))))
                                            );
                                        }
                                        break;
                                    default:
                                        mp_bad_unary(mp, mp_bytemap_value_operation);
                                        break;
                                }
                                if (range > value) {
                                    /* can be a single loop and div/mod */
                                    unsigned char *p = mp->bytemaps[index].data;
                                    for (int y = 0; y < ny; y++) {
                                        for (int x = 0; x < nx; x++) {
                                            /* here posit */
                                            if (*p >= value && *p <= range) {
                                                mp_knot k = mp_simple_int_knot(mp, x, bm_current_y(ny,y));
                                                if (head) {
                                                    mp_prev_knot(k) = tail;
                                                    mp_next_knot(tail) = k;
                                                    tail = k;
                                                } else {
                                                    head = k;
                                                    tail = k;
                                                }
                                            }
                                            ++p;
                                        }
                                    }
                                } else if (value >= 0) {
                                    /* can be a single loop and div/mod */
                                    unsigned char *p = mp->bytemaps[index].data;
                                    for (int y = 0; y < ny; y++) {
                                        int yy = bm_current_y(ny,y);
                                        for (int x = 0; x < nx; x++) {
                                            /* here posit */
                                            if (*p++ == value) {
                                                mp_knot k = mp_simple_int_knot(mp, x, yy);
                                                if (head) {
                                                    mp_prev_knot(k) = tail;
                                                    mp_next_knot(tail) = k;
                                                    tail = k;
                                                } else {
                                                    head = k;
                                                    tail = k;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                        case 3:
                            {
                                int r = -1;
                                int g = -1;
                                int b = -1;
                                switch (p->type) {
                                    case mp_numeric_type:  /* needed ? */
                                    case mp_known_type:
                                        r = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(p)));
                                        g = r;
                                        b = r;
                                        break;
                                    case mp_color_type:
                                        if (mp_rgb_color_is_known(mp_get_value_node(p))) {
                                            r = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part(mp_get_value_node(p)))));
                                            g = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(p)))));
                                            b = mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part(mp_get_value_node(p)))));
                                        }
                                        break;
                                    default:
                                        mp_bad_unary(mp, mp_bytemap_value_operation);
                                        break;
                                }
                                if (r >= 0) {
                                    /* can be a single loop and div/mod */
                                    unsigned char *bytes = mp->bytemaps[index].data;
                                    for (int y = 0; y < ny; y++) {
                                        int yy = bm_current_y(ny,y);
                                        for (int x = 0; x < nx; x++) {
                                            unsigned char red   = bytes[0];
                                            unsigned char green = bytes[1];
                                            unsigned char blue  = bytes[2];
                                            bytes += 3;
                                            if (red == r && green == g && blue == b) {
                                                mp_knot k = mp_simple_int_knot(mp, x, yy);
                                                if (head) {
                                                    mp_prev_knot(k) = tail;
                                                    mp_next_knot(tail) = k;
                                                    tail = k;
                                                } else {
                                                    head = k;
                                                    tail = k;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                    }
                }
                if (tail) {
                    mp_prev_knot(head) = tail;
                    mp_next_knot(tail) = head;
                    mp_set_cur_exp_knot(mp, head);
                    cur_exp_type = mp_path_type;
                } else {
                    /* what to do */
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_bounds(MP mp, mp_node p, int c, int clip) /* done */
{
    (void) c;
    (void) clip; /* todo */
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                if (mp_bytemap_valid_data(mp, index)) {
                    bytemap_data b = mp->bytemaps[index];
                    int llx, lly, urx, ury;
                    unsigned char value = 0;
                    switch (p->type) {
                        case mp_numeric_type:  /* needed ? */
                        case mp_known_type:
                         // value = mp_valid_byte((int) mp_round_unscaled(mp_get_value_number(p)));
                            value = mp_valid_byte(mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(p))));
                            break;
                        default:
                            /* error */
                            break;
                    }
                    bytemap_bounds(&b, value, &llx, &lly, &urx, &ury, 1);
                    mp_set_number_from_int(mp_minx, llx);
                    mp_set_number_from_int(mp_miny, ury);
                    mp_set_number_from_int(mp_maxx, urx);
                    mp_set_number_from_int(mp_maxy, lly);
                    mp_make_bounding_box(mp);
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_set_options(MP mp) /* no need */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            {
                int index   = (int) mp_round_unscaled(cur_exp_value_number);
                int options = 0;
                if (cur_cmd == mp_to_command) {
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    options = (int) mp_round_unscaled(cur_exp_value_number);
                }
                if (mp_bytemap_valid_data(mp, index)) {
                    /* here posit */
                    mp->bytemaps[index].options = options;
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_set(MP mp) /* done */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            if (cur_cmd == mp_to_command) {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                mp_get_x_next(mp);
                mp_scan_primary(mp);
                switch (cur_exp_type) {
                    case mp_numeric_type:
                    case mp_known_type:
                        if (mp_bytemap_valid_data(mp, index)) {
                            /* here posit */
                            bytemap_slice_gray(
                                &(mp->bytemaps[index]),
                                0, 0, mp->bytemaps[index].nx, mp->bytemaps[index].ny,
                             // (int) mp_round_unscaled(cur_exp_value_number)
                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(cur_exp_value_number))
                            );
                        }
                        break;
                    case mp_color_type:
                        if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                            if (mp_bytemap_valid_data(mp, index)) {
                                /* here posit */
                                bytemap_slice_rgb(
                                    &(mp->bytemaps[index]),
                                    0, 0, mp->bytemaps[index].nx, mp->bytemaps[index].ny,
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))))),
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))))),
                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node)))))
                                );
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_aux_reset_bytemap(MP mp, int index) /* done */
{
    if (mp->bytemaps[index].options & bytemap_option_persistent) {
        /* don't free */
    } else if (bytemap_reset(&(mp->bytemaps[index]),&(mp->memory_pool[mp_bytemap_data_pool].count))) {
        mp->memory_pool[mp_bytemaps_pool].used--;
    }
}

static void mp_aux_reset_bytemaps(MP mp) /* todo */
{
    if (mp->memory_pool[mp_bytemaps_pool].used) {
        for (int index = 0; index < mp->memory_pool[mp_bytemaps_pool].max; index++) {
            if (mp_bytemap_valid_data(mp, index)) {
                mp_aux_reset_bytemap(mp, index);
                if (! mp->memory_pool[mp_bytemaps_pool].used) {
                    break;
                }
            }
        }
    }
}

static void mp_bytemap_reset(MP mp) /* todo */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_numeric_type:
        case mp_known_type:
            {
                int index = (int) mp_round_unscaled(cur_exp_value_number);
                if (mp_bytemap_valid_data(mp, index)) {
                    mp_aux_reset_bytemap(mp, index);
                 // printf("[bitmap: reset, index %i\n", index);
                }
                break;
            }
        default:
          /* error */
          break;
    }
}

static void mp_bytemap_reset_all(MP mp)
{
    mp_get_x_next(mp);
    mp_aux_reset_bytemaps(mp);
}

static void mp_bytemap_set_byte(MP mp) /* done */ /* todo */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    switch (cur_exp_type) {
        case mp_pair_type:
            if (cur_cmd == mp_of_command) {
                if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                    int x = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                    int y = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    switch (cur_exp_type) {
                        case mp_numeric_type:
                        case mp_known_type:
                            if (cur_cmd == mp_to_command) {
                                int index = (int) mp_round_unscaled(cur_exp_value_number);
                                mp_get_x_next(mp);
                                mp_scan_primary(mp);
                                switch (cur_exp_type) {
                                    case mp_numeric_type:
                                    case mp_known_type:
                                        if (mp_bytemap_valid_data(mp, index)) {
                                            x += mp->bytemaps[index].ox;
                                            y += mp->bytemaps[index].oy;
                                         // mp_aux_set_bytemap_gray(
                                            bytemap_set_gray(
                                                &(mp->bytemaps[index]),
                                                x, y,
                                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(cur_exp_value_number))
                                            );
                                        }
                                        break;
                                    case mp_color_type:
                                        if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                                            if (mp_bytemap_valid_data(mp, index)) {
                                                x += mp->bytemaps[index].ox;
                                                y += mp->bytemaps[index].oy;
                                             // mp_aux_set_bytemap_rgb(
                                                bytemap_set_rgb(
                                                    &(mp->bytemaps[index]),
                                                    x, y,
                                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))))),
                                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))))),
                                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node)))))
                                                );
                                            }
                                        }
                                        break;
                                    default:
                                        break;
                                }
                            }
                            break;
                        case mp_color_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        case mp_cmykcolor_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
            break;
        case mp_color_type:
            if (cur_cmd == mp_of_command) {
                if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                    int x = (int) mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node))));
                    int y = (int) mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))));
                    int z = (int) mp_round_unscaled(mp_get_value_number(mp_blue_part(mp_get_value_node(cur_exp_node))));
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    switch (cur_exp_type) {
                        case mp_numeric_type:
                        case mp_known_type:
                            if (cur_cmd == mp_to_command) {
                                int index = (int) mp_round_unscaled(cur_exp_value_number);
                                mp_get_x_next(mp);
                                mp_scan_primary(mp);
                                switch (cur_exp_type) {
                                    case mp_numeric_type:
                                    case mp_known_type:
                                        if (mp_bytemap_valid_data(mp, index)) {
                                            x += mp->bytemaps[index].ox;
                                            y += mp->bytemaps[index].oy;
                                            mp_aux_set_bytemap_channel(
                                                &(mp->bytemaps[index]),
                                                x, y, z,
                                                mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(cur_exp_value_number))
                                            );
                                        }
                                        break;
                                    default:
                                        break;
                                }
                            }
                            break;
                        case mp_color_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        case mp_cmykcolor_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
            break;
        case mp_cmykcolor_type:
            if (cur_cmd == mp_of_command) {
                if (mp_cmyk_color_is_known(mp_get_value_node(cur_exp_node))) {
                    int x = (int) mp_round_unscaled(mp_get_value_number(mp_cyan_part(mp_get_value_node(cur_exp_node))));
                    int y = (int) mp_round_unscaled(mp_get_value_number(mp_magenta_part(mp_get_value_node(cur_exp_node))));
                    int dx = (int) mp_round_unscaled(mp_get_value_number(mp_yellow_part(mp_get_value_node(cur_exp_node))));
                    int dy = (int) mp_round_unscaled(mp_get_value_number(mp_black_part(mp_get_value_node(cur_exp_node))));
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    switch (cur_exp_type) {
                        case mp_numeric_type:
                        case mp_known_type:
                            if (cur_cmd == mp_to_command) {
                                int index = (int) mp_round_unscaled(cur_exp_value_number);
                                mp_get_x_next(mp);
                                mp_scan_primary(mp);
                                if (mp_bytemap_valid_data(mp, index)) {
                                    x += mp->bytemaps[index].ox;
                                    y += mp->bytemaps[index].oy;
                                    if (x >= 0 && y >= 0 && x < mp->bytemaps[index].nx && y < mp->bytemaps[index].ny) {
                                        switch (cur_exp_type) {
                                            case mp_numeric_type:
                                            case mp_known_type:
                                                bytemap_slice_gray(
                                                    &(mp->bytemaps[index]),
                                                    x, y, dx, dy,
                                                    mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(cur_exp_value_number))
                                                );
                                                break;
                                            case mp_pair_type:
                                                if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                                                    bytemap_slice_range(
                                                        &(mp->bytemaps[index]),
                                                        x, y, dx, dy,
                                                        (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))),
                                                        (int) mp_round_unscaled(mp_get_value_number(mp_y_part (mp_get_value_node(cur_exp_node))))
                                                    );
                                                }
                                                break;
                                            case mp_color_type:
                                                if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) {
                                                    /* here posit */
                                                    bytemap_slice_rgb(
                                                        &(mp->bytemaps[index]),
                                                        x, y, dx, dy,
                                                        mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_red_part  (mp_get_value_node(cur_exp_node))))),
                                                        mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node))))),
                                                        mp_aux_bytemap_get_byte(mp, &(mp->bytemaps[index]), &(mp_get_value_number(mp_blue_part (mp_get_value_node(cur_exp_node)))))
                                                    );
                                                }
                                                break;
                                            default:
                                                break;
                                        }
                                    }
                                }
                            }
                            break;
                        case mp_color_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        case mp_cmykcolor_type:
                            if (cur_cmd == mp_to_command) {
                                /* todo */
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
            break;
        default:
            /* error */
            break;
    }
}

static void mp_bytemap_set_offset(MP mp) /* todo */
{
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    if (cur_exp_type == mp_pair_type) {
        if (cur_cmd == mp_of_command) {
            if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) {
                int x = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                int y = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                mp_get_x_next(mp);
                mp_scan_primary(mp);
                switch (cur_exp_type) {
                    case mp_numeric_type:
                    case mp_known_type:
                        {
                            int index = (int) mp_round_unscaled(cur_exp_value_number);
                            if (mp_bytemap_valid_data(mp, index)) {
                                mp->bytemaps[index].ox = x;
                                mp->bytemaps[index].oy = y;
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

/*tex

The various |show| commands are distinguished by modifier fields in the usual way. 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.

*/

static void mp_do_show(MP mp)
{
    do {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_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);
}

void mp_display_token(MP mp)
{
    mp_print_nl(mp, "> ");
    if (cur_sym == NULL) {
        /*tex
            Show a numeric or string or capsule token.
        */
        switch (cur_cmd) {
            case mp_numeric_command:
                mp_print_number(mp, cur_mod_number);
                break;
            case mp_capsule_command:
                mp_print_capsule(mp, cur_mod_node);
                break;
            default:
                mp_print_format(mp, "%Q", cur_mod_str);
                mp_delete_string_reference(mp, cur_mod_str);
                break;
        }
    } else {
        mp_print_format(mp, "%S = %C", eq_text(cur_sym), 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| */
    }
}

/*

The following cases of |print_cmd_mod| might arise in connection with |disp_token|, although they
don't necessarily correspond to primitive tokens.

*/

static void mp_do_show_token(MP mp)
{
    do {
        mp_get_t_next(mp);
        mp_display_token(mp);
        mp_get_x_next(mp);
    } while (cur_cmd == mp_comma_command);
}

static void mp_do_show_stats(MP mp)
{
 // if (0) {
 //     mp_print_ln(mp);
 //     mp_print_format(mp,"%l[Memory usage: variables %i, strings %i, pool %i]",
 //         mp->var_used,
 //         mp->strs_in_use,
 //         mp->pool_in_use
 //     );
 //     mp_print_ln(mp);
 // } else {
        int selector = mp->selector;
        mp_normalize_selector(mp);
        mp->run_status(mp);
        mp->selector = selector;
 // }
    mp_get_x_next(mp);
}

/*tex

Here's a recursive procedure that gives an abbreviated account of a variable, for use by
|do_show_var|.

*/

static void mp_display_var(MP mp, mp_node p)
{
    if (p->type == mp_structured_type) {
        /* Descend the structure */
        mp_node q = mp_get_attribute_head(p);
        do {
            mp_display_var(mp, q);
            q = q->link;
        } while (q != mp->end_attr);
        q = mp_get_subscr_head(p);
        while (q->name_type == mp_subscript_operation) {
            mp_display_var(mp, q);
            q = q->link;
        }
    } else if (p->type >= mp_unsuffixed_macro_type) {
        /* Display a variable macro */
        mp_print_nl(mp, "");
        mp_print_variable_name(mp, p);
        if (p->type > mp_unsuffixed_macro_type) {
            mp_print_string(mp, "@#"); /* |suffixed_macro| */
        }
        mp_print_string(mp, "=macro:");
        mp_show_macro(mp, mp_get_value_node(p), NULL);
    } else if (p->type != mp_undefined_type) {
        mp_print_nl(mp, "");
        mp_print_variable_name(mp, p);
        mp_print_char(mp, '=');
        mp_print_exp(mp, p, 0);
    }
}

static void mp_do_show_var(MP mp)
{
    do {
        mp_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_display_var(mp, cur_mod_node);
                goto DONE;
            }
        }
        mp_display_token(mp);
      DONE:
        mp_get_x_next(mp);
    } while (cur_cmd == mp_comma_command);
}

static void mp_do_show_dependencies(MP mp)
{
    if (mp_number_positive(internal_value(mp_tracing_dependencies_internal))) {
        mp_value_node p = (mp_value_node) mp->dep_head->link;
        if (! p || p == mp->dep_head) {
            mp_print_format(mp, "%l[no dependencies]");
        } else {
            int i = 0;
            while (p != mp->dep_head) {
                int n = mp_count_dependency(mp, (mp_value_node) mp_get_dep_list(p));
                mp_print_format(mp, "%l[dependency %i: node %P, type '%s', ", ++i, p, mp_type_string(p->type));
                if (p->name_type == mp_capsule_operation) {
                    mp_print_string(mp, "capsule");
                } else {
                    mp_print_format(mp, "nametype '%s', serial %i, ", mp_operator_string(p->name_type), mp_get_indep_value(p));
                    mp_print_variable_name(mp, (mp_node) p);
                }
                mp_print_string(mp, n > 1 ? " = ( " : " = ");
                mp_print_dependency(mp, (mp_value_node) mp_get_dep_list(p), p->type);
                mp_print_string(mp, n > 1 ? " )]" : "]");
                p = (mp_value_node) mp_get_dep_list(p);
                while (mp_get_dep_info(p) != NULL) {
                    p = (mp_value_node) p->link;
                }
                p = (mp_value_node) p->link;
            }
        }
    } else {
        mp_value_node p = (mp_value_node) mp->dep_head->link;
        while (p != mp->dep_head) {
            if (mp_interesting(mp, (mp_node) p)) {
                mp_print_nl(mp, "");
                mp_print_format(mp, "type '%s' : ", mp_type_string(p->type));
                mp_print_variable_name(mp, (mp_node) p);
                mp_print_string(mp, " = ");
                mp_print_dependency(mp, (mp_value_node) mp_get_dep_list(p), p->type);
            }
            p = (mp_value_node) mp_get_dep_list(p);
            while (mp_get_dep_info(p) != NULL) {
                p = (mp_value_node) p->link;
            }
            p = (mp_value_node) p->link;
        }
    }
    mp_get_x_next(mp);
}

/*tex

Finally we are ready for the procedure that governs all of the show commands.

*/

static void mp_do_show_whatever(MP mp)
{
    if (mp->interaction == mp_error_stop_mode) {
        mp_print_flush_line(mp);
    }
    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 (mp_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);
        }
    }
}

/* void mp_clear_color(MP mp, void *n)
{
    mp_set_number_to_zero(((mp_shape_node) n)->cyan);
    mp_set_number_to_zero(((mp_shape_node) n)->magenta);
    mp_set_number_to_zero(((mp_shape_node) n)->yellow);
    mp_set_number_to_zero(((mp_shape_node) n)->black);
    mp_color_model(n) = mp_uninitialized_model;
} */

static void complain_invalid_with_list(MP mp, mp_with_codes t)
{
    mp_value new_expr;
    const char *hlp = NULL;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    switch (t) {
        case mp_with_pre_script_code:
        case mp_with_nested_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:
        case mp_with_nested_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;
        case mp_with_bytemap_code:
            hlp =
                "Next time say 'withbytemap <known numeric expression>'; I'll ignore the bad\n"
                "'with' clause and look for another.";
            break;
        case mp_with_curvature_code:
            hlp =
                "Next time say 'withcurvature <known numeric expression>'; I'll ignore the bad\n"
                "'with' clause and look for another.";
            break;
        case mp_with_mesh_code:
            hlp =
                "Next time say 'withmesh <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_display_error(mp, NULL);
    mp_back_error(mp, "Improper type", hlp);
    mp_get_x_next(mp);
    mp_flush_cur_exp(mp, new_expr);
}

/*tex

    Some properties are applied after scanning all |with*| which means that we can set the same
    property multiple times and only the last one applies. They get applied to all components of
    a picture. However the scripts are accumulating so these get a different treatment. Here we
    also need to be more selective to what it gets applied to because one doesn't always want
    a picture property to be applied to all components.

*/

static mp_string mp_aux_append_pre_script(MP mp, mp_string target)
{
    if (target != NULL) {
        int selector = mp->selector;
        mp_string old = target; /*tex For string cleanup after combining. */
        mp->selector = mp_new_string_selector;
        mp_str_room(mp, (int) (target->len + cur_exp_str->len + 2));
        mp_print_mp_string(mp, cur_exp_str);
        mp_str_room(mp, 1);
        mp_append_char(mp, 13);
        mp_print_mp_string(mp, target);
        target = mp_make_string(mp);
        mp_delete_string_reference(mp, old);
        mp->selector = selector;
    } else {
        target = cur_exp_str;
    }
    mp_add_string_reference(mp, target);
    return target;
}

static mp_string mp_aux_prepend_post_script(MP mp, mp_string target)
{
    if (target != NULL) {
        int selector = mp->selector;
        mp_string old = target; /*tex For string cleanup after combining. */
        mp->selector = mp_new_string_selector;
        mp_str_room(mp, (int) (target->len + cur_exp_str->len + 2));
        mp_print_mp_string(mp, target);
        mp_str_room(mp, 1);
        mp_append_char(mp, 13);
        mp_print_mp_string(mp, cur_exp_str);
        target = mp_make_string(mp);
        mp_delete_string_reference(mp, old);
        mp->selector = selector;
    } else {
        target = cur_exp_str;
    }
    mp_add_string_reference(mp, target);
    return target;
}

static void mp_scan_with_list(MP mp, mp_node p, mp_node pstop)
{
    mp_node cp = MP_VOID;
    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 stacking_set = 0;
    int miterlimit = 0;
    int linecap = -1;
    int linejoin = -1;
    int curvature = -1;
    int mesh = -1;
    int bytemap = -1;
    while (cur_cmd == mp_with_option_command) {
        /*tex
            |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 && t != mp_with_nothing_code) {
            mp_scan_expression(mp);
        }
        switch (t) {
            case mp_with_uninitialized_model_code :
                switch (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));
                            mp_new_number(new_expr.data.n);
                            if (cp == MP_VOID) {
                                make_cp_a_colored_object(cp, p);
                            }
                            if (cp != NULL) {
                                /*tex Transfer a color from the current expression to object~|cp|. */
                                switch (cur_exp_type) {
                                    case mp_color_type:
                                        {
                                            /*tex 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)));
                                            mp_set_number_to_zero(mp_black_color(cp));
                                        }
                                        break;
                                    case mp_cmykcolor_type:
                                        {
                                            /*tex 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:
                                        {
                                            /*tex Transfer a greyscale from the current expression to object~|cp|. */
                                         // mp_number qq;
                                         // mp_new_number_clone(qq, cur_exp_value_number);
                                            mp_color_model(cp) = mp_grey_model;
                                            mp_set_number_to_zero(mp_cyan_color(cp));
                                            mp_set_number_to_zero(mp_magenta_color(cp));
                                            mp_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);
                                         // mp_free_number(qq);
                                        }
                                        break;
                                    default:
                                        switch (cur_exp_value_boolean) {
                                            case mp_false_operation:
                                                /*tex Transfer a noncolor from the current expression to object~|cp|. */
                                                mp_color_model(cp) = mp_no_model;
                                                break;
                                            case mp_true_operation:
                                                /*tex Transfer no color from the current expression to object~|cp|. */
                                                mp_color_model(cp) = mp_uninitialized_model;
                                                break;
                                            default:
                                                break;
                                        }
                                        mp_set_number_to_zero(mp_cyan_color(cp));
                                        mp_set_number_to_zero(mp_magenta_color(cp));
                                        mp_set_number_to_zero(mp_yellow_color(cp));
                                        mp_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 (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));
                    mp_new_number(new_expr.data.n);
                    if (cp == MP_VOID) {
                        make_cp_a_colored_object(cp, p);
                    }
                    if (cp != NULL) {
                        /*tex 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)));
                        mp_set_number_to_zero(mp_black_color(cp));
                    }
                    mp_flush_cur_exp(mp, new_expr);
                }
                break;
            case mp_with_cmyk_model_code:
                if (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));
                    mp_new_number(new_expr.data.n);
                    if (cp == MP_VOID) {
                        make_cp_a_colored_object(cp, p);
                    }
                    if (cp != NULL) {
                        /*tex 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 (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));
                    mp_new_number(new_expr.data.n);
                    if (cp == MP_VOID) {
                        make_cp_a_colored_object(cp, p);
                    }
                    if (cp != NULL) {
                        /*tex Transfer a greyscale from the current expression to object~|cp|. */
                     // mp_number qq;
                     // mp_new_number_clone(qq, cur_exp_value_number);
                        mp_color_model(cp) = mp_grey_model;
                        mp_set_number_to_zero(mp_cyan_color(cp));
                        mp_set_number_to_zero(mp_magenta_color(cp));
                        mp_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);
                     // mp_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) {
                    /*tex Transfer a noncolor from the current expression to object~|cp|. */
                    mp_color_model(cp) = mp_no_model;
                    mp_set_number_to_zero(mp_cyan_color(cp));
                    mp_set_number_to_zero(mp_magenta_color(cp));
                    mp_set_number_to_zero(mp_yellow_color(cp));
                    mp_set_number_to_zero(mp_grey_color(cp));
                }
                break;
            case mp_with_pen_code:
                if (cur_exp_type != mp_pen_type && cur_exp_type != mp_nep_type) {
                    complain_invalid_with_list(mp, t);
                    goto CONTINUE;
                } else {
                    if (pp == MP_VOID) {
                        /*tex Make |pp| an object in list~|p| that needs a pen. */
                        pp = p;
                        while (pp != NULL) {
                            if (mp_has_pen(pp)) {
                                break;
                            } else {
                                pp = pp->link;
                            }
                        }
                    }
                    if (pp != NULL) {
                        switch (pp->type) {
                            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) = cur_exp_type == mp_nep_type;
                                break;
                            default:
                                break;
                        }
                        cur_exp_type = mp_vacuous_type;
                    }
                }
                break;
            case mp_with_pre_script_code:
            case mp_with_nested_pre_script_code:
                if (cur_exp_type != mp_string_type) {
                    complain_invalid_with_list(mp, t);
                    goto CONTINUE;
                } else if (cur_exp_str->len) {
                    /*tex
                        In this version we always can set scripts so the |mp_has_script|.test is
                        not really needed.
                    */
                    mp_node ap = p;
                 // if (ap == MP_VOID) {
                 //     ap = p;
                 // }
                 // while ((ap != NULL) && (! mp_has_script(ap))) {
                 //     ap = ap->link;
                 // }
                 // if (ap != NULL) {
                 //     mp_pre_script(ap) = mp_aux_append_pre_script(mp, mp_pre_script(ap));
                 //     cur_exp_type = mp_vacuous_type;
                 // }
                    mp_add_string_reference(mp, cur_exp_str);
                    if (ap && mp_has_script(ap)) {
                        mp_pre_script(ap) = mp_aux_append_pre_script(mp, mp_pre_script(ap));
                    }
                    if (t == mp_with_nested_pre_script_code) {
                        ap = ap->link;
                        while (ap) {
                            if (mp_has_script(ap)) {
                                mp_pre_script(ap) = mp_aux_append_pre_script(mp, mp_pre_script(ap));
                            }
                            ap = ap->link;
                        }
                    }
                    mp_delete_string_reference(mp, cur_exp_str);
                    cur_exp_type = mp_vacuous_type;
                }
                break;
            case mp_with_post_script_code:
            case mp_with_nested_post_script_code:
                if (cur_exp_type != mp_string_type) {
                    complain_invalid_with_list(mp, t);
                    goto CONTINUE;
                } else if (cur_exp_str->len) {
                    mp_node bp = p;
                 // if (bp == MP_VOID) {
                 //     bp = p;
                 // }
                 // while ((bp != NULL) && (! mp_has_script(bp))) {
                 //     bp = bp->link;
                 // }
                 // if (bp != NULL) {
                 //     mp_post_script(ap) = mp_aux_prepend_post_script(mp, mp_post_script(ap));
                 //     cur_exp_type = mp_vacuous_type;
                 // }
                    mp_add_string_reference(mp, cur_exp_str);
                    if (bp && mp_has_script(bp)) {
                        mp_post_script(bp) = mp_aux_prepend_post_script(mp, mp_post_script(bp));
                    }
                    if (t == mp_with_nested_pre_script_code) {
                        bp = bp->link;
                        while (bp) {
                            if (mp_has_script(bp)) {
                                mp_post_script(bp) = mp_aux_prepend_post_script(mp, mp_post_script(bp));
                            }
                            bp = bp->link;
                        }
                    }
                    mp_delete_string_reference(mp, cur_exp_str);
                    cur_exp_type = mp_vacuous_type;
                }
                break;
            case mp_with_stacking_code:
                switch (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) = (int) mp_round_unscaled(cur_exp_value_number);
                            }
                            if (pp && spstop != NULL) {
                                mp_stacking(spstop) = (int) mp_round_unscaled(cur_exp_value_number);
                            }
                            /* free ? */
                            cur_exp_type = mp_vacuous_type;
                            stacking_set = 1;
                        }
                        break;
                    case mp_pair_type:
                        {
                            if (pp && mp_nice_pair(mp, cur_exp_node, cur_exp_type)) {
                                if (sp == MP_VOID) {
                                    sp = p;
                                }
                                if (spstop == MP_VOID) {
                                    spstop = pstop;
                                }
                                if (sp != NULL) {
                                    mp_stacking(sp) = (int) mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))));
                                }
                                if (spstop != NULL) {
                                    mp_stacking(spstop) = (int) mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))));
                                }
                                /* free ? */
                                cur_exp_type = mp_vacuous_type;
                                stacking_set = 1;
                            } 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 (cur_exp_type) {
                    case mp_known_type:
                        {
                            linecap = (int) mp_round_unscaled(cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_linejoin_code:
                switch (cur_exp_type) {
                    case mp_known_type:
                        {
                            linejoin = (int) mp_round_unscaled(cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_miterlimit_code:
                switch (cur_exp_type) {
                    case mp_known_type:
                        {
                            miterlimit = 1;
                            mp_new_number_clone(ml, cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_curvature_code:
                switch (cur_exp_type) {
                    case mp_known_type:
                        {
                            curvature = (int) mp_round_unscaled(cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_mesh_code:
                switch (cur_exp_type) {
                    case mp_known_type:
                        {
                            mesh = (int) mp_round_unscaled(cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_bytemap_code:
                switch (cur_exp_type) {
                    case mp_known_type:
                        {
                            bytemap = (int) mp_round_unscaled(cur_exp_value_number);
                            cur_exp_type = mp_vacuous_type;
                            break;
                        }
                    default:
                        {
                            complain_invalid_with_list(mp, t);
                            goto CONTINUE;
                        }
                }
                break;
            case mp_with_nothing_code:
                /*tex Nicer would be a generic quit scanning expression. */
                continue;
            case mp_with_dashed_code:
                if (cur_exp_type != mp_picture_type) {
                    complain_invalid_with_list(mp, t);
                    goto CONTINUE;
                }
                // fall through
            default:
                if (dp == MP_VOID) {
                    /*tex
                        Make |dp| a stroked node in list~|p|.
                    */
                    dp = p;
                    while (dp != NULL) {
                        if (dp->type == mp_stroked_node_type) {
                            break;
                        } else {
                            dp = dp->link;
                        }
                    }
                }
                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);
                    mp_set_number_to_unity(((mp_shape_node) dp)->dashscale);
                    cur_exp_type = mp_vacuous_type;
                }
                break;
        }
    }
    /*tex
        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) {
        /*tex Copy |cp|'s color into the colored objects linked to~|cp|. */
        mp_node q = cp->link;
        while (q != NULL) {
            if (mp_has_color(q)) {
                mp_shape_node q0 = (mp_shape_node) q;
                mp_shape_node cp0 = (mp_shape_node) cp;
                mp_number_clone(q0->red, cp0->red);
                mp_number_clone(q0->green, cp0->green);
                mp_number_clone(q0->blue, cp0->blue);
                mp_number_clone(q0->black, cp0->black);
                mp_color_model(q) = mp_color_model(cp);
            }
            q = q->link;
        }
    }
    if (pp > MP_VOID) {
        /*tex Copy |mp_pen_ptr(pp)| into stroked and filled nodes linked to |pp|. */
        mp_node q = pp->link;
        while (q != NULL) {
            if (mp_has_pen(q)) {
                switch (q->type) {
                    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 = q->link;
        }
    }
    if (dp > MP_VOID) {
        /*tex Make stroked nodes linked to |dp| refer to |mp_dash_ptr(dp)|. */
        mp_node q = dp->link;
        while (q != NULL) {
            if (q->type == 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);
                mp_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 = q->link;
        }
    }
    if (linecap >= 0 && linecap < mp_weird_linecap_code) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_set_linecap(q, linecap);
                    break;
                default:
                    break;
            }
            q = q->link;
        }
    }
    if (linejoin >= 0 && linejoin < mp_weird_linejoin_code) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_set_linejoin(q, linejoin);
                    break;
                default:
                    break;
            }
            q = q->link;
        }
    }
    if (miterlimit) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_number_clone(mp_miterlimit(q), ml);
                    break;
                default:
                    break;
            }
            q = q->link;
        }
        mp_free_number(ml);
    }
    if (curvature >= 0 && curvature < mp_weird_curvature_code) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_set_curvature(q, curvature);
                    break;
                default:
                    break;
            }
            q = q->link;
        }
    }
    if (mesh >= 0) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_set_mesh(q, mesh);
                    break;
                default:
                    break;
            }
            q = q->link;
        }
    }
    if (stacking_set) {
     // if (! pp && sp > MP_VOID) {
     //     mp_node q = sp->link;
     //     while (q != NULL) {
     //         mp_stacking(q) = mp_stacking(sp);
     //         q = q->link;
     //     }
     // }
        mp_node q = p;
        while (q != NULL) {
            mp_stacking(q) = mp_stacking(sp);
            q = q->link;
        }
    }
    if (bytemap >= 0 && mp_bytemap_valid_data(mp, bytemap)) {
        mp_node q = p;
        while (q != NULL) {
            switch (q->type) {
                case mp_fill_node_type:
                case mp_stroked_node_type:
                    mp_set_bytemap(q, bytemap); /* a short */
                    break;
                default:
                    break;
            }
            q = q->link;
        }
    }
}

/*tex

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.

*/

static 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 (p->type != 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;
        snprintf(msg, 256, "Variable %s is the wrong type(%s)", mp_str(mp, sname), mp_type_string(p->type));
        mp_delete_string_reference(mp, 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;
}

/*tex

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|.

*/

static mp_node mp_start_draw_cmd(MP mp, int sep)
{
    mp_node lhv = NULL; /*tex variable to add to left */
    int add_type = 0;   /*tex value to be returned in |last_add_type| */
    mp_get_x_next(mp);
    mp->var_flag = sep;
    mp_scan_primary(mp);
    if (cur_exp_type != mp_token_list_type) {
        /*tex
            Abandon edges command because there's no variable.
        */
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        mp_display_error(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;
        cur_exp_type = mp_vacuous_type;
        mp_get_x_next(mp);
        mp_scan_expression(mp);
    }
    mp->last_add_type = add_type;
    return lhv;
}

/*tex

Here is an example of how to use |start_draw_cmd|.

*/

static void mp_do_bounds(MP mp)
{
    mp_edge_header_node lhe;
    int c = cur_cmd;
    int m = cur_mod;
    /*tex 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) {
            mp_new_number(new_expr.data.n);
            mp_flush_cur_exp(mp, new_expr);
        } else if (cur_exp_type != mp_path_type) {
            char msg[256];
            mp_new_number(new_expr.data.n);
            snprintf(msg, 256, "Improper '%s'", mp_cmd_mod_string(mp, c, m));
            mp_display_error(mp, NULL);
            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) {
         /* mp_display_error(mp, NULL); */ /* why not here, now just a message */
            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 {
            /*t x
                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);
            p->link = mp_edge_list(lhe)->link;
            mp_edge_list(lhe)->link = p;
            if (mp_obj_tail(lhe) == mp_edge_list(lhe)) {
                mp_obj_tail(lhe) = p;
            }
            mp_obj_tail(lhe)->link = pp;
            mp_obj_tail(lhe) = pp;
            mp_init_bbox(mp, lhe);
        }
    }
}

/*tex

The |do_add_to| procedure is a little like |do_clip| but there are a lot more cases to deal with.

*/

static 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;  /*tex variable on left, the corresponding edge structure */
        mp_node p;                /*tex the graphical object or list for |scan_with_list| to update */
        mp_edge_header_node e;    /*tex 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) {
            /*tex
                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 (cur_exp_type != mp_picture_type) {
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_new_number(new_expr.data.n);
                mp_display_error(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);
                cur_exp_type = mp_vacuous_type;
                p = mp_edge_list(e)->link;
            }
        } else {
            /*tex
                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 (cur_exp_type == mp_pair_type) {
                mp_pair_to_path(mp);
            }
            if (cur_exp_type != mp_path_type) {
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_new_number(new_expr.data.n);
                mp_display_error(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);
                cur_exp_type = mp_vacuous_type;
            } else if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) {
                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);
                cur_exp_type = mp_vacuous_type;
            }
        }
        mp_scan_with_list(mp, p, NULL);
        /*tex
            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_graphic_object(mp, p);
            }
            if (e != NULL) {
                mp_delete_edge_ref(mp, e);
            }
        } else if (add_type == mp_add_also_code) {
            if (e != NULL) {
                /*tex
                    Merge |e| into |lhe| and delete |e|.
                */
                if (mp_edge_list(e)->link != NULL) {
                    mp_obj_tail(lhe)->link = mp_edge_list(e)->link;
                    mp_obj_tail(lhe) = mp_obj_tail(e);
                    mp_obj_tail(e) = mp_edge_list(e);
                    mp_edge_list(e)->link = NULL;
                    mp_flush_dash_list(mp, lhe);
                }
                mp_toss_edges(mp, e);
            }
        } else if (p != NULL) {
            mp_obj_tail(lhe)->link = 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, &mp_zero_t);
//printf("both\n");
                } else {
//printf("fill\n");
                }
            } else {
//printf("outline\n");
            }
        }
    }
}

static void mp_do_ship_out(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    mp_set_number_to_zero(new_expr.data.n);
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if (cur_exp_type != mp_picture_type) {
        mp_display_error(mp, NULL);
        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);
        mp_flush_cur_exp(mp, new_expr);
    }
}

static 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));
    mp_new_number(new_expr.data.n);
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if (cur_exp_type != mp_string_type) {
        mp_display_error(mp, NULL);
        mp_back_error(mp, "Not a string", "A message should be a known string expression.");
        mp_get_x_next(mp); /* undo backup */
    } else {
        switch (m) {
            case mp_normal_message_code:
                mp_print_format(mp, "%l\n%S", cur_exp_str);
                break;
            case mp_error_message_code:
                /*tex
                    Print string |cur_exp| as an error message.
                */
                {
                    char msg[256];
                    snprintf(msg, 256, "%s", mp_str(mp, cur_exp_str));
                    if (mp->error_help != NULL) {
                        mp_back_error(mp, msg, NULL);
                    } else if (mp->long_help_seen) {
                        mp_back_error(mp, msg, "(That was another 'errmessage'.)");
                    } else {
                        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);
                }
                break;
            case mp_error_help_code:
                /*tex
                    Save string |cur_exp| as the |err_help|.
                */
                if (mp->error_help != NULL) {
                    mp_delete_string_reference(mp, mp->error_help);
                } if (cur_exp_str->len == 0) {
                    mp->error_help = NULL;
                } else {
                    mp->error_help = cur_exp_str;
                    mp_add_string_reference(mp, mp->error_help);
                }
                break;
        }
    }
    mp_set_number_to_zero(new_expr.data.n);
    mp_flush_cur_exp(mp, new_expr);
}

/*tex

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.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.

*/

static void mp_do_write(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if (cur_exp_type != mp_string_type) {
        mp_display_error(mp, NULL);
        mp_back_error(mp, "Not a string", "The text to be written should be a known string expression");
        mp_get_x_next(mp); /* undo backup */
    } 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); /* undo backup */
    } else {
        mp_string t = cur_exp_str; /*tex The line of text to be written. */
        cur_exp_type = mp_vacuous_type;
        mp_get_x_next(mp);
        mp_scan_expression(mp);
        if (cur_exp_type != mp_string_type) {
            mp_display_error(mp, NULL);
            mp_back_error(mp, "Not a string", "I can't write to that file name. It isn't a known string");
            mp_get_x_next(mp); /* undo backup */
        } else {
            mp_do_write_string(mp, t);
        }
    }
    mp_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->n_of_write_files;
    int n0 = mp->n_of_write_files;
    while (mp_strcmp(fn, mp->write_filenames[n]) != 0) {
        if (n == 0) {
            /*tex Bottom reached. */
            if (n0 == mp->n_of_write_files) {
                if (mp->n_of_write_files < mp->max_write_files) {
                    ++mp->n_of_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->write_filehandles[k];
                            wr_fname[k] = mp->write_filenames[k];
                        } else {
                            wr_file[k] = 0;
                            wr_fname[k] = NULL;
                        }
                    }
                    mp_memory_free(mp->write_filehandles);
                    mp_memory_free(mp->write_filenames);
                    mp->max_write_files = l;
                    mp->write_filehandles = wr_file;
                    mp->write_filenames = wr_fname;
                }
            }
            n = n0;
            mp_open_write_file(mp, fn, n);
        } else {
            --n;
            if (mp->write_filenames[n] == NULL) {
                n0 = n;
            }
        }
    }
    if (mp_str_vs_str(mp, t, mp->eof_line) == 0) {
        if (mp->write_filenames[n]) {
            (mp->close_file)(mp, mp->write_filehandles[n]);
            mp_memory_free(mp->write_filenames[n]);
            mp->write_filenames[n] = NULL;
            if (n == mp->n_of_write_files - 1) {
                mp->n_of_write_files = n;
            }
        } else {
        }
    } else if (mp->write_filenames[n]) {
        int selector = mp->selector;
        mp->selector = n + mp_first_file_selector;
        mp_print_format(mp, "%S\n", t);
        mp->selector = selector;
    } else {
        /* error */
    }
}

/*tex

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.

*/

static mp_edge_object_node mp_new_edge_object_node(MP mp)
{
    mp_edge_object_node p = mp->memory_pool[mp_edge_object_pool].list;
    mp->memory_pool[mp_edge_object_pool].used++;
    if (mp->memory_pool[mp_edge_object_pool].used > mp->memory_pool[mp_edge_object_pool].max) {
        mp->memory_pool[mp_edge_object_pool].max = mp->memory_pool[mp_edge_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_edge_object_pool].list = p->link;
        mp->memory_pool[mp_edge_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_edge_object));
//        p->type = mp_edge_node_type;
    }
    p->link = NULL;
    return p;
}

static void mp_free_edge_object_node(MP mp, mp_edge_object_node p)
{
    mp->memory_pool[mp_edge_object_pool].used--;
    if (mp->memory_pool[mp_edge_object_pool].pool < mp->memory_pool[mp_edge_object_pool].kept) {
        mp->memory_pool[mp_edge_object_pool].pool++;
        p->link = mp->memory_pool[mp_edge_object_pool].list;
        mp->memory_pool[mp_edge_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_edge_object_pool(MP mp)
{
    mp_edge_object_node p = mp->memory_pool[mp_edge_object_pool].list;
    while (p) {
        mp_edge_object_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_dash_object_node mp_new_dash_object_node(MP mp)
{
    mp_dash_object_node p = mp->memory_pool[mp_dash_object_pool].list;
    mp->memory_pool[mp_dash_object_pool].used++;
    if (mp->memory_pool[mp_dash_object_pool].used > mp->memory_pool[mp_dash_object_pool].max) {
        mp->memory_pool[mp_dash_object_pool].max = mp->memory_pool[mp_dash_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_dash_object_pool].list = p->link;
        mp->memory_pool[mp_dash_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_dash_object));
    }
    p->link = NULL;
    return p;
}

static void mp_free_dash_object_node(MP mp, mp_dash_object_node p)
{
    mp->memory_pool[mp_dash_object_pool].used--;
    if (mp->memory_pool[mp_dash_object_pool].pool < mp->memory_pool[mp_dash_object_pool].kept) {
        mp->memory_pool[mp_dash_object_pool].pool++;
        p->link = mp->memory_pool[mp_dash_object_pool].list;
        mp->memory_pool[mp_dash_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_dash_object_pool(MP mp)
{
    mp_dash_object_node p = mp->memory_pool[mp_dash_object_pool].list;
    while (p) {
        mp_dash_object_node n = p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_shape_object_node mp_new_shape_object_node(MP mp)
{
    mp_shape_object_node p = mp->memory_pool[mp_shape_object_pool].list;
    mp->memory_pool[mp_shape_object_pool].used++;
    if (mp->memory_pool[mp_shape_object_pool].used > mp->memory_pool[mp_shape_object_pool].max) {
        mp->memory_pool[mp_shape_object_pool].max = mp->memory_pool[mp_shape_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_shape_object_pool].list = p->link;
        mp->memory_pool[mp_shape_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_shape_object));
    }
    p->link = NULL;
    return p;
}

static void mp_free_shape_object_node(MP mp, mp_shape_object_node p)
{
    mp->memory_pool[mp_shape_object_pool].used--;
    if (mp->memory_pool[mp_shape_object_pool].pool < mp->memory_pool[mp_shape_object_pool].kept) {
        mp->memory_pool[mp_shape_object_pool].pool++;
        p->link = mp->memory_pool[mp_shape_object_pool].list;
        mp->memory_pool[mp_shape_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_shape_object_pool(MP mp)
{
    mp_shape_object_node p = mp->memory_pool[mp_shape_object_pool].list;
    while (p) {
        mp_shape_object_node n = (mp_shape_object_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_start_object_node mp_new_start_object_node(MP mp)
{
    mp_start_object_node p = mp->memory_pool[mp_start_object_pool].list;
    mp->memory_pool[mp_start_object_pool].used++;
    if (mp->memory_pool[mp_start_object_pool].used > mp->memory_pool[mp_start_object_pool].max) {
        mp->memory_pool[mp_start_object_pool].max = mp->memory_pool[mp_start_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_start_object_pool].list = p->link;
        mp->memory_pool[mp_start_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_start_object));
//        p->type = mp_edge_node_type;
    }
    p->link = NULL;
    return p;
}

static void mp_free_start_object_node(MP mp, mp_start_object_node p)
{
    mp->memory_pool[mp_start_object_pool].used--;
    if (mp->memory_pool[mp_start_object_pool].pool < mp->memory_pool[mp_start_object_pool].kept) {
        mp->memory_pool[mp_start_object_pool].pool++;
        p->link = mp->memory_pool[mp_start_object_pool].list;
        mp->memory_pool[mp_start_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_start_object_pool(MP mp)
{
    mp_start_object_node p = mp->memory_pool[mp_start_object_pool].list;
    while (p) {
        mp_start_object_node n = (mp_start_object_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_stop_object_node mp_new_stop_object_node(MP mp)
{
    mp_stop_object_node p = mp->memory_pool[mp_stop_object_pool].list;
    mp->memory_pool[mp_stop_object_pool].used++;
    if (mp->memory_pool[mp_stop_object_pool].used > mp->memory_pool[mp_stop_object_pool].max) {
        mp->memory_pool[mp_stop_object_pool].max = mp->memory_pool[mp_stop_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_stop_object_pool].list = p->link;
        mp->memory_pool[mp_stop_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_stop_object));
//        p->type = mp_edge_node_type;
    }
    p->link = NULL;
    return p;
}

static void mp_free_stop_object_node(MP mp, mp_stop_object_node p)
{
    mp->memory_pool[mp_stop_object_pool].used--;
    if (mp->memory_pool[mp_stop_object_pool].pool < mp->memory_pool[mp_stop_object_pool].kept) {
        mp->memory_pool[mp_stop_object_pool].pool++;
        p->link = mp->memory_pool[mp_stop_object_pool].list;
        mp->memory_pool[mp_stop_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_stop_object_pool(MP mp)
{
    mp_stop_object_node p = mp->memory_pool[mp_stop_object_pool].list;
    while (p) {
        mp_stop_object_node n = (mp_stop_object_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_graphic_object_node mp_new_graphic_object(MP mp, int type)
{
    switch (type) {
        case mp_fill_code:
        case mp_stroked_code:
            {
                mp_shape_object_node p = mp_new_shape_object_node(mp);
                memset(p, 0, sizeof(mp_shape_object));
                p->type = type;
                return (mp_graphic_object_node) p;
            }
        case mp_start_clip_code:
        case mp_start_group_code:
        case mp_start_bounds_code:
            {
                mp_start_object_node p = mp_new_start_object_node(mp);
                memset(p, 0, sizeof(mp_start_object));
                p->type = type;
                return (mp_graphic_object_node) p;
            }
        case mp_stop_clip_code:
        case mp_stop_group_code:
        case mp_stop_bounds_code:
            {
                mp_stop_object_node p = mp_new_stop_object_node(mp);
                memset(p, 0, sizeof(mp_stop_object));
                p->type = type;
                return (mp_graphic_object_node) p;
            }
    }
    return NULL;
}


/*tex

    The |export_path| routine makes a clone of a given path and converts the |value|s therein to
    |double|s.

*/

/*tex

    Just before |ship_out|, knot lists are exported for printing. The |export_knot_list| routine
    therefore also makes a clone of a given path. If we want to export a knot node, we can say
    |export_knot|:

*/

static mp_knot_object_node mp_new_knot_object_node(MP mp)
{
    mp_knot_object_node p = mp->memory_pool[mp_knot_object_pool].list;
    mp->memory_pool[mp_knot_object_pool].used++;
    if (mp->memory_pool[mp_knot_object_pool].used > mp->memory_pool[mp_knot_object_pool].max) {
        mp->memory_pool[mp_knot_object_pool].max = mp->memory_pool[mp_knot_object_pool].used;
    }
    if (p) {
        mp->memory_pool[mp_knot_object_pool].list = p->link;
        mp->memory_pool[mp_knot_object_pool].pool--;
    } else {
        p = mp_memory_allocate(sizeof(mp_knot_object));
    }
    p->link = NULL;
    return p;
}

static void mp_free_knot_object_node(MP mp, mp_knot_object_node p)
{
    mp->memory_pool[mp_knot_object_pool].used--;
    if (mp->memory_pool[mp_knot_object_pool].pool < mp->memory_pool[mp_knot_object_pool].kept) {
        mp->memory_pool[mp_knot_object_pool].pool++;
        p->link = mp->memory_pool[mp_knot_object_pool].list;
        mp->memory_pool[mp_knot_object_pool].list = p;
    } else {
        mp_memory_free(p);
    }
}

static void mp_flush_knot_object_pool(MP mp)
{
    mp_knot_object_node p = mp->memory_pool[mp_knot_object_pool].list;
    while (p) {
        mp_knot_object_node n = (mp_knot_object_node) p->link;
        mp_memory_free(p);
        p = n;
    }
}

static mp_knot_object_node mp_new_graphic_knot(MP mp)
{
    mp_knot_object_node k = mp_new_knot_object_node(mp);
    memset(k, 0, sizeof(struct mp_knot_object));
    return k;
}

static mp_knot_object_node mp_export_knot(MP mp, mp_knot p)
{
    mp_knot_object_node q = mp_new_graphic_knot(mp);
    q->x_coord    = mp_number_to_double(p->x_coord);
    q->y_coord    = mp_number_to_double(p->y_coord);
    q->left_x     = mp_number_to_double(p->left_x);
    q->left_y     = mp_number_to_double(p->left_y);
    q->right_x    = mp_number_to_double(p->right_x);
    q->right_y    = mp_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;
}

static mp_knot_object_node mp_export_path(MP mp, mp_knot p)
{
    if (p == NULL) {
        return NULL;
    } else {
        mp_knot_object_node q = mp_export_knot(mp, p);
        mp_knot_object_node qq = q;
        mp_knot pp = mp_next_knot(p);
        while (pp != p) {
            mp_knot_object_node 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;
    }
}

static mp_knot_object_node mp_export_knot_list(MP mp, mp_knot p)
{
    return p ? (mp_knot_object_node) mp_export_path(mp, p) : NULL;
}

/*tex
    Here we use the fact that |mp_get_dash_list(hh)=mp_link(hh)|.|h| is an edge structure.
*/

static mp_dash_object_node 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_node d;
        mp_dash_node p;
        mp_number scf; /* scale factor */
        mp_number dashoff;
        double *dashes = NULL;
        mp_new_number(scf);
        p = mp_get_dash_list(h);
        mp_get_pen_scale(mp, &scf, mp_pen_ptr(q));
        if (mp_number_zero(scf)) {
            if (mp_number_zero(*w)) {
                mp_number_clone(scf, q->dashscale);
            } else {
                mp_free_number(scf);
                return NULL;
            }
        } else {
            mp_number ret;
            mp_new_number(ret);
            mp_make_scaled(ret, *w, scf);
            mp_take_scaled(scf, ret, q->dashscale);
            mp_free_number(ret);
        }
        mp_number_clone(*w, scf);
        d = mp_new_dash_object_node(mp);
        mp_set_number_from_addition(mp->null_dash->start_x, p->start_x, h->dash_y);
        {
            int num_dashes = 1;
            mp_number ret, arg1;
            mp_new_number(ret);
            mp_new_number(arg1);
            mp_new_number(dashoff);
            while (p != mp->null_dash) {
                dashes = mp_memory_reallocate(dashes, (size_t) (num_dashes + 2) * sizeof(double));
                mp_set_number_from_subtraction(arg1, p->stop_x, p->start_x);
                mp_take_scaled(ret, arg1, scf);
                dashes[(num_dashes - 1)] = mp_number_to_double(ret);
                mp_set_number_from_subtraction(arg1, ((mp_dash_node) p->link)->start_x, p->stop_x);
                mp_take_scaled(ret, arg1, scf);
                dashes[(num_dashes)] = mp_number_to_double(ret);
                dashes[(num_dashes + 1)] = -1.0; /* terminus */
                num_dashes += 2;
                p = (mp_dash_node) p->link;
            }
            d->array = dashes;
            mp_dash_offset(mp, &dashoff, h);
            mp_take_scaled(ret, dashoff, scf);
            d->offset = mp_number_to_double(ret);
            mp_free_number(ret);
            mp_free_number(arg1);
        }
        mp_free_number(dashoff);
        mp_free_number(scf);
        return d;
    }
}

static mp_edge_object_node mp_graphic_export(MP mp, mp_edge_header_node h)
{
    mp_node p;                                            /*tex the current graphical object */
    mp_edge_object_node hh = mp_new_edge_object_node(mp); /*tex the first graphical object */
    mp_graphic_object_node hp = NULL;                     /*tex the current graphical object */
    mp_set_bbox(mp, h, 1);
    hh->parent = mp;
    hh->body = NULL;
    hh->next = NULL;
    hh->minx = mp_number_to_double(h->minx);
    hh->minx = fabs(hh->minx) < 0.00001 ? 0 : hh->minx;
    hh->miny = mp_number_to_double(h->miny);
    hh->miny = fabs(hh->miny) < 0.00001 ? 0 : hh->miny;
    hh->maxx = mp_number_to_double(h->maxx);
    hh->maxx = fabs(hh->maxx) < 0.00001 ? 0 : hh->maxx;
    hh->maxy = mp_number_to_double(h->maxy);
    hh->maxy = fabs(hh->maxy) < 0.00001 ? 0 : hh->maxy;
    hh->charcode = (int) mp_round_unscaled(internal_value(mp_char_code_internal));
    hh->width = mp_number_to_double(internal_value(mp_char_wd_internal));
    hh->height = mp_number_to_double(internal_value(mp_char_ht_internal));
    hh->depth = mp_number_to_double(internal_value(mp_char_dp_internal));
    hh->italic = mp_number_to_double(internal_value(mp_char_ic_internal));
    p = mp_edge_list(h)->link;
    while (p != NULL) {
        int graphictype = (p->type - mp_fill_node_type) + 1;
        mp_graphic_object_node hq = mp_new_graphic_object(mp, graphictype);
        switch (p->type) {
            /* todo: share code between fill and stroked */
            case mp_fill_node_type:
                {
                    mp_number d_width; /*tex The current pen width. */
                    mp_shape_node p0 = (mp_shape_node) p;
                    mp_shape_object_node tf = (mp_shape_object_node) hq;
                    graphic_pen_ptr(tf) = mp_export_knot_list(mp, mp_pen_ptr(p0));
                    mp_new_number(d_width);
                    mp_get_pen_scale(mp, &d_width, mp_pen_ptr(p0)); /* whats the point ? */
                    mp_free_number(d_width);
                    if ((mp_pen_ptr(p0) == NULL) || mp_pen_is_elliptical(mp_pen_ptr(p0))) {
                        graphic_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));
                        graphic_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));
                        graphic_htap_ptr(tf) = mp_export_knot_list(mp, pp);
                        mp_toss_knot_list(mp, pp);
                    }
                    mp_graphic_export_color(tf, p0);
                    mp_graphic_export_scripts(tf, p);
                    graphic_linejoin_val(tf) = p0->linejoin;
                    graphic_curvature_val(tf) = p0->curvature;
                    graphic_mesh_val(tf) = p0->mesh;
                    graphic_stacking_val(tf) = p0->stacking;
                    graphic_bytemap_val(tf) = mp_bytemap_get_value(mp, p0->bytemap, &(graphic_bytemap_nx_val(tf)), &(graphic_bytemap_ny_val(tf)), &(graphic_bytemap_nz_val(tf)));
                    graphic_miterlimit_val(tf) = mp_number_to_double(p0->miterlimit);
                }
                break;
            case mp_stroked_node_type:
                {
                    mp_number d_width; /*tex The current pen width. */
                    mp_shape_node p0 = (mp_shape_node) p;
                    mp_shape_object_node ts = (mp_shape_object_node) hq;
                    graphic_pen_ptr(ts) = mp_export_knot_list(mp, mp_pen_ptr(p0));
                    mp_new_number(d_width);
                    mp_get_pen_scale(mp, &d_width, mp_pen_ptr(p0));
                    if (mp_pen_is_elliptical(mp_pen_ptr(p0))) {
                        graphic_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));
                        graphic_path_ptr(ts) = mp_export_knot_list(mp, pc);
                        mp_toss_knot_list(mp, pc);
                    }
                    mp_graphic_export_color(ts, p0);
                    mp_graphic_export_scripts(ts, p);
                    graphic_linejoin_val(ts) = p0->linejoin;
                    graphic_miterlimit_val(ts) = mp_number_to_double(p0->miterlimit);
                    graphic_curvature_val(ts) = p0->curvature;
                    graphic_mesh_val(ts) = p0->mesh;
                    graphic_linecap_val(ts) = p0->linecap;
                    graphic_stacking_val(ts) = p0->stacking;
                    graphic_bytemap_val(ts) = mp_bytemap_get_value(mp, p0->bytemap, &(graphic_bytemap_nx_val(ts)), &(graphic_bytemap_ny_val(ts)), &(graphic_bytemap_nz_val(ts)));
                    graphic_dash_ptr(ts) = mp_export_dashes(mp, p0, &d_width);
                    mp_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_node tb = (mp_start_object_node) hq;
                    graphic_path_ptr(tb) = mp_export_knot_list(mp, mp_path_ptr((mp_start_node) p));
                    graphic_stacking_val(tb) = p0->stacking;
                    mp_graphic_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_node tb = (mp_stop_object_node) hq;
                    graphic_stacking_val(tb) = p0->stacking;
                }
                break;
            default:
                break;
        }
        if (hh->body == NULL) {
            hh->body = hq;
        } else {
            graphic_link(hp) = hq;
        }
        hp = hq;
        p = p->link;
    }
    return hh;
}

static void mp_do_graphic_toss_dashes(MP mp, mp_dash_object_node dl) {
    if (dl) {
        mp_memory_free(dl->array);
        mp_free_dash_object_node(mp, dl);
    }
}

static void mp_do_graphic_toss_knot_list(MP mp, mp_knot_object_node p)
{
    if (p) {
        mp_knot_object_node q = p;
        do {
            mp_knot_object_node r = (mp_knot_object_node) graphic_next_knot(q);
            mp_free_knot_object_node(mp, q);
            q = r;
        } while (q != p);
    }
}

void mp_graphic_toss_object(MP mp, mp_graphic_object_node p)
{
    switch (graphic_type(p)) {
        case mp_fill_code:
        case mp_stroked_code:
            {
                mp_shape_object_node o = (mp_shape_object_node) p;
                mp_memory_free(graphic_pre_script(o));
                mp_memory_free(graphic_post_script(o));
                mp_do_graphic_toss_knot_list(mp, graphic_pen_ptr(o));
                mp_do_graphic_toss_knot_list(mp, graphic_path_ptr(o));
                if (graphic_htap_ptr(o)) {
                    mp_do_graphic_toss_knot_list(mp, graphic_htap_ptr(o));
                }
                if (graphic_dash_ptr(o)) {
                    mp_do_graphic_toss_dashes(mp, graphic_dash_ptr(o));
                }
                if (graphic_bytemap_val(o)) {
                    mp_memory_free(graphic_bytemap_val(o));
                }
                mp_free_shape_object_node(mp, o);
            }
            break;
        case mp_start_clip_code:
        case mp_start_group_code:
        case mp_start_bounds_code:
            {
                mp_start_object_node o = (mp_start_object_node) p;
                mp_memory_free(graphic_pre_script(o));
                mp_memory_free(graphic_post_script(o));
                mp_do_graphic_toss_knot_list(mp, graphic_path_ptr(o));
                mp_free_start_object_node(mp, o);
            }
            break;
        case mp_stop_clip_code:
        case mp_stop_group_code:
        case mp_stop_bounds_code:
            mp_free_stop_object_node(mp, (mp_stop_object_node) p);
            break;
    }
}

void mp_graphic_toss_objects(MP mp, mp_edge_object_node hh) /* called at the lua end */
{
    mp_graphic_object_node p = hh->body;
    while (p) {
        mp_graphic_object_node q = graphic_link(p);
        mp_graphic_toss_object(mp, p);
        p = q;
    }
    mp_free_edge_object_node(mp, hh);
}

/*tex This function is now nearly trivial. */

static void mp_ship_out(MP mp, mp_node h) {
    (mp->shipout_backend)(mp, h);
}

/*tex We keep the template as comment: */

static void mp_shipout_backend(MP mp, void *voidh)
{
    (void) mp;
    (void) voidh;
}

/*tex

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|.

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.

*/

void mp_scan_symbol_value(MP mp, int keep, char **s, int expand)
{
    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);
        p->name_type = cur_sym_mod;
        if (p->type == mp_symbol_node_type) {
            mp_symbol sr = mp_get_sym_sym(p);
            mp_string rr = eq_text(sr);
            if (rr && rr->str) {
                r = rr->str;
            }
        } else if (p->name_type == mp_token_operation) {
            if (p->type == 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)
{
    mp_symbol 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)
{
    mp_get_next(mp);
    if (keep) {
        mp_back_input(mp);
    }
    *token = cur_cmd;
    *mode = cur_mod;
    *kind = cur_exp_type;
}

void mp_scan_expr_value(MP mp, int keep, int *kind)
{
    mp_get_next(mp);
    mp_scan_primary(mp);
    *kind = 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)
{
    mp_get_x_next(mp);
    if (keep) {
        mp_back_input(mp);
    }
    *token = cur_cmd;
    *mode = cur_mod;
    *kind = cur_exp_type;
}

int mp_skip_token_value(MP mp, int token)
{
    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 mp_expression_scan_code:
     //     mp_scan_expression(mp);
     //     break;
        case mp_primary_scan_code:
            mp_scan_primary(mp);
            break;
        case mp_secondary_scan_code:
            mp_scan_secondary(mp);
            break;
        case mp_tertiary_scan_code:
            mp_scan_tertiary(mp);
            break;
        default:
            mp_scan_expression(mp);
            break;
    }
}

void mp_scan_numeric_value(MP mp, int primary, double *d)
{
    mp_scan_something(mp, primary);
    if (cur_exp_type != mp_known_type) {
        mp_back_input(mp); /* hm */
    } else {
        mp_back_input(mp); /* hm */
        *d = mp_number_to_double(cur_exp_value_number);
    }
}

# define mp_set_double_value(mp,target,what) \
if (what->type == mp_known_type) { \
    *target = mp_number_to_double(mp_get_value_number(what)); \
}

void mp_scan_pair_value(MP mp, int primary, double *x, double *y)
{
    mp_scan_something(mp, primary);
    if (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)
{
    mp_scan_something(mp, primary);
    if (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)
{
    mp_scan_something(mp, primary);
    if (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)
{
    mp_scan_something(mp, primary);
    if (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)
{
    mp_scan_something(mp, primary);
    if (cur_exp_type != mp_path_type && 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)
{
    mp_scan_something(mp, primary);
    if (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 ;
    }
}

int mp_scan_string_value(MP mp, int primary, char **s, size_t *l)
{
    mp_scan_something(mp, primary);
    if (cur_exp_type != mp_string_type) {
        mp_back_input(mp);
        *s = NULL;
        *l = 0;
        return 0;
    } else {
        mp_back_input(mp);
        *s = (char *) cur_exp_str->str;
        *l = cur_exp_str->len;
        return 1;
    }
}

void mp_push_numeric_value(MP mp, double n)
{
    mp_number m;
    mp_new_number_from_double(mp, m, n);
    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;
    mp_new_number(m);
    mp_set_number_from_int(m, i);
    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)
{
    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)
{
    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);
    mp_new_number_from_double(mp, px, x);
    mp_new_number_from_double(mp, py, y);
    mp_x_part(v)->type = mp_known_type;
    mp_y_part(v)->type = mp_known_type;
    mp_set_value_number(mp_x_part(v), px);
    mp_set_value_number(mp_y_part(v), py);
    mp_free_number(px);
    mp_free_number(py);
    /*
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    new_expr.type = p->type;
    new_expr.data.node = p;
    mp_flush_cur_exp(mp, new_expr);
    cur_exp_type = mp_pair_type;
    p->name_type = mp_capsule_operation;
    */
    p->name_type = mp_capsule_operation;
    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);
    mp_new_number_from_double(mp, pr, r);
    mp_new_number_from_double(mp, pg, g);
    mp_new_number_from_double(mp, pb, b);
    mp_red_part  (v)->type = mp_known_type;
    mp_green_part(v)->type = mp_known_type;
    mp_blue_part (v)->type = 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);
    mp_free_number(pr);
    mp_free_number(pg);
    mp_free_number(pb);
    p->name_type = mp_capsule_operation;
    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);
    mp_new_number_from_double(mp, pc, c);
    mp_new_number_from_double(mp, pm, m);
    mp_new_number_from_double(mp, py, y);
    mp_new_number_from_double(mp, pk, k);
    mp_cyan_part   (v)->type = mp_known_type;
    mp_magenta_part(v)->type = mp_known_type;
    mp_yellow_part (v)->type = mp_known_type;
    mp_black_part  (v)->type = 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);
    mp_free_number(pc);
    mp_free_number(pm);
    mp_free_number(py);
    mp_free_number(pk);
    p->name_type = mp_capsule_operation;
    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);
    mp_new_number_from_double(mp, px, x);
    mp_new_number_from_double(mp, py, y);
    mp_new_number_from_double(mp, pxx, xx);
    mp_new_number_from_double(mp, pxy, xy);
    mp_new_number_from_double(mp, pyx, yx);
    mp_new_number_from_double(mp, pyy, yy);
    mp_x_part (v)->type = mp_known_type;
    mp_y_part (v)->type = mp_known_type;
    mp_xx_part(v)->type = mp_known_type;
    mp_xy_part(v)->type = mp_known_type;
    mp_yx_part(v)->type = mp_known_type;
    mp_yy_part(v)->type = 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);
    mp_free_number(px);
    mp_free_number(py);
    mp_free_number(pxx);
    mp_free_number(pxy);
    mp_free_number(pyx);
    mp_free_number(pyy);
    p->name_type = mp_capsule_operation;
    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)
{
    cur_exp_type = mp_path_type;
    mp_set_cur_exp_knot(mp, k);
    mp_back_expr(mp);
}

void mp_push_tokens_value(MP mp, const char *str, size_t length)
{
    (void) mp;
    if (str && length > 0) {
        printf("push tokens is not yet implemented\n");
     // mp_check_script_result(mp, (char *) str);
    }
}

/*tex

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.

*/

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_new_number(new_expr.data.n);
    mp_display_error(mp, NULL);
    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);
}

/*tex
    Scan a delimited primary.
*/

static void mp_do_command_left_delimiter(MP mp)
{
    mp_symbol l_delim = cur_sym;
    mp_symbol r_delim = eq_symbol(cur_sym);
    mp_get_x_next(mp);
    mp_scan_expression(mp);
    if ((cur_cmd == mp_comma_command) && (cur_exp_type >= mp_known_type)) {
        /*tex
            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 */
        q->name_type = mp_capsule_operation;
        mp_get_x_next(mp);
        mp_scan_expression(mp);
        /*tex
            Make sure the second part of a pair or color has a numeric type.
        */
        if (cur_exp_type < mp_known_type) {
            mp_primary_error(mp);
        }
        if (cur_cmd != mp_comma_command) {
            /*tex
                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);
            /*tex
                Scan the last of a triplet of numerics.
            */
            mp_get_x_next(mp);
            mp_scan_expression(mp);
            if (cur_exp_type < mp_known_type) {
                mp_primary_error(mp);
            }
            if (cur_cmd != mp_comma_command) {
                /*tex
                    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 (cur_exp_type < mp_known_type) {
                    mp_primary_error(mp);
                }
                if (cur_cmd != mp_comma_command) {
                    /*tex
                        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 (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 (cur_exp_type < mp_known_type) {
                        mp_primary_error(mp);
                    }
                    HERE:
                    mp_init_transform_node(mp, q);
                    /*tex 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);
        cur_exp_type = q->type;
        mp_set_cur_exp_node(mp, q);
    } else {
        mp_check_delimiter(mp, l_delim, r_delim);
    }
}

/*tex
    Convert a suffix to a boolean.
*/

static void mp_do_command_void(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    mp_get_x_next(mp);
    mp_scan_suffix(mp);
    if (cur_exp_node == NULL) {
        mp_set_number_from_boolean(new_expr.data.n, mp_true_operation);
    } else {
        mp_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, )| !! */
    cur_exp_type = mp_boolean_type;
}

/*tex
    Scan a string constant.
*/

static void mp_do_command_string(MP mp)
{
    cur_exp_type = mp_string_type;
    mp_set_cur_exp_str(mp, cur_mod_str);
}

/*tex
    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.
*/

static void mp_do_command_group(MP mp)
{
    int group_line = mp_true_line(mp); /* where a group began */
    if (mp_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];
        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);
    /*tex
        This might change |cur_type|, if independent variables are recycled.
    */
    if (mp_number_positive(internal_value(mp_tracing_commands_internal))) {
        mp_show_cmd_mod(mp, cur_cmd, cur_mod);
    }
}

/*tex
    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.)
*/

static int mp_do_command_internal(MP mp, int my_var_flag)
{
    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);
            cur_exp_node->name_type = mp_internal_operation;
            cur_exp_type = mp_token_list_type;
            return 1;
        }
        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, mp_number_to_int(internal_value(qq)), internal_name(qq));
        // }
    }
    cur_exp_type = internal_type(qq);
    return 0;
}

/*tex
    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.
*/

static int mp_do_command_numeric(MP mp, int my_var_flag)
{
    mp_number num, denom; /*tex For primaries that are fractions, like $1/2$. */
    (void) my_var_flag;
    mp_set_cur_exp_value_number(mp, &cur_mod_number);
    cur_exp_type = mp_known_type;
    mp_get_x_next(mp);
    if (cur_cmd != mp_slash_command) {
        mp_new_number(num);
        mp_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);
            return 1;
        } else {
            mp_new_number_clone(num, cur_exp_value_number);
            mp_new_number_clone(denom, cur_mod_number);
            if (mp_number_zero(denom)) {
                mp_error(mp, "Division by zero", "I'll pretend that you meant to divide by 1.");
            } else {
                mp_number ret;
                mp_new_number(ret);
                mp_make_scaled(ret, num, denom);
                mp_set_cur_exp_value_number(mp, &ret);
                mp_free_number(ret);
            }
            mp_check_arithmic(mp);
            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);
        mp_new_number_abs(absnum, num);
        mp_new_number_abs(absdenom, denom);
        if (mp_number_greaterequal(absnum, absdenom) || (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);
        }
        mp_free_number(absnum);
        mp_free_number(absdenom);
    }
    mp_free_number(num);
    mp_free_number(denom);
    return 1;
}

/*tex
    Convert a suffix to a string.
*/

static void mp_do_command_str(MP mp)
{
    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;
    cur_exp_type = mp_string_type;
}

/*
    Scan a unary operation.
*/

static void mp_do_command_plus_or_minus(MP mp)
{
    int c = (int) cur_mod; /* a primitive operation code */
    mp_get_x_next(mp);
    mp_scan_primary(mp);
    mp_do_unary(mp, c);
}

/*tex
    Scan a binary operation with |of| between its operands.
*/

static void mp_do_command_of_binary(MP mp)
{
    mp_node p;             /*tex for list manipulation */
    int c = (int) cur_mod; /*tex 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);
        snprintf(msg, 256, "Missing 'of' has been inserted for %s", mp_str(mp, sname));
        mp_delete_string_reference(mp, 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);
}

/*tex
    Scan a variable primary; |goto restart| if it turns out to be a macro.
*/

static int mp_do_command_tag(MP mp, int my_var_flag)
{
    mp_node macro_ref = 0;    /*tex reference count for a suffixed macro */
    int tt = mp_vacuous_type; /*tex 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) {
        mp_node t = mp_cur_tok(mp);
        tail->link = t;
        if (tt != mp_undefined_type) {
            /*tex
                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_node p = pre_head->link;
            mp_node q = NULL;
            mp_symbol 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 = eq_node(qq);
                if (q == NULL) {
                    goto DONE; /* done */
                }
                while (1) {
                    p = p->link;
                    if (p == NULL) {
                        tt = q->type;
                        goto DONE; /* done */
                    }
                    if (q->type != mp_structured_type) {
                        goto DONE; /* done */
                    }
                    /*tex The |mp_collective_subscript| attribute: */
                    q = mp_get_attribute_head(q)->link;
                    if (p->type == mp_symbol_node_type) {
                        /*tex it's not a subscript */
                        do {
                            q = q->link;
                        } while (! (mp_get_hashloc(q) >= mp_get_sym_sym(p)));
                        if (mp_get_hashloc(q) > mp_get_sym_sym(p)) {
                            goto DONE; /* done */
                        }
                    }
                }
            }
          DONE:
            if (tt >= mp_unsuffixed_macro_type) {
                /*tex
                    Either begin an unsuffixed macro call or prepare for a suffixed one.
                */
                tail->link = NULL;
                if (tt > mp_unsuffixed_macro_type) {
                    /* |tt=mp_suffixed_macro| */
                    post_head = mp_new_symbolic_node(mp);
                    tail = post_head;
                    tail->link = t;
                    tt = mp_undefined_type;
                    macro_ref = mp_get_value_node(q);
                    mp_add_mac_ref(macro_ref);
                } else {
                    /*tex
                        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.
                    */
                    mp_node p = mp_new_symbolic_node(mp);
                    mp_set_sym_sym(pre_head, pre_head->link);
                    pre_head->link = p;
                    mp_set_sym_sym(p, t);
                    mp_macro_call(mp, mp_get_value_node(q), pre_head, NULL);
                    mp_get_x_next(mp);
                    return 2; /* restart */
                }
            }
        }
        mp_get_x_next(mp);
        tail = t;
        if (cur_cmd == mp_left_bracket_command) {
            /*tex
                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) {
                /*tex
                    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);
                /*tex That was the token following the current expression. */
                mp_back_expr(mp);
                set_cur_cmd(mp_left_bracket_command);
                set_cur_mod_number(mp_zero_t);
                set_cur_sym(mp->frozen_left_bracket);
            } else {
                if (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;
        }
    }
    /*tex
        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) {
        /*tex
            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_node p, q;
        mp_back_input(mp);
        p = mp_new_symbolic_node(mp);
        q = post_head->link;
        mp_set_sym_sym(pre_head, pre_head->link);
        pre_head->link = post_head;
        mp_set_sym_sym(post_head, q);
        post_head->link = p;
        mp_set_sym_sym(p, q->link);
        q->link = NULL;
        mp_macro_call(mp, macro_ref, pre_head, NULL);
        mp_decr_mac_ref(macro_ref);
        mp_get_x_next(mp);
        return 2; /* restart */
    } else {
        mp_node q = pre_head->link;
        mp_free_symbolic_node(mp, pre_head);
        if (cur_cmd == my_var_flag) {
            cur_exp_type = mp_token_list_type;
            mp_set_cur_exp_node(mp, q);
            return 1; /* done */
        } else {
            mp_node p = mp_find_variable(mp, q);
            if (p != NULL) {
                mp_make_exp_copy(mp, p, 27);
            } else {
                char *msg = mp_obliterated(mp, q);
                mp_value new_expr;
                memset(&new_expr, 0, sizeof(mp_value));
                mp_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);
            return 1; /* done */
        }
    }
    return 0;
}

void mp_scan_primary(MP mp)
{
    mp_command_code my_var_flag = mp->var_flag;
    mp->var_flag = 0;
  RESTART:
    mp_check_arithmic(mp);
    switch (cur_cmd) {
        case mp_left_delimiter_command:
            mp_do_command_left_delimiter(mp);
            break;
        case mp_begin_group_command:
            mp_do_command_group(mp);
            break;
        case mp_string_command:
            mp_do_command_string(mp);
            break;
        case mp_numeric_command:
            if (mp_do_command_numeric(mp, my_var_flag)) {
                goto DONE;
            } else {
                break;
            }
        case mp_nullary_command:
            mp_do_command_nullary(mp, cur_mod);
            break;
        case mp_unary_command:
        case mp_type_name_command:
        case mp_cycle_command:
        case mp_plus_or_minus_command:
            mp_do_command_plus_or_minus(mp);
            goto DONE;
        case mp_of_binary_command:
            mp_do_command_of_binary(mp);
            goto DONE;
        case mp_str_command:
            mp_do_command_str(mp);
            goto DONE;
        case mp_void_command:
            mp_do_command_void(mp);
            goto DONE;
        case mp_internal_command:
            if (mp_do_command_internal(mp, my_var_flag)) {
                goto DONE;
            } else {
                break;
            }
        case mp_capsule_command:
            mp_make_exp_copy(mp, cur_mod_node, 26);
            break;
        case mp_tag_command:
            switch (mp_do_command_tag(mp, my_var_flag)) {
                case 1:
                    goto DONE;
                case 2:
                    goto RESTART;
                default:
                    break;
            }
        default:
            mp_bad_exp(mp, "A primary");
            goto RESTART;
    }
    /*tex The routines jump over this if they don't want this. */
    mp_get_x_next(mp);
  DONE:
    check_for_mediation(mp);
}

static void check_for_mediation(MP mp)
{
    if (cur_cmd == mp_left_bracket_command && 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) {
            /*tex
                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(mp_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, 28);
            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);
        }
    }
}

static void mp_scan_suffix(MP mp)
{
    mp_node h = mp_new_symbolic_node(mp); /*tex head of the list being built */
    mp_node t = h;                        /*tex tail of the list being built */
    while (1) {
        mp_node p;
        if (cur_cmd == mp_left_bracket_command) {
            /*tex
                Scan a bracketed subscript and set |cur_cmd:=numeric_token|.
            */
            mp_get_x_next(mp);
            mp_scan_expression(mp);
            if (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;
            mp_new_number_clone(arg1, cur_mod_number);
            p = mp_new_num_tok(mp, &arg1);
            mp_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);
            p->name_type = cur_sym_mod;
        } else {
            break;
        }
        t->link = p;
        t = p;
        mp_get_x_next(mp);
    }
    mp_set_cur_exp_node(mp, h->link);
    mp_free_symbolic_node(mp, h);
    cur_exp_type = mp_token_list_type;
}

static void mp_scan_secondary(MP mp)
{
    mp_node cc = NULL;
    mp_symbol 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");
    }
    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_back_input(mp);
            mp_binary_mac(mp, p, cc, mac_name);
            mp_decr_mac_ref(cc);
            mp_get_x_next(mp);
            goto RESTART;
        } else {
            mp_do_binary(mp, p, c);
            goto CONTINUE;
        }
    }
}

static void mp_scan_tertiary(MP mp)
{
    mp_node cc = NULL;
    mp_symbol 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");
    }
    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 d = cur_cmd;
        int c = cur_mod;
        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_back_input(mp);
            mp_binary_mac(mp, p, cc, mac_name);
            mp_decr_mac_ref(cc);
            mp_get_x_next(mp);
            goto RESTART;
        } else {
            mp_do_binary(mp, p, c);
            goto CONTINUE;
        }
    }
}

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");
    }
    mp_scan_tertiary(mp);
  CONTINUE:
    if (cur_cmd <= mp_max_expression_command && cur_cmd >= mp_min_expression_command) {
        if ((cur_cmd != mp_equals_command) || (my_var_flag != mp_assignment_command)) {
            mp_node cc = NULL;
            mp_symbol mac_name = NULL; /*tex Token defined with |tertiarydef|. */
            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) && ((p->type == mp_pair_type) || (p->type == mp_path_type)))) {
                /*tex
                    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--;
}

static void force_valid_tension_setting(MP mp)
{
    if ((cur_exp_type != mp_known_type) || mp_number_less(cur_exp_value_number, mp_min_tension)) {
        mp_value new_expr;
        memset(&new_expr, 0, sizeof(mp_value));
        mp_new_number(new_expr.data.n);
        mp_number_clone(new_expr.data.n, mp_unity_t);
        mp_display_error(mp, NULL);
        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);
    }
}

/*tex

The path constructor is somewhat complicated. We start with a pair or path and in the later case we
have to go to the end. Then we check for curly braced curl or direction settings (separated by |..|
when we have two), tension specifiers, or controls. When we finally pick up a point (after the
mandate |..|) we check for a cycle command and we abuse that command code to also provide a point
relative to the previous, with absolute x or y, or a combination which depends on the following
numeric or pair.

Finally we wrap up, and \METAPOST\ calculates the relation between the previous and current point
and then moves on to the next point specification. Because all this magick is packed into one
constructor there are some |goto|'s involved and also some |command| and |operation| tracking and
because an expression looks ahead (after all it has to know when it ends) we know what is coming
after it. The next function not only looks complicated, but also is.

*/

static int mp_scan_path(MP mp)
{
    mp_knot path_p;    /*tex first */
    mp_knot path_q;    /*tex last  */
    mp_knot pp = NULL; /*tex left  */
    mp_knot qq = NULL; /*tex right */
    mp_number x, y;    /*tex explicit coordinates or tension at a path join */
    int command;       /*tex command code, might change as we go */
    int operation;     /*tex operation (subcommand) code, might change as we go */
    int cycle = 0;     /*tex did a path expression just end with |cycle|? */
    int future = 0;
    int knottype = mp_endpoint_knot; /*tex knot type following a path join */
    /*tex
        Convert the left operand, |p|, into a partial path ending at~|q|; but |return| if |p|
        doesn't have a suitable type. Keep in mind that as we progress, cur_cmd can be ahead
        of the expression we just consumed.
    */
    mp_number_clone(mp->previous_x, mp_inf_t);
    mp_number_clone(mp->previous_y, mp_inf_t);
    switch (cur_exp_type) {
        case mp_pair_type:
            {
                path_p = mp_pair_to_knot(mp);
                path_q = path_p;
                break;
            }
        case mp_path_type:
            {
                path_p = cur_exp_knot;
                path_q = path_p;
                /*tex Goto the end of a the path. */
                while (mp_next_knot(path_q) != path_p) {
                    path_q = mp_next_knot(path_q);
                }
                /*tex Open up a cycle. */
                if (mp_left_type(path_p) != mp_endpoint_knot) {
                    mp_knot r = mp_copy_knot(mp, path_p);
                    mp_prev_knot(r) = path_q;
                    mp_next_knot(path_q) = r;
                    path_q = r;
                }
                break;
            }
        default:
            return 0;
    }
    mp_left_type(path_p) = mp_open_knot;
    mp_right_type(path_q) = mp_open_knot;
    mp_new_number(y);
    mp_new_number(x);
  CONTINUE_PATH:
    /*tex
        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|.
    */
    future = 0;
    switch (cur_cmd) {
        case mp_path_connect_command:
            {
                /*tex
                    This is a shortcut for the macro |--| that is defined as |{curl 1} .. {curl 1}|
                    so that we avoid scanning and (temporary) allocations, which could somewhat
                    (not that much in practice) pay off when we construct enormous paths.
                */
                command = cur_cmd;
                operation = cur_mod;
                /* { curl 1 } */
                knottype = mp_curl_knot;
                mp_right_type(path_q) = mp_curl_knot;
                mp_set_number_to_unity(path_q->right_given);
                if (mp_left_type(path_q) == mp_open_knot) {
                    mp_left_type(path_q) = mp_curl_knot;
                    mp_set_number_to_unity(path_q->left_given);
                }
                /* .. */
                mp_set_number_to_unity(path_q->right_tension);
                mp_set_number_to_unity(y);
                /* { curl 1 } */
                mp_set_number_to_unity(x);
                mp_get_x_next(mp);
                goto DONE_2;
            }
        case mp_left_brace_command:
            {
                /*tex
                    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 |.. { curl 2} ..| or |.. {z(0,0)}
                    ..|.
                */
                knottype = mp_scan_direction(mp);
                if (knottype != mp_open_knot) {
                    mp_right_type(path_q) = (unsigned char) knottype;
                    mp_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) knottype;
                        mp_number_clone(path_q->left_given, cur_exp_value_number);
                        /*tex Note that |left_given(q) = left_curl(q)|. */
                    }
                }
                break;
                /*tex We now can have a join command. */
            }
     // case mp_path_join_command:
     //     /* according to the comment above */
     //     break;
     // case mp_ampersand_command:
     //     /* according to the comment above */
     //     break;
        default:
            break;
    }
    command = cur_cmd;
    operation = cur_mod;
    if (command == mp_path_join_command) {
        /*tex Determine the tension and/or control points. */
        mp_get_x_next(mp);
        switch (cur_cmd) {
            case mp_tension_command:
                /*tex
                    Set explicit tensions. Checking if the number is |mp_at_least_command| code is
                    kind of curious. We could just have a boolean instead.
                */
// int atleast = 0;
                mp_get_x_next(mp);
                mp_set_number_from_scaled(y, cur_cmd);
// atleast = cur_cmd == mp_at_least_command;
// if (atleast) {
                if (cur_cmd == mp_at_least_command) {
                    mp_get_x_next(mp);
                }
                mp_scan_primary(mp);
                force_valid_tension_setting(mp);
// if (atleast && is_number(cur_exp_value_number)) {
                if (mp_number_to_scaled(y) == mp_at_least_command && is_number(cur_exp_value_number)) {
                    mp_number_negate(cur_exp_value_number);
                }
                mp_number_clone(path_q->right_tension, cur_exp_value_number);
                /* */
                if (cur_cmd == mp_and_command) {
                    mp_get_x_next(mp);
                    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 (mp_number_to_scaled(y) == mp_at_least_command && is_number(cur_exp_value_number)) {
                        mp_number_negate(cur_exp_value_number);
                    }
                }
                mp_number_clone(y, cur_exp_value_number);
                break;
            case mp_controls_command:
                /*tex Set explicit control points. */
                {
                    int mode = cur_mod;
                    mp_right_type(path_q) = mp_explicit_knot;
                    knottype = mp_explicit_knot;
                    mp_get_x_next(mp);
                    mp_scan_primary(mp);
                    mp_known_pair(mp);
                    switch (mode) {
                        case mp_both_controls_code:
                            {
                                mp_number_clone(path_q->right_x, mp->cur_x);
                                mp_number_clone(path_q->right_y, mp->cur_y);
                                if (cur_cmd == mp_and_command) {
                                    mp_get_x_next(mp);
                                    mp_scan_primary(mp);
                                    mp_known_pair(mp);
                                }
                                mp_number_clone(x, mp->cur_x);
                                mp_number_clone(y, mp->cur_y);
                                break;
                            }
                        case mp_first_control_code:
                            {
                                mp_number_clone(path_q->right_x, mp->cur_x);
                                mp_number_clone(path_q->right_y, mp->cur_y);
                                future = 1;
                                break;
                            }
                        case mp_second_control_code:
                            {
                                mp_number_clone(x, mp->cur_x);
                                mp_number_clone(y, mp->cur_y);
                                mp_number_clone(path_q->right_x, path_q->x_coord);
                                mp_number_clone(path_q->right_y, path_q->y_coord);
                                break;
                            }
                    }
                }
                break;
            default:
                /*tex Default tension. */
                mp_set_number_to_unity(path_q->right_tension);
                mp_set_number_to_unity(y);
                goto DONE_1;
        }
        if (cur_cmd != mp_path_join_command) {
            mp_back_error(
                mp,
                "Missing '..' has been inserted",
                "A path join command should end with two dots."
            );
        }
    } else if (command != mp_ampersand_command) {
        goto FINISH_PATH;
    }
    mp_get_x_next(mp);
  DONE_1:
    if (cur_cmd == mp_left_brace_command) {
        /*tex
            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.
        */
        knottype = mp_scan_direction(mp);
        if (mp_right_type(path_q) != mp_explicit_knot) {
            mp_number_clone(x, cur_exp_value_number);
        } else {
            /*tex
                The direction information is superfluous.
            */
            knottype = mp_explicit_knot;
        }
    } else if (mp_right_type(path_q) != mp_explicit_knot) {
        knottype = mp_open_knot;
        mp_set_number_to_zero(x);
    }
  DONE_2:
    if (cur_cmd == mp_cycle_command) {
        /*tex
            Get ready to close a cycle. If a user 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.
        */
        switch (cur_mod) {
            case mp_cycle_operation:
                {
                    cycle = 1;
                    mp_get_x_next(mp);
                    pp = path_p;
                    qq = path_p;
                    if (command == mp_ampersand_command && path_p == path_q) {
                        command = mp_path_join_command;
                        mp_set_number_to_unity(path_q->right_tension);
                        mp_set_number_to_unity(y);
                    }
                    mp_number_clone(mp->previous_x, mp_inf_t);
                    mp_number_clone(mp->previous_y, mp_inf_t);
                    break;
                }
            case mp_no_cycle_operation:
                {
                    mp_get_x_next(mp);
                    qq = pp;
                    mp_number_clone(mp->previous_x, mp_inf_t);
                    mp_number_clone(mp->previous_y, mp_inf_t);
                    goto FINISH_PATH;
                }
            case mp_recycle_operation:
                {
                    if (path_p != path_q) {
                        mp_knot pq = mp_prev_knot(path_q);
                     // printf(">>> %i %i\n",mp_right_type(pq),mp_right_type(path_q));
                        mp_free_knot(mp, path_q);
                        path_q = pq;
                        mp_next_knot(path_q) = NULL;
                        pq = mp_prev_knot(path_q);
                        mp_number_clone(mp->last_x, path_q->x_coord);
                        mp_number_clone(mp->last_y, path_q->y_coord);
                        if  (path_p == pq) {
                            mp_number_clone(mp->previous_x, mp_inf_t);
                            mp_number_clone(mp->previous_y, mp_inf_t);
                        } else {
                            mp_number_clone(mp->previous_x, pq->x_coord);
                            mp_number_clone(mp->previous_y, pq->y_coord);
                        }
                    }
                    mp_get_x_next(mp);
                    qq = path_q;
                    goto CONTINUE_PATH;
                }
            case mp_x_relative_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_x(mp, path_q->x_coord, path_q->y_coord);
                } else {
                    mp_bad_unary(mp, mp_x_relative_operation);
                    pp = mp_numeric_to_knot_no(mp, path_q->x_coord, path_q->y_coord);
                }
                qq = pp;
                break;
            case mp_x_absolute_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_pair_type) {
                    pp = mp_pair_to_knot_xy(mp, mp_zero_t, path_q->y_coord);
                } else if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_x(mp, mp_zero_t, path_q->y_coord);
                } else {
                    mp_bad_unary(mp, mp_x_absolute_operation);
                    pp = mp_numeric_to_knot_no(mp, mp_zero_t, path_q->y_coord);
                }
                qq = pp;
                break;
            case mp_y_relative_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_y(mp, path_q->x_coord, path_q->y_coord);
                } else {
                    mp_bad_unary(mp, mp_y_relative_operation);
                    pp = mp_numeric_to_knot_no(mp, path_q->x_coord, path_q->y_coord);
                }
                qq = pp;
                break;
            case mp_y_absolute_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_pair_type) {
                    pp = mp_pair_to_knot_xy(mp, path_q->x_coord, mp_zero_t);
                } else if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_y(mp, path_q->x_coord, mp_zero_t);
                } else {
                    mp_bad_unary(mp, mp_y_absolute_operation);
                    pp = mp_numeric_to_knot_no(mp, path_q->x_coord, mp_zero_t);
                }
                qq = pp;
                break;
            case mp_xy_relative_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_pair_type) {
                    pp = mp_pair_to_knot_xy(mp, path_q->x_coord, path_q->y_coord);
                } else if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_xy(mp, path_q->x_coord, path_q->y_coord);
                } else {
                    mp_bad_unary(mp, mp_xy_relative_operation);
                    pp = mp_numeric_to_knot_no(mp, path_q->x_coord, path_q->y_coord);
                }
                qq = pp;
                break;
            case mp_xy_absolute_operation:
                mp_scan_tertiary(mp);
                if (cur_exp_type == mp_pair_type) {
                    pp = mp_pair_to_knot_xy(mp, mp_zero_t, mp_zero_t);
                } else if (cur_exp_type == mp_known_type) {
                    pp = mp_numeric_to_knot_xy(mp, mp_zero_t, mp_zero_t);
                } else {
                    mp_bad_unary(mp, mp_xy_absolute_operation);
                    pp = mp_numeric_to_knot_no(mp, mp_zero_t, mp_zero_t);
                }
                qq = pp;
                break;
        }
    } else {
// mp_number_clone(mp->previous_x, mp_inf_t);
// mp_number_clone(mp->previous_y, mp_inf_t);
        mp_scan_tertiary(mp);
        /*tex
            Convert the right operand, |cur_exp|, into a partial path from |pp| to~|qq|.
        */
        if (cur_exp_type != mp_path_type) {
            pp = mp_pair_to_knot(mp);
            qq = pp;
        } 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) {
                /*tex Open up a cycle. */
                mp_knot 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;
    }
    if (future) {
        mp_number_clone(x, pp->x_coord);
        mp_number_clone(y, pp->y_coord);
    }
    /*tex
        Join the partial paths and reset |p| and |q| to the head and tail of the result. We use a
        switch here because it's more clear an the compiler will deal with the single case anyway
        and at some point we might combine and turn into functions.
    */
    if (command == mp_ampersand_command) {
        switch (operation) {
            case mp_concat_operation:
                if (! (mp_number_equal(path_q->x_coord, pp->x_coord)) || ! (mp_number_equal(path_q->y_coord, pp->y_coord))) {
                    if (mp_number_greater(internal_value(mp_join_tolerance_internal), mp_zero_t)) {
                        mp_number dx, dy;
                        mp_new_number_from_sub(dx, path_q->x_coord, pp->x_coord);
                        mp_new_number_from_sub(dy, path_q->y_coord, pp->y_coord);
                        mp_number_abs(dx);
                        mp_number_abs(dy);
                        if (mp_number_lessequal(dx, internal_value(mp_join_tolerance_internal))
                         && mp_number_lessequal(dy, internal_value(mp_join_tolerance_internal))) {
                            mp_number_half(dx);
                            mp_number_half(dy);
                            if (mp_number_less(path_q->x_coord, pp->x_coord)) {
                                mp_number_add(path_q->x_coord, dx);
                                mp_number_subtract(pp->x_coord, dx);
                            } else {
                                mp_number_subtract(path_q->x_coord, dx);
                                mp_number_add(pp->x_coord, dx);
                            }
                            if (mp_number_less(path_q->y_coord, pp->y_coord)) {
                                mp_number_add(path_q->y_coord, dy);
                                mp_number_subtract(pp->y_coord, dy);
                            } else {
                                mp_number_subtract(path_q->y_coord, dy);
                                mp_number_add(pp->y_coord, dy);
                            }
                            mp_free_number(dx);
                            mp_free_number(dy);
                            break;
                        } else {
                            mp_free_number(dx);
                            mp_free_number(dy);
                        }
                    }
                    /* todo: < 0 then skip error (warning) */
                    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."
                    );
                    /* todo: -- instead of .. */
                    mp_get_x_next(mp);
                    command = mp_path_join_command;
                    mp_set_number_to_unity(path_q->right_tension);
                    mp_set_number_to_unity(y);
                }
                break;
            case mp_just_append_operation:
            case mp_tolerant_concat_operation:
            case mp_tolerant_append_operation:
                break;
        }
    }
    /*tex
        Plug an opening in |mp_right_type(pp)|, if possible
    */
    if (mp_right_type(pp) == mp_open_knot && ((knottype == mp_curl_knot) || (knottype == mp_given_knot))) {
        mp_right_type(pp) = (unsigned char) knottype;
        mp_number_clone(pp->right_given, x);
    }
    if (command == mp_ampersand_command) {
        /*tex
            Splice independent paths together.
        */
        switch (operation) {
            case mp_concat_operation:      // &
            case mp_just_append_operation: // &&
                break;
            case mp_tolerant_concat_operation:
            case mp_tolerant_append_operation:
                {
                    mp_number dx, dy;
                    mp_new_number_from_sub(dx, path_q->x_coord, pp->x_coord);
                    mp_new_number_from_sub(dy, path_q->y_coord, pp->y_coord);
                    mp_number_abs(dx);
                    mp_number_abs(dy);
                    if (mp_number_lessequal(dx, mp_epsilon_t) && mp_number_lessequal(dy, mp_epsilon_t)) {
                        mp_set_number_half_from_addition(dx, path_q->x_coord, pp->x_coord);
                        mp_set_number_half_from_addition(dy, path_q->y_coord, pp->y_coord);
                        mp_number_clone(pp->left_x, dx);
                        mp_number_clone(path_q->right_x, dx);
                        mp_number_clone(pp->left_y, dy);
                        mp_number_clone(path_q->right_y, dy);
                    }
                    operation = operation == mp_tolerant_concat_operation ? mp_concat_operation : mp_just_append_operation;
                    mp_free_number(dx);
                    mp_free_number(dy);
                    break;
                }
        }
        /* Now we only have concat or append left: */
        switch (operation) {
            case mp_concat_operation:
                {
                    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;
                        mp_set_number_to_unity(path_q->left_curl);
                    }
                    if (mp_right_type(pp) == mp_open_knot && knottype == mp_open_knot) {
                        mp_right_type(pp) = mp_curl_knot;
                        mp_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);
                    mp_number_clone(path_q->right_x, pp->right_x);
                    mp_number_clone(path_q->right_y, pp->right_y);
                    mp_memory_free(pp);
                    break;
                }
            case 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;
                    mp_number_clone(pp->left_x, path_q->x_coord);
                    mp_number_clone(pp->left_y, path_q->y_coord);
                    mp_number_clone(path_q->right_x, pp->x_coord);
                    mp_number_clone(path_q->right_y, pp->y_coord);
                    mp_knotstate(path_q) = mp_knotstate(path_q) == mp_begin_knot ? mp_single_knot : mp_end_knot;
                    mp_knotstate(pp) = mp_begin_knot;
                    path_q = pp;
                    break;
                }
         // case mp_tolerant_concat_operation:
         // case mp_tolerant_append_operation:
        }
        if (qq == pp) {
            qq = path_q;
        }
    } else {
        /*tex
            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);
            mp_number_clone(path_q->right_given, path_q->left_given);
        }
        mp_prev_knot(pp) = path_q;
        mp_next_knot(path_q) = pp;
        mp_number_clone(pp->left_y, y);
        if (knottype != mp_open_knot) {
            mp_number_clone(pp->left_x, x);
            mp_left_type(pp) = (unsigned char) knottype;
        }
    }
    path_q = qq;
    if (cur_cmd >= mp_min_expression_command && cur_cmd <= mp_ampersand_command && ! cycle) {
        goto CONTINUE_PATH;
    }
  FINISH_PATH:
    /*tex
        Choose control points for the path and put the result into |cur_exp|
    */
    if (cycle) {
        if (command == 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;
            mp_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;
            mp_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);
    cur_exp_type = mp_path_type;
    mp_set_cur_exp_knot(mp, path_p);
    mp_free_number(x);
    mp_free_number(y);
    return 1;
}

static void mp_do_boolean_error(MP mp)
{
    mp_value new_expr;
    memset(&new_expr, 0, sizeof(mp_value));
    mp_new_number(new_expr.data.n);
    mp_set_number_from_boolean(new_expr.data.n, mp_false_operation);
    mp_display_error(mp, NULL);
    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);
    cur_exp_type = mp_boolean_type;
}

/*tex

    Declare miscellaneous procedures that were declared |forward|

*/

void mp_print_capsule(MP mp, mp_node p)
{
    mp_print_char(mp, '(');
    mp_print_exp(mp, p, 0);
    mp_print_char(mp, ')');
}

/*
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.

Watch out: we also close all files when we do a subrun (execute) so that's why we have this static
closer.

*/

static void mp_close_files(MP mp)
{
    if (mp->read_filenames) {
        for (int k = 0; k < mp->n_of_read_files; k++) {
            if (mp->read_filenames[k] != NULL) {
                (mp->close_file)(mp, mp->read_filehandles[k]);
                mp_memory_free(mp->read_filenames[k]);
                mp->read_filenames[k] = NULL;
            }
        }
    }
    if (mp->write_filenames) {
        for (int k = 0; k < mp->n_of_write_files; k++) {
            if (mp->write_filenames[k] != NULL) {
                (mp->close_file)(mp, mp->write_filehandles[k]);
                mp_memory_free(mp->write_filenames[k]);
                mp->write_filenames[k] = NULL;
            }
        }
    }
    if (mp->term_in != NULL) {
        (mp->close_file)(mp, mp->term_in);
        mp->term_in = NULL;
    }
}

void mp_close_files_and_terminate(MP mp)
{
    if (mp->finished) {
        return;
    } else {
        mp_close_files(mp);
        mp_print_flush_line(mp);
        mp_print_ln(mp);
        mp->finished = 1;
    }
}

/*tex

    We get to the |final_cleanup| routine when |end| or |dump| has been scanned.

*/

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_string(mp, " )");
            --mp->open_parens;
        }
    }
    while (mp->cond_ptr != NULL) {
        /* |if| or |elseif| or |else| */
        if (mp->if_line != 0) {
            mp_print_format(mp, "(end occurred when %C on line %i was incomplete", mp_fi_or_else_command, mp->cur_if, mp->if_line);
        } else {
            mp_print_format(mp, "(end occurred when %C was incomplete", mp_fi_or_else_command, mp->cur_if);
        }
        mp->if_line = mp->cond_ptr->if_line_field;
        mp->cur_if = mp->cond_ptr->name_type;
        mp->cond_ptr = mp->cond_ptr->link;
    }
    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)");
                mp->selector = mp_term_and_log_selector;
            }
        }
    }
}

static void mp_initialize_primitives(MP mp)
{
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingtitles",         mp_internal_command,         mp_tracing_titles_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingequations",      mp_internal_command,         mp_tracing_equations_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingcapsules",       mp_internal_command,         mp_tracing_capsules_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingdependencies",   mp_internal_command,         mp_tracing_dependencies_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingchoices",        mp_internal_command,         mp_tracing_choices_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingspecs",          mp_internal_command,         mp_tracing_specs_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingcommands",       mp_internal_command,         mp_tracing_commands_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingrestores",       mp_internal_command,         mp_tracing_restores_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingmacros",         mp_internal_command,         mp_tracing_macros_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingoutput",         mp_internal_command,         mp_tracing_output_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingstats",          mp_internal_command,         mp_tracing_stats_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tracingonline",         mp_internal_command,         mp_tracing_online_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "year",                  mp_internal_command,         mp_year_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "month",                 mp_internal_command,         mp_month_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "day",                   mp_internal_command,         mp_day_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "time",                  mp_internal_command,         mp_time_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "hour",                  mp_internal_command,         mp_hour_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "minute",                mp_internal_command,         mp_minute_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "charcode",              mp_internal_command,         mp_char_code_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "charwd",                mp_internal_command,         mp_char_wd_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "charht",                mp_internal_command,         mp_char_ht_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "chardp",                mp_internal_command,         mp_char_dp_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "charic",                mp_internal_command,         mp_char_ic_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "pausing",               mp_internal_command,         mp_pausing_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "showstopping",          mp_internal_command,         mp_showstopping_internal);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "texscriptmode",         mp_internal_command,         mp_texscriptmode_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "overloadmode",          mp_internal_command,         mp_overloadmode_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "linejoin",              mp_internal_command,         mp_linejoin_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "linecap",               mp_internal_command,         mp_linecap_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "stacking",              mp_internal_command,         mp_stacking_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "miterlimit",            mp_internal_command,         mp_miterlimit_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "warningcheck",          mp_internal_command,         mp_warning_check_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "defaultzeroangle",      mp_internal_command,         mp_default_zero_angle_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "truecorners",           mp_internal_command,         mp_true_corners_internal);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "defaultcolormodel",     mp_internal_command,         mp_default_color_model_internal);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "restoreclipcolor",      mp_internal_command,         mp_restore_clip_color_internal);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "numbersystem",          mp_internal_command,         mp_number_system_internal);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "numberprecision",       mp_internal_command,         mp_number_precision_internal);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "jobname",               mp_internal_command,         mp_job_name_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lessdigits",            mp_internal_command,         mp_less_digits_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "intersectionprecision", mp_internal_command,         mp_intersection_precision_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "jointolerance",         mp_internal_command,         mp_join_tolerance_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "singlequotemode",       mp_internal_command,         mp_single_quote_mode_internal);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pruneoptions",          mp_internal_command,         mp_prune_options_internal);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "..",                    mp_path_join_command,        0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "--",                    mp_path_connect_command,     0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "[",                     mp_left_bracket_command,     0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "]",                     mp_right_bracket_command,    0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "}",                     mp_right_brace_command,      0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "{",                     mp_left_brace_command,       0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ":",                     mp_colon_command,            0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ":=",                    mp_assignment_command,       0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ",",                     mp_comma_command,            0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ";",                     mp_semicolon_command,        0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "\\",                    mp_relax_command,            0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "addto",                 mp_add_to_command,           0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "atleast",               mp_at_least_command,         0);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "controls",              mp_controls_command,         mp_both_controls_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstcontrol",          mp_controls_command,         mp_first_control_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "secondcontrol",         mp_controls_command,         mp_second_control_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "curl",                  mp_curl_command,             0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "delimiters",            mp_delimiters_command,       0);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "begingroup",            mp_begin_group_command,      0); if (mp) { mp->bg_loc = cur_sym; }
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "endgroup",              mp_end_group_command,        0); if (mp) { mp->eg_loc = cur_sym; }

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "everyjob",              mp_every_job_command,        0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "exitif",                mp_exit_test_command,        0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "expandafter",           mp_expand_after_command,     0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "interim",               mp_interim_command,          0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "let",                   mp_let_command,              0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "newinternal",           mp_new_internal_command,     0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "of",                    mp_of_command,               0);

    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setbyte",               mp_bytemap_command,          mp_bytemap_set_byte_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setbytemapoffset",      mp_bytemap_command,          mp_bytemap_set_offset_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "copybytemap",           mp_bytemap_command,          mp_bytemap_copy_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "newbytemap",            mp_bytemap_command,          mp_bytemap_new_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setbytemap",            mp_bytemap_command,          mp_bytemap_set_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "clipbytemap",           mp_bytemap_command,          mp_bytemap_clip_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "reducebytemap",         mp_bytemap_command,          mp_bytemap_reduce_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setbytemapoptions",     mp_bytemap_command,          mp_bytemap_set_options_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "resetbytemap",          mp_bytemap_command,          mp_bytemap_reset_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "resetbytemaps",         mp_bytemap_command,          mp_bytemap_reset_all_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "randomseed",            mp_only_set_command,         mp_random_seed_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "maxknotpool",           mp_only_set_command,         mp_max_knot_pool_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "save",                  mp_save_command,             0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "scantokens",            mp_scan_tokens_command,      0);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "runscript",             mp_runscript_command,        0);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "maketext",              mp_maketext_command,         0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "shipout",               mp_ship_out_command,         0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "step",                  mp_step_command,             0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "str",                   mp_str_command,              0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "void",                  mp_void_command,             0); /* hidden gem */
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tension",               mp_tension_command,          0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "to",                    mp_to_command,               0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "until",                 mp_until_command,            0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "within",                mp_within_command,           0);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "write",                 mp_write_command,            0);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "btex",                  mp_btex_command,             mp_btex_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "verbatimtex",           mp_btex_command,             mp_verbatim_code)  ;

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "etex",                  mp_etex_command,             0);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "def",                   mp_macro_def_command,        mp_def_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "vardef",                mp_macro_def_command,        mp_var_def_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "primarydef",            mp_macro_def_command,        mp_primary_def_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "secondarydef",          mp_macro_def_command,        mp_secondary_def_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tertiarydef",           mp_macro_def_command,        mp_tertiary_def_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "enddef",                mp_macro_def_command,        mp_end_def_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "for",                   mp_iteration_command,        mp_start_for_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "forsuffixes",           mp_iteration_command,        mp_start_forsuffixes_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "forever",               mp_iteration_command,        mp_start_forever_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "endfor",                mp_iteration_command,        mp_end_for_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "quote",                 mp_macro_special_command,    mp_macro_quote_code); /* hidden gem */
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "#@",                    mp_macro_special_command,    mp_macro_prefix_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "@",                     mp_macro_special_command,    mp_macro_at_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "@#",                    mp_macro_special_command,    mp_macro_suffix_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "expr",                  mp_parameter_commmand,       mp_expr_parameter);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "suffix",                mp_parameter_commmand,       mp_suffix_parameter);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "text",                  mp_parameter_commmand,       mp_text_parameter);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "primary",               mp_parameter_commmand,       mp_primary_macro);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "secondary",             mp_parameter_commmand,       mp_secondary_macro);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "tertiary",              mp_parameter_commmand,       mp_tertiary_macro);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "input",                 mp_input_command,            mp_input_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "endinput",              mp_input_command,            mp_end_input_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "if",                    mp_if_test_command,          mp_if_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "fi",                    mp_fi_or_else_command,       mp_fi_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "else",                  mp_fi_or_else_command,       mp_else_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "elseif",                mp_fi_or_else_command,       mp_else_if_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "true",                  mp_nullary_command,          mp_true_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "false",                 mp_nullary_command,          mp_false_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "nullpicture",           mp_nullary_command,          mp_null_picture_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "nullpen",               mp_nullary_command,          mp_null_pen_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "readstring",            mp_nullary_command,          mp_read_string_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "pencircle",             mp_nullary_command,          mp_pen_circle_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "normaldeviate",         mp_nullary_command,          mp_normal_deviate_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathpoint",             mp_nullary_command,          mp_path_point_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathprecontrol",        mp_nullary_command,          mp_path_precontrol_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathpostcontrol",       mp_nullary_command,          mp_path_postcontrol_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathdirection",         mp_nullary_command,          mp_path_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathunitdirection",     mp_nullary_command,          mp_path_unit_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastxy",                mp_nullary_command,          mp_last_xy_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastx",                 mp_nullary_command,          mp_last_x_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lasty",                 mp_nullary_command,          mp_last_y_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "previousxy",            mp_nullary_command,          mp_previous_xy_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "previousx",             mp_nullary_command,          mp_previous_x_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "previousy",             mp_nullary_command,          mp_previous_y_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathstate",             mp_nullary_command,          mp_path_state_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathindex",             mp_nullary_command,          mp_path_index_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathlastindex",         mp_nullary_command,          mp_path_lastindex_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathlength",            mp_nullary_command,          mp_path_length_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathfirst",             mp_nullary_command,          mp_path_first_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathlast",              mp_nullary_command,          mp_path_last_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathxpart",             mp_nullary_command,          mp_path_xpart_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "pathypart",             mp_nullary_command,          mp_path_ypart_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "mpversion",             mp_nullary_command,          mp_version_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "iteratorindex",         mp_nullary_command,          mp_iterator_index_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "readfrom",              mp_unary_command,            mp_read_from_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "closefrom",             mp_unary_command,            mp_close_from_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "odd",                   mp_unary_command,            mp_odd_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "known",                 mp_unary_command,            mp_known_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "unknown",               mp_unary_command,            mp_unknown_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "not",                   mp_unary_command,            mp_not_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "decimal",               mp_unary_command,            mp_decimal_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "reverse",               mp_unary_command,            mp_reverse_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "uncycle",               mp_unary_command,            mp_uncycle_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "singularity",           mp_unary_command,            mp_singularity_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "makepath",              mp_unary_command,            mp_make_path_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "makepen",               mp_unary_command,            mp_make_pen_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "makenep",               mp_unary_command,            mp_make_nep_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "convexed",              mp_unary_command,            mp_convexed_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "uncontrolled",          mp_unary_command,            mp_uncontrolled_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "oct",                   mp_unary_command,            mp_oct_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "hex",                   mp_unary_command,            mp_hex_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "ASCII",                 mp_unary_command,            mp_ASCII_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "char",                  mp_unary_command,            mp_char_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "segments",              mp_unary_command,            mp_segments_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "prunesingularities",    mp_unary_command,            mp_prune_singularities_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "length",                mp_unary_command,            mp_length_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "nolength",              mp_unary_command,            mp_no_length_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstpoint",            mp_unary_command,            mp_first_point_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstprecontrol",       mp_unary_command,            mp_first_pre_control_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstpostcontrol",      mp_unary_command,            mp_first_post_control_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstdirection",        mp_unary_command,            mp_first_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "firstunitdirection",    mp_unary_command,            mp_first_unit_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastpoint",             mp_unary_command,            mp_last_point_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastprecontrol",        mp_unary_command,            mp_last_pre_control_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastpostcontrol",       mp_unary_command,            mp_last_post_control_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastdirection",         mp_unary_command,            mp_last_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "lastunitdirection",     mp_unary_command,            mp_last_unit_direction_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "turningnumber",         mp_unary_command,            mp_turning_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "xpart",                 mp_unary_command,            mp_x_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "ypart",                 mp_unary_command,            mp_y_part_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "zpart",                 mp_unary_command,            mp_z_part_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "wpart",                 mp_unary_command,            mp_w_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "xxpart",                mp_unary_command,            mp_xx_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "xypart",                mp_unary_command,            mp_xy_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "yxpart",                mp_unary_command,            mp_yx_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "yypart",                mp_unary_command,            mp_yy_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "redpart",               mp_unary_command,            mp_red_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "greenpart",             mp_unary_command,            mp_green_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "bluepart",              mp_unary_command,            mp_blue_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "cyanpart",              mp_unary_command,            mp_cyan_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "magentapart",           mp_unary_command,            mp_magenta_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "yellowpart",            mp_unary_command,            mp_yellow_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "blackpart",             mp_unary_command,            mp_black_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "greypart",              mp_unary_command,            mp_grey_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "colormodel",            mp_unary_command,            mp_color_model_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "prescriptpart",         mp_unary_command,            mp_prescript_part_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "postscriptpart",        mp_unary_command,            mp_postscript_part_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "stackingpart",          mp_unary_command,            mp_stacking_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "pathpart",              mp_unary_command,            mp_path_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "penpart",               mp_unary_command,            mp_pen_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "dashpart",              mp_unary_command,            mp_dash_part_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "sqrt",                  mp_unary_command,            mp_sqrt_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knownnorm",             mp_unary_command,            mp_norm_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "mexp",                  mp_unary_command,            mp_m_exp_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "mlog",                  mp_unary_command,            mp_m_log_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "sind",                  mp_unary_command,            mp_sin_d_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "cosd",                  mp_unary_command,            mp_cos_d_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "floor",                 mp_unary_command,            mp_floor_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knownnormalize",        mp_unary_command,            mp_normalize_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "uniformdeviate",        mp_unary_command,            mp_uniform_deviate_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "llcorner",              mp_unary_command,            mp_ll_corner_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "lrcorner",              mp_unary_command,            mp_lr_corner_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "ulcorner",              mp_unary_command,            mp_ul_corner_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "urcorner",              mp_unary_command,            mp_ur_corner_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "corners",               mp_unary_command,            mp_corners_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "centerof",              mp_unary_command,            mp_center_of_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "centerofmass",          mp_unary_command,            mp_center_of_mass_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xrange",                mp_unary_command,            mp_x_range_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "yrange",                mp_unary_command,            mp_y_range_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltapoint",            mp_unary_command,            mp_delta_point_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltaprecontrol",       mp_unary_command,            mp_delta_precontrol_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltapostcontrol",      mp_unary_command,            mp_delta_postcontrol_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltadirection",        mp_unary_command,            mp_delta_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltaunitdirection",    mp_unary_command,            mp_delta_unit_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltaarclength",        mp_unary_command,            mp_delta_arclength_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "deltaiteratorindex",    mp_unary_command,            mp_delta_iterator_index_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "arclength",             mp_unary_command,            mp_arc_length_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "angle",                 mp_unary_command,            mp_angle_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "stroked",               mp_unary_command,            mp_stroked_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "filled",                mp_unary_command,            mp_filled_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "clipped",               mp_unary_command,            mp_clipped_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "grouped",               mp_unary_command,            mp_grouped_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "bounded",               mp_unary_command,            mp_bounded_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "cycle",                 mp_cycle_command,            mp_cycle_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "recycle",               mp_cycle_command,            mp_recycle_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "nocycle",               mp_cycle_command,            mp_no_cycle_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xrelative",             mp_cycle_command,            mp_x_relative_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "yrelative",             mp_cycle_command,            mp_y_relative_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xyrelative",            mp_cycle_command,            mp_xy_relative_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xabsolute",             mp_cycle_command,            mp_x_absolute_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "yabsolute",             mp_cycle_command,            mp_y_absolute_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xyabsolute",            mp_cycle_command,            mp_xy_absolute_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "+",                     mp_plus_or_minus_command,    mp_plus_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "-",                     mp_plus_or_minus_command,    mp_minus_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "/",                     mp_slash_command,            mp_over_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "*",                     mp_secondary_binary_command, mp_times_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "^",                     mp_secondary_binary_command, mp_power_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "++",                    mp_tertiary_binary_command,  mp_pythag_add_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "+-+",                   mp_tertiary_binary_command,  mp_pythag_sub_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "or",                    mp_tertiary_binary_command,  mp_or_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knowndotprod",          mp_tertiary_binary_command,  mp_dotprod_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knowncrossprod",        mp_tertiary_binary_command,  mp_crossprod_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knowndiv",              mp_tertiary_binary_command,  mp_div_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "knownmod",              mp_tertiary_binary_command,  mp_mod_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "and",                   mp_and_command,              mp_and_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "<",                     mp_primary_binary_command,   mp_less_than_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "<=",                    mp_primary_binary_command,   mp_less_or_equal_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ">",                     mp_primary_binary_command,   mp_greater_than_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    ">=",                    mp_primary_binary_command,   mp_greater_or_equal_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "<>",                    mp_primary_binary_command,   mp_unequal_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "=",                     mp_equals_command,           mp_equal_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "substring",             mp_of_binary_command,        mp_substring_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "subpath",               mp_of_binary_command,        mp_subpath_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "segment",               mp_of_binary_command,        mp_segment_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "directiontime",         mp_of_binary_command,        mp_direction_time_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "point",                 mp_of_binary_command,        mp_point_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "precontrol",            mp_of_binary_command,        mp_precontrol_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "postcontrol",           mp_of_binary_command,        mp_postcontrol_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "direction",             mp_of_binary_command,        mp_direction_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "unitdirection",         mp_of_binary_command,        mp_unit_direction_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "penoffset",             mp_of_binary_command,        mp_pen_offset_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "arctime",               mp_of_binary_command,        mp_arc_time_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "arcpoint",              mp_of_binary_command,        mp_arc_point_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "arcpointlist",          mp_of_binary_command,        mp_arc_point_list_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "subarclength",          mp_of_binary_command,        mp_subarc_length_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "bytevalue",             mp_of_binary_command,        mp_bytemap_value_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "bytefound",             mp_of_binary_command,        mp_bytemap_found_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "bytepath",              mp_of_binary_command,        mp_bytemap_path_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "bytemapbounds",         mp_of_binary_command,        mp_bytemap_bounds_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "&",                     mp_ampersand_command,        mp_concat_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "&&",                    mp_ampersand_command,        mp_just_append_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "&&&",                   mp_ampersand_command,        mp_tolerant_concat_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "&&&&",                  mp_ampersand_command,        mp_tolerant_append_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "rotated",               mp_secondary_binary_command, mp_rotated_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "slanted",               mp_secondary_binary_command, mp_slanted_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "scaled",                mp_secondary_binary_command, mp_scaled_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "shifted",               mp_secondary_binary_command, mp_shifted_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "transformed",           mp_secondary_binary_command, mp_transformed_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "xscaled",               mp_secondary_binary_command, mp_x_scaled_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "yscaled",               mp_secondary_binary_command, mp_y_scaled_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "zscaled",               mp_secondary_binary_command, mp_z_scaled_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xyscaled",              mp_secondary_binary_command, mp_xy_scaled_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "xshifted",              mp_secondary_binary_command, mp_x_shifted_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "yshifted",              mp_secondary_binary_command, mp_y_shifted_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "bytemapscaled",         mp_secondary_binary_command, mp_bytemap_scaled_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "intersectiontimes",     mp_tertiary_binary_command,  mp_intertimes_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "intersectiontimeslist", mp_tertiary_binary_command,  mp_intertimes_list_operation);

    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "envelope",              mp_of_binary_command,        mp_envelope_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "boundingpath",          mp_of_binary_command,        mp_boundingpath_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "numeric",               mp_type_name_command,        mp_numeric_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "string",                mp_type_name_command,        mp_string_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "boolean",               mp_type_name_command,        mp_boolean_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "path",                  mp_type_name_command,        mp_path_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "pen",                   mp_type_name_command,        mp_pen_type_operation);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "nep",                   mp_type_name_command,        mp_nep_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "picture",               mp_type_name_command,        mp_picture_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "transform",             mp_type_name_command,        mp_transform_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "color",                 mp_type_name_command,        mp_color_type_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "rgbcolor",              mp_type_name_command,        mp_color_type_operation);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "cmykcolor",             mp_type_name_command,        mp_cmykcolor_type_operation);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "pair",                  mp_type_name_command,        mp_pair_type_operation);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "end",                   mp_stop_command,             mp_end_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "dump",                  mp_stop_command,             mp_dump_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "batchmode",             mp_mode_command,             mp_batch_mode);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "nonstopmode",           mp_mode_command,             mp_nonstop_mode);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "scrollmode",            mp_mode_command,             mp_scroll_mode);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "errorstopmode",         mp_mode_command,             mp_error_stop_mode);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "silentmode",            mp_mode_command,             mp_silent_mode);

    mp_primitive(mp, mp_origin_unset,      mp_legacy_language, "inner",                 mp_protection_command,       mp_inner_protection_code);
    mp_primitive(mp, mp_origin_unset,      mp_legacy_language, "outer",                 mp_protection_command,       mp_outer_protection_code);

    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setproperty",           mp_property_command,         1);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "showtoken",             mp_show_command,             mp_show_token_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "showstats",             mp_show_command,             mp_show_stats_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "show",                  mp_show_command,             mp_show_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "showvariable",          mp_show_command,             mp_show_var_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "showdependencies",      mp_show_command,             mp_show_dependencies_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "doublepath",            mp_thing_to_add_command,     mp_add_double_path_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "contour",               mp_thing_to_add_command,     mp_add_contour_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "also",                  mp_thing_to_add_command,     mp_add_also_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "withpen",               mp_with_option_command,      mp_with_pen_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "dashed",                mp_with_option_command,      mp_with_dashed_code);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "withprescript",         mp_with_option_command,      mp_with_pre_script_code);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "withpostscript",        mp_with_option_command,      mp_with_post_script_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withnestedprescript",   mp_with_option_command,      mp_with_nested_pre_script_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withnestedpostscript",  mp_with_option_command,      mp_with_nested_post_script_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withstacking",          mp_with_option_command,      mp_with_stacking_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withlinecap",           mp_with_option_command,      mp_with_linecap_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withlinejoin",          mp_with_option_command,      mp_with_linejoin_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withmiterlimit",        mp_with_option_command,      mp_with_miterlimit_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withoutcolor",          mp_with_option_command,      mp_with_no_model_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withgreyscale",         mp_with_option_command,      mp_with_grey_model_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "withcolor",             mp_with_option_command,      mp_with_uninitialized_model_code);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "withrgbcolor",          mp_with_option_command,      mp_with_rgb_model_code);
    mp_primitive(mp, mp_origin_luatex,     mp_legacy_unset,    "withcmykcolor",         mp_with_option_command,      mp_with_cmyk_model_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withcurvature",         mp_with_option_command,      mp_with_curvature_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withmesh",              mp_with_option_command,      mp_with_mesh_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withbytemap",           mp_with_option_command,      mp_with_bytemap_code);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "withnothing",           mp_with_option_command,      mp_with_nothing_code);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "clip",                  mp_bounds_command,           mp_start_clip_node_type);
    mp_primitive(mp, mp_origin_luametatex, mp_legacy_unset,    "setgroup",              mp_bounds_command,           mp_start_group_node_type);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "setbounds",             mp_bounds_command,           mp_start_bounds_node_type);

    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "message",               mp_message_command,          mp_normal_message_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "errmessage",            mp_message_command,          mp_error_message_code);
    mp_primitive(mp, mp_origin_metapost,   mp_legacy_unset,    "errhelp",               mp_message_command,          mp_error_help_code);

    /* These are only registered for documentation purposes. */

    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "tracinglostchars", mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "charexists",       mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "charext",          mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_font,   "glyph",            mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "kern",             mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "boundarychar",     mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "designsize",       mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "fontmaking",       mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "charlist",         mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "ligtable",         mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "extensible",       mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "headerbyte",       mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "fontdimen",        mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "skipto",           mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "::",               mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "||:",              mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "=:",               mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "=:|",              mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "=:|>",             mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "|=:",              mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "|=:>",             mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "|=:|",             mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "|=:|>",            mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_font,   "|=:|>>",           mp_undefined_command, 0);

    mp_primitive(mp, mp_origin_metapost, mp_legacy_text,   "fontpart",         mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_text,   "textual",          mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_text,   "textpart",         mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_text,   "infont",           mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_text,   "fontsize",         mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_text ,  "mpxbreak",         mp_undefined_command, 0);

    mp_primitive(mp, mp_origin_metapost, mp_legacy_output, "special",          mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "fontmapfile",      mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "fontmapline",      mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_metapost, mp_legacy_output, "prologues",        mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "mpprocset",        mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "troffmode",        mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "outputtemplate",   mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "outputfilename",   mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "filenametemplate", mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "hppp",             mp_undefined_command, 0);
    mp_primitive(mp, mp_origin_luatex,   mp_legacy_output, "vppp",             mp_undefined_command, 0);

 /* mp_primitive(mp, mp_origin_metapost, mp_legacy_unset, "interval_get_left_endpoint",  mp_undefined_command, 0); */
 /* mp_primitive(mp, mp_origin_metapost, mp_legacy_unset, "interval_get_right_endpoint", mp_undefined_command, 0); */
 /* mp_primitive(mp, mp_origin_metapost, mp_legacy_unset, "interval_set",                mp_undefined_command, 0); */

    if (mp) {

        mp->frozen_left_bracket    = mp_frozen_primitive(mp, "[",              mp_left_bracket_command,    0);
        mp->frozen_colon           = mp_frozen_primitive(mp, ":",              mp_colon_command,           0);
        mp->frozen_semicolon       = mp_frozen_primitive(mp, ";",              mp_semicolon_command,       0);
        mp->frozen_end_group       = mp_frozen_primitive(mp, "endgroup",       mp_end_group_command,       0);
        mp->frozen_etex            = mp_frozen_primitive(mp, "etex",           mp_etex_command,            0);
        mp->frozen_end_def         = mp_frozen_primitive(mp, "enddef",         mp_macro_def_command,       mp_end_def_code);
        mp->frozen_end_for         = mp_frozen_primitive(mp, "endfor",         mp_iteration_command,       mp_end_for_code);
        mp->frozen_fi              = mp_frozen_primitive(mp, "fi",             mp_fi_or_else_command,      mp_fi_code);
        mp->frozen_slash           = mp_frozen_primitive(mp, "/",              mp_slash_command,           mp_over_operation);
        mp->frozen_dump            = mp_frozen_primitive(mp, "dump",           mp_stop_command,            mp_dump_code);

        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);

        mp->frozen_repeat_loop     = mp_frozen_primitive(mp, " ENDFOR",        mp_repeat_loop_command,     0);

    }

    primitives_set = 1;
}

int mp_n_of_primitives(void)
{
    if (! primitives_set) {
        mp_initialize_primitives(NULL);
    }
    return n_of_primitives;
}

mp_primitive_data mp_get_primitive_data(int i)
{
    if (! primitives_set) {
        mp_initialize_primitives(NULL);
    }
    if (i >= 1 && i <= n_of_primitives) {
        return primitivedata[i-1];
    } else {
        return (mp_primitive_data) { .name = NULL, .command = 0, .operation = 0 };
    }
}

static void mp_initialize_tables(MP mp)
{
    /*tex
        There are quite some (formally global) variables that keep the current state. They are
        kept local per instance and we do need to initialize them.
    */

    mp_new_number(mp->txx);
    mp_new_number(mp->txy);
    mp_new_number(mp->tyx);
    mp_new_number(mp->tyy);
    mp_new_number(mp->tx);
    mp_new_number(mp->ty);

    mp_new_fraction(mp->st);
    mp_new_fraction(mp->ct);
    mp_new_fraction(mp->sf);
    mp_new_fraction(mp->cf);

    for (int i = 0; i <= mp_y_code; i++) {
        mp_new_number(mp->bbmin[i]);
        mp_new_number(mp->bbmax[i]);
    }

    mp_new_number(mp->cur_x);
    mp_new_number(mp->cur_y);

    mp_new_number_clone(mp->last_x, mp_zero_t);
    mp_new_number_clone(mp->last_y, mp_zero_t);
    mp_new_number_clone(mp->previous_x, mp_zero_t);
    mp_new_number_clone(mp->previous_y, mp_zero_t);

    mp_new_number(mp->cur_t);
    mp_new_number(mp->cur_tt);
    mp_new_number(mp->max_t);
    mp_new_number(mp->delx);
    mp_new_number(mp->dely);
    mp_new_number(mp->appr_t);
    mp_new_number(mp->appr_tt);

    for (int i = 0; i < mp_proto_dependent_type + 1; i++) {
        mp_new_number(mp->max_c[i]);
    }

    /*tex
        A few simple integers:
    */

    mp->serial_no = 0;

    /*tex
        So called internal variables:
    */

    mp_number_clone(internal_value(mp_default_color_model_internal),mp_unity_t);
    mp_number_multiply_int(internal_value(mp_default_color_model_internal), mp_rgb_model); /* two_t */
 // mp_number_clone(internal_value(mp_default_color_model_internal),mp_two_t);
    mp_number_clone(internal_value(mp_restore_clip_color_internal), mp_unity_t);
    mp_number_clone(internal_value(mp_number_precision_internal), mp_precision_default);
    mp_number_clone(internal_value(mp_texscriptmode_internal), mp_unity_t);
    mp_number_clone(internal_value(mp_overloadmode_internal), mp_zero_t);
    mp_number_clone(internal_value(mp_less_digits_internal), mp_zero_t);
    mp_number_clone(internal_value(mp_intersection_precision_internal), mp_two_t);
    mp_number_clone(internal_value(mp_join_tolerance_internal), mp_zero_t);
    mp_number_clone(internal_value(mp_default_zero_angle_internal), mp_negative_one_eighty_deg_t);

    set_internal_string(mp_number_system_internal, mp_intern(mp, "scaled"));

    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_dependencies_internal,   mp_strdup("tracingdependencies"));
    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_default_zero_angle_internal,     mp_strdup("defaultzeroangle"));
    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"));
    set_internal_name(mp_less_digits_internal,            mp_strdup("lessdigits"));
    set_internal_name(mp_intersection_precision_internal, mp_strdup("intersectionprecision"));
    set_internal_name(mp_join_tolerance_internal,         mp_strdup("jointolerance"));
    set_internal_name(mp_single_quote_mode_internal,      mp_strdup("singlequotemode"));
    set_internal_name(mp_prune_options_internal,          mp_strdup("pruneoptions"));

    /*tex
        Relatively siumple initializations:
    */

    mp->spec_head      = mp_new_symbolic_node(mp);
    mp->temp_head      = mp_new_symbolic_node(mp);
    mp->hold_head      = mp_new_symbolic_node(mp);
    mp->id_lookup_test = mp_new_symbols_entry(mp, NULL, 0);
    mp->end_attr       = (mp_node) mp_get_attribute_node(mp);
    mp->null_dash      = mp_new_dash_node(mp);
    mp->cur_mod_       = mp_new_symbolic_node(mp);

    /*tex
        More sophisticated initializations:
    */

    mp_set_hashloc(mp->end_attr, (mp_symbol)-1);
    mp_set_parent((mp_value_node) mp->end_attr, NULL);

    mp->dep_head = mp_get_dep_node(mp, 0); /* 8 */
    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);

    mp->bad_vardef = mp_new_value_node(mp);
    mp->bad_vardef->name_type = mp_root_operation;
    mp_set_value_sym(mp->bad_vardef, mp->frozen_bad_vardef);

    mp->temp_val = mp_new_value_node(mp);
    mp->temp_val->name_type = mp_capsule_operation;

    mp->inf_val = mp_new_value_node(mp);
    mp_set_value_number(mp->inf_val, mp_fraction_four_t);

    mp->zero_val = mp_new_value_node(mp);
    mp_set_value_number(mp->zero_val, mp_zero_t);
}

/*tex

For the retargetable math library, we need to have a pointer, at least. 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. This procedure gets things started properly.

*/

MP mp_initialize(MP_options *opt)
{
    if (! opt || ! opt->job_name || ! *(opt->job_name)) {
        return NULL;
    }
    MP mp = mp_new_instance();
    if (! mp) {
        return NULL;
    }
    mp->job_name        = mp_strdup(opt->job_name);
    mp->userdata        = opt->userdata;
    /* */
    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->close_file      = opt->close_file      ? opt->close_file      : mp_close_file     ;
    mp->read_file       = opt->read_file       ? opt->read_file       : mp_read_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->run_status      = opt->run_status      ? opt->run_status      : mp_run_status     ;
    mp->make_text       = opt->make_text       ? opt->make_text       : mp_make_text      ;
    /* */
    mp->find_file_id    = opt->find_file_id;
    mp->open_file_id    = opt->open_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->run_status_id   = opt->run_status_id;
    mp->make_text_id    = opt->make_text_id;
    /* */
    if (opt->banner && *(opt->banner)) {
        mp->banner = mp_strdup(opt->banner);
    } else {
        mp->banner = mp_strdup(mp_default_banner);
    }
    switch (opt->math_mode) {
        case mp_math_scaled_mode:
            mp->math = mp_initialize_scaled_math(mp);
            break;
     // case mp_math_double_mode:
     //     mp->math = mp_initialize_double_math(mp);
     //     break;
        case mp_math_binary_mode:
            mp->math = mp_initialize_binary_math(mp);
            break;
        case mp_math_decimal_mode:
            mp->math = mp_initialize_decimal_math(mp);
            break;
        case mp_math_posit_mode:
            mp->math = mp_initialize_posit_math(mp);
            break;
        case mp_math_interval_mode:
            mp->math = mp_initialize_interval_math(mp);
            break;
        default:
            mp->math = NULL;
            break;
    }
    if (! mp->math) {
        mp->math = mp_initialize_double_math(mp);
        opt->math_mode = mp_math_double_mode;
    }
    mp->parameter_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;
    mp->buf_size = 200;
    mp->buffer = mp_memory_allocate((size_t) (mp->buf_size + 1) * sizeof(unsigned char));
    mp_initialize_strings(mp);
    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->finished = 0;
    mp->arithmic_error = 0;
    mp->less_digits = 0;
    mp->math_mode = opt->math_mode;
    mp->random_seed = opt->random_seed;

    for (int i = 0; i < 55; i++) {
        mp_new_fraction(mp->randoms[i]);
    }

    /*tex Symbols are not freed, so we only keep statistics. */

    mp->memory_pool[mp_token_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_node_data) };
    mp->memory_pool[mp_symbol_pool]       = (mp_memory_pool_data) { .state = mp_pool_persistent, .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =    0, .size = sizeof(mp_symbol_data) };
    mp->memory_pool[mp_pair_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_pair_node_data) };
    mp->memory_pool[mp_color_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_color_node_data) };
    mp->memory_pool[mp_transform_pool]    = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_transform_node_data) };
    mp->memory_pool[mp_dash_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_dash_node_data) };
    mp->memory_pool[mp_knot_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_knot_data) };
    mp->memory_pool[mp_shape_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_shape_node_data) };
    mp->memory_pool[mp_start_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_start_node_data) };
    mp->memory_pool[mp_stop_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_stop_node_data) };
    mp->memory_pool[mp_value_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_value_node_data) };
    mp->memory_pool[mp_symbolic_pool]     = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_node_data) };
    mp->memory_pool[mp_save_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_save_data) };
    mp->memory_pool[mp_if_pool]           = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_if_data) };
    mp->memory_pool[mp_loop_pool]         = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_loop_data) };
    mp->memory_pool[mp_subst_pool]        = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_subst_data) };
    mp->memory_pool[mp_edge_object_pool]  = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_edge_object) };
    mp->memory_pool[mp_edge_header_pool]  = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_edge_header_node_data) };
    mp->memory_pool[mp_dash_object_pool]  = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_dash_object) };
    mp->memory_pool[mp_knot_object_pool]  = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_knot_object) };
    mp->memory_pool[mp_shape_object_pool] = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept = 1000, .size = sizeof(mp_shape_object) };
    mp->memory_pool[mp_start_object_pool] = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_start_object) };
    mp->memory_pool[mp_stop_object_pool]  = (mp_memory_pool_data) { .state = mp_pool_pooled,     .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =  250, .size = sizeof(mp_stop_object) };
    mp->memory_pool[mp_identifiers_pool]  = (mp_memory_pool_data) { .state = mp_pool_counted,    .list = NULL, .pool = 0, .used = 0, .max = 0, .kept =    0, .count = 0 };
    mp->memory_pool[mp_bytemaps_pool]     = (mp_memory_pool_data) { .state = mp_pool_persistent, .list = NULL, .pool = 0, .used = 0, .max = 0, .step =   25, .size = sizeof(bytemap_data) };
    mp->memory_pool[mp_bytemap_data_pool] = (mp_memory_pool_data) { .state = mp_pool_counted,    .list = NULL, .pool = 0, .used = 0, .max = 0, .step =    0, .count = 0 };
    mp->memory_pool[mp_internals_pool]    = (mp_memory_pool_data) { .state = mp_pool_counted,    .list = NULL, .pool = 0, .used = 0, .max = 0, .step = 1000, .count = 0 };

    {
        int max = mp->memory_pool[mp_internals_pool].step + max_given_internal;
        size_t size = (size_t) (max + 1) * sizeof(mp_internal);
        mp->memory_pool[mp_internals_pool].max = max;
        mp->internal = mp_memory_allocate(size);
        memset(mp->internal, 0, size);
        for (int i = 1; i <= max; i++) {
            mp_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_internal_type(mp_less_digits_internal, mp_boolean_type);
        set_internal_type(mp_single_quote_mode_internal, mp_boolean_type);
    }

    mp_bytemap_ensure(mp, 0); /* forces an initial lot */

    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);
    mp->bisect_stack = mp_memory_allocate((size_t) (bisect_stack_size + 1) * sizeof(mp_number));
    if (mp->bisect_stack) {
        for (int i = 0; i < bisect_stack_size + 1; i++) {
            mp_new_number(mp->bisect_stack[i]);
        }
    }
    mp->stack_size = 16;
    mp->input_stack = mp_memory_allocate((size_t) (mp->stack_size + 1) * sizeof(mp_in_state_record));
    mp_reallocate_input_stack(mp, mp_file_bottom_text + 4);
    mp->parameter_stack = mp_memory_allocate((size_t) (mp->parameter_size + 1) * sizeof(mp_node));
    mp->max_read_files = 8;
    mp->read_filehandles = mp_memory_allocate((size_t) (mp->max_read_files + 1) * sizeof(void *));
    mp->read_filenames = mp_memory_allocate((size_t) (mp->max_read_files + 1) * sizeof(char *));
    mp->max_write_files = 8;
    mp->write_filehandles = mp_memory_allocate((size_t) (mp->max_write_files + 1) * sizeof(void *));
    mp->write_filenames = mp_memory_allocate((size_t) (mp->max_write_files + 1) * sizeof(char *));
    memset(mp->read_filenames, 0, sizeof(char *) * (mp->max_read_files  + 1));
    memset(mp->write_filenames, 0, sizeof(char *) * (mp->max_write_files + 1));
    mp_reallocate_paths(mp, 1000);
    /* in case we quit during initialization: */
    mp->history = mp_fatal_error_stop;
    mp_do_initialize(mp);
    mp_initialize_tables(mp);
 // switch (opt->math_mode) {
    switch (mp->math_mode) {
        case mp_math_scaled_mode:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "scaled"));
            break;
     // case mp_math_double_mode:
     //     set_internal_string(mp_number_system_internal, mp_intern(mp, "double"));
     //     break;
        case mp_math_binary_mode:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "binary"));
            break;
        case mp_math_decimal_mode:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "decimal"));
            break;
        case mp_math_posit_mode:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "posit"));
            break;
        case mp_math_interval_mode:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "interval"));
            break;
        default:
            set_internal_string(mp_number_system_internal, mp_intern(mp, "double"));
            break;
    }
    mp_initialize_primitives(mp);
    mp_fix_date_and_time(mp);
    mp->history = mp_spotless;
    mp_set_precision();
    /*tex  fix up |job_name| */
    if (mp->job_name != NULL) {
        if (internal_string(mp_job_name_internal) != 0) {
            mp_delete_string_reference(mp, internal_string(mp_job_name_internal));
        }
        set_internal_string(mp_job_name_internal, mp_rts(mp, mp->job_name));
    }
    return mp;
}
