% \iffalse
%
%% File l3fp-symbolic.dtx (C) Copyright 2012-2025 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}
%</driver>
% \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 2026-08-10}
%
% \maketitle
%
% \begin{documentation}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3fp-symbolic} implementation}
%
%    \begin{macrocode}
%<*code>
%    \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}
% \cs{@@_sep:} 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} (typically empty) 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} \cs{@@_sep:}
% \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} \cs{@@_sep:}
% \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 is a user-defined function applied to some arguments
% it is stored as
% \begin{quote}
%   \cs{s_@@_symbolic} \cs{@@_symbolic_chk:w} \\
%   \cs{@@_function_o:w} \cs[no-index]{@@_\meta{identifier}_o:w} |,| \\
%   |{| \meta{operands} |}| \meta{junk} \cs{@@_sep:}
% \end{quote}
% 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} \cs{@@_sep:}
% \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 , { } \__fp_sep:
%     \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 , { } \__fp_sep:
%         \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 , { } \__fp_sep:
%           } \__fp_sep:
%       } \__fp_sep:
%   } \__fp_sep:
% \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\@@_sep:
  {
    \msg_error:nne { fp } { misused-fp }
      {
        \@@_to_tl_dispatch:w
          \s_@@_symbolic \@@_symbolic_chk:w #1,{#2}\@@_sep:
      }
  }
%    \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\@@_sep:
  {
    \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 \@@_sep:
    \exp:w \exp_end_continue_f:w #1
  }
\cs_new:Npn \@@_exp_after_symbolic_aux:w #1, #2\@@_sep:
  {
    \@@_if_has_symbolic:nTF {#2}
      { \s_@@_symbolic \@@_symbolic_chk:w #1, {#2} \@@_sep: }
      { #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\@@_sep: #3\@@_sep:
  {
    \@@_exp_after_symbolic_f:nw { \exp_after:wN \exp_stop_f: }
      \s_@@_symbolic \@@_symbolic_chk:w
      \@@_types_binary:Nww #1 , { #2\@@_sep: #3\@@_sep: } \@@_sep:
  }
%    \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\@@_sep: @
  {
    \@@_exp_after_symbolic_f:nw { \exp_after:wN \exp_stop_f: }
      \s_@@_symbolic \@@_symbolic_chk:w
      \@@_types_unary:NNw #1#2 , { #3\@@_sep: } \@@_sep:
  }
%    \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_fact_o:w     ,
%     \@@_symbolic_ln_o:w       ,
%     \@@_symbolic_not_o:w      ,
%     \@@_symbolic_sec_o:w      ,
%     \@@_symbolic_set_sign_o:w ,
%     \@@_symbolic_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} {fact} {ln}
    {not} {sec} {set_sign} {sin} {sign} {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 evaluated all the way.
%    \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\@@_sep: #3#4#5
  {
    \str_if_eq:nnTF {#1} { \s_@@_symbolic }
      { \@@_invalid_operation:nnw {#3} {#4} #1#2\@@_sep: }
      { #5 #1#2\@@_sep: }
  }
%    \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,
%     \@@_symbolic_variadic_to_tl:NNw
%   }
%   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\@@_sep:
  {
    \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 }
        { \@@_types_variadic:NNw } { \@@_symbolic_variadic_to_tl:NNw }
      }
      { #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\@@_sep: #3\@@_sep: @
  {
    \use:e
      {
        ( \@@_to_tl_dispatch:w #2\@@_sep: )
        \@@_types_cs_to_op:N #1
        ( \@@_to_tl_dispatch:w #3\@@_sep: )
      }
  }
\cs_new:Npn \@@_symbolic_function_to_tl:Nw #1, #2@
  {
    \use:e
      {
        \@@_types_cs_to_op:N #1
        ( \@@_array_to_clist:n {#2} )
      }
  }
\cs_new:Npn \@@_symbolic_variadic_to_tl:NNw #1#2 , #3 @
  {
    \use:e
      {
        \cs_if_eq:NNTF #1 \@@_minmax_o:Nw
          { \if_meaning:w 2 #2 max \else: min \fi: }
          {
            \cs_if_eq:NNTF #1 \@@_round_o:Nw
              {
                \cs_if_eq:NNTF #2 \@@_round_to_nearest:NNN { round }
                { \cs_if_eq:NNTF #2 \@@_round_to_zero:NNN { trunc }
                  { \cs_if_eq:NNTF #2 \@@_round_to_ninf:NNN { floor } { ceil } } }
              }
              {
                \cs_if_eq:NNTF #1 \@@_acot_o:Nw
                  { \if_meaning:w \use_i:nn #2 acot \else: acotd \fi: }
                  {
                    \cs_if_eq:NNTF #1 \@@_atan_o:Nw
                      { \if_meaning:w \use_i:nn #2 atan \else: atand \fi: }
                      {
                        \cs_if_eq:NNTF #1 \@@_rand_o:Nw { rand } { randint }
                      }
                  }
              }
          }
        ( \@@_array_to_clist:n {#3} )
      }
  }
%    \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 , { } \@@_sep:
          }
          {
            \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 \@@_sep:
            #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 , { } \@@_sep:
      }
    \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,
%   raise an error and 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} }
      {
        \cs_if_exist:cF { @@_parse_word_#1:N }
          {
            \msg_error:nnn {fp} { id-undefined } {#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-undefined }
  { Floating~point~identifier~'#1'~is~undefined. }
  {
    LaTeX~has~been~asked~to~set~a~floating~point~identifier~'#1'~
    but~this~name~has~not~been~declared.
  }
\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: |?:|, comparisons.
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
