% \iffalse meta-comment % %% File: l3fp-parse.dtx % % Copyright (C) 2011-2024 The LaTeX 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} % % \fi % % \title{^^A % The \pkg{l3fp-parse} module\\ % Floating point expression parsing^^A % } % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % \date{Released 2024-03-14} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3fp-parse} implementation} % % \begin{macrocode} %<*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{fp expr} % \end{syntax} % Evaluates the \meta{fp expr} 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_@@_expr_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_@@_expr_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_expr_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_@@_expr_mark} is a premature end, we call % \cs{@@_exp_after_expr_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_expr_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 } } { \msg_expandable_error:nn { 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 { \msg_expandable_error:nnn { kernel } { bad-variable } {#2} \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w #1 } \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 } { \msg_expandable_error:nnn { fp } { robust-cmd } } } { \msg_expandable_error:nnn { kernel } { bad-variable } {##2} } } } } \exp_args:Nc \@@_tmp:w { @unexpandable@protect } % \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:Npe \@@_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 \s_@@_stop } \exp_args:Nno \use:nn { \cs_new:Npn \@@_parse_one_register_auxii:wwwNw #1 . #2 } { \tl_to_str:n { pt } #3 ; #4#5 \s_@@_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} { \msg_expandable_error:nnn { 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_casefold:n {#2} :N } { \msg_expandable_error:nnn { fp } { 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 } { \msg_expandable_error:nnn { fp } { missing-number } {#1} \exp_after:wN \c_nan_fp \exp:w \exp_end_continue_f:w \@@_parse_infix:NN #3 #1 } { \msg_expandable_error:nnn { 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:NN} % 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| (or~|E|), leave an exponent of~$0$. If % there is an~|e| or~|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 \if:w E \exp_not:N #1 e \else: \exp_not:N #1 \fi: \exp_after:wN \@@_parse_exponent_aux:NN \exp_after:wN #1 \exp:w \else: 0 \@@_parse_return_semicolon:w #1 \fi: \@@_parse_expand:w } \cs_new:Npn \@@_parse_exponent_aux:NN #1#2 { \if_int_compare:w \if_catcode:w \scan_stop: \exp_not:N #2 0 \else: `#2 \fi: > `9 \exp_stop_f: 0 \exp_after:wN ; \exp_after:wN #1 \else: \exp_after:wN \@@_parse_exponent_sign:N \fi: #2 } % \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:w 0 \@@_str_if_eq:nn { \s_@@ } { \exp_not:N #1 } 0 \msg_expandable_error:nnn { fp } { after-e } { floating~point~ } \prg_return_true: \else: 0 \msg_expandable_error:nnn { kernel } { bad-variable } {#1} \prg_return_false: \fi: \else: \if:w 0 \@@_str_if_eq:nn { \int_value:w #1 } { \tex_the:D #1 } \int_value:w #1 \else: 0 \msg_expandable_error:nnn { fp } { after-e } { dimension~#1 } \fi: \prg_return_false: \fi: \else: 0 \msg_expandable_error:nnn { 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 @ ; . \s_@@_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 \s_@@_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 { #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:Npe \@@_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_@@_expr_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 \msg_expandable_error:nnn { 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: \msg_expandable_error:nnn { 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_@@_expr_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_@@_expr_mark \@@_parse_infix_end:N \s_@@_expr_stop \exp_end: } \cs_new:Npn \@@_parse_after:ww #1@ \@@_parse_infix_end:N \s_@@_expr_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_@@_expr_mark \@@_parse_infix_end:N \s_@@_expr_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:w 0 \@@_str_if_eq:nn { \s_@@_expr_mark } { \exp_not:N #2 } \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 \msg_expandable_error:nnn { 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:w 0 \@@_str_if_eq:nn { \s_@@_expr_mark } { \exp_not:N #2 } \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_@@_expr_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: \msg_expandable_error:nnn { 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 { \msg_expandable_error:nnnn { 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: { \msg_expandable_error:nnnn { 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 { 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 \s_@@_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 { 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 } \s_@@_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} \msg_new:nnn { fp } { deprecated } { '#1'~deprecated;~use~'#2' } \msg_new:nnn { fp } { unknown-fp-word } { Unknown~fp~word~#1. } \msg_new:nnn { fp } { missing } { Missing~#1~inserted #2. } \msg_new:nnn { fp } { extra } { Extra~#1~ignored. } \msg_new:nnn { fp } { early-end } { Premature~end~in~fp~expression. } \msg_new:nnn { fp } { after-e } { Cannot~use~#1 after~'e'. } \msg_new:nnn { fp } { missing-number } { Missing~number~before~'#1'. } \msg_new:nnn { fp } { unknown-symbol } { Unknown~symbol~#1~ignored. } \msg_new:nnn { fp } { extra-comma } { Unexpected~comma~turned~to~nan~result. } \msg_new:nnn { fp } { no-arg } { #1~got~no~argument;~used~nan. } \msg_new:nnn { fp } { multi-arg } { #1~got~more~than~one~argument;~used~nan. } \msg_new:nnn { fp } { num-args } { #1~expects~between~#2~and~#3~arguments. } \msg_new:nnn { fp } { bad-args } { Arguments~in~#1#2~are~invalid. } \msg_new:nnn { fp } { infty-pi } { Math~command~#1 is~not~an~fp } \cs_if_exist:cT { @unexpandable@protect } { \msg_new:nnn { fp } { robust-cmd } { Robust~command~#1 invalid~in~fp~expression! } } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex