/* 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 "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 soem 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 problen then is that the output primitives have to be brought in too, but in good \CWEB\ practices, that then can be done via change files (basically extending the data structures and such). However, a more modern variant could be to just use the library with \LUA, produce \PDF\ and convert that to any format needed. This is what we do in \CONTEXT. After a decade of usage I like to change a few interface aspects so here this happens. So: this variant is {\em not} the official \MP\ library but one meant for usage in \LUAMETATEX\ and experiments by Alan Braslau and Hans Hagen for more advanced graphics, produced by cooperation between \LUA\ and \MP. This strategy permits experiments without interference with the full blown version. Of course we can retrofit interesting extensions into its larger version at some point. It's all a work of love, done in our own time, so don't push our agenda in this. Stripping is easier than adding and the things I added were not at the level of the language or processing but the interface to \LUA\ as well as some details of text processing. Some more of that might happen. For instance, all file \IO\ now goes via \LUA\ so we assume the callbacks being set. On my agenda are to delegate printing of messages and errors to the plugin. Also filenames might be done differently. Messages are already normalized. As a start the psout.w file was stripped and turned into a mpcommon.w file. This means that the old \PS\ output code is no longer there. Because that file got small it eventually got merged in here which (1) permits some reshuffling and (2) gives room for optimizing the interface to \LUA\ (do we need the indirectness?). Quite some code has been stripped because we assume that \LUA\ can provide these features: file io, logging, management, error handling, etc. This saves quite a bit of code and also detangles a bit the mixed program vs. library code. For now the \quote {terminal} approach is kept. In the process I reformatted the source a bit. Sorry. It is no big deal because it looks like \METAPOST\ is not evolving, but what does evolve is the code here: scanners and more access, to mention a few. I've added braces so that comments can go with single statements and there can be no doubt when \WEB\ macros are used (some braces could go there. More variables will become local (to branches for instance). Messages are done more directly, etc. etc. One of the reasons for doing that is that it looks nicer in Visual Studio. There it helps to move some variables to a more local scope. Of course a side effect is that backporting is now no longer an option. In some cases redundant braces were removed (when it's clear in the w file) and some else statements have been added where confusion takes place because that one doesn't return (so compilers can for instance warn about uninitialized pointers). I made sure that the resulting code is readable in Visual Studio. Work in progress: prefix with |mp_| so that macros don't clash with fields and we can get rid of |_| hackery. Maybe some day: zpair zpath zdraw ztransform: just add an extra z dimension to the existing data types which makes it compatible too. \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) weve 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 undoubtly more to come. As with all \LUATEX\ and \LUAMETATEX\ development, most gets explained in the history documents in the \CONTEXT\ distribution and articles. It was around version 3.14 (end May 2022). 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 glonbal) variables. Comments might refer to these without showing the definitions as these are in the header file. */ # define odd(A) (abs(A) % 2 == 1) /*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 possibile. 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 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 unumerations in some places run in parallel (with an offset. That makes it possible the handle types with common code using a delta. In some cases the delta is multiplied by 2 because we have knowns and unknowns. A less sensitive to patches would be to just duplicate the code (or to use a function call). */ /*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$ typecasted 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 B麩er'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 B麩er 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 B麩er 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_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_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) 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_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 mneeded ? */ # 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. */ # 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 int_packets 20 # 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-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 int_increment (int_packets + int_packets + 5) /*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) (1<<(unsigned)(A)) # define cur_cmd mp->cur_mod_->command # define cur_mod 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 # define mp_if_line_field(A) ((mp_if_node) (A))->if_line_field /*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 accurary. */ # 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 privides the result as a structure that reflects the original \POSTSCRIPT\ backend. We could use more direct methods but for now we follow the route with an intermediate. Actually, it's that intermediate that is kind of the standard output \API. We no longer report the shipped outfigure because the backend can do that, but we keep the number. */ # 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_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); 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, 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, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, 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_def_delims (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_bytemap_get_byte (MP mp, int index, int x, int y, int z); static int mp_bytemap_has_byte_gray (MP mp, int index, int s); static int mp_bytemap_has_byte_rgb (MP mp, int index, int r, int g, int b); static int mp_bytemap_has_byte_range (MP mp, int index, int s1, int s2); 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 */ 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); /* hm, weird, no number */ 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) { 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; kpath_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_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; ibisect_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 <= 15; i++) { mp_memory_free(mp->bytemaps[i].data); } mp_memory_free(mp->bytemaps); /* */ mp_close_files(mp); 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; } /*tex finish non-interactive use */ mp_memory_free(mp->term_in); mp->term_in = 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['\''] = 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, 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_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_str_room(mp, (int) len); mp_append_str(mp, str); 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 commandline\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 occured 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 |arith_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. */ 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 mmemory 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_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_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_length_operation] = "length", [mp_no_length_operation] = "nolength", [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_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_arc_length_operation] = "arclength", [mp_angle_operation] = "angle", [mp_cycle_operation] = "cycle", [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_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_direction_time_operation] = "directiontime", [mp_point_operation] = "point", [mp_precontrol_operation] = "precontrol", [mp_postcontrol_operation] = "postcontrol", [mp_direction_operation] = "direction", [mp_path_point_operation] = "pathpoint", [mp_path_precontrol_operation] = "pathprecontrol", [mp_path_postcontrol_operation] = "pathpostcontrol", [mp_path_direction_operation] = "pathdirection", [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_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_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_root_operation && c <= mp_bytemap_bounds_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 straightword 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 void mp_primitive(MP mp, const char *str, int c, int o) { set_cur_sym(mp_id_lookup(mp, (char *) str, strlen(str), 1)); mp_set_eq_type(cur_sym, c); mp_set_eq_property(cur_sym, 0x1); /* todo: enumeration values */ mp_set_eq_valent(cur_sym, o); } /*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, 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. */ 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; } 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, " -> "); break; case mp_of_macro: mp_print_string(mp, " of -> "); break; case mp_suffix_macro: mp_print_string(mp, " -> "); break; case mp_text_macro: mp_print_string(mp, " -> "); 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. */ 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) { 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; 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->name_type == mp_subscript_operation) { 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. */ 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->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 biut 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. */ void mp_print_path_only(MP mp, mp_knot h) { mp_knot p = h; do { mp_knot q = mp_next_knot(p); if ((p == NULL) || (q == NULL)) { mp_print_nl(mp, "???"); return; /* this won't happen */ } else { /*tex print information for adjacent knots |p| and |q| */ mp_print_format(mp, "(%N,%N)", p->x_coord, p->y_coord); switch (mp_knotstate(p)) { case mp_begin_knot: mp_print_string(mp, " {begin}"); break; case mp_end_knot: mp_print_string(mp, " {end}"); break; } 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, " {?} "); /* 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. */ 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); } 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 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|. */ 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; kdelta_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. */ 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, <); } 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<=kuu[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, <, &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. */ 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. */ 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) { 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)) { mp_right_type(p) = mp_explicit_knot; mp_left_type(p) = mp_explicit_knot; return q; } else { mp_memory_free(q); return NULL; } } int mp_set_knot_curl(MP mp, mp_knot q, double value) /* same as mp_set_knot_right_curl */ { if (q == NULL) { return 0; } else if (TOO_LARGE(value)) { return 0; } else { mp_right_type(q) = mp_curl_knot; 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_right_type(q) = mp_curl_knot; mp_set_number_from_double(q->right_curl, 1.0); mp_left_type(q) = mp_curl_knot; mp_set_number_from_double(q->left_curl, 1.0); return 1; } } int mp_set_knotpair_curls(MP mp, mp_knot p, mp_knot q, double t1, double t2) { if (p == NULL || q == NULL) { return 0; } else if (mp_set_knot_curl(mp, p, t1)) { return mp_set_knot_curl(mp, q, t2); } else { return 0; } } int mp_set_knotpair_tensions(MP mp, mp_knot p, mp_knot q, double t1, double t2) { if (p == NULL || q == NULL) { return 0; } else if (TOO_LARGE(t1)) { return 0; } else if (TOO_LARGE(t2)) { return 0; } else if ((fabs(t1) < 0.75)) { return 0; } else if ((fabs(t2) < 0.75)) { return 0; } else { 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; int retval = 1; 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 { 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<=kx_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 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_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 b麩er 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 = 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); } /* 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. */ 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 b麩er 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 = 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); } } 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 = 1; 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 */ 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. */ 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]. */ 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|. */ 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 = 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|. */ void mp_sqrt_det(MP mp, mp_number *ret, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, mp_number *d_orig) { mp_number a, b, c, d; mp_number maxabs; /*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 = 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 = 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; } } 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); } 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|. */ 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_1component(MP mp, mp_node p) { int lev = 0; /* current nesting level */ (void) mp; do { if (mp_is_start_or_stop (p)) { if (mp_is_stop(p)) { --lev; } else { ++lev; } } p = p->link; } while (lev != 0); return p; } /*tex Here is a diagnostic routine for printing an edge structure in symbolic form. */ 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; 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). */ 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. */ 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 unoffset 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. */ 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]| */ 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 { return; } /*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_number x0a, y0a, x1a, y1a; 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_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"); } } /*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. */ 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)|. */ 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. */ 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. */ 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); } 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, w0; /*tex the pen knot for the current offset */ int k, k0; /*tex controls pen edge insertion */ 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 */ int kk; /*tex keeps track of the pen vertices being scanned */ mp_knot ww; /*tex the pen vertex being tested */ 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 { 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. */ 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.} */ 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. */ 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 = 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)) + 4) > bistack_size || ((u_packet (mp->uv)) + 4) > bistack_size || ((y_packet (mp->xy)) + 4) > bistack_size || ((v_packet (mp->uv)) + 4) > bistack_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 due 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 + 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(mp_number_to_scaled(mp->cur_tt))) { // if (mp_number_odd(mp->cur_tt)) { if (odd(mp_number_to_scaled(mp->cur_t))) { // if (mp_number_odd(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 -= 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 = mp_number_to_scaled(stack_tol); mp->uv = mp_number_to_scaled(stack_uv); mp->xy = 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 + int_packets; mp_set_number_from_scaled(mp->cur_tt, mp_number_to_scaled(mp->cur_tt) - 1); mp->xy = mp->xy - 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 + int_packets; } } mp_free_number(x_two_t); mp_free_number(x_two_t_low_precision); } static mp_knot mp_path_intersection_add(MP mp, mp_knot list, mp_knot *last, mp_number *t, mp_number *tt) { int a = mp_number_to_scaled(*t) >> mp_intersection_run_shift; int aa = mp_number_to_scaled(*tt) >> mp_intersection_run_shift; int b = (list ? mp_number_to_scaled((*last)->x_coord) : -1) >> mp_intersection_run_shift ; int bb = (list ? 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 = 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 = mp_number_to_scaled(mp->cur_t); tt = 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). */ 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), (int) 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. */ 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_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. */ 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", mp_true_line(mp)); } else if (mp_input_name == mp_input_from_terminal) { if (mp->file_ptr == 0) { mp_print_nl(mp, ""); } else { mp_print_nl(mp, ""); } } else if (mp_input_name == mp_input_from_tokens) { mp_print_nl(mp, ""); } else { mp_print_nl(mp, ""); } 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, " "); 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, "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, " "); break; case mp_backed_up_text: mp_print_nl(mp, nloc == NULL ? " " : " "); break; case mp_inserted_text: mp_print_nl(mp, " "); break; case mp_macro_text: mp_print_nl(mp, " "); 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. */ 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. */ 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. */ 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 { unsigned char cend = c == '"' ? '"' : 3 ; /* ASCII BTX ... ETX */ 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. */ 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 = 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; 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) { 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|. 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, 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 ' 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. */ 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. */ 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. */ 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; mp_if_line_field(p) = mp->if_line; mp->cond_ptr = p; mp->if_limit = mp_if_code; mp->if_line = mp_true_line(mp); mp->cur_if = mp_if_code; } static void mp_pop_condition_stack(MP mp) { mp_if_node p = mp->cond_ptr; mp->if_line = mp_if_line_field(p); 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; } } 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->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_1component(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. */ void mp_resume_iteration(MP mp) { mp_node p, q; /* link registers */ p = mp->loop_ptr->type; if (p == MP_PROGRESSION_FLAG) { /* mp_set_cur_exp_value_number(mp, &(mp->loop_ptr->value)); if ((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_1component(mp, q); } else { goto NOT_FOUND; } mp_set_cur_exp_node(mp, (mp_node) mp_copy_objects(mp, mp->loop_ptr->list, q)); mp_init_bbox(mp, (mp_edge_header_node) cur_exp_node); 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. */ 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 fo rhistoric 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. */ 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 ||, 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. */ 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"); } } 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); } 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); } 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; } 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; } 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; } 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_expname_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. */ 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. */ 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); } 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|. */ 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, " "); 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. */ 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. */ 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); 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); 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); 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); 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); 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); 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. */ 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: mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); break; case 1: if (mp_left_type(p) == mp_endpoint_knot) { mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); } else { mp_pair_value(mp, &(p->left_x), &(p->left_y)); } break; case 2: if (mp_right_type(p) == mp_endpoint_knot) { mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); } else { mp_pair_value(mp, &(p->right_x), &(p->right_y)); } break; case 3: { mp_number x, y; if (mp_right_type(p) == mp_endpoint_knot) { 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); } mp_pair_value(mp, &x, &y); mp_free_number(x); mp_free_number(y); } break; case 4: { 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 5: { 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 6: { mp_value expr; memset(&expr, 0, sizeof(mp_value)); mp_number_clone(expr.data.n, n); mp_flush_cur_exp(mp, expr); } break; case 7: { 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 8: /* 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 9: /* 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; } } /*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_path_point_operation: case mp_path_precontrol_operation: case mp_path_postcontrol_operation: case mp_path_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: 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; } 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_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: if (cur_exp_type == mp_pair_type) { mp_make_exp_copy(mp, mp_x_part(p), 3); } else { mp_make_exp_copy(mp, mp_tx_part(p), 4); } break; case mp_y_part_operation: if (cur_exp_type == mp_pair_type) { mp_make_exp_copy(mp, mp_y_part(p), 5); } else { mp_make_exp_copy(mp, mp_ty_part(p), 6); } break; case mp_xx_part_operation: mp_make_exp_copy(mp, mp_xx_part(p), 7); break; case mp_xy_part_operation: mp_make_exp_copy(mp, mp_xy_part(p), 8); break; case mp_yx_part_operation: mp_make_exp_copy(mp, mp_yx_part(p), 9); break; case mp_yy_part_operation: mp_make_exp_copy(mp, mp_yy_part(p), 10); break; case mp_red_part_operation: mp_make_exp_copy(mp, mp_red_part(p), 11); break; case mp_green_part_operation: mp_make_exp_copy(mp, mp_green_part(p), 12); break; case mp_blue_part_operation: mp_make_exp_copy(mp, mp_blue_part(p), 13); break; case mp_cyan_part_operation: mp_make_exp_copy(mp, mp_cyan_part(p), 14); break; case mp_magenta_part_operation: mp_make_exp_copy(mp, mp_magenta_part(p), 15); break; case mp_yellow_part_operation: mp_make_exp_copy(mp, mp_yellow_part(p), 16); break; case mp_black_part_operation: mp_make_exp_copy(mp, mp_black_part(p), 17); break; case mp_grey_part_operation: mp_make_exp_copy(mp, mp_grey_part(p), 18); 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_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) { ++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; } } ++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_1component(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_1component(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; 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, mp_not_operation); } 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_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, mp_angle_operation); } } static void mp_set_up_char(MP mp, int c) { if (cur_exp_type != mp_known_type) { mp_bad_unary(mp, mp_char_operation); } else { int n = 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 = 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; /* accumulator */ mp_value new_expr; memset(&new_expr, 0, sizeof(mp_value)); mp_new_number(new_expr.data.n); if (cur_exp_str->len == 0) { n = -1; } else { n = cur_exp_str->str[0]; } 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. * */ 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); 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 { mp_bad_unary(mp, c); } break; } } 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, mp_turning_operation); } 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) { 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 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, mp_arc_length_operation); } 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; memset(&expr, 0, sizeof(mp_value)); mp_new_number(expr.data.n); if (cur_exp_type != mp_picture_type) { mp_set_number_from_boolean(expr.data.n, mp_false_operation); } else if (mp_edge_list(cur_exp_node)->link == NULL) { mp_set_number_from_boolean(expr.data.n, mp_false_operation); } else { /* they are parallel: */ int type = c - mp_filled_operation + mp_fill_node_type; mp_set_number_from_boolean(expr.data.n, mp_edge_list(cur_exp_node)->link->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_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, mp_reverse_operation); 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, mp_uncycle_operation); 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); } else { mp_bad_unary(mp, mp_center_of_operation); } } 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, mp_center_of_mass_operation); } } static void mp_set_up_delta(MP mp, int c) { 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 = 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, mp_make_pen_operation); } 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 */ 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 */ 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_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_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_length_operation: mp_set_up_length(mp, c); break; case mp_no_length_operation: mp_set_up_no_length(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_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_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: 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" "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 (c == mp_minus_operation) { mp_number_negate(vv); } if (p->type == mp_known_type) { 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; } 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_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_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) { mp_number sx, sy; if (mp_pen_ptr(p) != NULL) { 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, mp_envelope_operation); } 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, mp_boundingpath_operation); } 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, mp_boundingpath_operation); 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; 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; break; } } /*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, mp_over_operation); 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, (int) 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, (int) 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 { mp_bad_binary(mp, p, mp_times_operation); 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, mp_subarc_length_operation); } } 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, mp_arc_point_list_operation); } } else { mp_bad_unary(mp, mp_arc_point_list_operation); } } 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)) { 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; } mp_pair_value(mp, &(k->x_coord), &(k->y_coord)); if (toss) { mp_free_knot(mp, k); } } else { mp_bad_unary(mp, mp_arc_point_operation); } } else { mp_bad_unary(mp, mp_arc_point_operation); } } 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, (int) 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, (int) 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, mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))), 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, mp_substring_operation); } } 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, 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, mp_subpath_operation); } 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, 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: 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: 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); 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 | 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_def_delims(mp); break; case mp_save_command: do { mp_get_symbol(mp); mp_save_variable(mp, cur_sym); mp_get_x_next(mp); } while (cur_cmd == mp_comma_command); break; case mp_interim_command: mp_do_interim(mp); break; case mp_let_command: mp_do_let(mp); break; case mp_new_internal_command: mp_do_new_internal(mp); break; case mp_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). */ 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|: */ 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. */ 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); } 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|. */ 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; } 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); char err[256]; 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) { 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 = (mp_number_to_scaled(internal_value(mp_time_internal))/mp_number_to_scaled(mp_unity_t)) + 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). */ 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 := '." ); }; 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. */ } } } 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 := '." ); }; 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(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. */ 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. */ 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. */ void mp_def_delims(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. */ 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. */ 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. */ 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 ; */ static inline unsigned char max_of_three(unsigned char a, unsigned char b, unsigned char c) { if (a > b && a > c) { return a; } else if (a > c) { return a; } else if (b > c) { return b; } else { return c; } } static inline unsigned char min_of_three(unsigned char a, unsigned char b, unsigned char c) { if (a < b && a < c) { return a; } else if (a < c) { return a; } else if (b < c) { return b; } else { return c; } } # define max_bytemaps 1024 static int mp_bytemap_grow(MP mp) { int oldmax = mp->memory_pool[mp_bytemaps_pool].max; int newmax = oldmax + mp->memory_pool[mp_bytemaps_pool].step; if (newmax > max_bytemaps) { /* todo: messaage */ // mp_confusion(mp, "out of memory"); /* can't be reached */ return 0; } else { size_t newsize = (size_t) (newmax + 1) * sizeof(mp_bytemap); mp->bytemaps = mp_memory_reallocate(mp->bytemaps, newsize); for (int index = oldmax; index < newmax; index++) { mp->bytemaps[index] = (mp_bytemap) { .data = NULL, .nx = 0, .ny = 0, .nz = 0, .ox = 0, .oy = 0, .options = 0, }; } 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 || mp_bytemap_grow(mp))); } static int mp_bytemap_valid_data(MP mp, int index) { // return index >= 0 && (index < mp->memory_pool[mp_bytemaps_pool].max || mp_bytemap_grow(mp)) // && mp->bytemaps[index].data; 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 <= 15 && mp->bytemaps[index].data) { *nx = mp->bytemaps[index].nx; *ny = mp->bytemaps[index].ny; *nz = mp->bytemaps[index].nz; if (nx > 0 && ny > 0) { size_t length = (size_t) ((*nx) * (*ny) * (*nz)); char *result = mp_memory_allocate(length); memcpy(result, mp->bytemaps[index].data, length); return result; } } return NULL; } static inline int mp_valid_byte(int value) { if (value < 0) { return 0; } else if (value > 255) { return 255; } else { return value; } } static inline int mp_aux_weighted(int r, int g, int b) { return round(0.299 * r + 0.587 * b + 0.114 * r ); } static inline int mp_aux_bytemap_get_byte(MP mp, mp_bytemap *bytemap, mp_number *source) { if (bytemap->options & mp_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, mp_bytemap *bytemap, mp_number *target, int value) { if (bytemap->options & mp_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_gray(mp_bytemap *bytemap, int x, int y, int s) { if (x >= 0 && y >= 0 && x < bytemap->nx && y < bytemap->ny) { switch (bytemap->nz) { case 1: bytemap->data[bm_current_y(bytemap->ny,y) * bytemap->nx + x] = mp_valid_byte(s); break; case 3: memset(bytemap->data + (bm_current_y(bytemap->ny,y) * bytemap->nx + x) * 3, mp_valid_byte(s), 3); break; } } } static inline void mp_aux_set_bytemap_rgb(mp_bytemap *bytemap, int x, int y, int r, int g, int b) { if (x >= 0 && y >= 0 && x < bytemap->nx && y < bytemap->ny) { switch (bytemap->nz) { case 1: bytemap->data[bm_current_y(bytemap->ny,y) * bytemap->nx + x] = mp_valid_byte(mp_aux_weighted(r, g, b)); break; case 3: { int offset = (bm_current_y(bytemap->ny,y) * bytemap->nx + x) * 3; bytemap->data[offset+0] = mp_valid_byte(r); bytemap->data[offset+1] = mp_valid_byte(g); bytemap->data[offset+2] = mp_valid_byte(b); } break; } } } static inline void mp_aux_set_bytemap_channel(mp_bytemap *bytemap, int x, int y, int z, int v) { if (x >= 0 && y >= 0 && x < bytemap->nx && y < bytemap->ny && 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 void mp_aux_set_bytemap_slice_gray(mp_bytemap *bytemap, int x, int y, int dx, int dy, int s); static void mp_aux_set_bytemap_slice_rgb (mp_bytemap *bytemap, int x, int y, int dx, int dy, int r, int g, int b); static void mp_aux_set_bytemap_slice_gray(mp_bytemap *bytemap, int x, int y, int dx, int dy, int s) { if (dx > 0 && dy > 0) { switch (bytemap->nz) { case 1: { unsigned char *p = bytemap->data; int w = bytemap->nx; int o = x; if (x + dx > bytemap->nx) { dx = bytemap->nx - x; } if (y + dy > bytemap->ny) { dy = bytemap->ny - y; } o += bm_current_y(bytemap->ny,y) * w; memset(p + o, (unsigned char) mp_valid_byte(s), dx); for (int i = bm_first_y(bytemap->ny,y,dy); i <= bm_last_y(bytemap->ny,y,dy); i++) { memcpy(p + x + i * w, p + o, dx); } } break; case 3: mp_aux_set_bytemap_slice_rgb(bytemap, x, y, dx, dy, s, s, s); break; } } } static void mp_aux_set_bytemap_slice_rgb(mp_bytemap *bytemap, int x, int y, int dx, int dy, int r, int g, int b) { if (dx > 0 && dy > 0) { switch (bytemap->nz) { case 1: mp_aux_set_bytemap_slice_gray(bytemap, x, y, dx, dy, mp_aux_weighted(r,g,b)); break; case 3: { unsigned char *p = bytemap->data; int w = 3 * bytemap->nx; int o = 3 * x; if (x + dx > bytemap->nx) { dx = bytemap->nx - x; } if (y + dy > bytemap->ny) { dy = bytemap->ny - y; } o += bm_current_y(bytemap->ny,y) * w; bytemap->data[o+1] = (unsigned char) mp_valid_byte(g); bytemap->data[o+0] = (unsigned char) mp_valid_byte(r); bytemap->data[o+2] = (unsigned char) mp_valid_byte(b); for (int i = 1; i < dx; i++) { memcpy(p + o + i * 3, p + o, 3); } for (int i = bm_first_y(bytemap->ny,y,dy); i <= bm_last_y(bytemap->ny,y,dy); i++) { memcpy(p + 3 * x + i * w, p + o, 3 * dx); } } break; } } } static int mp_bytemap_get_byte(MP mp, int index, int x, int y, int z) { if (mp_bytemap_valid_data(mp, index)) { int nx = mp->bytemaps[index].nx; int ny = mp->bytemaps[index].ny; if (x >= 0 && y >= 0 && x <= nx && y <= ny) { int nz = mp->bytemaps[index].nz; switch (nz) { case 1: return mp->bytemaps[index].data[bm_current_y(ny,y) * nx + x]; case 3: { int p = bm_current_y(ny,y) * ny * nz + x; if (z >= 0 && z <= nz) { return mp->bytemaps[index].data[p]; } else { return mp_aux_weighted ( mp->bytemaps[index].data[p+0], mp->bytemaps[index].data[p+1], mp->bytemaps[index].data[p+2] ); } } } } } return 0; } static int mp_bytemap_has_byte_gray(MP mp, int index, int s) { if (mp_bytemap_valid_data(mp, index)) { switch (mp->bytemaps[index].nz) { case 1: for (int i = 0; i < mp->bytemaps[index].nx * mp->bytemaps[index].ny; i++) { if (mp->bytemaps[index].data[i] == (unsigned char) s) { return 1; } } return 0; case 3: return mp_bytemap_has_byte_rgb(mp, index, s, s, s); } } return 0; } static int mp_bytemap_has_byte_range(MP mp, int index, int s1, int s2) { if (mp_bytemap_valid_data(mp, index)) { switch (mp->bytemaps[index].nz) { case 1: for (int i = 0; i < mp->bytemaps[index].nx * mp->bytemaps[index].ny; i++) { if (mp->bytemaps[index].data[i] >= (unsigned char) s1 && mp->bytemaps[index].data[i] <= (unsigned char) s2) { return 1; } } return 0; case 3: return 0; } } return 0; } static int mp_bytemap_has_byte_rgb(MP mp, int index, int r, int g, int b) { if (mp_bytemap_valid_data(mp, index)) { switch (mp->bytemaps[index].nz) { case 1: return mp_bytemap_has_byte_gray(mp, index, mp_aux_weighted(r, g, b)); case 3: { for (int i = 0; i < mp->bytemaps[index].nx * mp->bytemaps[index].ny * mp->bytemaps[index].nz; i += 3) { if (mp->bytemaps[index].data[i+0] == (unsigned char) r && mp->bytemaps[index].data[i+1] == (unsigned char) g && mp->bytemaps[index].data[i+2] == (unsigned char) b ) { return 1; } } return 0; } } } return 0; } 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 (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] = (mp_bytemap) { /* 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, }; 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 */ mp_bytemap *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_valid(mp, index) && mp_aux_bytemap_allocate(mp, index, nx, ny, nz, data); } /* end of public */ static void mp_bytemap_copy(MP mp) { 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 = 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 = mp_round_unscaled(cur_exp_value_number); if (mp_bytemap_valid_data(mp, oldindex)) { int size = mp->bytemaps[oldindex].nx * mp->bytemaps[oldindex].ny * mp->bytemaps[oldindex]. nz; if (mp_bytemap_valid_data(mp, newindex)) { mp_memory_free(mp->bytemaps[newindex].data); } mp->bytemaps[newindex] = (mp_bytemap) { .data = mp_memory_allocate(size), .nx = mp->bytemaps[oldindex].nx, .ny = mp->bytemaps[oldindex].ny, .nz = mp->bytemaps[oldindex].nz, .ox = mp->bytemaps[oldindex].ox, .oy = mp->bytemaps[oldindex].oy, .options = 0, }; memcpy(mp->bytemaps[newindex].data, mp->bytemaps[oldindex].data, size); } } break; default: break; } } break; default: /* error */ break; } } static int mp_aux_bytemap_bounds(mp_bytemap *b, int value, int *lx, int *ly, int *rx, int *ry) { unsigned char *d = b->data; int nx = b->nx; int ny = b->ny; int nz = b->nz; int llx = nx - 1; int lly = ny - 1; int urx = 0; int ury = 0; switch (nz) { case 1: for (int y = 0; y < ny; y++) { for (int x = 0; x < nx; x++) { /* here posit */ if (*d != value) { if (y < lly) { lly = y; } if (y > ury) { ury = y; } if (x < llx) { llx = x; } if (x > urx) { urx = x; } } d = d + 1; } if (llx == 0 && urx == nx && lly == 0 && ury == ny) { goto DONE; } } break; case 3: for (int y = 0; y < ny; y++) { for (int x = 0; x < nx; x++) { /* here posit */ if (*d != value || *(d+1) != value || *(d+2) != value) { if (y < lly) { lly = y; } if (y > ury) { ury = y; } if (x < llx) { llx = x; } if (x > urx) { urx = x; } } d = d + 3; if (llx == 0 && urx == nx && lly == 0 && ury == ny) { goto DONE; } } } break; } DONE: if (urx < llx || ury < lly) { *lx = 0; *ly = 0; *rx = nx - 1; *ry = ny - 1; } else { *lx = llx; *ly = lly; *rx = urx; *ry = ury; } return (*lx > 0 || *ly > 0 || *rx < nx || *ry < ny); } static void mp_bytemap_clip(MP mp) { 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 = 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 = mp_round_unscaled(cur_exp_value_number); if (mp_bytemap_valid_data(mp, index)) { mp_bytemap *b = &mp->bytemaps[index]; int value = mp_aux_bytemap_get_byte(mp, b, &(cur_exp_value_number)); int llx = 0; int urx = b->nx; int lly = 0; int ury = b->ny; if (mp_aux_bytemap_bounds(b, value, &llx, &lly, &urx, &ury)) { int oldnx = b->nx; int oldny = b->ny; int oldnz = b->nz; int newnx = urx - llx + 1; int newny = ury - lly + 1; unsigned char *p = b->data + lly * oldnx * oldnz + llx; unsigned char *c = mp_memory_allocate(newnx * newny * oldnz); unsigned char *d = c; for (int y=1; y <= newny; y++) { memcpy(c, p, newnx * oldnz); c = c + newnx * oldnz; p = p + oldnx * oldnz; } mp_memory_free(b->data); b->data = d; b->ox = 0; b->oy = 0; b->nx = newnx; b->ny = newny; mp->memory_pool[mp_bytemap_data_pool].count -= oldnx * oldny * oldnz; mp->memory_pool[mp_bytemap_data_pool].count += newnx * newny * oldnz; } } } break; default: break; } } break; default: /* error */ break; } } static void mp_bytemap_new(MP mp) { 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 = 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_valid(mp, index)) { nx = mp_round_unscaled(cur_exp_value_number); ny = 1; } break; case mp_pair_type: if (mp_bytemap_valid(mp, index)) { if (mp_pair_is_known(mp_get_value_node(cur_exp_node))) { nx = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))); ny = 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_valid(mp, index)) { if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) { nx = mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node)))); ny = mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node)))); nz = 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; } } } 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) { mp_get_x_next(mp); mp_scan_primary(mp); switch (cur_exp_type) { case mp_numeric_type: case mp_known_type: { int index = 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 = mp_round_unscaled(cur_exp_value_number); } if (mp_bytemap_valid_data(mp, index)) { int nz = mp->bytemaps[index].nz; if (nz == 3) { int nx = mp->bytemaps[index].nx; int ny = mp->bytemaps[index].ny; unsigned char *color = mp->bytemaps[index].data; unsigned char *gray = mp_memory_allocate(nx*ny); unsigned c = 0; switch (method) { case 1: /* average */ for (int g = 0; g < nx * ny; g++) { int s = round( (double) ( (unsigned char) color[c] + (unsigned char) color[c+1] + (unsigned char) color[c+2] ) / 3.0); c += 3; gray[g] = s > 255 ? 255 : (unsigned char) s; } break; case 2: /* min-max */ for (int g = 0; g < nx * ny; g++) { int s = round( (double) ( max_of_three(color[c], color[c+1], color[c+2]), + min_of_three(color[c], color[c+1], color[c+2]) ) / 2.0); c += 3; gray[g] = s > 255 ? 255 : (unsigned char) s; } break; default: /* weighted */ for (int g = 0; g < nx * ny; g++) { int s = round( 0.299 * (unsigned char) color[c] + 0.587 * (unsigned char) color[c+1] + 0.114 * (unsigned char) color[c+2] ); c += 3; gray[g] = s > 255 ? 255 : (unsigned char) s; } break; } mp->memory_pool[mp_bytemap_data_pool].count -= nx * ny * 2; mp_memory_free(color); mp->bytemaps[index] = (mp_bytemap) { .data = gray, .nx = nx, .ny = ny, .nz = 1, .ox = 0, .oy = 0, .options = 0, }; } } } break; default: /* error */ break; } } static void mp_bytemap_value(MP mp, mp_node p, int c) { switch (cur_exp_type) { case mp_numeric_type: /* needed ? */ case mp_known_type: { int index = 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 = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))); y = 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 = mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(p)))); y = mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(p)))); z = 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); mp_aux_bytemap_set_byte(mp, &(mp->bytemaps[index]), &r, mp_bytemap_get_byte(mp, 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) { switch (cur_exp_type) { case mp_numeric_type: /* needed ? */ case mp_known_type: { int index = mp_round_unscaled(cur_exp_value_number); int found = 0; switch (p->type) { case mp_numeric_type: /* needed ? */ case mp_known_type: /* here posit */ found = mp_bytemap_has_byte_gray(mp, index, // mp_round_unscaled(mp_get_value_number(mp_get_value_node(p))) // 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 */ found = mp_bytemap_has_byte_range(mp, index, // mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))), // 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 */ found = mp_bytemap_has_byte_rgb(mp, index, mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(p)))), mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(p)))), mp_round_unscaled(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) { switch (cur_exp_type) { case mp_numeric_type: /* needed ? */ case mp_known_type: { int index = 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 = 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 = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))); // range = 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_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(p)))), mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(p)))), mp_round_unscaled(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_round_unscaled(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_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(p)))); g = mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(p)))); b = mp_round_unscaled(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 *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++ == r) && (*p++ == g) && (*p++ == 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 */ } } default: /* error */ break; } } static void mp_bytemap_bounds(MP mp, mp_node p, int c, int clip) { switch (cur_exp_type) { case mp_numeric_type: case mp_known_type: { int index = mp_round_unscaled(cur_exp_value_number); if (mp_bytemap_valid_data(mp, index)) { mp_bytemap b = mp->bytemaps[index]; int nx = b.nx; int ny = b.ny; int llx = nx - 1; int lly = ny - 1; int urx = 0; int ury = 0; unsigned char value = 0; switch (p->type) { case mp_numeric_type: /* needed ? */ case mp_known_type: // value = mp_valid_byte(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; } mp_aux_bytemap_bounds(&b, value, &llx, &lly, &urx, &ury); lly = bm_current_y(ny,lly); ury = bm_current_y(ny,ury); 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); } } default: /* error */ break; } } static void mp_bytemap_set_options(MP mp) { mp_get_x_next(mp); mp_scan_primary(mp); switch (cur_exp_type) { case mp_numeric_type: case mp_known_type: { int index = 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 = 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) { 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 = 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 */ mp_aux_set_bytemap_slice_gray( &(mp->bytemaps[index]), 0, 0, mp->bytemaps[index].nx, mp->bytemaps[index].ny, // 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 */ mp_aux_set_bytemap_slice_rgb( &(mp->bytemaps[index]), 0, 0, mp->bytemaps[index].nx, mp->bytemaps[index].ny, mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node)))), mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node)))), mp_round_unscaled(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) { if (mp->bytemaps[index].options & mp_bytemap_option_persistent) { /* don't free */ } else { if (mp->bytemaps[index].data) { mp_memory_free(mp->bytemaps[index].data); mp->memory_pool[mp_bytemap_data_pool].count -= mp->bytemaps[index].nx * mp->bytemaps[index].ny * mp->bytemaps[index].nz; mp->memory_pool[mp_bytemaps_pool].used--; } mp->bytemaps[index] = (mp_bytemap) { .data = NULL, .nx = 0, .ny = 0, .nz = 0, .ox = 0, .oy = 0, .options = 0, }; } } static void mp_aux_reset_bytemaps(MP mp) { for (int index = 0; index <= 15; index++) { if (mp_bytemap_valid_data(mp, index)) { mp_aux_reset_bytemap(mp, index); } } } static void mp_bytemap_reset(MP mp) { mp_get_x_next(mp); mp_scan_primary(mp); switch (cur_exp_type) { case mp_numeric_type: case mp_known_type: { int index = 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) { 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 = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))); int y = 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 = 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( &(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( &(mp->bytemaps[index]), x, y, mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node)))), mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node)))), mp_round_unscaled(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 = mp_round_unscaled(mp_get_value_number(mp_red_part(mp_get_value_node(cur_exp_node)))); int y = mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node)))); int z = 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 = 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 = mp_round_unscaled(mp_get_value_number(mp_cyan_part(mp_get_value_node(cur_exp_node)))); int y = mp_round_unscaled(mp_get_value_number(mp_magenta_part(mp_get_value_node(cur_exp_node)))); int dx = mp_round_unscaled(mp_get_value_number(mp_yellow_part(mp_get_value_node(cur_exp_node)))); int dy = 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 = 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: mp_aux_set_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_color_type: if (mp_rgb_color_is_known(mp_get_value_node(cur_exp_node))) { /* here posit */ mp_aux_set_bytemap_slice_rgb( &(mp->bytemaps[index]), x, y, dx, dy, mp_round_unscaled(mp_get_value_number(mp_red_part (mp_get_value_node(cur_exp_node)))), mp_round_unscaled(mp_get_value_number(mp_green_part(mp_get_value_node(cur_exp_node)))), mp_round_unscaled(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) { 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 = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))); int y = 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 = 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. */ 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. */ 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); } 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|. */ 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); } } 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); } 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. */ 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_variable_type 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 '; 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 '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_stacking_code: hlp = "Next time say 'withstacking '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_dashed_code: hlp = "Next time say 'dashed '; I'll ignore the bad 'with'\n" "clause and look for another."; break; case mp_with_uninitialized_model_code: hlp = "Next time say 'withcolor '; I'll ignore the bad 'with'\n" "clause and look for another."; break; case mp_with_rgb_model_code: hlp = "Next time say 'withrgbcolor '; I'll ignore the bad 'with'\n" "clause and look for another."; break; case mp_with_cmyk_model_code: hlp = "Next time say 'withcmykcolor '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_grey_model_code: hlp = "Next time say 'withgreyscale '; I'll ignore the bad\n" " with' clause and look for another."; break; case mp_with_linecap_code: hlp = "Next time say 'withlinecap '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_linejoin_code: hlp = "Next time say 'withlinejoin '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_miterlimit_code: hlp = "Next time say 'miterlimit '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_bytemap_code: hlp = "Next time say 'withbytemap '; I'll ignore the bad\n" "'with' clause and look for another."; break; case mp_with_curvature_code: hlp = "Next time say 'withcurvature '; I'll ignore the bad\n" "'with' clause and look for another."; break; default: hlp = "Next time say 'withpen '; 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; } 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 miterlimit = 0; int linecap = -1; int linejoin = -1; int curvature = -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) = mp_round_unscaled(cur_exp_value_number); } if (pp && spstop != NULL) { mp_stacking(spstop) = mp_round_unscaled(cur_exp_value_number); } /* free ? */ cur_exp_type = mp_vacuous_type; } 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) = mp_round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))); } if (spstop != NULL) { mp_stacking(spstop) = mp_round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node)))); } /* free ? */ cur_exp_type = mp_vacuous_type; } else { complain_invalid_with_list(mp, t); goto CONTINUE; } } break; default: { complain_invalid_with_list(mp, t); goto CONTINUE; } } break; case mp_with_linecap_code: switch (cur_exp_type) { case mp_known_type: { linecap = 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 = 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 = 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 = 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 (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; } } if (! pp && sp > MP_VOID) { mp_node q = sp->link; while (q != NULL) { mp_stacking(q) = mp_stacking(sp); 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. */ 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|. */ 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|. */ 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. */ 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); } } } } } 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); } } 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. */ 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 '"); 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; int num_dashes = 1; 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); { 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 = 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_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_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 ; } } void 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); /* hm */ *s = NULL ; *l = 0; } else { mp_back_input(mp); /* hm */ *s = (char *) cur_exp_str->str ; *l = cur_exp_str->len; } } void mp_push_numeric_value(MP mp, double n) { mp_number m; 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); } /*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. */ 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. */ 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(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); } break; } case mp_no_cycle_operation: { mp_get_x_next(mp); qq = pp; goto FINISH_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_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(pp) = mp_begin_knot; mp_knotstate(path_q) = mp_end_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; } } } } 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_if_line_field(mp->cond_ptr); 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, "tracingtitles", mp_internal_command, mp_tracing_titles_internal); mp_primitive(mp, "tracingequations", mp_internal_command, mp_tracing_equations_internal); mp_primitive(mp, "tracingcapsules", mp_internal_command, mp_tracing_capsules_internal); mp_primitive(mp, "tracingdependencies", mp_internal_command, mp_tracing_dependencies_internal); mp_primitive(mp, "tracingchoices", mp_internal_command, mp_tracing_choices_internal); mp_primitive(mp, "tracingspecs", mp_internal_command, mp_tracing_specs_internal); mp_primitive(mp, "tracingcommands", mp_internal_command, mp_tracing_commands_internal); mp_primitive(mp, "tracingrestores", mp_internal_command, mp_tracing_restores_internal); mp_primitive(mp, "tracingmacros", mp_internal_command, mp_tracing_macros_internal); mp_primitive(mp, "tracingoutput", mp_internal_command, mp_tracing_output_internal); mp_primitive(mp, "tracingstats", mp_internal_command, mp_tracing_stats_internal); mp_primitive(mp, "tracingonline", mp_internal_command, mp_tracing_online_internal); mp_primitive(mp, "year", mp_internal_command, mp_year_internal); mp_primitive(mp, "month", mp_internal_command, mp_month_internal); mp_primitive(mp, "day", mp_internal_command, mp_day_internal); mp_primitive(mp, "time", mp_internal_command, mp_time_internal); mp_primitive(mp, "hour", mp_internal_command, mp_hour_internal); mp_primitive(mp, "minute", mp_internal_command, mp_minute_internal); mp_primitive(mp, "charcode", mp_internal_command, mp_char_code_internal); mp_primitive(mp, "charwd", mp_internal_command, mp_char_wd_internal); mp_primitive(mp, "charht", mp_internal_command, mp_char_ht_internal); mp_primitive(mp, "chardp", mp_internal_command, mp_char_dp_internal); mp_primitive(mp, "charic", mp_internal_command, mp_char_ic_internal); mp_primitive(mp, "pausing", mp_internal_command, mp_pausing_internal); mp_primitive(mp, "showstopping", mp_internal_command, mp_showstopping_internal); mp_primitive(mp, "texscriptmode", mp_internal_command, mp_texscriptmode_internal); mp_primitive(mp, "overloadmode", mp_internal_command, mp_overloadmode_internal); mp_primitive(mp, "linejoin", mp_internal_command, mp_linejoin_internal); mp_primitive(mp, "linecap", mp_internal_command, mp_linecap_internal); mp_primitive(mp, "stacking", mp_internal_command, mp_stacking_internal); mp_primitive(mp, "miterlimit", mp_internal_command, mp_miterlimit_internal); mp_primitive(mp, "warningcheck", mp_internal_command, mp_warning_check_internal); mp_primitive(mp, "defaultzeroangle", mp_internal_command, mp_default_zero_angle_internal); mp_primitive(mp, "truecorners", mp_internal_command, mp_true_corners_internal); mp_primitive(mp, "defaultcolormodel", mp_internal_command, mp_default_color_model_internal); mp_primitive(mp, "restoreclipcolor", mp_internal_command, mp_restore_clip_color_internal); mp_primitive(mp, "numbersystem", mp_internal_command, mp_number_system_internal); mp_primitive(mp, "numberprecision", mp_internal_command, mp_number_precision_internal); mp_primitive(mp, "jobname", mp_internal_command, mp_job_name_internal); mp_primitive(mp, "lessdigits", mp_internal_command, mp_less_digits_internal); mp_primitive(mp, "intersectionprecision", mp_internal_command, mp_intersection_precision_internal); mp_primitive(mp, "jointolerance", mp_internal_command, mp_join_tolerance_internal); mp_primitive(mp, "..", mp_path_join_command, 0); mp_primitive(mp, "--", mp_path_connect_command, 0); mp_primitive(mp, "[", mp_left_bracket_command, 0); mp_primitive(mp, "]", mp_right_bracket_command, 0); mp_primitive(mp, "}", mp_right_brace_command, 0); mp_primitive(mp, "{", mp_left_brace_command, 0); mp_primitive(mp, ":", mp_colon_command, 0); mp_primitive(mp, ":=", mp_assignment_command, 0); mp_primitive(mp, ",", mp_comma_command, 0); mp_primitive(mp, ";", mp_semicolon_command, 0); mp_primitive(mp, "\\", mp_relax_command, 0); mp_primitive(mp, "addto", mp_add_to_command, 0); mp_primitive(mp, "atleast", mp_at_least_command, 0); mp_primitive(mp, "begingroup", mp_begin_group_command, 0); mp->bg_loc = cur_sym; mp_primitive(mp, "controls", mp_controls_command, mp_both_controls_code); mp_primitive(mp, "firstcontrol", mp_controls_command, mp_first_control_code); mp_primitive(mp, "secondcontrol", mp_controls_command, mp_second_control_code); mp_primitive(mp, "curl", mp_curl_command, 0); mp_primitive(mp, "delimiters", mp_delimiters_command, 0); mp_primitive(mp, "endgroup", mp_end_group_command, 0); mp->eg_loc = cur_sym; mp_primitive(mp, "everyjob", mp_every_job_command, 0); mp_primitive(mp, "exitif", mp_exit_test_command, 0); mp_primitive(mp, "expandafter", mp_expand_after_command, 0); mp_primitive(mp, "interim", mp_interim_command, 0); mp_primitive(mp, "let", mp_let_command, 0); mp_primitive(mp, "newinternal", mp_new_internal_command, 0); mp_primitive(mp, "of", mp_of_command, 0); mp_primitive(mp, "setbyte", mp_bytemap_command, mp_bytemap_set_byte_code); mp_primitive(mp, "setbytemapoffset", mp_bytemap_command, mp_bytemap_set_offset_code); mp_primitive(mp, "copybytemap", mp_bytemap_command, mp_bytemap_copy_code); mp_primitive(mp, "newbytemap", mp_bytemap_command, mp_bytemap_new_code); mp_primitive(mp, "setbytemap", mp_bytemap_command, mp_bytemap_set_code); mp_primitive(mp, "clipbytemap", mp_bytemap_command, mp_bytemap_clip_code); mp_primitive(mp, "reducebytemap", mp_bytemap_command, mp_bytemap_reduce_code); mp_primitive(mp, "setbytemapoptions", mp_bytemap_command, mp_bytemap_set_options_code); mp_primitive(mp, "resetbytemap", mp_bytemap_command, mp_bytemap_reset_code); mp_primitive(mp, "resetbytemaps", mp_bytemap_command, mp_bytemap_reset_all_code); mp_primitive(mp, "randomseed", mp_only_set_command, mp_random_seed_code); mp_primitive(mp, "maxknotpool", mp_only_set_command, mp_max_knot_pool_code); mp_primitive(mp, "save", mp_save_command, 0); mp_primitive(mp, "scantokens", mp_scan_tokens_command, 0); mp_primitive(mp, "runscript", mp_runscript_command, 0); mp_primitive(mp, "maketext", mp_maketext_command, 0); mp_primitive(mp, "shipout", mp_ship_out_command, 0); mp_primitive(mp, "step", mp_step_command, 0); mp_primitive(mp, "str", mp_str_command, 0); mp_primitive(mp, "void", mp_void_command, 0); mp_primitive(mp, "tension", mp_tension_command, 0); mp_primitive(mp, "to", mp_to_command, 0); mp_primitive(mp, "until", mp_until_command, 0); mp_primitive(mp, "within", mp_within_command, 0); mp_primitive(mp, "write", mp_write_command, 0); mp_primitive(mp, "btex", mp_btex_command, mp_btex_code); mp_primitive(mp, "verbatimtex", mp_btex_command, mp_verbatim_code); mp_primitive(mp, "etex", mp_etex_command, 0); mp_primitive(mp, "def", mp_macro_def_command, mp_def_code); mp_primitive(mp, "vardef", mp_macro_def_command, mp_var_def_code); mp_primitive(mp, "primarydef", mp_macro_def_command, mp_primary_def_code); mp_primitive(mp, "secondarydef", mp_macro_def_command, mp_secondary_def_code); mp_primitive(mp, "tertiarydef", mp_macro_def_command, mp_tertiary_def_code); mp_primitive(mp, "enddef", mp_macro_def_command, mp_end_def_code); mp_primitive(mp, "for", mp_iteration_command, mp_start_for_code); mp_primitive(mp, "forsuffixes", mp_iteration_command, mp_start_forsuffixes_code); mp_primitive(mp, "forever", mp_iteration_command, mp_start_forever_code); mp_primitive(mp, "endfor", mp_iteration_command, mp_end_for_code); mp_primitive(mp, "quote", mp_macro_special_command, mp_macro_quote_code); mp_primitive(mp, "#@", mp_macro_special_command, mp_macro_prefix_code); mp_primitive(mp, "@", mp_macro_special_command, mp_macro_at_code); mp_primitive(mp, "@#", mp_macro_special_command, mp_macro_suffix_code); mp_primitive(mp, "expr", mp_parameter_commmand, mp_expr_parameter); mp_primitive(mp, "suffix", mp_parameter_commmand, mp_suffix_parameter); mp_primitive(mp, "text", mp_parameter_commmand, mp_text_parameter); mp_primitive(mp, "primary", mp_parameter_commmand, mp_primary_macro); mp_primitive(mp, "secondary", mp_parameter_commmand, mp_secondary_macro); mp_primitive(mp, "tertiary", mp_parameter_commmand, mp_tertiary_macro); mp_primitive(mp, "input", mp_input_command, mp_input_code); mp_primitive(mp, "endinput", mp_input_command, mp_end_input_code); mp_primitive(mp, "if", mp_if_test_command, mp_if_code); mp_primitive(mp, "fi", mp_fi_or_else_command, mp_fi_code); mp_primitive(mp, "else", mp_fi_or_else_command, mp_else_code); mp_primitive(mp, "elseif", mp_fi_or_else_command, mp_else_if_code); mp_primitive(mp, "true", mp_nullary_command, mp_true_operation); mp_primitive(mp, "false", mp_nullary_command, mp_false_operation); mp_primitive(mp, "nullpicture", mp_nullary_command, mp_null_picture_operation); mp_primitive(mp, "nullpen", mp_nullary_command, mp_null_pen_operation); mp_primitive(mp, "readstring", mp_nullary_command, mp_read_string_operation); mp_primitive(mp, "pencircle", mp_nullary_command, mp_pen_circle_operation); mp_primitive(mp, "normaldeviate", mp_nullary_command, mp_normal_deviate_operation); mp_primitive(mp, "pathpoint", mp_nullary_command, mp_path_point_operation); mp_primitive(mp, "pathprecontrol", mp_nullary_command, mp_path_precontrol_operation); mp_primitive(mp, "pathpostcontrol", mp_nullary_command, mp_path_postcontrol_operation); mp_primitive(mp, "pathdirection", mp_nullary_command, mp_path_direction_operation); mp_primitive(mp, "pathstate", mp_nullary_command, mp_path_state_operation); mp_primitive(mp, "pathindex", mp_nullary_command, mp_path_index_operation); mp_primitive(mp, "pathlastindex", mp_nullary_command, mp_path_lastindex_operation); mp_primitive(mp, "pathlength", mp_nullary_command, mp_path_length_operation); mp_primitive(mp, "pathfirst", mp_nullary_command, mp_path_first_operation); mp_primitive(mp, "pathlast", mp_nullary_command, mp_path_last_operation); mp_primitive(mp, "mpversion", mp_nullary_command, mp_version_operation); mp_primitive(mp, "readfrom", mp_unary_command, mp_read_from_operation); mp_primitive(mp, "closefrom", mp_unary_command, mp_close_from_operation); mp_primitive(mp, "odd", mp_unary_command, mp_odd_operation); mp_primitive(mp, "known", mp_unary_command, mp_known_operation); mp_primitive(mp, "unknown", mp_unary_command, mp_unknown_operation); mp_primitive(mp, "not", mp_unary_command, mp_not_operation); mp_primitive(mp, "decimal", mp_unary_command, mp_decimal_operation); mp_primitive(mp, "reverse", mp_unary_command, mp_reverse_operation); mp_primitive(mp, "uncycle", mp_unary_command, mp_uncycle_operation); mp_primitive(mp, "makepath", mp_unary_command, mp_make_path_operation); mp_primitive(mp, "makepen", mp_unary_command, mp_make_pen_operation); mp_primitive(mp, "makenep", mp_unary_command, mp_make_nep_operation); mp_primitive(mp, "convexed", mp_unary_command, mp_convexed_operation); mp_primitive(mp, "uncontrolled", mp_unary_command, mp_uncontrolled_operation); mp_primitive(mp, "oct", mp_unary_command, mp_oct_operation); mp_primitive(mp, "hex", mp_unary_command, mp_hex_operation); mp_primitive(mp, "ASCII", mp_unary_command, mp_ASCII_operation); mp_primitive(mp, "char", mp_unary_command, mp_char_operation); mp_primitive(mp, "segments", mp_unary_command, mp_segments_operation); mp_primitive(mp, "length", mp_unary_command, mp_length_operation); mp_primitive(mp, "nolength", mp_unary_command, mp_no_length_operation); mp_primitive(mp, "turningnumber", mp_unary_command, mp_turning_operation); mp_primitive(mp, "xpart", mp_unary_command, mp_x_part_operation); mp_primitive(mp, "ypart", mp_unary_command, mp_y_part_operation); mp_primitive(mp, "xxpart", mp_unary_command, mp_xx_part_operation); mp_primitive(mp, "xypart", mp_unary_command, mp_xy_part_operation); mp_primitive(mp, "yxpart", mp_unary_command, mp_yx_part_operation); mp_primitive(mp, "yypart", mp_unary_command, mp_yy_part_operation); mp_primitive(mp, "redpart", mp_unary_command, mp_red_part_operation); mp_primitive(mp, "greenpart", mp_unary_command, mp_green_part_operation); mp_primitive(mp, "bluepart", mp_unary_command, mp_blue_part_operation); mp_primitive(mp, "cyanpart", mp_unary_command, mp_cyan_part_operation); mp_primitive(mp, "magentapart", mp_unary_command, mp_magenta_part_operation); mp_primitive(mp, "yellowpart", mp_unary_command, mp_yellow_part_operation); mp_primitive(mp, "blackpart", mp_unary_command, mp_black_part_operation); mp_primitive(mp, "greypart", mp_unary_command, mp_grey_part_operation); mp_primitive(mp, "colormodel", mp_unary_command, mp_color_model_operation); mp_primitive(mp, "prescriptpart", mp_unary_command, mp_prescript_part_operation); mp_primitive(mp, "postscriptpart", mp_unary_command, mp_postscript_part_operation); mp_primitive(mp, "stackingpart", mp_unary_command, mp_stacking_part_operation); mp_primitive(mp, "pathpart", mp_unary_command, mp_path_part_operation); mp_primitive(mp, "penpart", mp_unary_command, mp_pen_part_operation); mp_primitive(mp, "dashpart", mp_unary_command, mp_dash_part_operation); mp_primitive(mp, "sqrt", mp_unary_command, mp_sqrt_operation); mp_primitive(mp, "knownnorm", mp_unary_command, mp_norm_operation); mp_primitive(mp, "mexp", mp_unary_command, mp_m_exp_operation); mp_primitive(mp, "mlog", mp_unary_command, mp_m_log_operation); mp_primitive(mp, "sind", mp_unary_command, mp_sin_d_operation); mp_primitive(mp, "cosd", mp_unary_command, mp_cos_d_operation); mp_primitive(mp, "floor", mp_unary_command, mp_floor_operation); mp_primitive(mp, "uniformdeviate", mp_unary_command, mp_uniform_deviate_operation); mp_primitive(mp, "llcorner", mp_unary_command, mp_ll_corner_operation); mp_primitive(mp, "lrcorner", mp_unary_command, mp_lr_corner_operation); mp_primitive(mp, "ulcorner", mp_unary_command, mp_ul_corner_operation); mp_primitive(mp, "urcorner", mp_unary_command, mp_ur_corner_operation); mp_primitive(mp, "corners", mp_unary_command, mp_corners_operation); mp_primitive(mp, "centerof", mp_unary_command, mp_center_of_operation); mp_primitive(mp, "centerofmass", mp_unary_command, mp_center_of_mass_operation); mp_primitive(mp, "xrange", mp_unary_command, mp_x_range_operation); mp_primitive(mp, "yrange", mp_unary_command, mp_y_range_operation); mp_primitive(mp, "deltapoint", mp_unary_command, mp_delta_point_operation); mp_primitive(mp, "deltaprecontrol", mp_unary_command, mp_delta_precontrol_operation); mp_primitive(mp, "deltapostcontrol", mp_unary_command, mp_delta_postcontrol_operation); mp_primitive(mp, "deltadirection", mp_unary_command, mp_delta_direction_operation); mp_primitive(mp, "arclength", mp_unary_command, mp_arc_length_operation); mp_primitive(mp, "angle", mp_unary_command, mp_angle_operation); mp_primitive(mp, "stroked", mp_unary_command, mp_stroked_operation); mp_primitive(mp, "filled", mp_unary_command, mp_filled_operation); mp_primitive(mp, "clipped", mp_unary_command, mp_clipped_operation); mp_primitive(mp, "grouped", mp_unary_command, mp_grouped_operation); mp_primitive(mp, "bounded", mp_unary_command, mp_bounded_operation); mp_primitive(mp, "cycle", mp_cycle_command, mp_cycle_operation); mp_primitive(mp, "nocycle", mp_cycle_command, mp_no_cycle_operation); mp_primitive(mp, "xrelative", mp_cycle_command, mp_x_relative_operation); mp_primitive(mp, "yrelative", mp_cycle_command, mp_y_relative_operation); mp_primitive(mp, "xyrelative", mp_cycle_command, mp_xy_relative_operation); mp_primitive(mp, "xabsolute", mp_cycle_command, mp_x_absolute_operation); mp_primitive(mp, "yabsolute", mp_cycle_command, mp_y_absolute_operation); mp_primitive(mp, "xyabsolute", mp_cycle_command, mp_xy_absolute_operation); mp_primitive(mp, "+", mp_plus_or_minus_command, mp_plus_operation); mp_primitive(mp, "-", mp_plus_or_minus_command, mp_minus_operation); mp_primitive(mp, "/", mp_slash_command, mp_over_operation); mp_primitive(mp, "*", mp_secondary_binary_command, mp_times_operation); mp_primitive(mp, "^", mp_secondary_binary_command, mp_power_operation); mp_primitive(mp, "++", mp_tertiary_binary_command, mp_pythag_add_operation); mp_primitive(mp, "+-+", mp_tertiary_binary_command, mp_pythag_sub_operation); mp_primitive(mp, "or", mp_tertiary_binary_command, mp_or_operation); mp_primitive(mp, "knowndotprod", mp_tertiary_binary_command, mp_dotprod_operation); mp_primitive(mp, "knowncrossprod", mp_tertiary_binary_command, mp_crossprod_operation); mp_primitive(mp, "knowndiv", mp_tertiary_binary_command, mp_div_operation); mp_primitive(mp, "knownmod", mp_tertiary_binary_command, mp_mod_operation); mp_primitive(mp, "and", mp_and_command, mp_and_operation); mp_primitive(mp, "<", mp_primary_binary_command, mp_less_than_operation); mp_primitive(mp, "<=", mp_primary_binary_command, mp_less_or_equal_operation); mp_primitive(mp, ">", mp_primary_binary_command, mp_greater_than_operation); mp_primitive(mp, ">=", mp_primary_binary_command, mp_greater_or_equal_operation); mp_primitive(mp, "<>", mp_primary_binary_command, mp_unequal_operation); mp_primitive(mp, "=", mp_equals_command, mp_equal_operation); mp_primitive(mp, "substring", mp_of_binary_command, mp_substring_operation); mp_primitive(mp, "subpath", mp_of_binary_command, mp_subpath_operation); mp_primitive(mp, "segment", mp_of_binary_command, mp_segment_operation); mp_primitive(mp, "directiontime", mp_of_binary_command, mp_direction_time_operation); mp_primitive(mp, "point", mp_of_binary_command, mp_point_operation); mp_primitive(mp, "precontrol", mp_of_binary_command, mp_precontrol_operation); mp_primitive(mp, "postcontrol", mp_of_binary_command, mp_postcontrol_operation); mp_primitive(mp, "direction", mp_of_binary_command, mp_direction_operation); mp_primitive(mp, "penoffset", mp_of_binary_command, mp_pen_offset_operation); mp_primitive(mp, "arctime", mp_of_binary_command, mp_arc_time_operation); mp_primitive(mp, "arcpoint", mp_of_binary_command, mp_arc_point_operation); mp_primitive(mp, "arcpointlist", mp_of_binary_command, mp_arc_point_list_operation); mp_primitive(mp, "subarclength", mp_of_binary_command, mp_subarc_length_operation); mp_primitive(mp, "bytevalue", mp_of_binary_command, mp_bytemap_value_operation); mp_primitive(mp, "bytefound", mp_of_binary_command, mp_bytemap_found_operation); mp_primitive(mp, "bytepath", mp_of_binary_command, mp_bytemap_path_operation); mp_primitive(mp, "bytemapbounds", mp_of_binary_command, mp_bytemap_bounds_operation); mp_primitive(mp, "&", mp_ampersand_command, mp_concat_operation); mp_primitive(mp, "&&", mp_ampersand_command, mp_just_append_operation); mp_primitive(mp, "&&&", mp_ampersand_command, mp_tolerant_concat_operation); mp_primitive(mp, "&&&&", mp_ampersand_command, mp_tolerant_append_operation); mp_primitive(mp, "rotated", mp_secondary_binary_command, mp_rotated_operation); mp_primitive(mp, "slanted", mp_secondary_binary_command, mp_slanted_operation); mp_primitive(mp, "scaled", mp_secondary_binary_command, mp_scaled_operation); mp_primitive(mp, "shifted", mp_secondary_binary_command, mp_shifted_operation); mp_primitive(mp, "transformed", mp_secondary_binary_command, mp_transformed_operation); mp_primitive(mp, "xscaled", mp_secondary_binary_command, mp_x_scaled_operation); mp_primitive(mp, "yscaled", mp_secondary_binary_command, mp_y_scaled_operation); mp_primitive(mp, "zscaled", mp_secondary_binary_command, mp_z_scaled_operation); mp_primitive(mp, "xyscaled", mp_secondary_binary_command, mp_xy_scaled_operation); mp_primitive(mp, "intersectiontimes", mp_tertiary_binary_command, mp_intertimes_operation); mp_primitive(mp, "intersectiontimeslist", mp_tertiary_binary_command, mp_intertimes_list_operation); mp_primitive(mp, "envelope", mp_of_binary_command, mp_envelope_operation); mp_primitive(mp, "boundingpath", mp_of_binary_command, mp_boundingpath_operation); mp_primitive(mp, "numeric", mp_type_name_command, mp_numeric_type_operation); mp_primitive(mp, "string", mp_type_name_command, mp_string_type_operation); mp_primitive(mp, "boolean", mp_type_name_command, mp_boolean_type_operation); mp_primitive(mp, "path", mp_type_name_command, mp_path_type_operation); mp_primitive(mp, "pen", mp_type_name_command, mp_pen_type_operation); mp_primitive(mp, "nep", mp_type_name_command, mp_nep_type_operation); mp_primitive(mp, "picture", mp_type_name_command, mp_picture_type_operation); mp_primitive(mp, "transform", mp_type_name_command, mp_transform_type_operation); mp_primitive(mp, "color", mp_type_name_command, mp_color_type_operation); mp_primitive(mp, "rgbcolor", mp_type_name_command, mp_color_type_operation); mp_primitive(mp, "cmykcolor", mp_type_name_command, mp_cmykcolor_type_operation); mp_primitive(mp, "pair", mp_type_name_command, mp_pair_type_operation); mp_primitive(mp, "end", mp_stop_command, mp_end_code); mp_primitive(mp, "dump", mp_stop_command, mp_dump_code); mp_primitive(mp, "batchmode", mp_mode_command, mp_batch_mode); mp_primitive(mp, "nonstopmode", mp_mode_command, mp_nonstop_mode); mp_primitive(mp, "scrollmode", mp_mode_command, mp_scroll_mode); mp_primitive(mp, "errorstopmode", mp_mode_command, mp_error_stop_mode); mp_primitive(mp, "silentmode", mp_mode_command, mp_silent_mode); mp_primitive(mp, "inner", mp_protection_command, mp_inner_protection_code); mp_primitive(mp, "outer", mp_protection_command, mp_outer_protection_code); mp_primitive(mp, "setproperty", mp_property_command, 1); mp_primitive(mp, "showtoken", mp_show_command, mp_show_token_code); mp_primitive(mp, "showstats", mp_show_command, mp_show_stats_code); mp_primitive(mp, "show", mp_show_command, mp_show_code); mp_primitive(mp, "showvariable", mp_show_command, mp_show_var_code); mp_primitive(mp, "showdependencies", mp_show_command, mp_show_dependencies_code); mp_primitive(mp, "doublepath", mp_thing_to_add_command, mp_add_double_path_code); mp_primitive(mp, "contour", mp_thing_to_add_command, mp_add_contour_code); mp_primitive(mp, "also", mp_thing_to_add_command, mp_add_also_code); mp_primitive(mp, "withpen", mp_with_option_command, mp_with_pen_code); mp_primitive(mp, "dashed", mp_with_option_command, mp_with_dashed_code); mp_primitive(mp, "withprescript", mp_with_option_command, mp_with_pre_script_code); mp_primitive(mp, "withpostscript", mp_with_option_command, mp_with_post_script_code); mp_primitive(mp, "withnestedprescript", mp_with_option_command, mp_with_nested_pre_script_code); mp_primitive(mp, "withnestedpostscript", mp_with_option_command, mp_with_nested_post_script_code); mp_primitive(mp, "withstacking", mp_with_option_command, mp_with_stacking_code); mp_primitive(mp, "withlinecap", mp_with_option_command, mp_with_linecap_code); mp_primitive(mp, "withlinejoin", mp_with_option_command, mp_with_linejoin_code); mp_primitive(mp, "withmiterlimit", mp_with_option_command, mp_with_miterlimit_code); mp_primitive(mp, "withoutcolor", mp_with_option_command, mp_with_no_model_code); mp_primitive(mp, "withgreyscale", mp_with_option_command, mp_with_grey_model_code); mp_primitive(mp, "withcolor", mp_with_option_command, mp_with_uninitialized_model_code); mp_primitive(mp, "withrgbcolor", mp_with_option_command, mp_with_rgb_model_code); mp_primitive(mp, "withcmykcolor", mp_with_option_command, mp_with_cmyk_model_code); mp_primitive(mp, "withcurvature", mp_with_option_command, mp_with_curvature_code); mp_primitive(mp, "withbytemap", mp_with_option_command, mp_with_bytemap_code); mp_primitive(mp, "withnothing", mp_with_option_command, mp_with_nothing_code); mp_primitive(mp, "clip", mp_bounds_command, mp_start_clip_node_type); mp_primitive(mp, "setgroup", mp_bounds_command, mp_start_group_node_type); mp_primitive(mp, "setbounds", mp_bounds_command, mp_start_bounds_node_type); mp_primitive(mp, "message", mp_message_command, mp_normal_message_code); mp_primitive(mp, "errmessage", mp_message_command, mp_error_message_code); mp_primitive(mp, "errhelp", mp_message_command, mp_error_help_code); 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); } 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(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")); /*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) { MP mp = mp_new_instance(); if (! mp) { return NULL; } if (! opt->job_name || ! *(opt->job_name)) { 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_decimal_mode: mp->math = mp_initialize_decimal_math(mp); break; case mp_math_binary_mode: mp->math = mp_initialize_binary_math(mp); break; case mp_math_posit_mode: mp->math = mp_initialize_posit_math(mp); break; default: mp->math = mp_initialize_double_math(mp); break; } 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(mp_bytemap) }; 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); } mp_bytemap_valid(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) (bistack_size + 1) * sizeof(mp_number)); for (int i=0; ibisect_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) { case mp_math_scaled_mode: set_internal_string(mp_number_system_internal, mp_intern(mp, "scaled")); 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; 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; }