7

Is there a command that asks whether an argument to \fp_eval is a parse error or not? E.g.:

\fp_if_valid:nTF { 1+2.3*2 } { yes } { no } % yes
\fp_if_valid:nTF { 1+2.3*  } { yes } { no } % no

I would like to be able to sanitize user input and throw my own error if their input is not valid. Anyways I was just curious whether there is a built-in command to do this, or whether the latex3 team intends to add this functionality. I wrote the following code, which seems to work (thanks to Rob Hall for fixing some mistakes):

\documentclass{article}
\usepackage{xfp}

\ExplSyntaxOn
% Add missing variant based on definition of \exp_last_unbraced:Nf in l3expan.dtx    
\cs_set:Npn \exp_last_unbraced:NNf #1#2#3
    { \exp_after:wN #1 \exp_after:wN #2 \exp:w \exp_end_continue_f:w #3 }

\tl_new:N \l_my_fp_if_valid_tl

% \return_marker: for maintaining true/false state outside \fp_eval program flow
\scan_new:N \my_fp_if_valid_return_marker:
% Protected wrappers for \prg_return_true: and \prg_return_false:
\cs_new_protected:Nn \my_fp_if_valid_return_true:  { \prg_return_true:  }
\cs_new_protected:Nn \my_fp_if_valid_return_false: { \prg_return_false: }

% Replace "\return_marker: \return_<true or false>:" 
% with "\return_marker: \return_false:"
\cs_new:Npn \my_fp_if_valid_set_return_false:wN #1 \my_fp_if_valid_return_marker: #2
    { #1 \my_fp_if_valid_return_marker: \my_fp_if_valid_return_false: }

\prg_new_protected_conditional:Nnn \my_fp_if_valid:n {T, F, TF} {
    \group_begin:
        % make error command throw away error message and call \set_return_false:wN
        \cs_set:Nn \__msg_expandable_error:n { \my_fp_if_valid_set_return_false:wN }
        % Sets \l_if_valid_tl to 
        % "<numerical result> \return_marker: \return_<true or false>:"
        \tl_set:Nx \l_my_fp_if_valid_tl { 
            \fp_eval:n { #1 } 
            \my_fp_if_valid_return_marker: \my_fp_if_valid_return_true:
        }
    % Put <numerical result> into \result: and evaluate \return_<true or false>:
    \exp_last_unbraced:NNf \group_end:
        \my_fp_if_valid_helper:w \tl_use:N \l_my_fp_if_valid_tl
}

% Put <numerical result> into \result:
\cs_new:Npn \my_fp_if_valid_helper:w #1 \my_fp_if_valid_return_marker: {
    \cs_set:Nn \my_fp_if_valid_result: { #1 }
} 

\cs_new_eq:NN \fpifvalid \my_fp_if_valid:nTF

\ExplSyntaxOff

\usepackage{xcolor}
\def\testfpifvalid#1{\fpifvalid{#1}{\color{blue}}{\color{red}}\texttt{#1}\par}
\begin{document}

\testfpifvalid{1+1}             % valid
\testfpifvalid{1+}              % invalid
\testfpifvalid{1+2.3*2}         % valid
\testfpifvalid{1+2.3 2}         % valid
\testfpifvalid{1+2.3.*2}        % invalid
\testfpifvalid{1++2}            % valid
\testfpifvalid{1+*2}            % invalid
\testfpifvalid{1*+2}            % valid
\testfpifvalid{1/0}             % invalid
\testfpifvalid{1)2}             % invalid
\testfpifvalid{1(2}             % invalid
\testfpifvalid{1(2)}            % valid
\testfpifvalid{1+1(2/6)+sin(7)} % valid
\testfpifvalid{floor(6,-1)}     % valid
\testfpifvalid{floor(6,7,7)}    % invalid

\end{document} 
Hood Chatham
  • 5,467
  • You should name them differently, at least, by using your own prefix and probably xparse to provide the document-level interface. – cfr Feb 25 '18 at 20:57
  • @cfr I agree with the name them differently suggestion, updated to add my_ prefix to everything. – Hood Chatham Feb 25 '18 at 21:07
  • I don't see any reason to use xparse instead of performing a \let (or maybe a \def wrapper, in case the definition of the inner command might change). The point of xparse is to handle complicated argument syntax. If my argument syntax is just that I want a single manditory short argument, what's the benefit? – Hood Chatham Feb 25 '18 at 21:08
  • 1
    Really, my point was you shouldn't use \cs_new_eq with a function name not in line with the naming. But you are handling input: you are providing \fpifvalid which takes 3 arguments. The point of xparse, as I understand it, is to provide document-level macros (at least in cases which one or more arguments are or may be involved). That is, it is not just for more complicated cases, it is for simple and complicated cases which expose expl3 functions at the document-level. (& of course there are good reasons to avoid \let, \def, though \let sometimes seems most suitable for variables?) – cfr Feb 25 '18 at 21:29
  • I want a function \fpfifvalid that takes three mandatory arguments, the first short, the other two long. \fp_if_valid:nTF takes three mandatory arguments, the first short, the other two long. Why do I need a fancy package for this? Also, \cs_new_eq:NN is just a variant of \let that does an existence safety check, and \cs_new:Npn is a version of \def that does an existence check. – Hood Chatham Feb 27 '18 at 01:55
  • @HoodChatham The point is to keep the programming clean. expl3 is for programming (and the naming conventions should be followed), xparse is for document commands (and those naming conventions do not apply). (This is similar to how you should use \tl_new:N even though you don't need to, or how you should use v and not o for expanding token list variables. It just makes everything so much tidyer.) Just do \NewDocumentCommand\fpifvalid{}{\my_fp_if_valid:nTF}. – schtandard Aug 08 '19 at 19:26

1 Answers1

1

I came across this question in 2020 having this same need. I am unaware (like OP) if there is some other canned solution in the LaTeX3 Interfaces at this time. If you want to use this approach, there are issues to be addressed:

  1. Some minor typo's in OPs example code need to be resolved
  2. \__scan_new:N has been relocated

For those looking for a copy-paste drop-in solution using OPs code, take the following

% depends on xfp, pgfmath
\usepackage{xfp}
\usepackage{pgfmath}

\ExplSyntaxOn
% Add missing variant based on definition of \exp_last_unbraced:Nf in l3expan.dtx    
\cs_set:Npn \exp_last_unbraced:NNf #1#2#3
    { \exp_after:wN #1 \exp_after:wN #2 \exp:w \exp_end_continue_f:w #3 }

\tl_new:N \l_my_fp_if_valid_tl

% \return_marker: for maintaining true/false state outside \fp_eval program flow
\scan_new:N \my_fp_if_valid_return_marker:
% Protected wrappers for \prg_return_true: and \prg_return_false:
\cs_new_protected:Nn \my_fp_if_valid_return_true:  { \prg_return_true:  }
\cs_new_protected:Nn \my_fp_if_valid_return_false: { \prg_return_false: }

% Replace "\return_marker: \return_<true or false>:" 
% with "\return_marker: \return_false:"
\cs_new:Npn \my_fp_if_valid_set_return_false:wN #1 \my_fp_if_valid_return_marker: #2
    { #1 \my_fp_if_valid_return_marker: \my_fp_if_valid_return_false: }

\prg_new_protected_conditional:Nnn \my_fp_if_valid:n {T, F, TF} {
    \group_begin:
        % make error command throw away error message and call \set_return_false:wN
        \cs_set:Nn \__msg_expandable_error:n { \my_fp_if_valid_set_return_false:wN }
        % Sets \l_if_valid_tl to 
        % "<numerical result> \return_marker: \return_<true or false>:"
        \tl_set:Nx \l_my_fp_if_valid_tl { 
            \fp_eval:n { #1 } 
            \my_fp_if_valid_return_marker: \my_fp_if_valid_return_true:
        }
    % Put <numerical result> into \result: and evaluate \return_<true or false>:
    \exp_last_unbraced:NNf \group_end:
        \my_fp_if_valid_helper:w \tl_use:N \l_my_fp_if_valid_tl
}

% Put <numerical result> into \result:
\cs_new:Npn \my_fp_if_valid_helper:w #1 \my_fp_if_valid_return_marker: {
    \cs_set:Nn \my_fp_if_valid_result: { #1 }
} 

\cs_new_eq:NN \fpifvalid \my_fp_if_valid:nTF

\ExplSyntaxOff

OPs original demonstration will now continue to work

% demo depends on xcolor
\usepackage{xcolor}
\begin{document}
\def\testfpifvalid#1{\fpifvalid{#1}{\color{blue}}{\color{red}}\texttt{#1}\par}
\testfpifvalid{1+1}             % valid
\testfpifvalid{1+}              % invalid
\testfpifvalid{1+2.3*2}         % valid
\testfpifvalid{1+2.3 2}         % valid
\testfpifvalid{1+2.3.*2}        % invalid
\testfpifvalid{1++2}            % valid
\testfpifvalid{1+*2}            % invalid
\testfpifvalid{1*+2}            % valid
\testfpifvalid{1/0}             % invalid
\testfpifvalid{1)2}             % invalid
\testfpifvalid{1(2}             % invalid
\testfpifvalid{1(2)}            % valid
\testfpifvalid{1+1(2/6)+sin(7)} % valid
\testfpifvalid{floor(6,-1)}     % valid
\testfpifvalid{floor(6,7,7)}    % invalid
Rob Hall
  • 143
  • The pgfmath include seems irrelevant. I suspect that I was writing this at the same time as I made similar code for \pgfmathparse redefining the error commands to escape out of parsing and return false. Unfortunately, life is much harder for the etex built-in \numexpr and for \int_eval which is a wrapper around \numexpr. I had to write a whole finite state machine to sanitize input for \numexpr. – Hood Chatham May 01 '20 at 18:18
  • Do you mind if I copy your fixed version back into my post? – Hood Chatham May 01 '20 at 18:20
  • Not at all. That's really the reference people are going to see first, anyway – Rob Hall May 01 '20 at 20:19