diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/l3kernel/l3fp-parse.dtx |
Initial commit
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3fp-parse.dtx')
-rw-r--r-- | macros/latex/contrib/l3kernel/l3fp-parse.dtx | 2908 |
1 files changed, 2908 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3kernel/l3fp-parse.dtx b/macros/latex/contrib/l3kernel/l3fp-parse.dtx new file mode 100644 index 0000000000..b3d7b32bc8 --- /dev/null +++ b/macros/latex/contrib/l3kernel/l3fp-parse.dtx @@ -0,0 +1,2908 @@ +% \iffalse meta-comment +% +%% File: l3fp-parse.dtx +% +% Copyright (C) 2011-2019 The LaTeX3 Project +% +% It may be distributed and/or modified under the conditions of the +% LaTeX Project Public License (LPPL), either version 1.3c of this +% license or (at your option) any later version. The latest version +% of this license is in the file +% +% https://www.latex-project.org/lppl.txt +% +% This file is part of the "l3kernel bundle" (The Work in LPPL) +% and all files in that bundle must be distributed together. +% +% ----------------------------------------------------------------------- +% +% The development version of the bundle can be found at +% +% https://github.com/latex3/latex3 +% +% for those people who are interested. +% +%<*driver> +\documentclass[full,kernel]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +%</driver> +% \fi +% +% \title{The \textsf{l3fp-parse} package\\ +% Floating point expression parsing} +% \author{^^A +% The \LaTeX3 Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% \date{Released 2019-08-25} +% +% \maketitle +% +% \begin{documentation} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3fp-parse} implementation} +% +% \begin{macrocode} +%<*initex|package> +% \end{macrocode} +% +% \begin{macrocode} +%<@@=fp> +% \end{macrocode} +% +% \subsection{Work plan} +% +% The task at hand is non-trivial, and some previous failed attempts +% show that the code leads to unreadable logs, so we had better get it +% (almost) right the first time. Let us first describe our goal, then +% discuss the design precisely before writing any code. +% +% In this file at least, a \meta{floating point object} is a floating +% point number or tuple. This can be extended to anything that starts +% with \cs{s_@@} or \cs{s_@@_\meta{type}} and ends with |;| with some +% internal structure that depends on the \meta{type}. +% +% \begin{macro}[EXP]{\@@_parse:n} +% \begin{syntax} +% \cs{@@_parse:n} \Arg{fpexpr} +% \end{syntax} +% Evaluates the \meta{floating point expression} and leaves the result +% in the input stream as a floating point object. This +% function forms the basis of almost all public \pkg{l3fp} functions. +% During evaluation, each token is fully \texttt{f}-expanded. +% +% \cs{@@_parse_o:n} does the same but expands once after its result. +% \begin{texnote} +% Registers (integers, toks, etc.) are automatically unpacked, +% without requiring a function such as \cs{int_use:N}. Invalid +% tokens remaining after \texttt{f}-expansion lead to +% unrecoverable low-level \TeX{} errors. +% \end{texnote} +% \end{macro} +% +% \begin{variable} +% { +% \c_@@_prec_func_int, +% \c_@@_prec_hatii_int, +% \c_@@_prec_hat_int, +% \c_@@_prec_not_int, +% \c_@@_prec_juxt_int, +% \c_@@_prec_times_int, +% \c_@@_prec_plus_int, +% \c_@@_prec_comp_int, +% \c_@@_prec_and_int, +% \c_@@_prec_or_int, +% \c_@@_prec_quest_int, +% \c_@@_prec_colon_int, +% \c_@@_prec_comma_int, +% \c_@@_prec_tuple_int, +% \c_@@_prec_end_int, +% } +% Floating point expressions are composed of numbers, given in various +% forms, infix operators, such as |+|, |**|, or~|,| (which joins two +% numbers into a list), and prefix operators, such as the unary~|-|, +% functions, or opening parentheses. Here is a list of precedences +% which control the order of evaluation (some distinctions are +% irrelevant for the order of evaluation, but serve as signals), from +% the tightest binding to the loosest binding. +% \begin{itemize} +% \item[16] Function calls. +% \item[13/14] Binary |**| and~|^| (right to left). +% \item[12] Unary |+|, |-|, |!| (right to left). +% \item[11] Juxtaposition (implicit~|*|) with no parenthesis. +% \item[10] Binary |*| and~|/|. +% \item[9] Binary |+| and~|-|. +% \item[7] Comparisons. +% \item[6] Logical \texttt{and}, denoted by~|&&|. +% \item[5] Logical \texttt{or}, denoted by~\verb*+||+. +% \item[4] Ternary operator |?:|, piece~|?|. +% \item[3] Ternary operator |?:|, piece~|:|. +% \item[2] Commas. +% \item[1] Place where a comma is allowed and generates a tuple. +% \item[0] Start and end of the expression. +% \end{itemize} +% \begin{macrocode} +\int_const:Nn \c_@@_prec_func_int { 16 } +\int_const:Nn \c_@@_prec_hatii_int { 14 } +\int_const:Nn \c_@@_prec_hat_int { 13 } +\int_const:Nn \c_@@_prec_not_int { 12 } +\int_const:Nn \c_@@_prec_juxt_int { 11 } +\int_const:Nn \c_@@_prec_times_int { 10 } +\int_const:Nn \c_@@_prec_plus_int { 9 } +\int_const:Nn \c_@@_prec_comp_int { 7 } +\int_const:Nn \c_@@_prec_and_int { 6 } +\int_const:Nn \c_@@_prec_or_int { 5 } +\int_const:Nn \c_@@_prec_quest_int { 4 } +\int_const:Nn \c_@@_prec_colon_int { 3 } +\int_const:Nn \c_@@_prec_comma_int { 2 } +\int_const:Nn \c_@@_prec_tuple_int { 1 } +\int_const:Nn \c_@@_prec_end_int { 0 } +% \end{macrocode} +% \end{variable} +% +% \subsubsection{Storing results} +% +% The main question in parsing expressions expandably is to decide where +% to put the intermediate results computed for various subexpressions. +% +% One option is to store the values at the start of the expression, and +% carry them together as the first argument of each macro. However, we +% want to \texttt{f}-expand tokens one by one in the expression (as +% \cs{int_eval:n} does), and with this approach, expanding the next +% unread token forces us to jump with \cs{exp_after:wN} over every value +% computed earlier in the expression. With this approach, the run-time +% grows at least quadratically in the length of the expression, if +% not as its cube (inserting the \cs{exp_after:wN} is tricky and slow). +% +% A second option is to place those values at the end of the expression. +% Then expanding the next unread token is straightforward, but this +% still hits a performance issue: for long expressions we would be +% reaching all the way to the end of the expression at every step of the +% calculation. The run-time is again quadratic. +% +% A variation of the above attempts to place the intermediate results +% which appear when computing a parenthesized expression near the +% closing parenthesis. This still lets us expand tokens as we go, and +% avoids performance problems as long as there are enough parentheses. +% However, it would be better to avoid requiring the closing +% parenthesis to be present as soon as the corresponding opening +% parenthesis is read: the closing parenthesis may still be hidden in a +% macro yet to be expanded. +% +% Hence, we need to go for some fine expansion control: the result is +% stored \emph{before} the start! +% +% Let us illustrate this idea in a simple model: adding positive +% integers which may be resulting from the expansion of macros, or may +% be values of registers. Assume that one number, say, $12345$, has +% already been found, and that we want to parse the next number. The +% current status of the code may look as follows. +% \begin{syntax} +% \cs{exp_after:wN} |\add:ww| \cs{int_value:w} 12345 \cs{exp_after:wN} ; +% \cs{exp:w} |\operand:w| \meta{stuff} +% \end{syntax} +% One step of expansion expands \cs{exp_after:wN}, which triggers the +% primitive \cs{int_value:w}, which reads the five digits we have +% already found, |12345|. This integer is unfinished, causing the +% second \cs{exp_after:wN} to expand, and to trigger the construction +% \cs{exp:w}, which expands |\operand:w|, defined to read +% what follows and make a number out of it, then leave \cs{exp_end:}, the +% number, and a semicolon in the input stream. Once |\operand:w| is +% done expanding, we obtain essentially +% \begin{syntax} +% \cs{exp_after:wN} |\add:ww| \cs{int_value:w} 12345 ; +% \cs{exp:w} \cs{exp_end:} 333444 ; +% \end{syntax} +% where in fact \cs{exp_after:wN} has already been expanded, +% \cs{int_value:w} has already seen |12345|, and +% \cs{exp:w} is still looking for a number. It finds +% \cs{exp_end:}, hence expands to nothing. Now, \cs{int_value:w} sees +% the \texttt{;}, which cannot be part of a number. The expansion +% stops, and we are left with +% \begin{syntax} +% |\add:ww| 12345 ; 333444 ; +% \end{syntax} +% which can safely perform the addition by grabbing two arguments +% delimited by~|;|. +% +% If we were to continue parsing the expression, then the following +% number should also be cleaned up before the next use of a binary +% operation such as |\add:ww|. Just like \cs{int_value:w} |12345| +% \cs{exp_after:wN}~|;| expanded what follows once, we need |\add:ww| +% to do the calculation, and in the process to expand the following +% once. This is also true in our real application: all the functions of +% the form \cs[no-index]{@@_\ldots_o:ww} expand what follows once. This comes at the +% cost of leaving tokens in the input stack, and we need to be +% careful not to waste this memory. All of our discussion above is nice +% but simplistic, as operations should not simply be performed in the +% order they appear. +% +% \subsubsection{Precedence and infix operators} +% +% The various operators we will encounter have different precedences, +% which influence the order of calculations: $1+2\times 3 = 1+(2\times +% 3)$ because $\times$~has a higher precedence than~$+$. The true +% analog of our macro |\operand:w| must thus take care of that. When +% looking for an operand, it needs to perform calculations until +% reaching an operator which has lower precedence than the one which +% called |\operand:w|. This means that |\operand:w| must know what the +% previous binary operator is, or rather, its precedence: we thus rename +% it |\operand:Nw|. Let us describe as an example how we plan to do +% the calculation |41-2^3*4+5|. More precisely we describe how to +% perform the first operation in this expression. Here, we abuse +% notations: the first argument of |\operand:Nw| should be an integer +% constant (\cs{c_@@_prec_plus_int}, \ldots{}) equal to the precedence +% of the given operator, not directly the operator itself. +% \begin{itemize} +% \item Clean up~|41| and find~|-|. We call |\operand:Nw|~|-| to find +% the second operand. +% \item Clean up~|2| and find~|^|. +% \item Compare the precedences of |-| and~|^|. Since the latter is +% higher, we need to compute the exponentiation. For this, find the +% second operand with a nested call to |\operand:Nw|~|^|. +% \item Clean up~|3| and find~|*|. +% \item Compare the precedences of |^| and~|*|. Since the former is +% higher, |\operand:Nw|~|^| has found the second operand of the +% exponentiation, which is computed: $2^{3} = 8$. +% \item We now have |41-8*4+5|, and |\operand:Nw|~|-| is still +% looking for a second operand for the subtraction. Is it~$8$? +% \item Compare the precedences of |-| and~|*|. Since the latter is +% higher, we are not done with~$8$. Call |\operand:Nw|~|*| to find +% the second operand of the multiplication. +% \item Clean up~|4|, and find~|+|. +% \item Compare the precedences of |*| and~|+|. Since the former is +% higher, |\operand:Nw|~|*| has found the second operand of the +% multiplication, which is computed: $8*4 = 32$. +% \item We now have |41-32+5|, and |\operand:Nw|~|-| is still looking +% for a second operand for the subtraction. Is it~$32$? +% \item Compare the precedences of |-| and~|+|. Since they are equal, +% |\operand:Nw|~|-| has found the second operand for the +% subtraction, which is computed: $41-32=9$. +% \item We now have |9+5|. +% \end{itemize} +% The procedure above stops short of performing all computations, but +% adding a surrounding call to |\operand:Nw| with a very low precedence +% ensures that all computations are performed before |\operand:Nw| +% is done. Adding a trailing marker with the same very low precedence +% prevents the surrounding |\operand:Nw| from going beyond the marker. +% +% The pattern above to find an operand for a given operator, is to find +% one number and the next operator, then compare precedences to know if +% the next computation should be done. If it should, then perform it +% after finding its second operand, and look at the next operator, then +% compare precedences to know if the next computation should be done. +% This continues until we find that the next computation should not be +% done. Then, we stop. +% +% We are now ready to get a bit more technical and describe which of the +% \pkg{l3fp-parse} functions correspond to each step above. +% +% First, \cs{@@_parse_operand:Nw} is the |\operand:Nw| function above, +% with small modifications due to expansion issues discussed later. We +% denote by \meta{precedence} the argument of \cs{@@_parse_operand:Nw}, +% that is, the precedence of the binary operator whose operand we are +% trying to find. The basic action is to read numbers from the input +% stream. This is done by \cs{@@_parse_one:Nw}. A first approximation +% of this function is that it reads one \meta{number}, performing no +% computation, and finds the following binary \meta{operator}. Then it +% expands to +% \begin{quote} +% \meta{number}\\ +% | \__fp_parse_infix_|\meta{operator}|:N| \meta{precedence} +% \end{quote} +% expanding the \texttt{infix} auxiliary before leaving the above in the +% input stream. +% +% We now explain the \texttt{infix} auxiliaries. We need some +% flexibility in how we treat the case of equal precedences: most often, +% the first operation encountered should be performed, such as |1-2-3| +% being computed as |(1-2)-3|, but |2^3^4| should be evaluated as +% |2^(3^4)| instead. For this reason, and to support the equivalence +% between |**| and~|^| more easily, each binary operator is converted to +% a control sequence |\__fp_parse_infix_|\meta{operator}|:N| when it is +% encountered for the first time. Instead of passing both precedences +% to a test function to do the comparison steps above, we pass the +% \meta{precedence} (of the earlier operator) to the \texttt{infix} +% auxiliary for the following \meta{operator}, to know whether to +% perform the computation of the \meta{operator}. If it should not be +% performed, the \texttt{infix} auxiliary expands to +% \begin{syntax} +% |@| \cs{use_none:n} |\__fp_parse_infix_|\meta{operator}|:N| +% \end{syntax} +% and otherwise it calls \cs{@@_parse_operand:Nw} with the precedence of +% the \meta{operator} to find its second operand \meta{number_2} and the +% next \meta{operator_2}, and expands to +% \begin{syntax} +% |@| \cs{@@_parse_apply_binary:NwNwN} +% ~~~~\meta{operator} \meta{number_2} +% |@| |\__fp_parse_infix_|\meta{operator_2}|:N| +% \end{syntax} +% The \texttt{infix} function is responsible for comparing precedences, +% but cannot directly call the computation functions, because the first +% operand \meta{number} is before the \texttt{infix} function in the +% input stream. This is why we stop the expansion here and give control +% to another function to close the loop. +% +% A definition of \cs{@@_parse_operand:Nw} \meta{precedence} with some +% of the expansion control removed is +% \begin{syntax} +% \cs{exp_after:wN} \cs{@@_parse_continue:NwN} +% \cs{exp_after:wN} \meta{precedence} +% \cs{exp:w} \cs{exp_end_continue_f:w} +% ~~\cs{@@_parse_one:Nw} \meta{precedence} +% \end{syntax} +% This expands \cs{@@_parse_one:Nw} \meta{precedence} completely, which +% finds a number, wraps the next \meta{operator} into an \texttt{infix} +% function, feeds this function the \meta{precedence}, and expands it, +% yielding either +% \begin{syntax} +% \cs{@@_parse_continue:NwN} \meta{precedence} +% \meta{number} |@| +% \cs{use_none:n} |\__fp_parse_infix_|\meta{operator}|:N| +% \end{syntax} +% or +% \begin{syntax} +% \cs{@@_parse_continue:NwN} \meta{precedence} +% \meta{number} |@| +% \cs{@@_parse_apply_binary:NwNwN} +% ~~\meta{operator} \meta{number_2} +% |@| |\__fp_parse_infix_|\meta{operator_2}|:N| +% \end{syntax} +% The definition of \cs{@@_parse_continue:NwN} is then very simple: +% \begin{syntax} +% |\cs_new:Npn \__fp_parse_continue:NwN #1#2@#3 { #3 #1 #2 @ }| +% \end{syntax} +% In the first case, |#3|~is \cs{use_none:n}, yielding +% \begin{syntax} +% \cs{use_none:n} \meta{precedence} \meta{number} |@| +% |\__fp_parse_infix_|\meta{operator}|:N| +% \end{syntax} +% then \meta{number} |@| |\__fp_parse_infix_|\meta{operator}|:N|. In +% the second case, |#3|~is \cs{@@_parse_apply_binary:NwNwN}, whose role +% is to compute \meta{number} \meta{operator} \meta{number_2} and to +% prepare for the next comparison of precedences: first we get +% \begin{syntax} +% \cs{@@_parse_apply_binary:NwNwN} +% ~~\meta{precedence} \meta{number} |@| +% ~~\meta{operator} \meta{number_2} +% |@| |\__fp_parse_infix_|\meta{operator_2}|:N| +% \end{syntax} +% then +% \begin{syntax} +% \cs{exp_after:wN} \cs{@@_parse_continue:NwN} +% \cs{exp_after:wN} \meta{precedence} +% \cs{exp:w} \cs{exp_end_continue_f:w} +% |\__fp_|\meta{operator}|_o:ww| \meta{number} \meta{number_2} +% \cs{exp:w} \cs{exp_end_continue_f:w} +% |\__fp_parse_infix_|\meta{operator_2}|:N| \meta{precedence} +% \end{syntax} +% where |\__fp_|\meta{operator}|_o:ww| computes \meta{number} +% \meta{operator} \meta{number_2} and expands after the result, thus +% triggers the comparison of the precedence of the \meta{operator_2} and +% the \meta{precedence}, continuing the loop. +% +% We have introduced the most important functions here, and the next few +% paragraphs we describe various subtleties. +% +% \subsubsection{Prefix operators, parentheses, and functions} +% +% Prefix operators (unary |-|, |+|,~|!|) and parentheses are taken care +% of by the same mechanism, and functions (\texttt{sin}, \texttt{exp}, +% etc.) as well. Finding the argument of the unary~|-|, for instance, +% is very similar to grabbing the second operand of a binary infix +% operator, with a subtle precedence explained below. Once that operand +% is found, the operator can be applied to it (for the unary~|-|, this +% simply flips the sign). A left parenthesis is just a prefix operator +% with a very low precedence equal to that of the closing parenthesis +% (which is treated as an infix operator, since it normally appears just +% after numbers), so that all computations are performed until the +% closing parenthesis. The prefix operator associated to the left +% parenthesis does not alter its argument, but it removes the closing +% parenthesis (with some checks). +% +% Prefix operators are the reason why we only summarily described the +% function \cs{@@_parse_one:Nw} earlier. This function is responsible +% for reading in the input stream the first possible \meta{number} and +% the next infix \meta{operator}. If what follows \cs{@@_parse_one:Nw} +% \meta{precedence} is a prefix operator, then we must find the operand +% of this prefix operator through a nested call to +% \cs{@@_parse_operand:Nw} with the appropriate precedence, then apply +% the operator to the operand found to yield the result of +% \cs{@@_parse_one:Nw}. So far, all is simple. +% +% The unary operators |+|, |-|,~|!| complicate things a little bit: +% |-3**2| should be $-(3^2)=-9$, and not $(-3)^2=9$. This would easily +% be done by giving~|-| a lower precedence, equal to that of the infix +% |+| and~|-|. Unfortunately, this fails in cases such as |3**-2*4|, +% yielding $3^{-2\times 4}$ instead of the correct $3^{-2}\times 4$. A +% second attempt would be to call \cs{@@_parse_operand:Nw} with the +% \meta{precedence} of the previous operator, but |0>-2+3| is then +% parsed as |0>-(2+3)|: the addition is performed because it binds more +% tightly than the comparision which precedes~|-|. The correct approach +% is for a unary~|-| to perform operations whose precedence is greater +% than both that of the previous operation, and that of the unary~|-| +% itself. The unary~|-| is given a precedence higher than +% multiplication and division. This does not lead to any surprising +% result, since $-(x/y) = (-x)/y$ and similarly for multiplication, and +% it reduces the number of nested calls to \cs{@@_parse_operand:Nw}. +% +% Functions are implemented as prefix operators with very high +% precedence, so that their argument is the first number that can +% possibly be built. +% +% Note that contrarily to the \texttt{infix} functions discussed +% earlier, the \texttt{prefix} functions do perform tests on the +% previous \meta{precedence} to decide whether to find an argument or +% not, since we know that we need a number, and must never stop there. +% +% \subsubsection{Numbers and reading tokens one by one} +% +% So far, we have glossed over one important point: what is a +% \enquote{number}? A number is typically given in the form +% \meta{significand}|e|\meta{exponent}, where the \meta{significand} is +% any non-empty string composed of decimal digits and at most one +% decimal separator (a period), the exponent +% \enquote{\texttt{e}\meta{exponent}} is optional and is composed of an +% exponent mark~|e| followed by a possibly empty string of signs +% |+| or~|-| and a non-empty string of decimal digits. The +% \meta{significand} can also be an integer, dimension, skip, or muskip +% variable, in which case dimensions are converted from points (or mu +% units) to floating points, and the \meta{exponent} can also be an +% integer variable. Numbers can also be given as floating point +% variables, or as named constants such as |nan|, |inf| or~|pi|. We may +% add more types in the future. +% +% When \cs{@@_parse_one:Nw} is looking for a \enquote{number}, here is +% what happens. +% \begin{itemize} +% \item If the next token is a control sequence with the meaning of +% \cs{scan_stop:}, it can be: \cs{s_@@}, in which case our job is +% done, as what follows is an internal floating point number, or +% \cs{s_@@_mark}, in which case the expression has come to an early +% end, as we are still looking for a number here, or something else, +% in which case we consider the control sequence to be a bad +% variable resulting from \texttt{c}-expansion. +% \item If the next token is a control sequence with a different +% meaning, we assume that it is a register, unpack it with +% \cs{tex_the:D}, and use its value (in \texttt{pt} for dimensions +% and skips, \texttt{mu} for muskips) as the \meta{significand} of a +% number: we look for an exponent. +% \item If the next token is a digit, we remove any leading zeros, +% then read a significand larger than~$1$ if the next character is a +% digit, read a significand smaller than~$1$ if the next character +% is a period, or we have found a significand equal to~$0$ +% otherwise, and look for an exponent. +% \item If the next token is a letter, we collect more letters until +% the first non-letter: the resulting word may denote a function +% such as |asin|, a constant such as |pi| or be unknown. In the +% first case, we call \cs{@@_parse_operand:Nw} to find the argument +% of the function, then apply the function, before declaring that we +% are done. Otherwise, we are done, either with the value of the +% constant, or with the value |nan| for unknown words. +% \item If the next token is anything else, we check whether it is a +% known prefix operator, in which case \cs{@@_parse_operand:Nw} +% finds its operand. If it is not known, then either a number is +% missing (if the token is a known infix operator) or the token is +% simply invalid in floating point expressions. +% \end{itemize} +% Once a number is found, \cs{@@_parse_one:Nw} also finds an infix +% operator. This goes as follows. +% \begin{itemize} +% \item If the next token is a control sequence, it could be the +% special marker \cs{s_@@_mark}, and +% otherwise it is a case of juxtaposing numbers, such as +% |2\c_zero_int|, with an implied multiplication. +% \item If the next token is a letter, it is also a case of +% juxtaposition, as letters cannot be proper infix operators. +% \item Otherwise (including in the case of digits), if the token is a +% known infix operator, the appropriate +% |\__fp_infix_|\meta{operator}|:N| function is built, and if it +% does not exist, we complain. In particular, the juxtaposition +% |\c_zero_int 2| is disallowed. +% \end{itemize} +% +% In the above, we need to test whether a character token~|#1| is a +% digit: +% \begin{verbatim} +% \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: +% is a digit +% \else: +% not a digit +% \fi: +% \end{verbatim} +% To exclude |0|, replace |9| by |10|. The use of +% \cs{token_to_str:N} ensures that a digit with any catcode is detected. +% To test if a character token is a letter, we need to work with its +% character code, testing if |`#1| lies in $[65,90]$ (uppercase letters) +% or $[97,112]$ (lowercase letters) +% \begin{verbatim} +% \if_int_compare:w \__fp_int_eval:w +% ( `#1 \if_int_compare:w `#1 > `Z - 32 \fi: ) / 26 = 3 \exp_stop_f: +% is a letter +% \else: +% not a letter +% \fi: +% \end{verbatim} +% At all steps, we try to accept all category codes: when |#1|~is kept +% to be used later, it is almost always converted to category code other +% through \cs{token_to_str:N}. More precisely, catcodes $\{3, 6, 7, 8, +% 11, 12\}$ should work without trouble, but not $\{1, 2, 4, 10, 13\}$, +% and of course $\{0, 5, 9\}$ cannot become tokens. +% +% Floating point expressions should behave as much as possible like +% \eTeX{}-based integer expressions and dimension expressions. In +% particular, \texttt{f}-expansion should be performed as the expression +% is read, token by token, forcing the expansion of protected macros, +% and ignoring spaces. One advantage of expanding at every step is that +% restricted expandable functions can then be used in floating point +% expressions just as they can be in other kinds of expressions. +% Problematically, spaces stop \texttt{f}-expansion: for instance, the +% macro~|\X| below would not be expanded if we simply performed +% \texttt{f}-expansion. +% \begin{verbatim} +% \DeclareDocumentCommand {\test} {m} { \fp_eval:n {#1} } +% \ExplSyntaxOff +% \test { 1 + \X } +% \end{verbatim} +% Of course, spaces typically do not appear in a code setting, but may very +% easily come in document-level input, from which some expressions may +% come. To avoid this problem, at every step, we do essentially what +% \cs{use:f} would do: take an argument, put it back in the input +% stream, then \texttt{f}-expand it. This is not a complete solution, +% since a macro's expansion could contain leading spaces which would stop +% the \texttt{f}-expansion before further macro calls are performed. +% However, in practice it should be enough: in particular, floating +% point numbers are correctly expanded to the underlying \cs{s_@@} +% \ldots{} structure. The \texttt{f}-expansion is performed by +% \cs{@@_parse_expand:w}. +% +% ^^A begin[todo] +% +% \subsection{Main auxiliary functions} +% +% \begin{macro}[rEXP]{\@@_parse_operand:Nw} +% \begin{syntax} +% \cs{exp:w} \cs{@@_parse_operand:Nw} \meta{precedence} \cs{@@_parse_expand:w} +% \end{syntax} +% Reads the \enquote{\ttfamily\ldots{}}, performing every computation +% with a precedence higher than \meta{precedence}, then expands to +% \begin{syntax} +% \meta{result} |@| |\__fp_parse_infix_|\meta{operation}|:N| \ldots{} +% \end{syntax} +% where the \meta{operation} is the first operation with a lower +% precedence, possibly \texttt{end}, and the +% \enquote{\ttfamily\ldots{}} start just after the \meta{operation}. +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_infix_+:N} +% \begin{syntax} +% \cs{@@_parse_infix_+:N} \meta{precedence} \ldots{} +% \end{syntax} +% If |+|~has a precedence higher than the \meta{precedence}, cleans up +% a second \meta{operand} and finds the \meta{operation_2} which +% follows, and expands to +% \begin{syntax} +% |@| \cs{@@_parse_apply_binary:NwNwN} |+| \meta{operand} |@| \cs{@@_parse_infix_\meta{operation_2}:N} \ldots{} +% \end{syntax} +% Otherwise expands to +% \begin{syntax} +% |@| \cs{use_none:n} \cs{@@_parse_infix_+:N} \ldots{} +% \end{syntax} +% A similar function exists for each infix operator. +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_one:Nw} +% \begin{syntax} +% \cs{@@_parse_one:Nw} \meta{precedence} \ldots{} +% \end{syntax} +% Cleans up one or two operands depending on how the precedence of the +% next operation compares to the \meta{precedence}. If the following +% \meta{operation} has a precedence higher than \meta{precedence}, +% expands to +% \begin{syntax} +% \meta{operand_1} |@| \cs{@@_parse_apply_binary:NwNwN} \meta{operation} \meta{operand_2} |@| |\__fp_parse_infix_|\meta{operation_2}|:N| \ldots{} +% \end{syntax} +% and otherwise expands to +% \begin{syntax} +% \meta{operand} |@| \cs{use_none:n} |\__fp_parse_infix_|\meta{operation}|:N| \ldots{} +% \end{syntax} +% \end{macro} +% +% ^^A end[todo] +% +% \subsection{Helpers} +% +% \begin{macro}[rEXP]{\@@_parse_expand:w} +% \begin{syntax} +% \cs{exp:w} \cs{@@_parse_expand:w} \meta{tokens} +% \end{syntax} +% This function must always come within a \cs{exp:w} expansion. +% The \meta{tokens} should be the part of the expression that we have +% not yet read. This requires in particular closing all conditionals +% properly before expanding. +% \begin{macrocode} +\cs_new:Npn \@@_parse_expand:w #1 { \exp_end_continue_f:w #1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_return_semicolon:w} +% This very odd function swaps its position with the following +% \cs{fi:} and removes \cs{@@_parse_expand:w} normally responsible for +% expansion. That turns out to be useful. +% \begin{macrocode} +\cs_new:Npn \@@_parse_return_semicolon:w + #1 \fi: \@@_parse_expand:w { \fi: ; #1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP] +% { +% \@@_parse_digits_vii:N , +% \@@_parse_digits_vi:N , +% \@@_parse_digits_v:N , +% \@@_parse_digits_iv:N , +% \@@_parse_digits_iii:N , +% \@@_parse_digits_ii:N , +% \@@_parse_digits_i:N , +% \@@_parse_digits_:N +% } +% These functions must be called within an \cs{int_value:w} or +% \cs{@@_int_eval:w} construction. The first token which follows must +% be \texttt{f}-expanded prior to calling those functions. The +% functions read tokens one by one, and output digits into the input +% stream, until meeting a non-digit, or up to a number of digits equal +% to their index. The full expansion is +% \begin{syntax} +% \meta{digits} |;| \meta{filling 0} |;| \meta{length} +% \end{syntax} +% where \meta{filling 0} is a string of zeros such that \meta{digits} +% \meta{filling 0} has the length given by the index of the function, +% and \meta{length} is the number of zeros in the \meta{filling 0} +% string. Each function puts a digit into the input stream and calls +% the next function, until we find a non-digit. We are careful to +% pass the tested tokens through \cs{token_to_str:N} to normalize +% their category code. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 #2 #3 + { + \cs_new:cpn { @@_parse_digits_ #1 :N } ##1 + { + \if_int_compare:w 9 < 1 \token_to_str:N ##1 \exp_stop_f: + \token_to_str:N ##1 \exp_after:wN #2 \exp:w + \else: + \@@_parse_return_semicolon:w #3 ##1 + \fi: + \@@_parse_expand:w + } + } +\@@_tmp:w {vii} \@@_parse_digits_vi:N { 0000000 ; 7 } +\@@_tmp:w {vi} \@@_parse_digits_v:N { 000000 ; 6 } +\@@_tmp:w {v} \@@_parse_digits_iv:N { 00000 ; 5 } +\@@_tmp:w {iv} \@@_parse_digits_iii:N { 0000 ; 4 } +\@@_tmp:w {iii} \@@_parse_digits_ii:N { 000 ; 3 } +\@@_tmp:w {ii} \@@_parse_digits_i:N { 00 ; 2 } +\@@_tmp:w {i} \@@_parse_digits_:N { 0 ; 1 } +\cs_new:Npn \@@_parse_digits_:N { ; ; 0 } +% \end{macrocode} +% \end{macro} +% +% \subsection{Parsing one number} +% +% \begin{macro}[EXP]{\@@_parse_one:Nw} +% This function finds one number, and packs the symbol which follows +% in an \cs[no-index]{@@_parse_infix_\ldots{}} csname. +% |#1|~is the previous \meta{precedence}, +% and |#2|~the first token of the operand. We distinguish four cases: +% |#2|~is equal to \cs{scan_stop:} in meaning, |#2|~is a different +% control sequence, |#2|~is a digit, and |#2|~is something else (this +% last case is split further later). Despite the earlier +% \texttt{f}-expansion, |#2|~may still be expandable if it was +% protected by \cs{exp_not:N}, as may happen with the \LaTeXe{} command +% \tn{protect}. Using a well placed \cs{reverse_if:N}, this case is +% sent to \cs{@@_parse_one_fp:NN} which deals with it robustly. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one:Nw #1 #2 + { + \if_catcode:w \scan_stop: \exp_not:N #2 + \exp_after:wN \if_meaning:w \exp_not:N #2 #2 \else: + \exp_after:wN \reverse_if:N + \fi: + \if_meaning:w \scan_stop: #2 + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_one_fp:NN + \else: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_one_register:NN + \fi: + \else: + \if_int_compare:w 9 < 1 \token_to_str:N #2 \exp_stop_f: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_one_digit:NN + \else: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_one_other:NN + \fi: + \fi: + #1 #2 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP] +% { +% \@@_parse_one_fp:NN, +% \@@_exp_after_mark_f:nw, +% \@@_exp_after_?_f:nw +% } +% This function receives a \meta{precedence} and a control sequence +% equal to \cs{scan_stop:} in meaning. There are three cases. +% \begin{itemize} +% \item \cs{s_@@} starts a floating point number, and we call +% \cs{@@_exp_after_f:nw}, which |f|-expands after the floating +% point. +% \item \cs{s_@@_mark} is a premature end, we call +% \cs{@@_exp_after_mark_f:nw}, which triggers an |fp-early-end| +% error. +% \item For a control sequence not containing \cs[no-index]{s_@@}, we call +% \cs{@@_exp_after_?_f:nw}, causing a |bad-variable| error. +% \end{itemize} +% This scheme is extensible: additional types can be added by starting +% the variables with a scan mark of the form \cs[no-index]{s_@@_\meta{type}} and +% defining |\__fp_exp_after_|\meta{type}|_f:nw|. In all cases, we +% make sure that the second argument of \cs{@@_parse_infix:NN} is +% correctly expanded. +% A special case only enabled in \LaTeXe{} is that if \tn{protect} is +% encountered then the error message mentions the control sequence +% which follows it rather than \tn{protect} itself. The test for +% \LaTeXe{} uses \tn{@unexpandable@protect} rather than \tn{protect} +% because \tn{protect} is often \cs{scan_stop:} hence \enquote{does +% not exist}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one_fp:NN #1 + { + \@@_exp_after_any_f:nw + { + \exp_after:wN \@@_parse_infix:NN + \exp_after:wN #1 \exp:w \@@_parse_expand:w + } + } +\cs_new:Npn \@@_exp_after_mark_f:nw #1 + { + \int_case:nnF { \exp_after:wN \use_i:nnn \use_none:nnn #1 } + { + \c_@@_prec_comma_int { } + \c_@@_prec_tuple_int { } + \c_@@_prec_end_int + { + \exp_after:wN \c_@@_empty_tuple_fp + \exp:w \exp_end_continue_f:w + } + } + { + \__kernel_msg_expandable_error:nn { kernel } { fp-early-end } + \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w + } + #1 + } +\cs_new:cpn { @@_exp_after_?_f:nw } #1#2 + { + \__kernel_msg_expandable_error:nnn { kernel } { bad-variable } + {#2} + \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w #1 + } +%<*package> +\cs_set_protected:Npn \@@_tmp:w #1 + { + \cs_if_exist:NT #1 + { + \cs_gset:cpn { @@_exp_after_?_f:nw } ##1##2 + { + \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w ##1 + \str_if_eq:nnTF {##2} { \protect } + { + \cs_if_eq:NNTF ##2 #1 { \use_i:nn } { \use:n } + { + \__kernel_msg_expandable_error:nnn { kernel } + { fp-robust-cmd } + } + } + { + \__kernel_msg_expandable_error:nnn { kernel } + { bad-variable } {##2} + } + } + } + } +\exp_args:Nc \@@_tmp:w { @unexpandable@protect } +%</package> +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP] +% { +% \@@_parse_one_register:NN, +% \@@_parse_one_register_aux:Nw, +% \@@_parse_one_register_auxii:wwwNw, +% \@@_parse_one_register_int:www, +% \@@_parse_one_register_mu:www, +% \@@_parse_one_register_dim:ww, +% } +% This is called whenever~|#2| is a control sequence other than +% \cs{scan_stop:} in meaning. We special-case \tn{wd}, \tn{ht}, \tn{dp} +% (see later) and otherwise assume that it is a register, but +% carefully unpack it with \cs{tex_the:D} within braces. First, we +% find the exponent following~|#2|. Then we unpack~|#2| with +% \cs{tex_the:D}, and the \texttt{auxii} auxiliary distinguishes +% integer registers from dimensions/skips from muskips, according to +% the presence of a period and/or of |pt|. For integers, simply +% convert \meta{value}|e|\meta{exponent} to a floating point number +% with \cs{@@_parse:n} (this is somewhat wasteful). For other +% registers, the decimal rounding provided by \TeX{} does not +% accurately represent the binary value that it manipulates, so we +% extract this binary value as a number of scaled points with +% \cs{int_value:w} \cs{dim_to_decimal_in_sp:n} |{| \meta{decimal value} |pt| |}|, and +% use an auxiliary of \cs{dim_to_fp:n}, which performs the +% multiplication by $2^{-16}$, correctly rounded. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one_register:NN #1#2 + { + \exp_after:wN \@@_parse_infix_after_operand:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \@@_parse_one_register_special:N #2 + \exp_after:wN \@@_parse_one_register_aux:Nw + \exp_after:wN #2 + \int_value:w + \exp_after:wN \@@_parse_exponent:N + \exp:w \@@_parse_expand:w + } +\cs_new:Npx \@@_parse_one_register_aux:Nw #1 + { + \exp_not:n + { + \exp_after:wN \use:nn + \exp_after:wN \@@_parse_one_register_auxii:wwwNw + } + \exp_not:N \exp_after:wN { \exp_not:N \tex_the:D #1 } + ; \exp_not:N \@@_parse_one_register_dim:ww + \tl_to_str:n { pt } ; \exp_not:N \@@_parse_one_register_mu:www + . \tl_to_str:n { pt } ; \exp_not:N \@@_parse_one_register_int:www + \exp_not:N \q_stop + } +\exp_args:Nno \use:nn + { \cs_new:Npn \@@_parse_one_register_auxii:wwwNw #1 . #2 } + { \tl_to_str:n { pt } #3 ; #4#5 \q_stop } + { #4 #1.#2; } +\exp_args:Nno \use:nn + { \cs_new:Npn \@@_parse_one_register_mu:www #1 } + { \tl_to_str:n { mu } ; #2 ; } + { \@@_parse_one_register_dim:ww #1 ; } +\cs_new:Npn \@@_parse_one_register_int:www #1; #2.; #3; + { \@@_parse:n { #1 e #3 } } +\cs_new:Npn \@@_parse_one_register_dim:ww #1; #2; + { + \exp_after:wN \@@_from_dim_test:ww + \int_value:w #2 \exp_after:wN , + \int_value:w \dim_to_decimal_in_sp:n { #1 pt } ; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \@@_parse_one_register_special:N, +% \@@_parse_one_register_math:NNw, +% \@@_parse_one_register_wd:w, +% \@@_parse_one_register_wd:Nw +% } +% The \tn{wd}, \tn{dp}, \tn{ht} primitives expect an integer argument. +% We abuse the exponent parser to find the integer argument: simply +% include the exponent marker~|e|. Once that \enquote{exponent} is +% found, use \cs{tex_the:D} to find the box dimension and then copy +% what we did for dimensions. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one_register_special:N #1 + { + \if_meaning:w \box_wd:N #1 \@@_parse_one_register_wd:w \fi: + \if_meaning:w \box_ht:N #1 \@@_parse_one_register_wd:w \fi: + \if_meaning:w \box_dp:N #1 \@@_parse_one_register_wd:w \fi: + \if_meaning:w \infty #1 + \@@_parse_one_register_math:NNw \infty #1 + \fi: + \if_meaning:w \pi #1 + \@@_parse_one_register_math:NNw \pi #1 + \fi: + } +\cs_new:Npn \@@_parse_one_register_math:NNw + #1#2#3#4 \@@_parse_expand:w + { + #3 + \str_if_eq:nnTF {#1} {#2} + { + \__kernel_msg_expandable_error:nnn + { kernel } { fp-infty-pi } {#1} + \c_nan_fp + } + { #4 \@@_parse_expand:w } + } +\cs_new:Npn \@@_parse_one_register_wd:w + #1#2 \exp_after:wN #3#4 \@@_parse_expand:w + { + #1 + \exp_after:wN \@@_parse_one_register_wd:Nw + #4 \@@_parse_expand:w e + } +\cs_new:Npn \@@_parse_one_register_wd:Nw #1#2 ; + { + \exp_after:wN \@@_from_dim_test:ww + \exp_after:wN 0 \exp_after:wN , + \int_value:w \dim_to_decimal_in_sp:n { #1 #2 } ; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_one_digit:NN} +% A digit marks the beginning of an explicit floating point number. +% Once the number is found, we catch the case of overflow and +% underflow with \cs{@@_sanitize:wN}, then +% \cs{@@_parse_infix_after_operand:NwN} expands \cs{@@_parse_infix:NN} +% after the number we find, to wrap the following infix operator as +% required. Finding the number itself begins by removing leading +% zeros: further steps are described later. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one_digit:NN #1 + { + \exp_after:wN \@@_parse_infix_after_operand:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \exp_after:wN \@@_sanitize:wN + \int_value:w \@@_int_eval:w 0 \@@_parse_trim_zeros:N + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_one_other:NN} +% For this function, |#2|~is a character token which is not a digit. +% If it is an \textsc{ascii} letter, \cs{@@_parse_letters:N} beyond this one and give +% the result to \cs{@@_parse_word:Nw}. Otherwise, the character is +% assumed to be a prefix operator, and we build +% |\__fp_parse_prefix_|\meta{operator}|:Nw|. +% \begin{macrocode} +\cs_new:Npn \@@_parse_one_other:NN #1 #2 + { + \if_int_compare:w + \@@_int_eval:w + ( `#2 \if_int_compare:w `#2 > `Z - 32 \fi: ) / 26 + = 3 \exp_stop_f: + \exp_after:wN \@@_parse_word:Nw + \exp_after:wN #1 + \exp_after:wN #2 + \exp:w \exp_after:wN \@@_parse_letters:N + \exp:w + \else: + \exp_after:wN \@@_parse_prefix:NNN + \exp_after:wN #1 + \exp_after:wN #2 + \cs:w + @@_parse_prefix_ \token_to_str:N #2 :Nw + \exp_after:wN + \cs_end: + \exp:w + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_word:Nw} +% \begin{macro}[rEXP]{\@@_parse_letters:N} +% Finding letters is a simple recursion. Once \cs{@@_parse_letters:N} +% has done its job, we try to build a control sequence from the +% word~|#2|. If it is a known word, then the corresponding action is +% taken, and otherwise, we complain about an unknown word, yield +% \cs{c_nan_fp}, and look for the following infix operator. Note that +% the unknown word could be a mistyped function as well as a mistyped +% constant, so there is no way to tell whether to look for arguments; +% we do not. +% The standard requires \enquote{inf} and \enquote{infinity} and +% \enquote{nan} to be recognized regardless of case, but we probably +% don't want to allow every \pkg{l3fp} word to have an arbitrary +% mixture of lower and upper case, so we test and use a +% differently-named control sequence. +% \begin{macrocode} +\cs_new:Npn \@@_parse_word:Nw #1#2; + { + \cs_if_exist_use:cF { @@_parse_word_#2:N } + { + \cs_if_exist_use:cF + { @@_parse_caseless_ \str_fold_case:n {#2} :N } + { + \__kernel_msg_expandable_error:nnn + { kernel } { unknown-fp-word } {#2} + \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w + \@@_parse_infix:NN + } + } + #1 + } +\cs_new:Npn \@@_parse_letters:N #1 + { + \exp_end_continue_f:w + \if_int_compare:w + \if_catcode:w \scan_stop: \exp_not:N #1 + 0 + \else: + \@@_int_eval:w + ( `#1 \if_int_compare:w `#1 > `Z - 32 \fi: ) / 26 + \fi: + = 3 \exp_stop_f: + \exp_after:wN #1 + \exp:w \exp_after:wN \@@_parse_letters:N + \exp:w + \else: + \@@_parse_return_semicolon:w #1 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP] +% {\@@_parse_prefix:NNN, \@@_parse_prefix_unknown:NNN} +% For this function, |#1|~is the previous \meta{precedence}, |#2|~is +% the operator just seen, and |#3|~is a control sequence which +% implements the operator if it is a known operator. If this control +% sequence is \cs{scan_stop:}, then the operator is in fact unknown. +% Either the expression is missing a number there (if the operator is +% valid as an infix operator), and we put \texttt{nan}, wrapping the +% infix operator in a csname as appropriate, or the character is +% simply invalid in floating point expressions, and we continue +% looking for a number, starting again from \cs{@@_parse_one:Nw}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_prefix:NNN #1#2#3 + { + \if_meaning:w \scan_stop: #3 + \exp_after:wN \@@_parse_prefix_unknown:NNN + \exp_after:wN #2 + \fi: + #3 #1 + } +\cs_new:Npn \@@_parse_prefix_unknown:NNN #1#2#3 + { + \cs_if_exist:cTF { @@_parse_infix_ \token_to_str:N #1 :N } + { + \__kernel_msg_expandable_error:nnn + { kernel } { fp-missing-number } {#1} + \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w + \@@_parse_infix:NN #3 #1 + } + { + \__kernel_msg_expandable_error:nnn + { kernel } { fp-unknown-symbol } {#1} + \@@_parse_one:Nw #3 + } + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Numbers: trimming leading zeros} +% +% Numbers are parsed as follows: first we trim leading zeros, then +% if the next character is a digit, start reading a significand $\geq 1$ +% with the set of functions |\__fp_parse_large|\ldots{}; if it is a +% period, the significand is~$<1$; and otherwise it is zero. In the +% second case, trim additional zeros after the period, counting them for +% an exponent shift $\meta{exp_1}<0$, then read the significand with the +% set of functions |\__fp_parse_small|\ldots{} Once the significand is +% read, read the exponent if |e|~is present. +% +% \begin{macro}[rEXP]{\@@_parse_trim_zeros:N, \@@_parse_trim_end:w} +% This function expects an already expanded token. It removes any +% leading zero, then distinguishes three cases: if the first non-zero +% token is a digit, then call \cs{@@_parse_large:N} (the significand +% is $\geq 1$); if it is |.|, then continue trimming zeros with +% \cs{@@_parse_strim_zeros:N}; otherwise, our number is exactly zero, +% and we call \cs{@@_parse_zero:} to take care of that case. +% \begin{macrocode} +\cs_new:Npn \@@_parse_trim_zeros:N #1 + { + \if:w 0 \exp_not:N #1 + \exp_after:wN \@@_parse_trim_zeros:N + \exp:w + \else: + \if:w . \exp_not:N #1 + \exp_after:wN \@@_parse_strim_zeros:N + \exp:w + \else: + \@@_parse_trim_end:w #1 + \fi: + \fi: + \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_trim_end:w #1 \fi: \fi: \@@_parse_expand:w + { + \fi: + \fi: + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + \exp_after:wN \@@_parse_large:N + \else: + \exp_after:wN \@@_parse_zero: + \fi: + #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP] +% {\@@_parse_strim_zeros:N, \@@_parse_strim_end:w} +% If we have removed all digits until a period (or if the body started +% with a period), then enter the \enquote{\texttt{small_trim}} loop +% which outputs $-1$ for each removed~$0$. Those $-1$ are added to an +% integer expression waiting for the exponent. If the first non-zero +% token is a digit, call \cs{@@_parse_small:N} (our significand is +% smaller than~$1$), and otherwise, the number is an exact zero. The +% name \texttt{strim} stands for \enquote{small trim}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_strim_zeros:N #1 + { + \if:w 0 \exp_not:N #1 + - 1 + \exp_after:wN \@@_parse_strim_zeros:N \exp:w + \else: + \@@_parse_strim_end:w #1 + \fi: + \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_strim_end:w #1 \fi: \@@_parse_expand:w + { + \fi: + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + \exp_after:wN \@@_parse_small:N + \else: + \exp_after:wN \@@_parse_zero: + \fi: + #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_zero:} +% After reading a significand of~$0$, find any exponent, then put a +% sign of~|1| for \cs{@@_sanitize:wN}, which removes everything +% and leaves an exact zero. +% \begin{macrocode} +\cs_new:Npn \@@_parse_zero: + { + \exp_after:wN ; \exp_after:wN 1 + \int_value:w \@@_parse_exponent:N + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Number: small significand} +% +% \begin{macro}[rEXP]{\@@_parse_small:N} +% This function is called after we have passed the decimal separator +% and removed all leading zeros from the significand. It is followed +% by a non-zero digit (with any catcode). The goal is to read up to +% $16$ digits. But we can't do that all at once, because +% \cs{int_value:w} (which allows us to collect digits and continue +% expanding) can only go up to $9$ digits. Hence we grab digits in +% two steps of $8$ digits. Since |#1| is a digit, read seven more +% digits using \cs{@@_parse_digits_vii:N}. The \texttt{small_leading} +% auxiliary leaves those digits in the \cs{int_value:w}, and +% grabs some more, or stops if there are no more digits. Then the +% \texttt{pack_leading} auxiliary puts the various parts in the +% appropriate order for the processing further up. +% \begin{macrocode} +\cs_new:Npn \@@_parse_small:N #1 + { + \exp_after:wN \@@_parse_pack_leading:NNNNNww + \int_value:w \@@_int_eval:w 1 \token_to_str:N #1 + \exp_after:wN \@@_parse_small_leading:wwNN + \int_value:w 1 + \exp_after:wN \@@_parse_digits_vii:N + \exp:w \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_small_leading:wwNN} +% \begin{syntax} +% \cs{@@_parse_small_leading:wwNN} |1| \meta{digits} |;| \meta{zeros} |;| \meta{number~of~zeros} +% \end{syntax} +% We leave \meta{digits} \meta{zeros} in the input stream: the +% functions used to grab digits are such that this constitutes digits +% $1$ through~$8$ of the significand. Then prepare to pack $8$~more +% digits, with an exponent shift of zero (this shift is used in +% the case of a large significand). If |#4|~is a digit, leave it +% behind for the packing function, and read $6$~more digits to reach a +% total of $15$~digits: further digits are involved in the rounding. +% Otherwise put $8$~zeros in to complete the significand, then look +% for an exponent. +% \begin{macrocode} +\cs_new:Npn \@@_parse_small_leading:wwNN 1 #1 ; #2; #3 #4 + { + #1 #2 + \exp_after:wN \@@_parse_pack_trailing:NNNNNNww + \exp_after:wN 0 + \int_value:w \@@_int_eval:w 1 + \if_int_compare:w 9 < 1 \token_to_str:N #4 \exp_stop_f: + \token_to_str:N #4 + \exp_after:wN \@@_parse_small_trailing:wwNN + \int_value:w 1 + \exp_after:wN \@@_parse_digits_vi:N + \exp:w + \else: + 0000 0000 \@@_parse_exponent:Nw #4 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_small_trailing:wwNN} +% \begin{syntax} +% \cs{@@_parse_small_trailing:wwNN} |1| \meta{digits} |;| \meta{zeros} |;| \meta{number~of~zeros} \meta{next~token} +% \end{syntax} +% Leave digits $10$ to~$15$ (arguments |#1| and |#2|) in the input +% stream. If the \meta{next~token} is a digit, it is the $16$th +% digit, we keep it, then the \texttt{small_round} auxiliary considers +% this digit and all further digits to perform the rounding: the +% function expands to nothing, to |+0| or to |+1|. +% Otherwise, there is no $16$-th digit, so we put a~$0$, and look for +% an exponent. +% \begin{macrocode} +\cs_new:Npn \@@_parse_small_trailing:wwNN 1 #1 ; #2; #3 #4 + { + #1 #2 + \if_int_compare:w 9 < 1 \token_to_str:N #4 \exp_stop_f: + \token_to_str:N #4 + \exp_after:wN \@@_parse_small_round:NN + \exp_after:wN #4 + \exp:w + \else: + 0 \@@_parse_exponent:Nw #4 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP] +% { +% \@@_parse_pack_trailing:NNNNNNww , +% \@@_parse_pack_leading:NNNNNww , +% \@@_parse_pack_carry:w +% } +% Those functions are expanded after all the digits are found, we took +% care of the rounding, as well as the exponent. The last argument is +% the exponent. The previous five arguments are $8$~digits which we +% pack in groups of~$4$, and the argument before that is~$1$, except +% in the rare case where rounding lead to a carry, in which case the +% argument is~$2$. The \texttt{trailing} function has an exponent +% shift as its first argument, which we add to the exponent found in +% the |e...| syntax. If the trailing digits cause a carry, the +% integer expression for the leading digits is incremented (|+1| +% in the code below). If the leading digits propagate this carry all +% the way up, the function \cs{@@_parse_pack_carry:w} increments the +% exponent, and changes the significand from |0000...| to |1000...|: +% this is simple because such a carry can only occur to give rise to a +% power of~$10$. +% \begin{macrocode} +\cs_new:Npn \@@_parse_pack_trailing:NNNNNNww #1 #2 #3#4#5#6 #7; #8 ; + { + \if_meaning:w 2 #2 + 1 \fi: + ; #8 + #1 ; {#3#4#5#6} {#7}; + } +\cs_new:Npn \@@_parse_pack_leading:NNNNNww #1 #2#3#4#5 #6; #7; + { + + #7 + \if_meaning:w 2 #1 \@@_parse_pack_carry:w \fi: + ; 0 {#2#3#4#5} {#6} + } +\cs_new:Npn \@@_parse_pack_carry:w \fi: ; 0 #1 + { \fi: + 1 ; 0 {1000} } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Number: large significand} +% +% Parsing a significand larger than~$1$ is a little bit more difficult +% than parsing small significands. We need to count the number of +% digits before the decimal separator, and add that to the final +% exponent. We also need to test for the presence of a dot each time we +% run out of digits, and branch to the appropriate \texttt{parse_small} +% function in those cases. +% +% \begin{macro}[EXP]{\@@_parse_large:N} +% This function is followed by the first non-zero digit of a +% \enquote{large} significand ($\geq 1$). It is called within an +% integer expression for the exponent. Grab up to $7$~more digits, +% for a total of $8$~digits. +% \begin{macrocode} +\cs_new:Npn \@@_parse_large:N #1 + { + \exp_after:wN \@@_parse_large_leading:wwNN + \int_value:w 1 \token_to_str:N #1 + \exp_after:wN \@@_parse_digits_vii:N + \exp:w \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_large_leading:wwNN} +% \begin{syntax} +% \cs{@@_parse_large_leading:wwNN} |1| \meta{digits} |;| \meta{zeros} |;| \meta{number~of~zeros} \meta{next~token} +% \end{syntax} +% We shift the exponent by the number of digits in~|#1|, namely the +% target number, $8$, minus the \meta{number of zeros} (number of +% digits missing). Then prepare to pack the $8$~first digits. If the +% \meta{next token} is a digit, read up to $6$~more digits (digits +% $10$ to~$15$). If it is a period, try to grab the end of our +% $8$~first digits, branching to the \texttt{small} functions since +% the number of digit does not affect the exponent anymore. Finally, +% if this is the end of the significand, insert the \meta{zeros} to +% complete the $8$~first digits, insert $8$~more, and look for an +% exponent. +% \begin{macrocode} +\cs_new:Npn \@@_parse_large_leading:wwNN 1 #1 ; #2; #3 #4 + { + + \c_@@_half_prec_int - #3 + \exp_after:wN \@@_parse_pack_leading:NNNNNww + \int_value:w \@@_int_eval:w 1 #1 + \if_int_compare:w 9 < 1 \token_to_str:N #4 \exp_stop_f: + \exp_after:wN \@@_parse_large_trailing:wwNN + \int_value:w 1 \token_to_str:N #4 + \exp_after:wN \@@_parse_digits_vi:N + \exp:w + \else: + \if:w . \exp_not:N #4 + \exp_after:wN \@@_parse_small_leading:wwNN + \int_value:w 1 + \cs:w + @@_parse_digits_ + \@@_int_to_roman:w #3 + :N \exp_after:wN + \cs_end: + \exp:w + \else: + #2 + \exp_after:wN \@@_parse_pack_trailing:NNNNNNww + \exp_after:wN 0 + \int_value:w 1 0000 0000 + \@@_parse_exponent:Nw #4 + \fi: + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_large_trailing:wwNN} +% \begin{syntax} +% \cs{@@_parse_large_trailing:wwNN} |1| \meta{digits} |;| \meta{zeros} |;| \meta{number~of~zeros} \meta{next~token} +% \end{syntax} +% We have just read $15$~digits. If the \meta{next token} is a digit, +% then the exponent shift caused by this block of $8$~digits is~$8$, +% first argument to the \texttt{pack_trailing} function. We keep the +% \meta{digits} and this $16$-th digit, and find how this should be +% rounded using \cs{@@_parse_large_round:NN}. Otherwise, the exponent +% shift is the number of \meta{digits}, $7$~minus the \meta{number of +% zeros}, and we test for a decimal point. This case happens in +% |123451234512345.67| with exactly $15$ digits before the decimal +% separator. Then branch to the appropriate \texttt{small} auxiliary, +% grabbing a few more digits to complement the digits we already +% grabbed. Finally, if this is truly the end of the significand, look +% for an exponent after using the \meta{zeros} and providing a $16$-th +% digit of~$0$. +% \begin{macrocode} +\cs_new:Npn \@@_parse_large_trailing:wwNN 1 #1 ; #2; #3 #4 + { + \if_int_compare:w 9 < 1 \token_to_str:N #4 \exp_stop_f: + \exp_after:wN \@@_parse_pack_trailing:NNNNNNww + \exp_after:wN \c_@@_half_prec_int + \int_value:w \@@_int_eval:w 1 #1 \token_to_str:N #4 + \exp_after:wN \@@_parse_large_round:NN + \exp_after:wN #4 + \exp:w + \else: + \exp_after:wN \@@_parse_pack_trailing:NNNNNNww + \int_value:w \@@_int_eval:w 7 - #3 \exp_stop_f: + \int_value:w \@@_int_eval:w 1 #1 + \if:w . \exp_not:N #4 + \exp_after:wN \@@_parse_small_trailing:wwNN + \int_value:w 1 + \cs:w + @@_parse_digits_ + \@@_int_to_roman:w #3 + :N \exp_after:wN + \cs_end: + \exp:w + \else: + #2 0 \@@_parse_exponent:Nw #4 + \fi: + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Number: beyond 16 digits, rounding} +% +% \begin{macro}[rEXP]{\@@_parse_round_loop:N, \@@_parse_round_up:N} +% This loop is called when rounding a number (whether the mantissa is +% small or large). It should appear in an integer expression. This +% function reads digits one by one, until reaching a non-digit, and +% adds~$1$ to the integer expression for each digit. If all digits +% found are~$0$, the function ends the expression by |;0|, +% otherwise by |;1|. This is done by switching the loop to +% |round_up| at the first non-zero digit, thus we avoid to test +% whether digits are~$0$ or not once we see a first non-zero digit. +% \begin{macrocode} +\cs_new:Npn \@@_parse_round_loop:N #1 + { + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + + 1 + \if:w 0 \token_to_str:N #1 + \exp_after:wN \@@_parse_round_loop:N + \exp:w + \else: + \exp_after:wN \@@_parse_round_up:N + \exp:w + \fi: + \else: + \@@_parse_return_semicolon:w 0 #1 + \fi: + \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_round_up:N #1 + { + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + + 1 + \exp_after:wN \@@_parse_round_up:N + \exp:w + \else: + \@@_parse_return_semicolon:w 1 #1 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_round_after:wN} +% After the loop \cs{@@_parse_round_loop:N}, this function fetches an +% exponent with \cs{@@_parse_exponent:N}, and combines it with the +% number of digits counted by \cs{@@_parse_round_loop:N}. At the same +% time, the result |0| or |1| is added to the +% surrounding integer expression. +% \begin{macrocode} +\cs_new:Npn \@@_parse_round_after:wN #1; #2 + { + + #2 \exp_after:wN ; + \int_value:w \@@_int_eval:w #1 + \@@_parse_exponent:N + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP] +% {\@@_parse_small_round:NN, \@@_parse_round_after:wN} +% Here, |#1|~is the digit that we are currently rounding (we only care +% whether it is even or odd). If |#2|~is not a digit, then fetch an +% exponent and expand to |;|\meta{exponent} only. Otherwise, we +% expand to |+0| or |+1|, then |;|\meta{exponent}. To +% decide which, call \cs{@@_round_s:NNNw} to know whether to round up, +% giving it as arguments a sign~$0$ (all explicit numbers are +% positive), the digit |#1|~to round, the first following digit~|#2|, +% and either |+0| or |+1| depending on whether the +% following digits are all zero or not. This last argument is +% obtained by \cs{@@_parse_round_loop:N}, whose number of digits we +% discard by multiplying it by~$0$. The exponent which follows the +% number is also fetched by \cs{@@_parse_round_after:wN}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_small_round:NN #1#2 + { + \if_int_compare:w 9 < 1 \token_to_str:N #2 \exp_stop_f: + + + \exp_after:wN \@@_round_s:NNNw + \exp_after:wN 0 + \exp_after:wN #1 + \exp_after:wN #2 + \int_value:w \@@_int_eval:w + \exp_after:wN \@@_parse_round_after:wN + \int_value:w \@@_int_eval:w 0 * \@@_int_eval:w 0 + \exp_after:wN \@@_parse_round_loop:N + \exp:w + \else: + \@@_parse_exponent:Nw #2 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}[rEXP] +% { +% \@@_parse_large_round:NN, +% \@@_parse_large_round_test:NN, +% \@@_parse_large_round_aux:wNN, +% } +% Large numbers are harder to round, as there may be a period in the +% way. Again, |#1|~is the digit that we are currently rounding (we +% only care whether it is even or odd). If there are no more digits +% (|#2|~is not a digit), then we must test for a period: if there is +% one, then switch to the rounding function for small significands, +% otherwise fetch an exponent. If there are more digits (|#2|~is a +% digit), then round, checking with \cs{@@_parse_round_loop:N} if all +% further digits vanish, or some are non-zero. This loop is not +% enough, as it is stopped by a period. After the loop, the +% \texttt{aux} function tests for a period: if it is present, then we +% must continue looking for digits, this time discarding the number of +% digits we find. +% \begin{macrocode} +\cs_new:Npn \@@_parse_large_round:NN #1#2 + { + \if_int_compare:w 9 < 1 \token_to_str:N #2 \exp_stop_f: + + + \exp_after:wN \@@_round_s:NNNw + \exp_after:wN 0 + \exp_after:wN #1 + \exp_after:wN #2 + \int_value:w \@@_int_eval:w + \exp_after:wN \@@_parse_large_round_aux:wNN + \int_value:w \@@_int_eval:w 1 + \exp_after:wN \@@_parse_round_loop:N + \else: %^^A could be dot, or e, or other + \exp_after:wN \@@_parse_large_round_test:NN + \exp_after:wN #1 + \exp_after:wN #2 + \fi: + } +\cs_new:Npn \@@_parse_large_round_test:NN #1#2 + { + \if:w . \exp_not:N #2 + \exp_after:wN \@@_parse_small_round:NN + \exp_after:wN #1 + \exp:w + \else: + \@@_parse_exponent:Nw #2 + \fi: + \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_large_round_aux:wNN #1 ; #2 #3 + { + + #2 + \exp_after:wN \@@_parse_round_after:wN + \int_value:w \@@_int_eval:w #1 + \if:w . \exp_not:N #3 + + 0 * \@@_int_eval:w 0 + \exp_after:wN \@@_parse_round_loop:N + \exp:w \exp_after:wN \@@_parse_expand:w + \else: + \exp_after:wN ; + \exp_after:wN 0 + \exp_after:wN #3 + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Number: finding the exponent} +% +% Expansion is a little bit tricky here, in part because we accept input +% where multiplication is implicit. +% \begin{syntax} +% \cs{@@_parse:n} |{ 3.2 erf(0.1) }| +% \cs{@@_parse:n} |{ 3.2 e\l_my_int }| +% \cs{@@_parse:n} |{ 3.2 \c_pi_fp }| +% \end{syntax} +% The first case indicates that just looking one character ahead for an +% \enquote{\texttt{e}} is not enough, since we would mistake the +% function \texttt{erf} for an exponent of \enquote{\texttt{rf}}. An +% alternative would be to look two tokens ahead and check if what +% follows is a sign or a digit, considering in that case that we must be +% finding an exponent. But taking care of the second case requires that +% we unpack registers after \texttt{e}. However, blindly expanding the +% two tokens ahead completely would break the third example (unpacking +% is even worse). Indeed, in the course of reading $3.2$, \cs{c_pi_fp} +% is expanded to \cs{s_@@} \cs{@@_chk:w} |1| |0| |{-1}| |{3141}| +% $\cdots$ |;| and \cs{s_@@} stops the expansion. Expanding two tokens +% ahead would then force the expansion of \cs{@@_chk:w} (despite it +% being protected), and that function tries to produce an error. +% +% What can we do? Really, the reason why this last case breaks is that +% just as \TeX{} does, we should read ahead as little as possible. +% Here, the only case where there may be an exponent is if the first +% token ahead is |e|. Then we expand (and possibly unpack) the second +% token. +% +% \begin{macro}[rEXP]{\@@_parse_exponent:Nw} +% This auxiliary is convenient to smuggle some material through +% \cs{fi:} ending conditional processing. We place those \cs{fi:} +% (argument~|#2|) at a very odd place because this allows us to insert +% \cs{@@_int_eval:w} \ldots{} there if needed. +% \begin{macrocode} +\cs_new:Npn \@@_parse_exponent:Nw #1 #2 \@@_parse_expand:w + { + \exp_after:wN ; + \int_value:w #2 \@@_parse_exponent:N #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP] +% {\@@_parse_exponent:N, \@@_parse_exponent_aux:N} +% This function should be called within an \cs{int_value:w} +% expansion (or within an integer expression). It leaves digits of the +% exponent behind it in the input stream, and terminates the expansion +% with a semicolon. If there is no~|e|, leave an exponent of~$0$. If +% there is an~|e|, expand the next token to run some tests on it. The +% first rough test is that if the character code of~|#1| is greater +% than that of~|9| (largest code valid for an exponent, less than any +% code valid for an identifier), there was in fact no exponent; +% otherwise, we search for the sign of the exponent. +% \begin{macrocode} +\cs_new:Npn \@@_parse_exponent:N #1 + { + \if:w e \exp_not:N #1 + \exp_after:wN \@@_parse_exponent_aux:N + \exp:w + \else: + 0 \@@_parse_return_semicolon:w #1 + \fi: + \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_exponent_aux:N #1 + { + \if_int_compare:w \if_catcode:w \scan_stop: \exp_not:N #1 + 0 \else: `#1 \fi: > `9 \exp_stop_f: + 0 \exp_after:wN ; \exp_after:wN e + \else: + \exp_after:wN \@@_parse_exponent_sign:N + \fi: + #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_exponent_sign:N} +% Read signs one by one (if there is any). +% \begin{macrocode} +\cs_new:Npn \@@_parse_exponent_sign:N #1 + { + \if:w + \if:w - \exp_not:N #1 + \fi: \token_to_str:N #1 + \exp_after:wN \@@_parse_exponent_sign:N + \exp:w \exp_after:wN \@@_parse_expand:w + \else: + \exp_after:wN \@@_parse_exponent_body:N + \exp_after:wN #1 + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_exponent_body:N} +% An exponent can be an explicit integer (most common case), or +% various other things (most of which are invalid). +% \begin{macrocode} +\cs_new:Npn \@@_parse_exponent_body:N #1 + { + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + \token_to_str:N #1 + \exp_after:wN \@@_parse_exponent_digits:N + \exp:w + \else: + \@@_parse_exponent_keep:NTF #1 + { \@@_parse_return_semicolon:w #1 } + { + \exp_after:wN ; + \exp:w + } + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_exponent_digits:N} +% Read digits one by one, and leave them behind in the input stream. +% When finding a non-digit, stop, and insert a semicolon. Note that +% we do not check for overflow of the exponent, hence there can be a +% \TeX{} error. It is mostly harmless, except when parsing +% |0e9876543210|, which should be a valid representation of $0$, but +% is not. +% \begin{macrocode} +\cs_new:Npn \@@_parse_exponent_digits:N #1 + { + \if_int_compare:w 9 < 1 \token_to_str:N #1 \exp_stop_f: + \token_to_str:N #1 + \exp_after:wN \@@_parse_exponent_digits:N + \exp:w + \else: + \@@_parse_return_semicolon:w #1 + \fi: + \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[rEXP]{\@@_parse_exponent_keep:NTF} +% This is the last building block for parsing exponents. The +% argument~|#1| is already fully expanded, and neither |+| nor~|-| nor +% a digit. It can be: +% \begin{itemize} +% \item \cs{s_@@}, marking the start of an internal floating point, +% invalid here; +% \item another control sequence equal to \tn{relax}, probably a bad +% variable; +% \item a register: in this case we make sure that it is an integer +% register, not a dimension; +% \item a character other than |+|, |-| or digits, again, an error. +% \end{itemize} +% \begin{macrocode} +\prg_new_conditional:Npnn \@@_parse_exponent_keep:N #1 { TF } + { + \if_catcode:w \scan_stop: \exp_not:N #1 + \if_meaning:w \scan_stop: #1 + \if_int_compare:w + \@@_str_if_eq:nn { \s_@@ } { \exp_not:N #1 } + = 0 \exp_stop_f: + 0 + \__kernel_msg_expandable_error:nnn + { kernel } { fp-after-e } { floating~point~ } + \prg_return_true: + \else: + 0 + \__kernel_msg_expandable_error:nnn + { kernel } { bad-variable } {#1} + \prg_return_false: + \fi: + \else: + \if_int_compare:w + \@@_str_if_eq:nn { \int_value:w #1 } { \tex_the:D #1 } + = 0 \exp_stop_f: + \int_value:w #1 + \else: + 0 + \__kernel_msg_expandable_error:nnn + { kernel } { fp-after-e } { dimension~#1 } + \fi: + \prg_return_false: + \fi: + \else: + 0 + \__kernel_msg_expandable_error:nnn + { kernel } { fp-missing } { exponent } + \prg_return_true: + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Constants, functions and prefix operators} +% +% \subsubsection{Prefix operators} +% +% \begin{macro}[EXP]{\@@_parse_prefix_+:Nw} +% A unary~|+| does nothing: we should continue looking for a number. +% \begin{macrocode} +\cs_new_eq:cN { @@_parse_prefix_+:Nw } \@@_parse_one:Nw +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_apply_function:NNNwN} +% Here, |#1| is a precedence, |#2| is some extra data used by some +% functions, |#3| is \emph{e.g.}, \cs{@@_sin_o:w}, and expands once +% after the calculation, |#4| is the operand, and |#5| is a +% \cs[no-index]{@@_parse_infix_\ldots{}:N} function. We feed the data~|#2|, and the +% argument~|#4|, to the function~|#3|, which expands +% \cs{exp:w} thus the \texttt{infix} function~|#5|. +% \begin{macrocode} +\cs_new:Npn \@@_parse_apply_function:NNNwN #1#2#3#4@#5 + { + #3 #2 #4 @ + \exp:w \exp_end_continue_f:w #5 #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_apply_unary:NNNwN} +% \begin{macro}[EXP]{\@@_parse_apply_unary_chk:NwNw, \@@_parse_apply_unary_chk:nNNNw} +% \begin{macro}[EXP]{\@@_parse_apply_unary_type:NNN, \@@_parse_apply_unary_error:NNw} +% In contrast to \cs{@@_parse_apply_function:NNNwN}, this checks that +% the operand |#4| is a single argument (namely there is a single +% |;|). We use the fact that any floating point starts with a +% \enquote{safe} token like \cs{s_@@}. If there is no argument +% produce the |fp-no-arg| error; if there are at least two produce +% |fp-multi-arg|. For the error message extract the mathematical +% function name (such as |sin|) from the \pkg{expl3} function that +% computes it, such as \cs{@@_sin_o:w}. +% +% In addition, since there is a single argument we can dispatch on +% type and check that the resulting function exists. This catches +% things like |sin((1,2))| where it does not make sense to take the +% sine of a tuple. +% \begin{macrocode} +\cs_new:Npn \@@_parse_apply_unary:NNNwN #1#2#3#4@#5 + { + \@@_parse_apply_unary_chk:NwNw #4 @ ; . \q_stop + \@@_parse_apply_unary_type:NNN + #3 #2 #4 @ + \exp:w \exp_end_continue_f:w #5 #1 + } +\cs_new:Npn \@@_parse_apply_unary_chk:NwNw #1#2 ; #3#4 \q_stop + { + \if_meaning:w @ #3 \else: + \token_if_eq_meaning:NNTF . #3 + { \@@_parse_apply_unary_chk:nNNNNw { no } } + { \@@_parse_apply_unary_chk:nNNNNw { multi } } + \fi: + } +\cs_new:Npn \@@_parse_apply_unary_chk:nNNNNw #1#2#3#4#5#6 @ + { + #2 + \@@_error:nffn { fp-#1-arg } { \@@_func_to_name:N #4 } { } { } + \exp_after:wN #4 \exp_after:wN #5 \c_nan_fp @ + } +\cs_new:Npn \@@_parse_apply_unary_type:NNN #1#2#3 + { + \@@_change_func_type:NNN #3 #1 \@@_parse_apply_unary_error:NNw + #2 #3 + } +\cs_new:Npn \@@_parse_apply_unary_error:NNw #1#2#3 @ + { \@@_invalid_operation_o:fw { \@@_func_to_name:N #1 } #3 } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_prefix_-:Nw, \@@_parse_prefix_!:Nw} +% The unary~|-| and boolean not are harder: we parse the operand using +% a precedence equal to the maximum of the previous precedence~|##1| +% and the precedence \cs{c_@@_prec_not_int} of the unary operator, then call +% the appropriate |\__fp_|\meta{operation}|_o:w| function, +% where the \meta{operation} is |set_sign| or |not|. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1#2#3#4 + { + \cs_new:cpn { @@_parse_prefix_ #1 :Nw } ##1 + { + \exp_after:wN \@@_parse_apply_unary:NNNwN + \exp_after:wN ##1 + \exp_after:wN #4 + \exp_after:wN #3 + \exp:w + \if_int_compare:w #2 < ##1 + \@@_parse_operand:Nw ##1 + \else: + \@@_parse_operand:Nw #2 + \fi: + \@@_parse_expand:w + } + } +\@@_tmp:w - \c_@@_prec_not_int \@@_set_sign_o:w 2 +\@@_tmp:w ! \c_@@_prec_not_int \@@_not_o:w ? +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_prefix_.:Nw} +% Numbers which start with a decimal separator (a~period) end up here. +% Of course, we do not look for an operand, but for the rest of the +% number. This function is very similar to \cs{@@_parse_one_digit:NN} +% but calls \cs{@@_parse_strim_zeros:N} to trim zeros after the +% decimal point, rather than the \texttt{trim_zeros} function for +% zeros before the decimal point. +% \begin{macrocode} +\cs_new:cpn { @@_parse_prefix_.:Nw } #1 + { + \exp_after:wN \@@_parse_infix_after_operand:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \exp_after:wN \@@_sanitize:wN + \int_value:w \@@_int_eval:w 0 \@@_parse_strim_zeros:N + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP] +% {\@@_parse_prefix_(:Nw, \@@_parse_lparen_after:NwN} +% The left parenthesis is treated as a unary prefix operator because +% it appears in exactly the same settings. If the previous precedence +% is \cs{c_@@_prec_func_int} we are parsing arguments of a function +% and commas should not build tuples; otherwise commas should build +% tuples. We distinguish these cases by precedence: +% \cs{c_@@_prec_comma_int} for the case of arguments, +% \cs{c_@@_prec_tuple_int} for the case of tuples. +% Once the operand is found, the \texttt{lparen_after} auxiliary makes +% sure that there was a closing parenthesis (otherwise it complains), +% and leaves in the input stream an operand, +% fetching the following infix operator. +% \begin{macrocode} +\cs_new:cpn { @@_parse_prefix_(:Nw } #1 + { + \exp_after:wN \@@_parse_lparen_after:NwN + \exp_after:wN #1 + \exp:w + \if_int_compare:w #1 = \c_@@_prec_func_int + \@@_parse_operand:Nw \c_@@_prec_comma_int + \else: + \@@_parse_operand:Nw \c_@@_prec_tuple_int + \fi: + \@@_parse_expand:w + } +\cs_new:Npx \@@_parse_lparen_after:NwN #1#2 @ #3 + { + \exp_not:N \token_if_eq_meaning:NNTF #3 + \exp_not:c { @@_parse_infix_):N } + { + \exp_not:N \@@_exp_after_array_f:w #2 \s_@@_stop + \exp_not:N \exp_after:wN + \exp_not:N \@@_parse_infix_after_paren:NN + \exp_not:N \exp_after:wN #1 + \exp_not:N \exp:w + \exp_not:N \@@_parse_expand:w + } + { + \exp_not:N \__kernel_msg_expandable_error:nnn + { kernel } { fp-missing } { ) } + \exp_not:N \tl_if_empty:nT {#2} \exp_not:N \c_@@_empty_tuple_fp + #2 @ + \exp_not:N \use_none:n #3 + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_prefix_):Nw} +% The right parenthesis can appear as a prefix in two similar cases: +% in an empty tuple or tuple ending with a comma, or in an empty +% argument list or argument list ending with a comma, such as in +% |max(1,2,)| or in |rand()|. +% \begin{macrocode} +\cs_new:cpn { @@_parse_prefix_):Nw } #1 + { + \if_int_compare:w #1 = \c_@@_prec_comma_int + \else: + \if_int_compare:w #1 = \c_@@_prec_tuple_int + \exp_after:wN \c_@@_empty_tuple_fp \exp:w + \else: + \__kernel_msg_expandable_error:nnn + { kernel } { fp-missing-number } { ) } + \exp_after:wN \c_nan_fp \exp:w + \fi: + \exp_end_continue_f:w + \fi: + \@@_parse_infix_after_paren:NN #1 ) + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Constants} +% +% \begin{macro}[EXP] +% { +% \@@_parse_word_inf:N , \@@_parse_word_nan:N , +% \@@_parse_word_pi:N , \@@_parse_word_deg:N , +% \@@_parse_word_true:N , \@@_parse_word_false:N , +% } +% Some words correspond to constant floating points. The floating +% point constant is left as a result of \cs{@@_parse_one:Nw} after +% expanding \cs{@@_parse_infix:NN}. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 #2 + { + \cs_new:cpn { @@_parse_word_#1:N } + { \exp_after:wN #2 \exp:w \exp_end_continue_f:w \@@_parse_infix:NN } + } +\@@_tmp:w { inf } \c_inf_fp +\@@_tmp:w { nan } \c_nan_fp +\@@_tmp:w { pi } \c_pi_fp +\@@_tmp:w { deg } \c_one_degree_fp +\@@_tmp:w { true } \c_one_fp +\@@_tmp:w { false } \c_zero_fp +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP] +% { +% \@@_parse_caseless_inf:N, +% \@@_parse_caseless_infinity:N, +% \@@_parse_caseless_nan:N +% } +% Copies of \cs[no-index]{@@_parse_word_\ldots{}:N} commands, to allow +% arbitrary case as mandated by the standard. +% \begin{macrocode} +\cs_new_eq:NN \@@_parse_caseless_inf:N \@@_parse_word_inf:N +\cs_new_eq:NN \@@_parse_caseless_infinity:N \@@_parse_word_inf:N +\cs_new_eq:NN \@@_parse_caseless_nan:N \@@_parse_word_nan:N +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP] +% { +% \@@_parse_word_pt:N , \@@_parse_word_in:N , +% \@@_parse_word_pc:N , \@@_parse_word_cm:N , \@@_parse_word_mm:N , +% \@@_parse_word_dd:N , \@@_parse_word_cc:N , \@@_parse_word_nd:N , +% \@@_parse_word_nc:N , \@@_parse_word_bp:N , \@@_parse_word_sp:N , +% } +% Dimension units are also floating point constants but their value is +% not stored as a floating point constant. We give the values +% explicitly here. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 #2 + { + \cs_new:cpn { @@_parse_word_#1:N } + { + \@@_exp_after_f:nw { \@@_parse_infix:NN } + \s_@@ \@@_chk:w 10 #2 ; + } + } +\@@_tmp:w {pt} { {1} {1000} {0000} {0000} {0000} } +\@@_tmp:w {in} { {2} {7227} {0000} {0000} {0000} } +\@@_tmp:w {pc} { {2} {1200} {0000} {0000} {0000} } +\@@_tmp:w {cm} { {2} {2845} {2755} {9055} {1181} } +\@@_tmp:w {mm} { {1} {2845} {2755} {9055} {1181} } +\@@_tmp:w {dd} { {1} {1070} {0085} {6496} {0630} } +\@@_tmp:w {cc} { {2} {1284} {0102} {7795} {2756} } +\@@_tmp:w {nd} { {1} {1066} {9783} {4645} {6693} } +\@@_tmp:w {nc} { {2} {1280} {3740} {1574} {8031} } +\@@_tmp:w {bp} { {1} {1003} {7500} {0000} {0000} } +\@@_tmp:w {sp} { {-4} {1525} {8789} {0625} {0000} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_word_em:N, \@@_parse_word_ex:N} +% The font-dependent units |em| and |ex| must be evaluated on the fly. +% We reuse an auxiliary of \cs{dim_to_fp:n}. +% \begin{macrocode} +\tl_map_inline:nn { {em} {ex} } + { + \cs_new:cpn { @@_parse_word_#1:N } + { + \exp_after:wN \@@_from_dim_test:ww + \exp_after:wN 0 \exp_after:wN , + \int_value:w \dim_to_decimal_in_sp:n { 1 #1 } \exp_after:wN ; + \exp:w \exp_end_continue_f:w \@@_parse_infix:NN + } + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Functions} +% +% ^^A begin[todo] +% +% \begin{macro}[EXP] +% {\@@_parse_unary_function:NNN, \@@_parse_function:NNN} +% \begin{macrocode} +\cs_new:Npn \@@_parse_unary_function:NNN #1#2#3 + { + \exp_after:wN \@@_parse_apply_unary:NNNwN + \exp_after:wN #3 + \exp_after:wN #2 + \exp_after:wN #1 + \exp:w + \@@_parse_operand:Nw \c_@@_prec_func_int \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_function:NNN #1#2#3 + { + \exp_after:wN \@@_parse_apply_function:NNNwN + \exp_after:wN #3 + \exp_after:wN #2 + \exp_after:wN #1 + \exp:w + \@@_parse_operand:Nw \c_@@_prec_func_int \@@_parse_expand:w + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Main functions} +% +% \begin{macro}[EXP]{\@@_parse:n, \@@_parse_o:n} +% \begin{macro}[EXP]{\@@_parse_after:ww} +% Start an \cs{exp:w} expansion so that \cs{@@_parse:n} expands +% in two steps. The \cs{@@_parse_operand:Nw} function performs +% computations until reaching an operation with precedence +% \cs{c_@@_prec_end_int} or less, namely, the end of the expression. The +% marker \cs{s_@@_mark} indicates that the next token is an already +% parsed version of an infix operator, and \cs{@@_parse_infix_end:N} +% has infinitely negative precedence. Finally, clean up a +% (well-defined) set of extra tokens and stop the initial expansion +% with \cs{exp_end:}. +% \begin{macrocode} +\cs_new:Npn \@@_parse:n #1 + { + \exp:w + \exp_after:wN \@@_parse_after:ww + \exp:w + \@@_parse_operand:Nw \c_@@_prec_end_int + \@@_parse_expand:w #1 + \s_@@_mark \@@_parse_infix_end:N + \s_@@_stop + \exp_end: + } +\cs_new:Npn \@@_parse_after:ww + #1@ \@@_parse_infix_end:N \s_@@_stop #2 { #2 #1 } +\cs_new:Npn \@@_parse_o:n #1 + { + \exp:w + \exp_after:wN \@@_parse_after:ww + \exp:w + \@@_parse_operand:Nw \c_@@_prec_end_int + \@@_parse_expand:w #1 + \s_@@_mark \@@_parse_infix_end:N + \s_@@_stop + { + \exp_end_continue_f:w + \@@_exp_after_any_f:nw { \exp_after:wN \exp_stop_f: } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_operand:Nw} +% \begin{macro}[EXP]{\@@_parse_continue:NwN} +% This is just a shorthand which sets up both \cs{@@_parse_continue:NwN} +% and \cs{@@_parse_one:Nw} with the same precedence. Note the +% trailing \cs{exp:w}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_operand:Nw #1 + { + \exp_end_continue_f:w + \exp_after:wN \@@_parse_continue:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \exp_after:wN \@@_parse_one:Nw + \exp_after:wN #1 + \exp:w + } +\cs_new:Npn \@@_parse_continue:NwN #1 #2 @ #3 { #3 #1 #2 @ } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_apply_binary:NwNwN} +% \begin{macro}[EXP] +% {\@@_parse_apply_binary_chk:NN, \@@_parse_apply_binary_error:NNN} +% Receives \meta{precedence} \meta{operand_1} |@| \meta{operation} +% \meta{operand_2} |@| \meta{infix command}. Builds the appropriate +% call to the \meta{operation}~|#3|, dispatching on both types. +% If the resulting control sequence does not exist, the operation is +% not allowed. +% +% This is redefined in \pkg{l3fp-extras}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_apply_binary:NwNwN #1 #2#3@ #4 #5#6@ #7 + { + \exp_after:wN \@@_parse_continue:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \exp_after:wN \@@_parse_apply_binary_chk:NN + \cs:w + @@ + \@@_type_from_scan:N #2 + _#4 + \@@_type_from_scan:N #5 + _o:ww + \cs_end: + #4 + #2#3 #5#6 + \exp:w \exp_end_continue_f:w #7 #1 + } +\cs_new:Npn \@@_parse_apply_binary_chk:NN #1#2 + { + \if_meaning:w \scan_stop: #1 + \@@_parse_apply_binary_error:NNN #2 + \fi: + #1 + } +\cs_new:Npn \@@_parse_apply_binary_error:NNN #1#2#3 + { + #2 + \@@_invalid_operation_o:Nww #1 + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_binary_type_o:Nww, \@@_binary_rev_type_o:Nww} +% Applies the operator |#1| to its two arguments, dispatching +% according to their types, and expands once after the result. +% The |rev| version swaps its arguments before doing this. +% \begin{macrocode} +\cs_new:Npn \@@_binary_type_o:Nww #1 #2#3 ; #4 + { + \exp_after:wN \@@_parse_apply_binary_chk:NN + \cs:w + @@ + \@@_type_from_scan:N #2 + _ #1 + \@@_type_from_scan:N #4 + _o:ww + \cs_end: + #1 + #2 #3 ; #4 + } +\cs_new:Npn \@@_binary_rev_type_o:Nww #1 #2#3 ; #4#5 ; + { + \exp_after:wN \@@_parse_apply_binary_chk:NN + \cs:w + @@ + \@@_type_from_scan:N #4 + _ #1 + \@@_type_from_scan:N #2 + _o:ww + \cs_end: + #1 + #4 #5 ; #2 #3 ; + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Infix operators} +% +% \begin{macro}[EXP]{\@@_parse_infix_after_operand:NwN} +% \begin{macrocode} +\cs_new:Npn \@@_parse_infix_after_operand:NwN #1 #2; + { + \@@_exp_after_f:nw { \@@_parse_infix:NN #1 } + #2; + } +\cs_new:Npn \@@_parse_infix:NN #1 #2 + { + \if_catcode:w \scan_stop: \exp_not:N #2 + \if_int_compare:w + \@@_str_if_eq:nn { \s_@@_mark } { \exp_not:N #2 } + = 0 \exp_stop_f: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_mark:NNN + \else: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_juxt:N + \fi: + \else: + \if_int_compare:w + \@@_int_eval:w + ( `#2 \if_int_compare:w `#2 > `Z - 32 \fi: ) / 26 + = 3 \exp_stop_f: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_juxt:N + \else: + \exp_after:wN \@@_parse_infix_check:NNN + \cs:w + @@_parse_infix_ \token_to_str:N #2 :N + \exp_after:wN \exp_after:wN \exp_after:wN + \cs_end: + \fi: + \fi: + #1 + #2 + } +\cs_new:Npn \@@_parse_infix_check:NNN #1#2#3 + { + \if_meaning:w \scan_stop: #1 + \__kernel_msg_expandable_error:nnn + { kernel } { fp-missing } { * } + \exp_after:wN \@@_parse_infix_mul:N + \exp_after:wN #2 + \exp_after:wN #3 + \else: + \exp_after:wN #1 + \exp_after:wN #2 + \exp:w \exp_after:wN \@@_parse_expand:w + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_parse_infix_after_paren:NN} +% Variant of \cs{@@_parse_infix:NN} for use after a closing +% parenthesis. The only difference is that \cs{@@_parse_infix_juxt:N} +% is replaced by \cs{@@_parse_infix_mul:N}. +% \begin{macrocode} +\cs_new:Npn \@@_parse_infix_after_paren:NN #1 #2 + { + \if_catcode:w \scan_stop: \exp_not:N #2 + \if_int_compare:w + \@@_str_if_eq:nn { \s_@@_mark } { \exp_not:N #2 } + = 0 \exp_stop_f: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_mark:NNN + \else: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_mul:N + \fi: + \else: + \if_int_compare:w + \@@_int_eval:w + ( `#2 \if_int_compare:w `#2 > `Z - 32 \fi: ) / 26 + = 3 \exp_stop_f: + \exp_after:wN \exp_after:wN + \exp_after:wN \@@_parse_infix_mul:N + \else: + \exp_after:wN \@@_parse_infix_check:NNN + \cs:w + @@_parse_infix_ \token_to_str:N #2 :N + \exp_after:wN \exp_after:wN \exp_after:wN + \cs_end: + \fi: + \fi: + #1 + #2 + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Closing parentheses and commas} +% +% \begin{macro}[EXP]{\@@_parse_infix_mark:NNN} +% As an infix operator, \cs{s_@@_mark} means that the next +% token~(|#3|) has already gone through \cs{@@_parse_infix:NN} and +% should be provided the precedence~|#1|. The scan mark~|#2| is +% discarded. +% \begin{macrocode} +\cs_new:Npn \@@_parse_infix_mark:NNN #1#2#3 { #3 #1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_infix_end:N} +% This one is a little bit odd: force every previous operator to end, +% regardless of the precedence. +% \begin{macrocode} +\cs_new:Npn \@@_parse_infix_end:N #1 + { @ \use_none:n \@@_parse_infix_end:N } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]+\@@_parse_infix_):N+ +% This is very similar to \cs{@@_parse_infix_end:N}, complaining about +% an extra closing parenthesis if the previous operator was the +% beginning of the expression, with precedence \cs{c_@@_prec_end_int}. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 + { + \cs_new:Npn #1 ##1 + { + \if_int_compare:w ##1 > \c_@@_prec_end_int + \exp_after:wN @ + \exp_after:wN \use_none:n + \exp_after:wN #1 + \else: + \__kernel_msg_expandable_error:nnn { kernel } { fp-extra } { ) } + \exp_after:wN \@@_parse_infix:NN + \exp_after:wN ##1 + \exp:w \exp_after:wN \@@_parse_expand:w + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_):N } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[verb, EXP]{\__fp_parse_infix_,:N} +% \begin{macro}[EXP]{\@@_parse_infix_comma:w, \@@_parse_apply_comma:NwNwN} +% As for other infix operations, if the previous operations has higher +% precedence the comma waits. Otherwise we call +% \cs{@@_parse_operand:Nw} to read more comma-delimited arguments that +% \cs{@@_parse_infix_comma:w} simply concatenates into a |@|-delimited +% array. The first comma in a tuple that is not a function argument +% is distinguished: in that case call \cs{@@_parse_apply_comma:NwNwN} +% whose job is to convert the first item of the tuple and an array of +% the remaining items into a tuple. In contrast to +% \cs{@@_parse_apply_binary:NwNwN} this function's operands are not +% single-object arrays. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 + { + \cs_new:Npn #1 ##1 + { + \if_int_compare:w ##1 > \c_@@_prec_comma_int + \exp_after:wN @ + \exp_after:wN \use_none:n + \exp_after:wN #1 + \else: + \if_int_compare:w ##1 < \c_@@_prec_comma_int + \exp_after:wN @ + \exp_after:wN \@@_parse_apply_comma:NwNwN + \exp_after:wN , + \exp:w + \else: + \exp_after:wN \@@_parse_infix_comma:w + \exp:w + \fi: + \@@_parse_operand:Nw \c_@@_prec_comma_int + \exp_after:wN \@@_parse_expand:w + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_,:N } +\cs_new:Npn \@@_parse_infix_comma:w #1 @ + { #1 @ \use_none:n } +\cs_new:Npn \@@_parse_apply_comma:NwNwN #1 #2@ #3 #4@ #5 + { + \exp_after:wN \@@_parse_continue:NwN + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \@@_exp_after_tuple_f:nw { } + \s_@@_tuple \@@_tuple_chk:w { #2 #4 } ; + #5 #1 + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsubsection{Usual infix operators} +% +% \begin{macro}[EXP] +% { +% \@@_parse_infix_+:N, \@@_parse_infix_-:N, +% \@@_parse_infix_juxt:N, +% \@@_parse_infix_/:N, \@@_parse_infix_mul:N, +% \@@_parse_infix_and:N, \@@_parse_infix_or:N, +% } +% \begin{macro}[EXP]+\@@_parse_infix_^:N+ +% As described in the \enquote{work plan}, each infix operator has an +% associated |\..._infix_...| function, a computing function, and +% precedence, given as arguments to \cs{@@_tmp:w}. Using the general +% mechanism for arithmetic operations. The power operation must be +% associative in the opposite order from all others. For this, we use +% two distinct precedences. +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1#2#3#4 + { + \cs_new:Npn #1 ##1 + { + \if_int_compare:w ##1 < #3 + \exp_after:wN @ + \exp_after:wN \@@_parse_apply_binary:NwNwN + \exp_after:wN #2 + \exp:w + \@@_parse_operand:Nw #4 + \exp_after:wN \@@_parse_expand:w + \else: + \exp_after:wN @ + \exp_after:wN \use_none:n + \exp_after:wN #1 + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_^:N } ^ + \c_@@_prec_hatii_int \c_@@_prec_hat_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_juxt:N } * + \c_@@_prec_juxt_int \c_@@_prec_juxt_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_/:N } / + \c_@@_prec_times_int \c_@@_prec_times_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_mul:N } * + \c_@@_prec_times_int \c_@@_prec_times_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_-:N } - + \c_@@_prec_plus_int \c_@@_prec_plus_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_+:N } + + \c_@@_prec_plus_int \c_@@_prec_plus_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_and:N } & + \c_@@_prec_and_int \c_@@_prec_and_int +\exp_args:Nc \@@_tmp:w { @@_parse_infix_or:N } | + \c_@@_prec_or_int \c_@@_prec_or_int +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsubsection{Juxtaposition} +% +% \begin{macro}[EXP]+\@@_parse_infix_(:N+ +% When an opening parenthesis appears where we expect an infix +% operator, we compute the product of the previous operand and the +% contents of the parentheses using \cs{@@_parse_infix_mul:N}. +% \begin{macrocode} +\cs_new:cpn { @@_parse_infix_(:N } #1 + { \@@_parse_infix_mul:N #1 ( } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Multi-character cases} +% +% \begin{macro}[EXP]{\@@_parse_infix_*:N} +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1 + { + \cs_new:cpn { @@_parse_infix_*:N } ##1##2 + { + \if:w * \exp_not:N ##2 + \exp_after:wN #1 + \exp_after:wN ##1 + \else: + \exp_after:wN \@@_parse_infix_mul:N + \exp_after:wN ##1 + \exp_after:wN ##2 + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_^:N } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]+\@@_parse_infix_|:Nw+ +% \begin{macro}[EXP]+\@@_parse_infix_&:Nw+ +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1#2#3 + { + \cs_new:Npn #1 ##1##2 + { + \if:w #2 \exp_not:N ##2 + \exp_after:wN #1 + \exp_after:wN ##1 + \exp:w \exp_after:wN \@@_parse_expand:w + \else: + \exp_after:wN #3 + \exp_after:wN ##1 + \exp_after:wN ##2 + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_|:N } | \@@_parse_infix_or:N +\exp_args:Nc \@@_tmp:w { @@_parse_infix_&:N } & \@@_parse_infix_and:N +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsubsection{Ternary operator} +% +% \begin{macro}[EXP]{\@@_parse_infix_?:N, \@@_parse_infix_::N} +% \begin{macrocode} +\cs_set_protected:Npn \@@_tmp:w #1#2#3#4 + { + \cs_new:Npn #1 ##1 + { + \if_int_compare:w ##1 < \c_@@_prec_quest_int + #4 + \exp_after:wN @ + \exp_after:wN #2 + \exp:w + \@@_parse_operand:Nw #3 + \exp_after:wN \@@_parse_expand:w + \else: + \exp_after:wN @ + \exp_after:wN \use_none:n + \exp_after:wN #1 + \fi: + } + } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_?:N } + \@@_ternary:NwwN \c_@@_prec_quest_int { } +\exp_args:Nc \@@_tmp:w { @@_parse_infix_::N } + \@@_ternary_auxii:NwwN \c_@@_prec_colon_int + { + \__kernel_msg_expandable_error:nnnn + { kernel } { fp-missing } { ? } { ~for~?: } + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Comparisons} +% +% \begin{macro}[EXP] +% { +% \@@_parse_infix_<:N, \@@_parse_infix_=:N, +% \@@_parse_infix_>:N, \@@_parse_infix_!:N +% } +% \begin{macro}[EXP] +% { +% \@@_parse_excl_error:, +% \@@_parse_compare:NNNNNNN, +% \@@_parse_compare_auxi:NNNNNNN, +% \@@_parse_compare_auxii:NNNNN, +% \@@_parse_compare_end:NNNNw, +% \@@_compare:wNNNNw, +% } +% \begin{macrocode} +\cs_new:cpn { @@_parse_infix_<:N } #1 + { \@@_parse_compare:NNNNNNN #1 1 0 0 0 0 < } +\cs_new:cpn { @@_parse_infix_=:N } #1 + { \@@_parse_compare:NNNNNNN #1 1 0 0 0 0 = } +\cs_new:cpn { @@_parse_infix_>:N } #1 + { \@@_parse_compare:NNNNNNN #1 1 0 0 0 0 > } +\cs_new:cpn { @@_parse_infix_!:N } #1 + { + \exp_after:wN \@@_parse_compare:NNNNNNN + \exp_after:wN #1 + \exp_after:wN 0 + \exp_after:wN 1 + \exp_after:wN 1 + \exp_after:wN 1 + \exp_after:wN 1 + } +\cs_new:Npn \@@_parse_excl_error: + { + \__kernel_msg_expandable_error:nnnn + { kernel } { fp-missing } { = } { ~after~!. } + } +\cs_new:Npn \@@_parse_compare:NNNNNNN #1 + { + \if_int_compare:w #1 < \c_@@_prec_comp_int + \exp_after:wN \@@_parse_compare_auxi:NNNNNNN + \exp_after:wN \@@_parse_excl_error: + \else: + \exp_after:wN @ + \exp_after:wN \use_none:n + \exp_after:wN \@@_parse_compare:NNNNNNN + \fi: + } +\cs_new:Npn \@@_parse_compare_auxi:NNNNNNN #1#2#3#4#5#6#7 + { + \if_case:w + \@@_int_eval:w \exp_after:wN ` \token_to_str:N #7 - `< + \@@_int_eval_end: + \@@_parse_compare_auxii:NNNNN #2#2#4#5#6 + \or: \@@_parse_compare_auxii:NNNNN #2#3#2#5#6 + \or: \@@_parse_compare_auxii:NNNNN #2#3#4#2#6 + \or: \@@_parse_compare_auxii:NNNNN #2#3#4#5#2 + \else: #1 \@@_parse_compare_end:NNNNw #3#4#5#6#7 + \fi: + } +\cs_new:Npn \@@_parse_compare_auxii:NNNNN #1#2#3#4#5 + { + \exp_after:wN \@@_parse_compare_auxi:NNNNNNN + \exp_after:wN \prg_do_nothing: + \exp_after:wN #1 + \exp_after:wN #2 + \exp_after:wN #3 + \exp_after:wN #4 + \exp_after:wN #5 + \exp:w \exp_after:wN \@@_parse_expand:w + } +\cs_new:Npn \@@_parse_compare_end:NNNNw #1#2#3#4#5 \fi: + { + \fi: + \exp_after:wN @ + \exp_after:wN \@@_parse_apply_compare:NwNNNNNwN + \exp_after:wN \c_one_fp + \exp_after:wN #1 + \exp_after:wN #2 + \exp_after:wN #3 + \exp_after:wN #4 + \exp:w + \@@_parse_operand:Nw \c_@@_prec_comp_int \@@_parse_expand:w #5 + } +\cs_new:Npn \@@_parse_apply_compare:NwNNNNNwN + #1 #2@ #3 #4#5#6#7 #8@ #9 + { + \if_int_odd:w + \if_meaning:w \c_zero_fp #3 + 0 + \else: + \if_case:w \@@_compare_back_any:ww #8 #2 \exp_stop_f: + #5 \or: #6 \or: #7 \else: #4 + \fi: + \fi: + \exp_stop_f: + \exp_after:wN \@@_parse_apply_compare_aux:NNwN + \exp_after:wN \c_one_fp + \else: + \exp_after:wN \@@_parse_apply_compare_aux:NNwN + \exp_after:wN \c_zero_fp + \fi: + #1 #8 #9 + } +\cs_new:Npn \@@_parse_apply_compare_aux:NNwN #1 #2 #3; #4 + { + \if_meaning:w \@@_parse_compare:NNNNNNN #4 + \exp_after:wN \@@_parse_continue_compare:NNwNN + \exp_after:wN #1 + \exp_after:wN #2 + \exp:w \exp_end_continue_f:w + \@@_exp_after_o:w #3; + \exp:w \exp_end_continue_f:w + \else: + \exp_after:wN \@@_parse_continue:NwN + \exp_after:wN #2 + \exp:w \exp_end_continue_f:w + \exp_after:wN #1 + \exp:w \exp_end_continue_f:w + \fi: + #4 #2 + } +\cs_new:Npn \@@_parse_continue_compare:NNwNN #1#2 #3@ #4#5 + { #4 #2 #3@ #1 } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Tools for functions} +% +% \begin{macro}[EXP]{\@@_parse_function_all_fp_o:fnw} +% Followed by \Arg{function name} \Arg{code} \meta{float array} |@| +% this checks all floats are floating point numbers (no tuples). +% \begin{macrocode} +\cs_new:Npn \@@_parse_function_all_fp_o:fnw #1#2#3 @ + { + \@@_array_if_all_fp:nTF {#3} + { #2 #3 @ } + { + \@@_error:nffn { fp-bad-args } + {#1} + { \fp_to_tl:n { \s_@@_tuple \@@_tuple_chk:w {#3} ; } } + { } + \exp_after:wN \c_nan_fp + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_parse_function_one_two:nnw} +% \begin{macro}[EXP] +% { +% \@@_parse_function_one_two_error_o:w, +% \@@_parse_function_one_two_aux:nnw, +% \@@_parse_function_one_two_auxii:nnw +% } +% This is followed by \Arg{function name} \Arg{code} \meta{float +% array} |@|. It checks that the \meta{float array} consists of one +% or two floating point numbers (not tuples), then leaves the +% \meta{code} (if there is one float) or its tail (if there are two +% floats) followed by the \meta{float array}. The \meta{code} should +% start with a single token such as \cs{@@_atan_default:w} that deals +% with the single-float case. +% +% The first \cs{@@_if_type_fp:NTwFw} test catches the case of no +% argument and the case of a tuple argument. The next one +% distinguishes the case of a single argument (no error, just add +% \cs{c_one_fp}) from a tuple second argument. Finally check there is +% no further argument. +% \begin{macrocode} +\cs_new:Npn \@@_parse_function_one_two:nnw #1#2#3 + { + \@@_if_type_fp:NTwFw + #3 { } \s_@@ \@@_parse_function_one_two_error_o:w \q_stop + \@@_parse_function_one_two_aux:nnw {#1} {#2} #3 + } +\cs_new:Npn \@@_parse_function_one_two_error_o:w #1#2#3#4 @ + { + \@@_error:nffn { fp-bad-args } + {#2} + { \fp_to_tl:n { \s_@@_tuple \@@_tuple_chk:w {#4} ; } } + { } + \exp_after:wN \c_nan_fp + } +\cs_new:Npn \@@_parse_function_one_two_aux:nnw #1#2 #3; #4 + { + \@@_if_type_fp:NTwFw + #4 { } + \s_@@ + { + \if_meaning:w @ #4 + \exp_after:wN \use_iv:nnnn + \fi: + \@@_parse_function_one_two_error_o:w + } + \q_stop + \@@_parse_function_one_two_auxii:nnw {#1} {#2} #3; #4 + } +\cs_new:Npn \@@_parse_function_one_two_auxii:nnw #1#2#3; #4; #5 + { + \if_meaning:w @ #5 \else: + \exp_after:wN \@@_parse_function_one_two_error_o:w + \fi: + \use_ii:nn {#1} { \use_none:n #2 } #3; #4; #5 + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_tuple_map_o:nw, \@@_tuple_map_loop_o:nw} +% Apply |#1| to all items in the following tuple and expand once +% afterwards. The code |#1| should itself expand once after its +% result. +% \begin{macrocode} +\cs_new:Npn \@@_tuple_map_o:nw #1 \s_@@_tuple \@@_tuple_chk:w #2 ; + { + \exp_after:wN \s_@@_tuple + \exp_after:wN \@@_tuple_chk:w + \exp_after:wN { + \exp:w \exp_end_continue_f:w + \@@_tuple_map_loop_o:nw {#1} #2 + { \s_@@ \prg_break: } ; + \prg_break_point: + \exp_after:wN } \exp_after:wN ; + } +\cs_new:Npn \@@_tuple_map_loop_o:nw #1#2#3 ; + { + \use_none:n #2 + #1 #2 #3 ; + \exp:w \exp_end_continue_f:w + \@@_tuple_map_loop_o:nw {#1} + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_tuple_mapthread_o:nww, \@@_tuple_mapthread_loop_o:nw} +% Apply |#1| to pairs of items in the two following tuples and expand once +% afterwards. +% \begin{macrocode} +\cs_new:Npn \@@_tuple_mapthread_o:nww #1 + \s_@@_tuple \@@_tuple_chk:w #2 ; + \s_@@_tuple \@@_tuple_chk:w #3 ; + { + \exp_after:wN \s_@@_tuple + \exp_after:wN \@@_tuple_chk:w + \exp_after:wN { + \exp:w \exp_end_continue_f:w + \@@_tuple_mapthread_loop_o:nw {#1} + #2 { \s_@@ \prg_break: } ; @ + #3 { \s_@@ \prg_break: } ; + \prg_break_point: + \exp_after:wN } \exp_after:wN ; + } +\cs_new:Npn \@@_tuple_mapthread_loop_o:nw #1#2#3 ; #4 @ #5#6 ; + { + \use_none:n #2 + \use_none:n #5 + #1 #2 #3 ; #5 #6 ; + \exp:w \exp_end_continue_f:w + \@@_tuple_mapthread_loop_o:nw {#1} #4 @ + } +% \end{macrocode} +% \end{macro} +% +% ^^A end[todo] +% +% \subsection{Messages} +% +% \begin{macrocode} +\__kernel_msg_new:nnn { kernel } { fp-deprecated } + { '#1'~deprecated;~use~'#2' } +\__kernel_msg_new:nnn { kernel } { unknown-fp-word } + { Unknown~fp~word~#1. } +\__kernel_msg_new:nnn { kernel } { fp-missing } + { Missing~#1~inserted #2. } +\__kernel_msg_new:nnn { kernel } { fp-extra } + { Extra~#1~ignored. } +\__kernel_msg_new:nnn { kernel } { fp-early-end } + { Premature~end~in~fp~expression. } +\__kernel_msg_new:nnn { kernel } { fp-after-e } + { Cannot~use~#1 after~'e'. } +\__kernel_msg_new:nnn { kernel } { fp-missing-number } + { Missing~number~before~'#1'. } +\__kernel_msg_new:nnn { kernel } { fp-unknown-symbol } + { Unknown~symbol~#1~ignored. } +\__kernel_msg_new:nnn { kernel } { fp-extra-comma } + { Unexpected~comma~turned~to~nan~result. } +\__kernel_msg_new:nnn { kernel } { fp-no-arg } + { #1~got~no~argument;~used~nan. } +\__kernel_msg_new:nnn { kernel } { fp-multi-arg } + { #1~got~more~than~one~argument;~used~nan. } +\__kernel_msg_new:nnn { kernel } { fp-num-args } + { #1~expects~between~#2~and~#3~arguments. } +\__kernel_msg_new:nnn { kernel } { fp-bad-args } + { Arguments~in~#1#2~are~invalid. } +\__kernel_msg_new:nnn { kernel } { fp-infty-pi } + { Math~command~#1 is~not~an~fp } +%<*package> +\cs_if_exist:cT { @unexpandable@protect } + { + \__kernel_msg_new:nnn { kernel } { fp-robust-cmd } + { Robust~command~#1 invalid~in~fp~expression! } + } +%</package> +% \end{macrocode} +% +% \begin{macrocode} +%</initex|package> +% \end{macrocode} +% +% \end{implementation} +% +% \PrintChanges +% +% \PrintIndex |