% \iffalse meta-comment % %% File: l3fp-parse.dtx Copyright (C) 2011-2014 The LaTeX3 Project %% %% It may be distributed and/or modified under the conditions of the %% LaTeX Project Public License (LPPL), either version 1.3c of this %% license or (at your option) any later version. The latest version %% of this license is in the file %% %% http://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 released version of this bundle is available from CTAN. %% %% ----------------------------------------------------------------------- %% %% The development version of the bundle can be found at %% %% http://www.latex-project.org/svnroot/experimental/trunk/ %% %% for those people who are interested. %% %%%%%%%%%%% %% NOTE: %% %%%%%%%%%%% %% %% Snapshots taken from the repository represent work in progress and may %% not work or may contain conflicting material! We therefore ask %% people _not_ to put them into distributions, archives, etc. without %% prior consultation with the LaTeX Project Team. %% %% ----------------------------------------------------------------------- %% % %<*driver> \documentclass[full]{l3doc} \GetIdInfo$Id: l3fp-parse.dtx 5383 2014-08-25 13:17:55Z bruno $ {L3 Floating-point expression parsing} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{The \textsf{l3fp-parse} package\thanks{This file % has version number \fileversion, last % revised \filedate.}\\ % Floating point expression parsing} % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % \date{Released \filedate} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3fp-parse} implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \begin{macrocode} %<@@=fp> % \end{macrocode} % % \subsection{Work plan} % % The task at hand is non-trivial, and some previous failed attempts % show that the code leads to unreadable logs, so we had better get it % (almost) right the first time. Let us first describe our goal, then % discuss the design precisely before writing any code. % % \begin{macro}[EXP, int]{\@@_parse:n} % \begin{syntax} % \cs{@@_parse:n} \Arg{fpexpr} % \end{syntax} % Evaluates the \meta{floating point expression} and leaves the result % in the input stream as an internal floating point number. This % function forms the basis of almost all public \pkg{l3fp} functions. % During evaluation, each token is fully \texttt{f}-expanded. % \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 will lead to % unrecoverable low-level \TeX{} errors. % \end{texnote} % \end{macro} % % 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 with multiple arguments. % \item[15] Function calls expecting exactly one argument. % \item[14] Binary |**| and~|^| (right to left). % \item[12] Unary |+|, |-|, |!| (right to left). % \item[10] Binary |*|, |/|, and juxtaposition (implicit~|*|). % \item[9] Binary |+| and~|-|. % \item[7] Comparisons. % \item[5] Logical \texttt{and}, denoted by~|&&|. % \item[4] Logical \texttt{or}, denoted by~\verb*+||+. % \item[3] Ternary operator |?:|, piece~|?|. % \item[2] Ternary operator |?:|, piece~|:|. % \item[1] Commas, and parentheses accepting commas. % \item[0] Parentheses expecting exactly one argument. % \item[-1] Start and end of the expression. % \end{itemize} % % \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 % will grow 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 much 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{quote}\ttfamily % \cs{exp_after:wN} \cs{add:ww} % \cs{__int_value:w} 12345 \cs{exp_after:wN} ; \newline % \cs{tex_romannumeral:D} |\operand:w| \meta{stuff} % \end{quote} % 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{tex_romannumeral:D}, which expands |\operand:w|, defined to read % what follows and make a number out of it, then leave \cs{c_zero}, the % number, and a semicolon in the input stream. Once |\operand:w| is % done expanding, we obtain essentially % \begin{quote}\ttfamily % \cs{exp_after:wN} \cs{add:ww} \cs{__int_value:w} 12345 ; \newline % \cs{tex_romannumeral:D} \cs{c_zero} 333444 ; % \end{quote} % where in fact \cs{exp_after:wN} has already been expanded, % \cs{__int_value:w} has already seen |12345|, and % \cs{tex_romannumeral:D} is still looking for a number. It finds % \cs{c_zero}, 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{quote}\ttfamily % \cs{add:ww} 12345 ; 333444 ; % \end{quote} % 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 \cs{add:ww}. Just like \cs{__int_value:w} |12345| % \cs{exp_after:wN}~|;| expanded what follows once, we need \cs{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{@@_..._o:ww} expand what follows once. This comes at the % cost of leaving tokens in the input stack, and we will 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 the calculation % |41-2^3*4+5| will be done. Here, we abuse notations: the first % argument of |\operand:Nw| should be an integer constant (\cs{c_three}, % \cs{c_nine}, \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 will be 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} \newline % ~~|\__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{quote} % |@| \cs{use_none:n} |\__fp_parse_infix_|\meta{operator}|:N| % \end{quote} % 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{quote} % |@| \cs{@@_parse_apply_binary:NwNwN} \newline % ~~~~\meta{operator} \meta{number_2} \newline % |@| |\__fp_parse_infix_|\meta{operator_2}|:N| % \end{quote} % 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{quote} % \cs{exp_after:wN} \cs{@@_parse_continue:NwN} \newline % \cs{exp_after:wN} \meta{precedence} \newline % \cs{tex_romannumeral:D} |-`0| \newline % ~~\cs{@@_parse_one:Nw} \meta{precedence} % \end{quote} % 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{quote} % \cs{@@_parse_continue:NwN} \meta{precedence} \newline % \meta{number} |@| \newline % \cs{use_none:n} |\__fp_parse_infix_|\meta{operator}|:N| % \end{quote} % or % \begin{quote} % \cs{@@_parse_continue:NwN} \meta{precedence} \newline % \meta{number} |@| \newline % \cs{@@_parse_apply_binary:NwNwN} \newline % ~~\meta{operator} \meta{number_2} \newline % |@| |\__fp_parse_infix_|\meta{operator_2}|:N| % \end{quote} % The definition of \cs{@@_parse_continue:NwN} is then very simple: % \begin{verbatim} % \cs_new:Npn \__fp_parse_continue:NwN #1#2@#3 { #3 #1 #2 @ } % \end{verbatim} % In the first case, |#3|~is \cs{use_none:n}, yielding % \begin{quote} % \cs{use_none:n} \meta{precedence} \meta{number} |@| \newline % |\__fp_parse_infix_|\meta{operator}|:N| % \end{quote} % 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{quote} % \cs{@@_parse_apply_binary:NwNwN} \newline % ~~\meta{precedence} \meta{number} |@| \newline % ~~\meta{operator} \meta{number_2} \newline % |@| |\__fp_parse_infix_|\meta{operator_2}|:N| % \end{quote} % then % \begin{quote} % \cs{exp_after:wN} \cs{@@_parse_continue:NwN} \newline % \cs{exp_after:wN} \meta{precedence} \newline % \cs{tex_romannumeral:D} |-`0| \newline % |\__fp_|\meta{operator}|_o:ww| \meta{number} \meta{number_2} \newline % \cs{tex_romannumeral:D} |-`0| \newline % |\__fp_parse_infix_|\meta{operator_2}|:N| \meta{precedence} % \end{quote} % 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 will describe various subtleties. % % \subsubsection{Prefix operators, parentheses, and functions} % % Prefix operators (unary |-|, |+|,~|!|) and parentheses are taken care % of by the same mechanism, and functions (\texttt{sin}, \texttt{exp}, % etc.) as well. Finding the argument of the unary~|-|, for instance, % is very similar to grabbing the second operand of a binary infix % operator, with a subtle precedence explained below. Once that operand % is found, the operator can be applied to it (for the unary~|-|, this % simply flips the sign). A left parenthesis is just a prefix operator % with a very low precedence equal to that of the closing parenthesis % (which is treated as an infix operator, since it normally appears just % after numbers), so that all computations are performed until the % closing parenthesis. The prefix operator associated to the left % parenthesis does not alter its argument, but it removes the closing % parenthesis (with some checks). % % Prefix operators are the reason why we only summarily described the % function \cs{@@_parse_one:Nw} earlier. This function is responsible % for reading in the input stream the first possible \meta{number} and % the next infix \meta{operator}. If what follows \cs{@@_parse_one:Nw} % \meta{precedence} is a prefix operator, then we must find the operand % of this prefix operator through a nested call to % \cs{@@_parse_operand:Nw} with the appropriate precedence, then apply % the operator to the operand found to yield the result of % \cs{@@_parse_one:Nw}. So far, all is simple. % % The unary operators |+|, |-|,~|!| complicate things a little bit: % |-3**2| should be $-(3^2)=-9$, and not $(-3)^2=9$. This would easily % be done by giving~|-| a lower precedence, equal to that of the infix % |+| and~|-|. Unfortunately, this fails in cases such as |3**-2*4|, % yielding $3^{-2\times 4}$ instead of the correct $3^{-2}\times 4$. A % second attempt would be to call \cs{@@_parse_operand:Nw} with the % \meta{precedence} of the previous operator, but |0>-2+3| is then % parsed as |0>-(2+3)|: the addition is performed because it binds more % tightly than the comparision which precedes~|-|. The correct approach % is for a unary~|-| to perform operations whose precedence is greater % than both that of the previous operation, and that of the unary~|-| % itself. The unary~|-| is given a precedence higher than % multiplication and division. This does not lead to any surprising % result, since $-(x/y) = (-x)/y$ and similarly for multiplication, and % it reduces the number of nested calls to \cs{@@_parse_operand:Nw}. % % Functions are implemented as prefix operators with very high % precedence, so that their argument is the first number that can % possibly be built. % % Note that contrarily to the \texttt{infix} functions discussed % earlier, the \texttt{prefix} functions do perform tests on the % previous \meta{precedence} to decide whether to find an argument or % not, since we know that we need a number, and must never stop there. % % \subsubsection{Numbers and reading tokens one by one} % % So far, we have glossed over one important point: what is a % \enquote{number}? A number is typically given in the form % \meta{significand}|e|\meta{exponent}, where the \meta{significand} is % any non-empty string composed of decimal digits and at most one % decimal separator (a period), the exponent % \enquote{\texttt{e}\meta{exponent}} is optional and is composed of an % exponent mark~|e| followed by a possibly empty string of signs % |+| or~|-| and a non-empty string of decimal digits. The % \meta{significand} can also be an integer, dimension, skip, or muskip % variable, in which case dimensions are converted from points (or mu % units) to floating points, and the \meta{exponent} can also be an % integer variable. Numbers can also be given as floating point % variables, or as named constants such as |nan|, |inf| or~|pi|. We may % add more types in the future. % % When \cs{@@_parse_one:Nw} is looking for a \enquote{number}, here is % what happens. % \begin{itemize} % \item If the next token is a control sequence with the meaning of % \cs{scan_stop:}, it can be: \cs{s_@@}, in which case our job is % done, as what follows is an internal floating point number, or % \cs{s_@@_mark}, in which case the expression has come to an early % end, as we are still looking for a number here, or something else, % in which case we consider the control sequence to be a bad % variable resulting from \texttt{c}-expansion. % \item If the next token is a control sequence with a different % meaning, we assume that it is a register, unpack it with % \cs{tex_the:D}, and use its value (in \texttt{pt} for dimensions % and skips, \texttt{mu} for muskips) as the \meta{significand} of a % number: we look for an exponent. % \item If the next token is a digit, we remove any leading zeros, % then read a significand larger than~$1$ if the next character is a % digit, read a significand smaller than~$1$ if the next character % is a period, or we have found a significand equal to~$0$ % otherwise, and look for an exponent. % \item If the next token is a letter, we collect more letters until % the first non-letter: the resulting word may denote a function % such as |asin|, a constant such as |pi| or be unknown. In the % first case, we call \cs{@@_parse_operand:Nw} to find the argument % of the function, then apply the function, before declaring that we % are done. Otherwise, we are done, either with the value of the % constant, or with the value |nan| for unknown words. % \item If the next token is anything else, we check whether it is a % known prefix operator, in which case \cs{@@_parse_operand:Nw} % finds its operand. If it is not known, then either a number is % missing (if the token is a known infix operator) or the token is % simply invalid in floating point expressions. % \end{itemize} % Once a number is found, \cs{@@_parse_one:Nw} also finds an infix % operator. This goes as follows. % \begin{itemize} % \item If the next token is a control sequence, it could be the % special marker \cs{s_@@_mark}, and % otherwise it is a case of juxtaposing numbers, such as % |2\c_three|, 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_three 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 \c_nine < 1 \token_to_str:N #1 \exp_stop_f: % is a digit % \else: % not a digit % \fi: % \end{verbatim} % To exclude |0|, replace \cs{c_nine} by \cs{c_ten}. 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 \__int_eval:w % ( `#1 \if_int_compare:w `#1 > `Z - 32 \fi: ) / 26 = \c_three % 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 $\{1, 2, 4, 10, 13\}$ will % not work, 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 will not be expanded if we simply perform % \texttt{f}-expansion. % \begin{verbatim} % \DeclareDocumentCommand {\test} {m} { \fp_eval:n {#1} } % \ExplSyntaxOff % \test { 1 + \X } % \end{verbatim} % Of course, spaces will 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 will stop % the \texttt{f}-expansion before further macro calls are performed. % However, in practice it should be enough: in particular, floating % point numbers will correctly be 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, aux]{\@@_parse_operand:Nw} % \begin{syntax} % \cs{tex_romannumeral:D} \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, aux]{\@@_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, aux]{\@@_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}[aux, rEXP]{\@@_parse_expand:w} % \begin{syntax} % \cs{tex_romannumeral:D} \cs{@@_parse_expand:w} \meta{tokens} % \end{syntax} % This function must always come within a \tn{romannumeral} 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 { -`0 #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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}[aux, EXP]{\@@_type_from_scan:N, \@@_type_from_scan:w} % \begin{syntax} % \cs{@@_type_from_scan:N} \meta{token} % \end{syntax} % Grabs the pieces of the stringified \meta{token} which lies after % the first |s__fp|. If the \meta{token} does not contain that % string, the result is |_?|. % \begin{macrocode} \group_begin: \char_set_catcode_other:N \S \char_set_catcode_other:N \F \char_set_catcode_other:N \P \char_set_lccode:nn { `\- } { `\_ } \tl_to_lowercase:n { \group_end: \cs_new:Npn \@@_type_from_scan:N #1 { \exp_after:wN \@@_type_from_scan:w \token_to_str:N #1 \q_mark S--FP-? \q_mark \q_stop } \cs_new:Npn \@@_type_from_scan:w #1 S--FP #2 \q_mark #3 \q_stop {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}[rEXP, aux] % { % \@@_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 % } % 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{quote} % \meta{digits} |;| \meta{filling 0} |;| \meta{length} % \end{quote} % 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 \c_nine < 1 \token_to_str:N ##1 \exp_stop_f: \token_to_str:N ##1 \exp_after:wN #2 \tex_romannumeral:D \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_nopar:Npn \@@_parse_digits_:N { ; ; 0 } % \end{macrocode} % \end{macro} % % \subsection{Parsing one number} % % \begin{macro}[aux, EXP]{\@@_parse_one:Nw} % This function finds one number, and packs the symbol which follows % in an \cs{infix_} 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 will be split further. Despite the earlier % \texttt{f}-expansion, |#2|~may still be expandable if it was % protected by \cs{exp_not:N}, as happens with the \LaTeXe{} command % \tn{protect}. Testing if |#2|~is a control sequence thus includes % \cs{exp_not:N}. % \begin{macrocode} \cs_new:Npn \@@_parse_one:Nw #1 #2 { \if_catcode:w \scan_stop: \exp_not:N #2 \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 \c_nine < 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}[aux, EXP] % { % \@@_parse_one_fp:NN, % \@@_exp_after_mark_f:nw, % \@@_exp_after_?_f:nw % } % This function receives a \meta{precedence} and a control sequence % equal to \cs{scan_stop:} in meaning. There are three cases, % dispatched using \cs{@@_type_from_scan:N}. % \begin{itemize} % \item \cs{s_@@} starts a floating point number, and we call % \cs{@@_exp_after_f:nw}, which |f|-expands after the floating % point. % \item \cs{s_@@_mark} is a premature end, we call % \cs{@@_exp_after_mark_f:nw}, which triggers an |fp-early-end| % error. % \item For a control sequence not containing |\s__fp|, 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 |\s__fp_|\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. % \begin{macrocode} \cs_new:Npn \@@_parse_one_fp:NN #1#2 { \cs:w @@_exp_after \@@_type_from_scan:N #2 _f:nw \cs_end: { \exp_after:wN \@@_parse_infix:NN \exp_after:wN #1 \tex_romannumeral:D \@@_parse_expand:w } #2 } \cs_new:Npn \@@_exp_after_mark_f:nw #1 { \__msg_kernel_expandable_error:nn { kernel } { fp-early-end } \exp_after:wN \c_nan_fp \tex_romannumeral:D -`0 #1 } \cs_new:cpn { @@_exp_after_?_f:nw } #1#2 { \__msg_kernel_expandable_error:nnn { kernel } { bad-variable } {#2} \exp_after:wN \c_nan_fp \tex_romannumeral:D -`0 #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 assume that it is a register, but % carefully unpacking 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{fp_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_eval:w} \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 \tex_romannumeral:D -`0 \exp_after:wN \@@_parse_one_register_aux:Nw \exp_after:wN #2 \__int_value:w \exp_after:wN \@@_parse_exponent:N \tex_romannumeral:D \@@_parse_expand:w } \group_begin: \char_set_catcode_other:N \P \char_set_catcode_other:N \T \char_set_catcode_other:N \M \char_set_catcode_other:N \U \tl_to_lowercase:n { \group_end: \cs_new:Npn \@@_parse_one_register_aux:Nw #1 { \exp_after:wN \use:nn \exp_after:wN \@@_parse_one_register_auxii:wwwNw \exp_after:wN { \tex_the:D \exp_not:N #1 } ; \@@_parse_one_register_dim:ww PT ; \@@_parse_one_register_mu:www . PT ; \@@_parse_one_register_int:www \q_stop } \cs_new:Npn \@@_parse_one_register_auxii:wwwNw #1 . #2 PT #3 ; #4#5 \q_stop { #4 #1.#2; } \cs_new:Npn \@@_parse_one_register_mu:www #1 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_eval:w #1 pt ; } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_one_digit:NN} % A digit marks the beginning of an explicit floating point number. % Once the number is found, we will 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 \tex_romannumeral:D -`0 \exp_after:wN \@@_sanitize:wN \int_use:N \__int_eval:w \c_zero \@@_parse_trim_zeros:N } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_one_other:NN} % For this function, |#2|~is a character token which is not a digit. % If it is a 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 - \c_thirty_two \fi: ) / 26 = \c_three \exp_after:wN \@@_parse_word:Nw \exp_after:wN #1 \exp_after:wN #2 \tex_romannumeral:D \exp_after:wN \@@_parse_letters:N \tex_romannumeral:D \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: \tex_romannumeral:D \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_word:Nw} % \begin{macro}[aux, 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. % \begin{macrocode} \cs_new:Npn \@@_parse_word:Nw #1#2; { \cs_if_exist_use:cF { @@_parse_word_#2:N } { \__msg_kernel_expandable_error:nnn { kernel } { unknown-fp-word } {#2} \exp_after:wN \c_nan_fp \tex_romannumeral:D -`0 \@@_parse_infix:NN } #1 } \cs_new:Npn \@@_parse_letters:N #1 { -`0 \if_int_compare:w \if_catcode:w \scan_stop: \exp_not:N #1 \c_zero \else: \__int_eval:w ( `#1 \if_int_compare:w `#1 > `Z - \c_thirty_two \fi: ) / 26 \fi: = \c_three \exp_after:wN #1 \tex_romannumeral:D \exp_after:wN \@@_parse_letters:N \tex_romannumeral:D \else: \@@_parse_return_semicolon:w #1 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux, 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_kernel_expandable_error:nnn { kernel } { fp-missing-number } {#1} \exp_after:wN \c_nan_fp \tex_romannumeral:D -`0 \@@_parse_infix:NN #3 #1 } { \__msg_kernel_expandable_error:nnn { kernel } { fp-unknown-symbol } {#1} \@@_parse_one:Nw #3 } } % \end{macrocode} % \end{macro} % % \subsubsection{Numbers: trimming leading zeros} % % Numbers will be 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}[aux, 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 \tex_romannumeral:D \else: \if:w . \exp_not:N #1 \exp_after:wN \@@_parse_strim_zeros:N \tex_romannumeral:D \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 \c_nine < 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}[aux, 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 - \c_one \exp_after:wN \@@_parse_strim_zeros:N \tex_romannumeral:D \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 \c_nine < 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}[aux, EXP]{\@@_parse_zero:} % After reading a significand of~$0$, we need to remove any exponent, % then put a sign of~|1| for \cs{@@_sanitize:wN}, small hack to denote % an exact zero (rather than an underflow). % \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}[aux, 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 will leave those digits in the \cs{__int_value:w}, and % grab some more, or stop 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_use:N \__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 \tex_romannumeral:D \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 \cs{c_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 \c_zero \int_use:N \__int_eval:w 1 \if_int_compare:w \c_nine < 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 \tex_romannumeral:D \else: 0000 0000 \@@_parse_exponent:Nw #4 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 |+\c_zero| or to |+\c_one|. % 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 \c_nine < 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 \tex_romannumeral:D \else: 0 \@@_parse_exponent:Nw #4 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 (|+ \c_one| % 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 + \c_one \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: + \c_one ; 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}[aux, 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 \tex_romannumeral:D \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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_eight - #3 \exp_after:wN \@@_parse_pack_leading:NNNNNww \int_use:N \__int_eval:w 1 #1 \if_int_compare:w \c_nine < 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 \tex_romannumeral:D \else: \if:w . \exp_not:N #4 \exp_after:wN \@@_parse_small_leading:wwNN \__int_value:w 1 \cs:w @@_parse_digits_ \tex_romannumeral:D #3 :N \exp_after:wN \cs_end: \tex_romannumeral:D \else: #2 \exp_after:wN \@@_parse_pack_trailing:NNNNNNww \exp_after:wN \c_zero \__int_value:w 1 0000 0000 \@@_parse_exponent:Nw #4 \fi: \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 \c_nine < 1 \token_to_str:N #4 \exp_stop_f: \exp_after:wN \@@_parse_pack_trailing:NNNNNNww \exp_after:wN \c_eight \int_use:N \__int_eval:w 1 #1 \token_to_str:N #4 \exp_after:wN \@@_parse_large_round:NN \exp_after:wN #4 \tex_romannumeral:D \else: \exp_after:wN \@@_parse_pack_trailing:NNNNNNww \int_use:N \__int_eval:w \c_seven - #3 \exp_stop_f: \int_use:N \__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_ \tex_romannumeral:D #3 :N \exp_after:wN \cs_end: \tex_romannumeral:D \else: #2 0 \@@_parse_exponent:Nw #4 \fi: \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \subsubsection{Number: beyond 16 digits, rounding} % % \begin{macro}[aux, 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 |;\c_zero|, % otherwise by |;\c_one|. 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 \c_nine < 1 \token_to_str:N #1 \exp_stop_f: + \c_one \if:w 0 \token_to_str:N #1 \exp_after:wN \@@_parse_round_loop:N \tex_romannumeral:D \else: \exp_after:wN \@@_parse_round_up:N \tex_romannumeral:D \fi: \else: \@@_parse_return_semicolon:w \c_zero #1 \fi: \@@_parse_expand:w } \cs_new:Npn \@@_parse_round_up:N #1 { \if_int_compare:w \c_nine < 1 \token_to_str:N #1 \exp_stop_f: + \c_one \exp_after:wN \@@_parse_round_up:N \tex_romannumeral:D \else: \@@_parse_return_semicolon:w \c_one #1 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 \cs{c_zero} or \cs{c_one} is added to the % surrounding integer expression. % \begin{macrocode} \cs_new:Npn \@@_parse_round_after:wN #1; #2 { + #2 \exp_after:wN ; \int_use:N \__int_eval:w #1 + \@@_parse_exponent:N } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 will % expand to |+\c_zero| or |+\c_one|, 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 |+\c_zero| or |+\c_one| 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 \c_nine < 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_use:N \__int_eval:w \exp_after:wN \@@_parse_round_after:wN \int_use:N \__int_eval:w \c_zero * \__int_eval:w \c_zero \exp_after:wN \@@_parse_round_loop:N \tex_romannumeral:D \else: \@@_parse_exponent:Nw #2 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % % \begin{macro}[aux, 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 \c_nine < 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_use:N \__int_eval:w \exp_after:wN \@@_parse_large_round_aux:wNN \int_use:N \__int_eval:w \c_one \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 \tex_romannumeral:D \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_use:N \__int_eval:w #1 \if:w . \exp_not:N #3 + \c_zero * \__int_eval:w \c_zero \exp_after:wN \@@_parse_round_loop:N \tex_romannumeral:D \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN ; \exp_after:wN \c_zero \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{verbatim} % \@@_parse:n { 3.2 erf(0.1) } % \@@_parse:n { 3.2 e\l_my_int } % \@@_parse:n { 3.2 \c_pi_fp } % \end{verbatim} % 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}[aux, 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}[aux, rEXP] % {\@@_parse_exponent:N, \@@_parse_exponent_aux:N} % This function should be called within an \cs{__int_value:w} % expansion (or within an integer expression. It leaves digits of the % exponent behind it in the input stream, and terminates the expansion % with a semicolon. If there is no~|e|, leave an exponent of~$0$. If % there is an~|e|, expand the next token to run some tests on it. The % first rough test is that if the character code of~|#1| is greater % than that of~|9| (largest code valid for an exponent, less than any % code valid for an identifier), there was in fact no exponent; % otherwise, we search for the sign of the exponent. % \begin{macrocode} \cs_new:Npn \@@_parse_exponent:N #1 { \if:w e \exp_not:N #1 \exp_after:wN \@@_parse_exponent_aux:N \tex_romannumeral:D \else: 0 \@@_parse_return_semicolon:w #1 \fi: \@@_parse_expand:w } \cs_new:Npn \@@_parse_exponent_aux:N #1 { \if_int_compare:w \if_catcode:w \scan_stop: \exp_not:N #1 \c_zero \else: `#1 \fi: > `9 \exp_stop_f: 0 \exp_after:wN ; \exp_after:wN e \else: \exp_after:wN \@@_parse_exponent_sign:N \fi: #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 \tex_romannumeral:D \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}[aux, 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 \c_nine < 1 \token_to_str:N #1 \exp_stop_f: \token_to_str:N #1 \exp_after:wN \@@_parse_exponent_digits:N \tex_romannumeral:D \else: \@@_parse_exponent_keep:NTF #1 { \@@_parse_return_semicolon:w #1 } { \exp_after:wN ; \tex_romannumeral:D } \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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 \c_nine < 1 \token_to_str:N #1 \exp_stop_f: \token_to_str:N #1 \exp_after:wN \@@_parse_exponent_digits:N \tex_romannumeral:D \else: \@@_parse_return_semicolon:w #1 \fi: \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, rEXP]{\@@_parse_exponent_keep:NTF} % This is the last building block for parsing exponents. The % argument~|#1| is already fully expanded, and neither |+| nor~|-| nor % a digit. It can be: % \begin{itemize} % \item \cs{s_@@}, marking the start of an internal floating point, % invalid here; % \item another control sequence equal to \tn{relax}, probably a bad % variable; % \item a register: in this case we make sure that it is an integer % register, not a dimension; % \item a character other than |+|, |-| or digits, again, an error. % \end{itemize} % \begin{macrocode} \prg_new_conditional:Npnn \@@_parse_exponent_keep:N #1 { TF } { \if_catcode:w \scan_stop: \exp_not:N #1 \if_meaning:w \scan_stop: #1 \if_int_compare:w \__str_if_eq_x:nn { \s_@@ } { \exp_not:N #1 } = \c_zero 0 \__msg_kernel_expandable_error:nnn { kernel } { fp-after-e } { floating~point~ } \prg_return_true: \else: 0 \__msg_kernel_expandable_error:nnn { kernel } { bad-variable } {#1} \prg_return_false: \fi: \else: \if_int_compare:w \__str_if_eq_x:nn { \__int_value:w #1 } { \tex_the:D #1 } = \c_zero \__int_value:w #1 \else: 0 \__msg_kernel_expandable_error:nnn { kernel } { fp-after-e } { dimension~#1 } \fi: \prg_return_false: \fi: \else: 0 \__msg_kernel_expandable_error:nnn { kernel } { fp-missing } { exponent } \prg_return_true: \fi: } % \end{macrocode} % \end{macro} % % \subsection{Constants, functions and prefix operators} % % \subsubsection{Prefix operators} % % \begin{macro}[EXP, aux]{\@@_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}[aux, EXP]{\@@_parse_apply_unary: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 % |\__fp_parse_infix_...:N| function. We feed the data~|#2|, and the % argument~|#4|, to the function~|#3|, which expands % \cs{tex_romannumeral:D} thus the \texttt{infix} function~|#5|. % \begin{macrocode} \cs_new:Npn \@@_parse_apply_unary:NNNwN #1#2#3#4@#5 { #3 #2 #4 @ \tex_romannumeral:D -`0 #5 #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP, aux]{\@@_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_twelve} 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 \tex_romannumeral:D \if_int_compare:w #2 < ##1 \@@_parse_operand:Nw ##1 \else: \@@_parse_operand:Nw #2 \fi: \@@_parse_expand:w } } \@@_tmp:w - \c_twelve \@@_set_sign_o:w 2 \@@_tmp:w ! \c_twelve \@@_not_o:w ? % \end{macrocode} % \end{macro} % % \begin{macro}[EXP, aux]{\@@_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 \tex_romannumeral:D -`0 \exp_after:wN \@@_sanitize:wN \int_use:N \__int_eval:w \c_zero \@@_parse_strim_zeros:N } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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. Commas will be allowed if % the previous precedence is $16$ (function with multiple arguments) % or $13$ (unary boolean \enquote{not}). In this case, find an % operand using the precedence~$1$; otherwise the precedence~$0$. % 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 the array it found as an operand, % fetching the following infix operator. % \begin{macrocode} \group_begin: \char_set_catcode_letter:N ( \char_set_catcode_letter:N ) \cs_new:Npn \@@_parse_prefix_(:Nw #1 { \exp_after:wN \@@_parse_lparen_after:NwN \exp_after:wN #1 \tex_romannumeral:D \if_int_compare:w #1 = \c_sixteen \@@_parse_operand:Nw \c_one \else: \@@_parse_operand:Nw \c_zero \fi: \@@_parse_expand:w } \cs_new:Npn \@@_parse_lparen_after:NwN #1#2 @ #3 { \token_if_eq_meaning:NNTF #3 \@@_parse_infix_):N { \@@_exp_after_array_f:w #2 \s_@@_stop \exp_after:wN \@@_parse_infix:NN \exp_after:wN #1 \tex_romannumeral:D \@@_parse_expand:w } { \__msg_kernel_expandable_error:nnn { kernel } { fp-missing } { ) } #2 @ \use_none:n #3 } } \group_end: % \end{macrocode} % \end{macro} % % \subsubsection{Constants} % % \begin{macro}[aux, 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_nopar:cpn { @@_parse_word_#1:N } { \exp_after:wN #2 \tex_romannumeral:D -`0 \@@_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}[aux, 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_nopar: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}[aux, 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_nopar:cpn { @@_parse_word_#1:N } { \exp_after:wN \@@_from_dim_test:ww \exp_after:wN 0 \exp_after:wN , \__int_value:w \__dim_eval:w 1 #1 \exp_after:wN ; \tex_romannumeral:D -`0 \@@_parse_infix:NN } } % \end{macrocode} % \end{macro} % % \subsubsection{Functions} % % ^^A begin[todo] % % ^^A todo: test <15 digits>1500000000.1 % ^^A todo: test <15 digits>1517263572.000 % % ^^A todo: word 'e' == 'invalid syntax', word 'E' == "use 'e' instead" % % \begin{macro}[aux, 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 \cs:w @@_#1_o:w \exp_after:wN \cs_end: \tex_romannumeral:D \@@_parse_operand:Nw \c_fifteen \@@_parse_expand:w } \cs_new:Npn \@@_parse_function:NNN #1#2#3 { \exp_after:wN \@@_parse_apply_unary:NNNwN \exp_after:wN #3 \exp_after:wN #2 \exp_after:wN #1 \tex_romannumeral:D \@@_parse_operand:Nw \c_sixteen \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP] % { % \@@_parse_word_acot:N , \@@_parse_word_acotd:N, % \@@_parse_word_atan:N , \@@_parse_word_atand:N, % \@@_parse_word_max:N , \@@_parse_word_min:N , % } % Those functions are also unary (not binary), but may receive a % variable number of arguments. % \begin{macrocode} \cs_new_nopar:Npn \@@_parse_word_acot:N { \@@_parse_function:NNN \@@_acot_o:Nw \use_i:nn } \cs_new_nopar:Npn \@@_parse_word_acotd:N { \@@_parse_function:NNN \@@_acot_o:Nw \use_ii:nn } \cs_new_nopar:Npn \@@_parse_word_atan:N { \@@_parse_function:NNN \@@_atan_o:Nw \use_i:nn } \cs_new_nopar:Npn \@@_parse_word_atand:N { \@@_parse_function:NNN \@@_atan_o:Nw \use_ii:nn } \cs_new_nopar:Npn \@@_parse_word_max:N { \@@_parse_function:NNN \@@_minmax_o:Nw 2 } \cs_new_nopar:Npn \@@_parse_word_min:N { \@@_parse_function:NNN \@@_minmax_o:Nw 0 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP] % { % \@@_parse_word_abs:N , % \@@_parse_word_exp:N , % \@@_parse_word_ln:N , % \@@_parse_word_sqrt:N , % } % Unary functions. % \begin{macrocode} \cs_new:Npn \@@_parse_word_abs:N { \@@_parse_unary_function:nNN { set_sign } 0 } \cs_new_nopar:Npn \@@_parse_word_exp:N { \@@_parse_unary_function:nNN {exp} ? } \cs_new_nopar:Npn \@@_parse_word_ln:N { \@@_parse_unary_function:nNN {ln} ? } \cs_new_nopar:Npn \@@_parse_word_sqrt:N { \@@_parse_unary_function:nNN {sqrt} ? } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP] % { % \@@_parse_word_acos:N , % \@@_parse_word_acosd:N , % \@@_parse_word_acsc:N , % \@@_parse_word_acscd:N , % \@@_parse_word_asec:N , % \@@_parse_word_asecd:N , % \@@_parse_word_asin:N , % \@@_parse_word_asind:N , % \@@_parse_word_cos:N , % \@@_parse_word_cosd:N , % \@@_parse_word_cot:N , % \@@_parse_word_cotd:N , % \@@_parse_word_csc:N , % \@@_parse_word_cscd:N , % \@@_parse_word_sec:N , % \@@_parse_word_secd:N , % \@@_parse_word_sin:N , % \@@_parse_word_sind:N , % \@@_parse_word_tan:N , % \@@_parse_word_tand:N , % } % Unary functions. % \begin{macrocode} \tl_map_inline:nn { {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {sec} {sin} {tan} } { \cs_new_nopar:cpn { @@_parse_word_#1:N } { \@@_parse_unary_function:nNN {#1} \use_i:nn } \cs_new_nopar:cpn { @@_parse_word_#1d:N } { \@@_parse_unary_function:nNN {#1} \use_ii:nn } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP] % { % \@@_parse_word_trunc:N, % \@@_parse_word_floor:N, % \@@_parse_word_ceil:N % } % \begin{macrocode} \cs_new_nopar:Npn \@@_parse_word_trunc:N { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_zero:NNN } \cs_new_nopar:Npn \@@_parse_word_floor:N { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_ninf:NNN } \cs_new_nopar:Npn \@@_parse_word_ceil:N { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_pinf:NNN } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_word_round:N, \@@_parse_round:Nw} % \begin{macrocode} \cs_new:Npn \@@_parse_word_round:N #1#2 { \if_meaning:w + #2 \@@_parse_round:Nw \@@_round_to_pinf:NNN \else: \if_meaning:w 0 #2 \@@_parse_round:Nw \@@_round_to_zero:NNN \else: \if_meaning:w - #2 \@@_parse_round:Nw \@@_round_to_ninf:NNN \fi: \fi: \fi: \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_nearest:NNN #1 #2 } \cs_new:Npn \@@_parse_round:Nw #1 #2 \@@_round_to_nearest:NNN #3#4 { #2 #1 #3 } % \end{macrocode} % \end{macro} % % \subsection{Main functions} % % \begin{macro}[int, EXP]{\@@_parse:n} % \begin{macro}[aux, EXP]{\@@_parse_after:ww} % Start a \tn{romannumeral} expansion so that \cs{@@_parse:n} expands % in two steps. The \cs{@@_parse_operand:Nw} function will perform % computations until reaching an operation with precedence % \cs{c_minus_one} or less, namely, the end of the expression. The % marker \cs{s_@@_mark} indicates that the next token is an already % parsed version of an infix operator, and \cs{@@_parse_infix_end:N} % has infinitely negative precedence. Finally, clean up a % (well-defined) set of extra tokens and stop the initial expansion % with \cs{c_zero}. % \begin{macrocode} \cs_new:Npn \@@_parse:n #1 { \tex_romannumeral:D \exp_after:wN \@@_parse_after:ww \tex_romannumeral:D \@@_parse_operand:Nw \c_minus_one \@@_parse_expand:w #1 \s_@@_mark \@@_parse_infix_end:N \s_@@_stop } \cs_new:Npn \@@_parse_after:ww #1@ \@@_parse_infix_end:N \s_@@_stop { \c_zero #1 } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_operand:Nw} % \begin{macro}[aux, EXP]{\@@_parse_continue:NwN} % The \cs{@@_parse_operand} % This is just a shorthand which sets up both \cs{@@_parse_continue} % and \cs{@@_parse_one} with the same precedence. Note the % trailing \cs{tex_romannumeral:D}. This function should be % used with much care. % \begin{macrocode} \cs_new:Npn \@@_parse_operand:Nw #1 { -`0 \exp_after:wN \@@_parse_continue:NwN \exp_after:wN #1 \tex_romannumeral:D -`0 \exp_after:wN \@@_parse_one:Nw \exp_after:wN #1 \tex_romannumeral:D } \cs_new:Npn \@@_parse_continue:NwN #1 #2 @ #3 { #3 #1 #2 @ } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_parse_apply_binary:NwNwN} % Receives \meta{precedence} \meta{operand_1} |@| \meta{operation} % \meta{operand_2} |@| \meta{infix command}. Builds the appropriate % call to the \meta{operation}~|#3|. % \begin{macrocode} \cs_new:Npn \@@_parse_apply_binary:NwNwN #1 #2@ #3 #4@ #5 { \exp_after:wN \@@_parse_continue:NwN \exp_after:wN #1 \tex_romannumeral:D -`0 \cs:w @@_#3_o:ww \cs_end: #2 #4 \tex_romannumeral:D -`0 #5 #1 } % \end{macrocode} % \end{macro} % % \subsection{Infix operators} % % \begin{macro}[aux, 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; } \group_begin: \char_set_catcode_letter:N \* \cs_new:Npn \@@_parse_infix:NN #1 #2 { \if_catcode:w \scan_stop: \exp_not:N #2 \if_int_compare:w \__str_if_eq_x:nn { \s_@@_mark } { \exp_not:N #2 } = \c_zero \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_juxtapose:N \fi: \else: \if_int_compare:w \__int_eval:w ( `#2 \if_int_compare:w `#2 > `Z - \c_thirty_two \fi: ) / 26 = \c_three \exp_after:wN \exp_after:wN \exp_after:wN \@@_parse_infix_juxtapose: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_kernel_expandable_error:nnn { kernel } { fp-missing } { * } \exp_after:wN \@@_parse_infix_*:N \exp_after:wN #2 \exp_after:wN #3 \else: \exp_after:wN #1 \exp_after:wN #2 \tex_romannumeral:D \exp_after:wN \@@_parse_expand:w \fi: } \group_end: % \end{macrocode} % \end{macro} % % \subsubsection{Closing parentheses and commas} % % \begin{macro}[aux, EXP]{\@@_parse_infix_mark:NNN} % As an infix operator, \cs{s_@@_mark} means that the next % token~(|#3|) has already gone through \cs{@@_parse_infix:NN} and % should be provided the precedence~|#1|. The scan mark~|#2| is % discarded. % \begin{macrocode} \cs_new:Npn \@@_parse_infix_mark:NNN #1#2#3 { #3 #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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}[aux, 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. % \begin{macrocode} \group_begin: \char_set_catcode_letter:N \) \cs_new:Npn \@@_parse_infix_):N #1 { \if_int_compare:w #1 < \c_zero \__msg_kernel_expandable_error:nnn { kernel } { fp-extra } { ) } \exp_after:wN \@@_parse_infix:NN \exp_after:wN #1 \tex_romannumeral:D \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN @ \exp_after:wN \use_none:n \exp_after:wN \@@_parse_infix_):N \fi: } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]+\@@_parse_infix_,:N+ % \begin{macrocode} \group_begin: \char_set_catcode_letter:N \, \cs_new:Npn \@@_parse_infix_,:N #1 { \if_int_compare:w #1 > \c_one \exp_after:wN @ \exp_after:wN \use_none:n \exp_after:wN \@@_parse_infix_,:N \else: \if_int_compare:w #1 = \c_one \exp_after:wN \@@_parse_infix_comma:w \tex_romannumeral:D \else: \exp_after:wN \@@_parse_infix_comma_gobble:w \tex_romannumeral:D \fi: \@@_parse_operand:Nw \c_one \exp_after:wN \@@_parse_expand:w \fi: } \cs_new:Npn \@@_parse_infix_comma:w #1 @ { #1 @ \use_none:n } \cs_new:Npn \@@_parse_infix_comma_gobble:w #1 @ { \__msg_kernel_expandable_error:nn { kernel } { fp-extra-comma } @ \use_none:n } \group_end: % \end{macrocode} % \end{macro} % % \subsubsection{Usual infix operators} % % \begin{macro}[aux, EXP] % { % \@@_parse_infix_+:N, \@@_parse_infix_-:N, % \@@_parse_infix_/:N, \@@_parse_infix_mul:N, % \@@_parse_infix_and:N, \@@_parse_infix_or:N, % } % \begin{macro}[aux, EXP]+\@@_parse_infix_^:N+ As described in the % \enquote{work plan}, each infix operator has an associated % \cs{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. % % The odd requirement to set \cs{+} here is to cover the case where % \pkg{expl3} is loaded by plain \TeX{}: \cs{+} is an \cs{outer} macro there, % and so the following code would otherwise give an error in that case. % \begin{macrocode} \group_begin: %<*package> \cs_set_nopar:Npn \+ { } % \char_set_catcode_other:N \& \char_set_catcode_letter:N \^ \char_set_catcode_letter:N \/ \char_set_catcode_letter:N \- \char_set_catcode_letter:N \+ \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 \tex_romannumeral:D \@@_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: } } \@@_tmp:w \@@_parse_infix_^:N ^ \c_fifteen \c_fourteen \@@_tmp:w \@@_parse_infix_/:N / \c_ten \c_ten \@@_tmp:w \@@_parse_infix_mul:N * \c_ten \c_ten \@@_tmp:w \@@_parse_infix_-:N - \c_nine \c_nine \@@_tmp:w \@@_parse_infix_+:N + \c_nine \c_nine \@@_tmp:w \@@_parse_infix_and:N & \c_five \c_five \@@_tmp:w \@@_parse_infix_or:N | \c_four \c_four \group_end: % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Juxtaposition} % % \begin{macro}[aux, 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_juxtapose:N}. % \begin{macrocode} \cs_new:cpn { @@_parse_infix_(:N } #1 { \@@_parse_infix_juxtapose:N #1 ( } % \end{macrocode} % \end{macro} % % ^^A todo: can |...(1,2,3)pt| really occur? If not, simplify. % \begin{macro}[aux, EXP] % {\@@_parse_infix_juxtapose:N, \@@_parse_apply_juxtapose:NwwN} % Juxtaposition follows the same scheme as other binary operations, % but calls \cs{@@_parse_apply_juxtapose:NwwN} rather than directly % calling \cs{@@_parse_apply_binary:NwNwN}. This lets us catch errors % such as |...(1,2,3)pt| where one operand of the juxtaposition is not % a single number: both |#3| and~|#5| of the \texttt{apply} auxiliary % must be empty. % \begin{macrocode} \cs_new:Npn \@@_parse_infix_juxtapose:N #1 { \if_int_compare:w #1 < \c_ten \exp_after:wN @ \exp_after:wN \@@_parse_apply_juxtapose:NwwN \tex_romannumeral:D \@@_parse_operand:Nw \c_ten \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN @ \exp_after:wN \use_none:n \exp_after:wN \@@_parse_infix_juxtapose:N \fi: } \cs_new:Npn \@@_parse_apply_juxtapose:NwwN #1 #2;#3@ #4;#5@ { \if_catcode:w ^ \tl_to_str:n { #3 #5 } ^ \else: \@@_error:nffn { invalid-ii } { \@@_array_to_clist:n { #2; #3 } } { \@@_array_to_clist:n { #4; #5 } } { } \fi: \@@_parse_apply_binary:NwNwN #1 #2;@ * #4;@ } % \end{macrocode} % \end{macro} % % \subsubsection{Multi-character cases} % % \begin{macro}[aux, EXP]{\@@_parse_infix_*:N} % \begin{macrocode} \group_begin: \char_set_catcode_letter:N ^ \cs_new:cpn { @@_parse_infix_*:N } #1#2 { \if:w * \exp_not:N #2 \exp_after:wN \@@_parse_infix_^:N \exp_after:wN #1 \else: \exp_after:wN \@@_parse_infix_mul:N \exp_after:wN #1 \exp_after:wN #2 \fi: } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]+\@@_parse_infix_|:Nw+ % \begin{macro}[aux, EXP]+\@@_parse_infix_&:Nw+ % \begin{macrocode} \group_begin: \char_set_catcode_letter:N \| \char_set_catcode_letter:N \& \cs_new:Npn \@@_parse_infix_|:N #1#2 { \if:w | \exp_not:N #2 \exp_after:wN \@@_parse_infix_|:N \exp_after:wN #1 \tex_romannumeral:D \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN \@@_parse_infix_or:N \exp_after:wN #1 \exp_after:wN #2 \fi: } \cs_new:Npn \@@_parse_infix_&:N #1#2 { \if:w & \exp_not:N #2 \exp_after:wN \@@_parse_infix_&:N \exp_after:wN #1 \tex_romannumeral:D \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN \@@_parse_infix_and:N \exp_after:wN #1 \exp_after:wN #2 \fi: } \group_end: % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Ternary operator} % % \begin{macro}[aux, EXP]{\@@_parse_infix_?:N, \@@_parse_infix_::N} % \begin{macrocode} \group_begin: \char_set_catcode_letter:N \? \cs_new:Npn \@@_parse_infix_?:N #1 { \if_int_compare:w #1 < \c_three \exp_after:wN @ \exp_after:wN \@@_ternary:NwwN \tex_romannumeral:D \@@_parse_operand:Nw \c_three \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN @ \exp_after:wN \use_none:n \exp_after:wN \@@_parse_infix_?:N \fi: } \cs_new:Npn \@@_parse_infix_::N #1 { \if_int_compare:w #1 < \c_three \__msg_kernel_expandable_error:nnnn { kernel } { fp-missing } { ? } { ~for~?: } \exp_after:wN @ \exp_after:wN \@@_ternary_auxii:NwwN \tex_romannumeral:D \@@_parse_operand:Nw \c_two \exp_after:wN \@@_parse_expand:w \else: \exp_after:wN @ \exp_after:wN \use_none:n \exp_after:wN \@@_parse_infix_::N \fi: } \group_end: % \end{macrocode} % \end{macro} % % \subsubsection{Comparisons} % % \begin{macro}[aux, EXP] % { % \@@_parse_infix_<:N, \@@_parse_infix_=:N, % \@@_parse_infix_>:N, \@@_parse_infix_!:N % } % \begin{macro}[aux, 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 \c_one \c_zero \c_zero \c_zero \c_zero < } \cs_new:cpn { @@_parse_infix_=:N } #1 { \@@_parse_compare:NNNNNNN #1 \c_one \c_zero \c_zero \c_zero \c_zero = } \cs_new:cpn { @@_parse_infix_>:N } #1 { \@@_parse_compare:NNNNNNN #1 \c_one \c_zero \c_zero \c_zero \c_zero > } \cs_new:cpn { @@_parse_infix_!:N } #1 { \exp_after:wN \@@_parse_compare:NNNNNNN \exp_after:wN #1 \exp_after:wN \c_zero \exp_after:wN \c_one \exp_after:wN \c_one \exp_after:wN \c_one \exp_after:wN \c_one } \cs_new:Npn \@@_parse_excl_error: { \__msg_kernel_expandable_error:nnnn { kernel } { fp-missing } { = } { ~after~!. } } \cs_new:Npn \@@_parse_compare:NNNNNNN #1 { \if_int_compare:w #1 < \c_seven \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 \if_catcode:w \scan_stop: \exp_not:N #7 \c_minus_one \else: \__int_eval:w `#7 - `< \__int_eval_end: \fi: \@@_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 \tex_romannumeral:D \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 \tex_romannumeral:D \@@_parse_operand:Nw \c_seven \@@_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 \c_zero \else: \if_case:w \@@_compare_back:ww #8 #2 \exp_stop_f: #5 \or: #6 \or: #7 \else: #4 \fi: \fi: \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 \tex_romannumeral:D -`0 \@@_exp_after_o:w #3; \tex_romannumeral:D -`0 \else: \exp_after:wN \@@_parse_continue:NwN \exp_after:wN #2 \tex_romannumeral:D -`0 \exp_after:wN #1 \tex_romannumeral:D -`0 \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{Candidate: defining new \pkg{l3fp} functions} % % \begin{macro}[EXP]{\fp_function:Nw} % Parse the argument of the function~|#1| using % \cs{@@_parse_operand:Nw} with a precedence of~$16$, and pass the % function and argument to \cs{@@_function_apply:nw}. % \begin{macrocode} \cs_new:Npn \fp_function:Nw #1 { \exp_after:wN \@@_function_apply:nw \exp_after:wN #1 \tex_romannumeral:D \@@_parse_operand:Nw \c_sixteen \@@_parse_expand:w } % \end{macrocode} % \end{macro} % % \begin{macro}{\fp_new_function:Npn} % \begin{macro}[aux]{\@@_new_function:NNnnn, \@@_new_function:Ncfnn} % \begin{macro}[aux]{\@@_function_args:Nwn} % Save the code provided by the user in the control sequence % |\__fp_user_#1|. Define |#1| to call \cs{@@_function_apply:nw} % after parsing one operand using \cs{@@_parse_operand:Nw} with % precedence~$16$. The auxiliary \cs{@@_function_args:Nwn} receives % the user function and the number of arguments (half of the number of % tokens in the parameter text~|#2|), followed by the operand (as a % token list of floating points). It checks the number of arguments, % and applies the user function to the arguments (without the outer % brace group). % \begin{macrocode} \cs_new_protected:Npn \fp_new_function:Npn #1#2# { \@@_new_function:Ncfnn #1 { @@_user_ \cs_to_str:N #1 } { \int_eval:n { \tl_count:n {#2} / \c_two } } {#2} } \cs_new_protected:Npn \@@_new_function:NNnnn #1#2#3#4#5 { \cs_new_nopar:Npn #1 { \exp_after:wN \@@_function_apply:nw \exp_after:wN { \exp_after:wN \@@_function_args:Nwn \exp_after:wN #2 \__int_value:w #3 \exp_after:wN ; \exp_after:wN } \tex_romannumeral:D \@@_parse_operand:Nw \c_sixteen \@@_parse_expand:w } \cs_new:Npn #2 #4 {#5} } \cs_generate_variant:Nn \@@_new_function:NNnnn { Ncf } \cs_new:Npn \@@_function_args:Nwn #1#2; #3 { \int_compare:nNnTF { \tl_count:n {#3} } = {#2} { #1 #3 } { \__msg_kernel_expandable_error:nnnnn { kernel } { fp-num-args } { #1() } {#2} {#2} \c_nan_fp } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP] % { % \@@_function_apply:nw, % \@@_function_store:wwNwnn, % \@@_function_store_end:wnnn % } % The auxiliary \cs{@@_function_apply:nw} is called after parsing an % operand, so it receives some code~|#1|, then the operand ending % with~|@|, then a function such as \cs{@@_parse_infix_+:N} (but not % always of this form, see comparisons for instance). Package the % operand (an array) into a token list with floating point items: this % is the role of \cs{@@_function_store:wwNwnn} and % \cs{@@_function_store_end:wnnn}. Then apply \cs{@@_parse:n} to the % code~|#1| followed by a brace group with this token list. This % results in a floating point result, which will correctly be parsed % as the next operand of whatever was looking for one. The trailing % \cs{s_@@_mark} is used as a special infix operator to indicate that % the next token has already gone through \cs{@@_parse_infix:NN}. % \begin{macrocode} \cs_new:Npn \@@_function_apply:nw #1#2 @ { \@@_parse:n { \@@_function_store:wwNwnn #2 \s_@@_mark \@@_function_store:wwNwnn ; \s_@@_mark \@@_function_store_end:wnnn \s_@@_stop { } { } {#1} } \s_@@_mark } \cs_new:Npn \@@_function_store:wwNwnn #1; #2 \s_@@_mark #3#4 \s_@@_stop #5#6 { #3 #2 \s_@@_mark #3#4 \s_@@_stop { #5 #6 } { { #1; } } } \cs_new:Npn \@@_function_store_end:wnnn #1 \s_@@_stop #2#3#4 { #4 {#2} } % \end{macrocode} % \end{macro} % % ^^A end[todo] % % \subsection{Messages} % % \begin{macrocode} \__msg_kernel_new:nnn { kernel } { unknown-fp-word } { Unknown~fp~word~#1. } \__msg_kernel_new:nnn { kernel } { fp-missing } { Missing~#1~inserted #2. } \__msg_kernel_new:nnn { kernel } { fp-extra } { Extra~#1~ignored. } \__msg_kernel_new:nnn { kernel } { fp-early-end } { Premature~end~in~fp~expression. } \__msg_kernel_new:nnn { kernel } { fp-after-e } { Cannot~use~#1 after~'e'. } \__msg_kernel_new:nnn { kernel } { fp-missing-number } { Missing~number~before~'#1'. } \__msg_kernel_new:nnn { kernel } { fp-unknown-symbol } { Unknown~symbol~#1~ignored. } \__msg_kernel_new:nnn { kernel } { fp-extra-comma } { Unexpected~comma:~extra~arguments~ignored. } \__msg_kernel_new:nnn { kernel } { fp-num-args } { #1~expects~between~#2~and~#3~arguments. } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex