% \iffalse % %% File l3fp-symbolic.dtx (C) Copyright 2012-2015,2017,2018,2020,2021,2023 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % 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 development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full]{l3doc} \usepackage{amsmath} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3fp-symbolic} module\\ Symbolic expressions^^A % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2024-03-14} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3fp-symbolic} implementation} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=fp> % \end{macrocode} % % \subsection{Misc} % % \begin{variable}{\l_@@_symbolic_fp} % Scratch floating point. % \begin{macrocode} \fp_new:N \l_@@_symbolic_fp % \end{macrocode} % \end{variable} % % \subsection{Building blocks for expressions} % % Every symbolic expression has the form \cs{s_@@_symbolic} % \cs{@@_symbolic_chk:w} \meta{operation} |,| \Arg{operands} \meta{junk} % |;| where the \meta{operation} is a list of \texttt{N}-type tokens, % the \meta{operands} is an array of floating point objects, and the % \meta{junk} is to be discarded. If the outermost operator (last to be % evaluated) is unary, the expression has the form % \begin{quote} % \cs{s_@@_symbolic} \cs{@@_symbolic_chk:w} \\ % \cs{@@_types_unary:NNw} |\__fp_|\meta{op}|_o:w| \meta{token} |,| \\ % |{| \meta{operand} |}| \meta{junk} |;| % \end{quote} % where the \meta{op} is a unary operation (|set_sign|, |cos|, % \ldots{}), and the \meta{token} and \meta{operand} are used as % arguments for \cs{@@_\meta{op}_o:w} (or the type-specific analog of % this function). If the outermost operator is binary, the expression % has the form % \begin{quote} % \cs{s_@@_symbolic} \cs{@@_symbolic_chk:w} \\ % \cs{@@_types_binary:Nww} |\__fp_|\meta{op}|_o:ww| |,| \\ % |{| \meta{operand_1} \meta{operand_2} |}| \meta{junk} |;| % \end{quote} % where the \meta{op} is an operation (|+|, |&|, \ldots{}), and % |\__fp_|\meta{op}|_o:ww| receives the \meta{operands} as arguments. % If the expression consists of a single variable, it is stored as % \begin{quote} % \cs{s_@@_symbolic} \cs{@@_symbolic_chk:w} \\ % \cs{@@_variable_o:w} \meta{identifier} |,| \\ % |{| |}| \meta{junk} |;| % \end{quote} % % Symbolic expressions are stored in a prefix form. When encountering a % symbolic expression in a floating point computation, we attempt to % evaluate the operands as much as possible, and if that yields floating % point numbers rather than expressions, we apply the operator which % follows (if the function is known). % % For instance, the expression |a + b * sin(c)| is stored as % \begin{verbatim} % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_types_binary:Nww \__fp_+_o:ww , % { % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_variable_o:w a , { } ; % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_types_binary:Nww \__fp_*_o:ww , % { % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_variable_o:w b , { } ; % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_types_unary:NNw \__fp_sin_o:w \use_i:nn , % { % \s__fp_symbolic \__fp_symbolic_chk:w % \__fp_variable_o:w c , { } ; % } ; % } ; % } ; % \end{verbatim} % % \begin{variable}{\s_@@_symbolic} % Scan mark indicating the start of a symbolic expression. % \begin{macrocode} \scan_new:N \s_@@_symbolic % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_symbolic_chk:w} % Analog of \cs{@@_chk:w} for symbolic expressions. % \begin{macrocode} \cs_new_protected:Npn \@@_symbolic_chk:w #1,#2#3; { \msg_error:nne { fp } { misused-fp } { \@@_to_tl_dispatch:w \s_@@_symbolic \@@_symbolic_chk:w #1,{#2}; } } % \end{macrocode} % \end{macro} % % \subsection{Expanding after a symbolic expression} % % \begin{macro}[EXP] % {\@@_if_has_symbolic:nTF, \@@_if_has_symbolic_aux:w} % Tests if |#1| contains \cs{s_@@_symbolic} at top-level. This test % should be precise enough to determine if a given array contains a % symbolic expression or only consists of floating points. Used in % \cs{@@_exp_after_symbolic_f:nw}. % \begin{macrocode} \cs_new:Npn \@@_if_has_symbolic:nTF #1 { \@@_if_has_symbolic_aux:w #1 \s_@@_mark \use_i:nn \s_@@_symbolic \s_@@_mark \use_ii:nn \s_@@_stop } \cs_new:Npn \@@_if_has_symbolic_aux:w #1 \s_@@_symbolic #2 \s_@@_mark #3#4 \s_@@_stop { #3 } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_exp_after_symbolic_f:nw} % \begin{macro}[EXP] % {\@@_exp_after_symbolic_aux:w, \@@_exp_after_symbolic_loop:N} % This function does two things: trigger an \texttt{f}-expansion of % the argument~|#1| after the following symbolic expression, and % evaluate all pieces of the expression which can be evaluated. % \begin{macrocode} \cs_new:Npn \@@_exp_after_symbolic_f:nw #1 \s_@@_symbolic \@@_symbolic_chk:w #2, #3#4; { \exp_after:wN \@@_exp_after_symbolic_aux:w \exp:w \@@_exp_after_symbolic_loop:N #2 { , \exp:w \use_none:nn } \exp_after:wN \exp_end: \exp_after:wN { \exp:w \exp_end_continue_f:w \@@_exp_after_array_f:w #3 \s_@@_expr_stop \exp_after:wN } \exp_after:wN ; \exp:w \exp_end_continue_f:w #1 } \cs_new:Npn \@@_exp_after_symbolic_aux:w #1, #2; { \@@_if_has_symbolic:nTF {#2} { \s_@@_symbolic \@@_symbolic_chk:w #1, {#2} ; } { #1 #2 @ \prg_do_nothing: } } \cs_new:Npn \@@_exp_after_symbolic_loop:N #1 { \exp_after:wN \exp_end: \exp_after:wN #1 \exp:w \@@_exp_after_symbolic_loop:N } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Applying infix operators to expressions} % % \begin{macro}[EXP]{\@@_symbolic_binary_o:Nww} % Used when applying infix operators to expressions. % \begin{macrocode} \cs_new:Npn \@@_symbolic_binary_o:Nww #1 #2; #3; { \@@_exp_after_symbolic_f:nw { \exp_after:wN \exp_stop_f: } \s_@@_symbolic \@@_symbolic_chk:w \@@_types_binary:Nww #1 , { #2; #3; } ; } % \end{macrocode} % \end{macro} % % \begin{@makeother}{^} %^^A Hack! % \begin{@makeother}{|} %^^A Hack! % \begin{@makeother}{&} %^^A Hack! % \begin{macro}[EXP] % { % \@@_symbolic_+_symbolic_o:ww, % \@@_symbolic_+_o:ww, % \@@_+_symbolic_o:ww, % \@@_symbolic_-_symbolic_o:ww, % \@@_symbolic_-_o:ww, % \@@_-_symbolic_o:ww, % \@@_symbolic_*_symbolic_o:ww, % \@@_symbolic_*_o:ww, % \@@_*_symbolic_o:ww, % \@@_symbolic_/_symbolic_o:ww, % \@@_symbolic_/_o:ww, % \@@_/_symbolic_o:ww, % \@@_symbolic_^_symbolic_o:ww, % \@@_symbolic_^_o:ww, % \@@_^_symbolic_o:ww, % \@@_symbolic_|_symbolic_o:ww, % \@@_symbolic_|_o:ww, % \@@_|_symbolic_o:ww, % \@@_symbolic_&_symbolic_o:ww, % \@@_symbolic_&_o:ww, % \@@_&_symbolic_o:ww, % } % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2 { \cs_new:cpn { @@_symbolic_#2_symbolic_o:ww } { \@@_symbolic_binary_o:Nww #1 } \cs_new_eq:cc { @@_symbolic_#2 _o:ww } { @@_symbolic_#2_symbolic_o:ww } \cs_new_eq:cc { @@ _#2_symbolic_o:ww } { @@_symbolic_#2_symbolic_o:ww } } \tl_map_inline:nn { + - * / ^ & | } { \exp_args:Nc \@@_tmp:w { @@_#1_o:ww } {#1} } % \end{macrocode} % \end{macro} % \end{@makeother} % \end{@makeother} % \end{@makeother} % % \subsection{Applying prefix functions to expressions} % % \begin{macro}[EXP]{\@@_symbolic_unary_o:NNw} % Used when applying infix operators to expressions. % \begin{macrocode} \cs_new:Npn \@@_symbolic_unary_o:NNw #1#2#3; @ { \@@_exp_after_symbolic_f:nw { \exp_after:wN \exp_stop_f: } \s_@@_symbolic \@@_symbolic_chk:w \@@_types_unary:NNw #1#2 , { #3; } ; } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP] % { % \@@_symbolic_acos_o:w , % \@@_symbolic_acsc_o:w , % \@@_symbolic_asec_o:w , % \@@_symbolic_asin_o:w , % \@@_symbolic_cos_o:w , % \@@_symbolic_cot_o:w , % \@@_symbolic_csc_o:w , % \@@_symbolic_exp_o:w , % \@@_symbolic_ln_o:w , % \@@_symbolic_not_o:w , % \@@_symbolic_sec_o:w , % \@@_symbolic_set_sign_o:w , % \@@_symbolic_sin_o:w , % \@@_symbolic_tan_o:w , % } % \begin{macrocode} \tl_map_inline:nn { {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {exp} {ln} {not} {sec} {set_sign} {sin} {sqrt} {tan} } { \cs_new:cpe { @@_symbolic_#1_o:w } { \exp_not:N \@@_symbolic_unary_o:NNw \exp_not:c { @@_#1_o:w } } } % \end{macrocode} % \end{macro} % % \subsection{Conversions} % % \begin{macro}[EXP] % { % \@@_symbolic_to_decimal:w, % \@@_symbolic_to_int:w, % \@@_symbolic_to_scientific:w % } % \begin{macro}[EXP]{\@@_symbolic_convert:wnnN} % Symbolic expressions cannot be converted to decimal, integer, or % scientific notation unless they can be reduced to % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \cs_new:cpn { @@_symbolic_to_#1:w } { \exp_after:wN \@@_symbolic_convert:wnnN \exp:w \exp_end_continue_f:w \@@_exp_after_symbolic_f:nw { { #2 } { fp_to_#1 } #3 } } } \@@_tmp:w { decimal } { 0 } \@@_to_decimal_dispatch:w \@@_tmp:w { int } { 0 } \@@_to_int_dispatch:w \@@_tmp:w { scientific } { nan } \@@_to_scientific_dispatch:w \cs_new:Npn \@@_symbolic_convert:wnnN #1#2; #3#4#5 { \str_if_eq:nnTF {#1} { \s_@@_symbolic } { \@@_invalid_operation:nnw {#3} {#4} #1#2; } { #5 #1#2; } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP] % {\@@_symbolic_cs_arg_to_fn:NN, \@@_symbolic_op_arg_to_fn:nN} % \begin{macrocode} \cs_new:Npn \@@_symbolic_cs_arg_to_fn:NN #1 { \exp_args:Nf \@@_symbolic_op_arg_to_fn:nN { \@@_types_cs_to_op:N #1 } } \cs_new:Npn \@@_symbolic_op_arg_to_fn:nN #1#2 { \str_case:nnF { #1 #2 } { { not ? } { ! } { set_sign 0 } { abs } { set_sign 2 } { - } } { \token_if_eq_meaning:NNTF #2 \use_ii:nn { #1 d } {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_symbolic_to_tl:w} % \begin{macro}[rEXP] % { % \@@_symbolic_unary_to_tl:NNw, % \@@_symbolic_binary_to_tl:Nww, % \@@_symbolic_function_to_tl:Nw % } % Converting a symbolic expression to a token list is possible. % \begin{macrocode} \cs_new:Npn \@@_symbolic_to_tl:w \s_@@_symbolic \@@_symbolic_chk:w #1#2, #3#4; { \str_case:nnTF {#1} { { \@@_types_unary:NNw } { \@@_symbolic_unary_to_tl:NNw } { \@@_types_binary:Nww } { \@@_symbolic_binary_to_tl:Nww } { \@@_function_o:w } { \@@_symbolic_function_to_tl:Nw } } { #2, #3 @ } { \tl_to_str:n {#2} } } \cs_new:Npn \@@_symbolic_unary_to_tl:NNw #1#2 , #3 @ { \use:e { \@@_symbolic_cs_arg_to_fn:NN #1#2 ( \@@_to_tl_dispatch:w #3 ) } } \cs_new:Npn \@@_symbolic_binary_to_tl:Nww #1, #2; #3; @ { \use:e { ( \@@_to_tl_dispatch:w #2; ) \@@_types_cs_to_op:N #1 ( \@@_to_tl_dispatch:w #3; ) } } \cs_new:Npn \@@_symbolic_function_to_tl:Nw #1, #2@ { \use:e { \@@_types_cs_to_op:N #1 ( \@@_array_to_clist:n {#2} ) } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Identifiers} % % Functions defined here are not necessarily tied to symbolic % expressions. % % \begin{macro}[TF]{\@@_id_if_invalid:n} % \begin{macro}[EXP]{\@@_id_if_invalid_aux:N} % If |#1| contains a space, it is not a valid identifier. Otherwise, % loop through letters in |#1|: if it is not a letter, break the loop % and return \texttt{true}. If the end of the loop is reached % without finding any non-letter, return \texttt{false}. % Note |#1| must be a str (i.e., resulted from \cs{tl_to_str:n}). % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_id_if_invalid:n #1 { T , F , TF } { \tl_if_empty:nTF {#1} { \prg_return_true: } { \tl_if_in:nnTF { #1 } { ~ } { \prg_return_true: } { \@@_id_if_invalid_aux:N #1 { ? \prg_break:n \prg_return_false: } \prg_break_point: } } } \cs_new:Npn \@@_id_if_invalid_aux:N #1 { \use_none:n #1 \int_compare:nF { `a <= `#1 <= `z } { \int_compare:nF { `A <= `#1 <= `Z } { \prg_break:n \prg_return_true: } } \@@_id_if_invalid_aux:N } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Declaring variables and assigning values} % % \begin{macro}[EXP]{\@@_variable_o:w} % We do not use \cs{exp_last_unbraced:Nv} to extract the value of % |\l__fp_variable_#1_fp| because in \cs{fp_set_variable:nn} we define % this \texttt{fp} variable to be something which \texttt{f}-expands % to an actual floating point, rather than a genuine floating point. % \begin{macrocode} \cs_new:Npn \@@_variable_o:w #1 @ #2 { \fp_if_exist:cTF { l_@@_variable_#1_fp } { \exp_last_unbraced:Nf \@@_exp_after_array_f:w { \use:c { l_@@_variable_#1_fp } } \s_@@_expr_stop \exp_after:wN \exp_stop_f: #2 } { \token_if_eq_meaning:NNTF #2 \prg_do_nothing: { \s_@@_symbolic \@@_symbolic_chk:w \@@_variable_o:w #1 , { } ; } { \exp_after:wN \s_@@_symbolic \exp_after:wN \@@_symbolic_chk:w \exp_after:wN \@@_variable_o:w \exp:w \@@_exp_after_symbolic_loop:N #1 { , \exp:w \use_none:nn } \exp_after:wN \exp_end: \exp_after:wN { \exp_after:wN } \exp_after:wN ; #2 } } } % \end{macrocode} % \end{macro} % % \begin{macro} % {\@@_variable_set_parsing:Nn, \@@_variable_set_parsing_aux:NNn} % \begin{macrocode} \cs_new_protected:Npn \@@_variable_set_parsing:Nn #1#2 { \cs_set:Npn \@@_tmp:w { \@@_exp_after_symbolic_f:nw { \@@_parse_infix:NN } \s_@@_symbolic \@@_symbolic_chk:w \@@_variable_o:w #2 , { } ; } \exp_args:NNc \@@_variable_set_parsing_aux:NNn #1 { @@_parse_word_#2:N } {#2} } \cs_new_protected:Npn \@@_variable_set_parsing_aux:NNn #1#2#3 { \cs_if_eq:NNF #2 \@@_tmp:w { \cs_if_exist:NTF #2 { \msg_warning:nnnn { fp } { id-used-elsewhere } {#3} { variable } #1 #2 \@@_tmp:w } { \cs_new_eq:NN #2 \scan_stop: % to declare the function #1 #2 \@@_tmp:w } } } % \end{macrocode} % \end{macro} % % \begin{macro} % { % \fp_clear_variable:n, % \@@_clear_variable:n, \@@_clear_variable_aux:n % } % We need local undefining, so have to do it low-level. % \cs{@@_clear_variable_aux:n} is needed by \cs{@@_set_function:Nnnn} % to skip \cs{@@_id_if_invalid:nTF}. % \begin{macrocode} \cs_new_protected:Npn \fp_clear_variable:n #1 { \exp_args:No \@@_clear_variable:n { \tl_to_str:n {#1} } } \cs_new_protected:Npn \@@_clear_variable:n #1 { \@@_id_if_invalid:nTF {#1} { \msg_error:nnn { fp } { id-invalid } {#1} } { \@@_clear_variable_aux:n {#1} } } \cs_new_protected:Npn \@@_clear_variable_aux:n #1 { \cs_set_eq:cN { l_@@_variable_#1_fp } \tex_undefined:D \@@_variable_set_parsing:Nn \cs_set_eq:NN {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\fp_new_variable:n} % \begin{macro}{\@@_new_variable:n} % Check that |#1| is a valid identifier. If the identifier is already % in use, complain. Then set |\__fp_parse_word_#1:N| to use % |\__fp_variable_o:w|. % \begin{macrocode} \cs_new_protected:Npn \fp_new_variable:n #1 { \exp_args:No \@@_new_variable:n { \tl_to_str:n {#1} } } \cs_new_protected:Npn \@@_new_variable:n #1 { \@@_id_if_invalid:nTF {#1} { \msg_error:nnn { fp } { id-invalid } {#1} } { \cs_if_exist:cT { @@_parse_word_#1:N } { \msg_error:nnn { fp } { id-already-defined } {#1} \cs_undefine:c { @@_parse_word_#1:N } \cs_set_eq:cN { l_@@_variable_#1_fp } \tex_undefined:D } \@@_variable_set_parsing:Nn \cs_gset_eq:NN {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{variable}{\l_@@_symbolic_flag} % \begin{macro}{\fp_set_variable:nn, \@@_set_variable:nn} % Refuse invalid identifiers. If the variable does not exist yet, % define it just as in \cs{fp_new_variable:n} (but without unnecessary % checks). Then evaluate~|#2|. If the result contains the % identifier~|#1|, we would later get a loop in cases such as % \begin{quote} % \cs{fp_set_variable:nn} |{A}| |{A}|\\ % \cs{fp_show:n} |{A}| % \end{quote} % To detect this, define |\l__fp_variable_#1_fp| to raise an % internal flag and evaluate to \texttt{nan}. Then re-evaluate % \cs{l_@@_symbolic_fp}, and store the result in~|#1|. If the flag is % raised, |#1|~was present in \cs{l_@@_symbolic_fp}. In all cases, % the |#1|-free result ends up in |\l__fp_variable_#1_fp|. % \begin{macrocode} \flag_new:N \l_@@_symbolic_flag \cs_new_protected:Npn \fp_set_variable:nn #1 { \exp_args:No \@@_set_variable:nn { \tl_to_str:n {#1} } } \cs_new_protected:Npn \@@_set_variable:nn #1#2 { \@@_id_if_invalid:nTF {#1} { \msg_error:nnn { fp } { id-invalid } {#1} } { \@@_variable_set_parsing:Nn \cs_set_eq:NN {#1} \fp_set:Nn \l_@@_symbolic_fp {#2} \cs_set_nopar:cpn { l_@@_variable_#1_fp } { \flag_ensure_raised:N \l_@@_symbolic_flag \c_nan_fp } \flag_clear:N \l_@@_symbolic_flag \fp_set:cn { l_@@_variable_#1_fp } { \l_@@_symbolic_fp } \flag_if_raised:NT \l_@@_symbolic_flag { \msg_error:nneee { fp } { id-loop } { #1 } { \tl_to_str:n {#2} } { \fp_to_tl:N \l_@@_symbolic_fp } } } } % \end{macrocode} % \end{macro} % \end{variable} % % \subsection{Messages} % % \begin{macrocode} \msg_new:nnnn { fp } { id-invalid } { Floating~point~identifier~'#1'~invalid. } { LaTeX~has~been~asked~to~create~a~new~floating~point~identifier~'#1'~ but~this~may~only~contain~ASCII~letters. } \msg_new:nnnn { fp } { id-already-defined } { Floating~point~identifier~'#1'~already~defined. } { LaTeX~has~been~asked~to~create~a~new~floating~point~identifier~'#1'~ but~this~name~has~already~been~used~elsewhere. } \msg_new:nnnn { fp } { id-used-elsewhere } { Floating~point~identifier~'#1'~already~used~for~something~else. } { LaTeX~has~been~asked~to~create~a~new~floating~point~identifier~'#1'~ but~this~name~is~used,~and~is~not~a~user-defined~#2. } \msg_new:nnnn { fp } { id-loop } { Variable~'#1'~used~in~the~definition~of~'#1'. } { LaTeX~has~been~asked~to~set~the~floating~point~identifier~'#1'~ to~the~expression~'#2'.~Evaluating~this~expression~yields~'#3',~ which~contains~'#1'~itself. } % \end{macrocode} % % \subsection{Road-map} % % The following functions are not implemented: |min|, |max|, |?:|, % comparisons, |round|, |atan|, |acot|. % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex