3

Here is the boiled-down extract of what I'm trying to do. Apologies beforehand if some of the computations do not make sense, I've modified them for the sake of simplicity.

Basically, I've a function called \basicMacro which takes an argument, and depending on its value, makes some computations (floating point computations, even if in the simplified example it's not the case).

I want to create another macro (\anotherUseMacro) which will do some modification to the input variable, and then call the \basicMacro with an argument.

However, the second step poses problem, and I end up with an empty () floating point variable, and I don't know why.

Here is the MWE :

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\fp_new:N \l_cvssAV_fp
\fp_new:N \l_argument_fp
\fp_new:N \l_value_fp
\fp_new:N \l_result_fp
\str_new:N \l_input_str

\NewDocumentCommand \chooseValue { m }{
    \str_case:nn {#1}
    {
        { N } { 0.85 } % This value will be selected
        { A } { 0.62 } % This will not
    }
}

\NewExpandableDocumentCommand \computeValue {m}{
    \fp_set:Nn \l_argument_fp { \chooseValue{#1} }
    \fp_show:N \l_argument_fp %  \l_argument_fp=(). <<<< HERE IS THE ERROR
    \fp_set:Nn \l_value_fp { 1 - (1 - \l_argument_fp )}
    %                        1 - (1 -      0.85      )
}

\NewDocumentCommand \factorScaling {m}{%
    \fp_eval:n { 8.22 * (#1) }%
    %            8.22 * 0.85
}%

% https://tex.stackexchange.com/a/615358/28926
\NewDocumentCommand \roundup {m}{
    \fp_eval:n { ceil(#1,1) }
    \fp_compare:nT { ceil(#1,1)=ceil(#1,0) } {.0}
}


\cs_new_protected:Npn \basicMacro #1 {
    \computeValue{#1}
    % \l_argument_fp=0.85
    \fp_set:Nn \l_result_fp { \factorScaling{\chooseValue{#1}} }%
    % \l_result_fp=8.22*0.85
    % \l_result_fp=6.987
    %
    \fp_compare:nTF { \l_value_fp <= 0 }
    % IF value <=0
    {
        % value <=0
        0.0
    }{
        \fp_eval:n { round( min( 1.08 * (\l_value_fp + \l_result_fp), 10) ) }%
    }%
}




\NewDocumentCommand \anotherUseMacro { m }{%
    \str_set:Nn \l_input_str { #1 }
    % CALLS basicMACRO
    \basicMacro{\l_input_str}

}%
\ExplSyntaxOff

\begin{document}
    
    \basicMacro{N}
    
    \anotherUseMacro{N} 
\end{document}

Looking at the logs, I can see that the variable \l_argument_fp correcly equals 0.85 in the 1st call, but in the second case (when used inside \anotherUseMacro), it is empty :

l.69     \basicMacro{N}
                 

> \l_argument_fp=().
<recently read> }

Thus, the next operation is invalid, hence the error thrown :

Invalid operation (1)-(())

How can I solve my problem ? It seems that the expression \fp_set:Nn \l_argument_fp { \chooseValue{#1} } is not properly executed when it's called from \anotherUserMacro.

NB : I've little experience with expl3.

2 Answers 2

3

You defined \basicMacro such that it takes a single character as argument, then \chooseValue does:

\str_case:nn {#1}
  {
    { N } { 0.85 } % This value will be selected
    { A } { 0.62 } % This will not
 }

to select one. However when you do, in \anotherUseMacro:

\str_set:Nn \l_input_str { #1 }
\basicMacro{\l_input_str}

you are effectively calling \chooseValue with the string \l_input_str, and not with its value. You can easily see the problem if you change the definition of \chooseValue to add a default case to the \str_case:nn call:

\NewDocumentCommand \chooseValue { m }
  {
    \str_case:nnF {#1}
      {
        { N } { 0.85 } % This value will be selected
        { A } { 0.62 } % This will not
      }
      {
        \msg_expandable_error:nnn { 3isenheim } { wrong-case } {#1}
        1.00 % default value
      }
  }
\msg_new:nnn { 3isenheim } { wrong-case }
  { Value~'#1'~invalid~for~\iow_char:N\\chooseValue. }

then you'll see the error message:

! Use of \??? doesn't match its definition.
<argument> \???
                 ! Package 3isenheim Error: Value '\l_input_str ' invalid fo...
l.79 \anotherUseMacro{N}

?

You have to expand the string variable before comparing it. One option is to always exhaustively expand the argument, then you can use \str_case_e:nnF instead of \str_case:nnF (in the definition above). The other one is to expand \l_input_str in \anotherUseMacro before passing it on to \basicMacro:

\NewDocumentCommand \anotherUseMacro { m }
  {
    \str_set:Nn \l_input_str {#1}
    \exp_args:NV \basicMacro \l_input_str
  }

Other poitns:

  • Your variables should be called \l__eisenheim_input_str and so on to have a proper <module> part. There's an explanation here;

  • \chooseValue looks like an internal macro, so I'd call it \__eisenheim_choose_value:n and define it with \cs_new:Npn;

  • \computeValue is not expandable (because \fp_set:Nn is not expandable) so you should define it with \NewDocumentCommand instead (or \cs_new_protected:Npn if it's internal).


Complete code:

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\fp_new:N \l_cvssAV_fp
\fp_new:N \l_argument_fp
\fp_new:N \l_value_fp
\fp_new:N \l_result_fp
\str_new:N \l_input_str

\NewDocumentCommand \chooseValue { m }
  {
    \str_case:nnF {#1}
      {
        { N } { 0.85 } % This value will be selected
        { A } { 0.62 } % This will not
      }
      {
        \msg_expandable_error:nnn { 3isenheim } { wrong-case } {#1}
        1.00 % default value
      }
  }
\msg_new:nnn { 3isenheim } { wrong-case }
  { Value~'#1'~invalid~for~\iow_char:N\\chooseValue. }


\NewDocumentCommand \computeValue {m}{
    \fp_set:Nn \l_argument_fp { \chooseValue{#1} }
    \fp_show:N \l_argument_fp %  \l_argument_fp=(). <<<< HERE IS THE ERROR
    \fp_set:Nn \l_value_fp { 1 - (1 - \l_argument_fp )}
    %                        1 - (1 -      0.85      )
}

\NewDocumentCommand \factorScaling {m}{%
    \fp_eval:n { 8.22 * (#1) }%
    %            8.22 * 0.85
}%

% https://tex.stackexchange.com/a/615358/28926
\NewDocumentCommand \roundup {m}{
    \fp_eval:n { ceil(#1,1) }
    \fp_compare:nT { ceil(#1,1)=ceil(#1,0) } {.0}
}


\cs_new_protected:Npn \basicMacro #1 {
    \computeValue{#1}
    % \l_argument_fp=0.85
    \fp_set:Nn \l_result_fp { \factorScaling{\chooseValue{#1}} }%
    % \l_result_fp=8.22*0.85
    % \l_result_fp=6.987
    %
    \fp_compare:nTF { \l_value_fp <= 0 }
    % IF value <=0
    {
        % value <=0
        0.0
    }{
        \fp_eval:n { round( min( 1.08 * (\l_value_fp + \l_result_fp), 10) ) }%
    }%
}


\NewDocumentCommand \anotherUseMacro { m }{%
    \str_set:Nn \l_input_str { #1 }
    % CALLS basicMACRO
    \exp_args:NV \basicMacro \l_input_str
}%

\ExplSyntaxOff

\begin{document}

\basicMacro{N}

\anotherUseMacro{N}

\end{document}

Here is a fully expandable implementation of \basicMacro. Since you are doing basically only floating point computations, it is easy to do that. Roughly you want to replace every occurrence of:

\cs_new:Npn \some_macro:n #1
  {
    \fp_set:Nn \l_some_temp_fp { <computations> }
    \fp_eval:n { <more computations with \l_some_temp_fp> }
  }

by

\cs_new:Npn \some_macro:n #1
  {
    \exp_args:Ne \__some_macro_internal:n
      { \fp_eval:n { <computations> } }
  }
\cs_new:Npn \__some_macro_internal:n #1
  { \fp_eval:n { <more computations with #1> } }

so that you remove the non-expandable \fp_set:Nn and instead just use \fp_eval:n (expandable) and pass its result around as arguments (expandable).

Here's the full code:

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\cs_new:Npn \__eisenheim_choose_value:n #1
  {
    \str_case:nnF {#1}
      {
        { N } { 0.85 } % This value will be selected
        { A } { 0.62 } % This will not
      }
      {
        \msg_expandable_error:nnn { 3isenheim } { wrong-case } {#1}
        1.00 % default value
      }
  }
\msg_new:nnn { 3isenheim } { wrong-case }
  { Value~'#1'~invalid~for~\iow_char:N\\chooseValue. }

\cs_new:Npn \__eisenheim_compute_value:n #1 { 1 - (1 - \__eisenheim_choose_value:n {#1} ) }
\cs_new:Npn \__eisenheim_factor_scaling:n #1 { 8.22 * (#1) }

\NewExpandableDocumentCommand \basicMacro { m }
  {
    \exp_args:Nee \__eisenheim_basic_aux:nn
      { \fp_eval:n { \__eisenheim_compute_value:n {#1} } }
      { \fp_eval:n { \__eisenheim_factor_scaling:n { \__eisenheim_choose_value:n {#1} } } }
  }
\cs_new:Npn \__eisenheim_basic_aux:nn #1 #2
  {
    \fp_compare:nTF { #1 <= 0 }
      { 0.0 }
      { \fp_eval:n { round( min( 1.08 * (#1 + #2), 10) ) } }
  }
\ExplSyntaxOff

\begin{document}

\edef\x{\basicMacro{N}}

\texttt{\meaning\x}

\end{document}
5
  • Thanks. If \fp_set:Nn is not expandable, how can I use it in an expandable macro ?
    – 3isenHeim
    Nov 4, 2021 at 10:34
  • @3isenHeim You can't. For a macro to be "expandable", all the macros used in its definition must themselves be expandable. What do you want to do with \fp_set:Nn in an expandable macro? Though you could change the definition of \computeValue to \NewExpandableDocumentCommand \computeValue {m} { 1 - (1 - \chooseValue{#1} ) }, then instead of using \l_value_fp you'd use \computeValue directly Nov 4, 2021 at 10:36
  • In anotherMacro, I'd like to store the result of \basicMacro. I need to use its output multiple times I believe it's more logical to store the output inside a variable and re-use it, than calling the macro everytime (in my project it does some more computations).
    – 3isenHeim
    Nov 4, 2021 at 10:40
  • 1
    @3isenHeim "storing the result" implies an assignment, which is by definition not expandable. You could instead just expand the value of \computeValue (changed as I suggested in the comment above) and \factorScaling, then pass them as #1 and #2 to an internal macro: \cs_new:Npn \basicMacro #1 { \exp_args:Nee \__eisenheim_basic_aux:nn { \computeValue{#1} } { { \factorScaling { \chooseValue {#1} } } } } \cs_new:Npn \__eisenheim_basic_aux:nn #1 #2 { \fp_compare:nTF { #1 <= 0 } { 0.0 } { \fp_eval:n { round( min( 1.08 * (#1 + #2), 10) ) } } } Nov 4, 2021 at 10:50
  • 1
    @3isenHeim I added a fully expandable version of \basicMacro to the answer with some explanation. Nov 4, 2021 at 11:06
4

You have to access the value of the string, not the container.

Here's a modified version of your code, with the internal functions named according to the recommended style and with a more precise distinction between expandable and protected functions (you were mixing up them).

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\fp_new:N \l_eisenheim_cvssAV_fp
\fp_new:N \l_eisenheim_argument_fp
\fp_new:N \l_eisenheim_value_fp
\fp_new:N \l_eisenheim_result_fp
\str_new:N \l_eisenheim_input_str

\cs_new:Nn \eisenheim_choosevalue:n
  {
    \str_case:nn {#1}
    {
        { N } { 0.85 } % This value will be selected
        { A } { 0.62 } % This will not
    }
  }

\cs_new_protected:Nn \eisenheim_computevalue:n
  {
    \fp_set:Nn \l_eisenheim_argument_fp { \eisenheim_choosevalue:n {#1} }
    \fp_show:N \l_eisenheim_argument_fp %  \l_eisenheim_argument_fp=(). <<<< HERE IS THE ERROR
    \fp_set:Nn \l_eisenheim_value_fp { 1 - (1 - \l_eisenheim_argument_fp )}
    %                        1 - (1 -      0.85      )
 }

\cs_new:Nn \eisenheim_factorscaling:n
  {
    \fp_eval:n { 8.22 * (#1) }%
    %            8.22 * 0.85
  }

% https://tex.stackexchange.com/a/615358/28926
\cs_new:Nn \eisenheim_roundup:n
  {
    \fp_eval:n { ceil(#1,1) }
    \fp_compare:nT { ceil(#1,1)=ceil(#1,0) } {.0}
 }


\NewDocumentCommand{\basicMacro} {m}
  {
   \eisenheim_basicmacro:n {#1}
  }

\cs_new_protected:Nn \eisenheim_basicmacro:n
  {
    \eisenheim_computevalue:n {#1}
    % \l_eisenheim_argument_fp=0.85
    \fp_set:Nn \l_eisenheim_result_fp
      {
       \eisenheim_factorscaling:n { \eisenheim_choosevalue:n {#1} }
      }
    % \l_eisenheim_result_fp=8.22*0.85
    % \l_eisenheim_result_fp=6.987
    %
    \fp_compare:nTF { \l_eisenheim_value_fp <= 0 }
    % IF value <=0
      {
        % value <=0
        0.0
      }
      {
        \fp_eval:n { round( min( 1.08 * (\l_eisenheim_value_fp + \l_eisenheim_result_fp), 10) ) }
      }
  }
\cs_generate_variant:Nn \eisenheim_basicmacro:n { V }

\NewDocumentCommand \anotherUseMacro { m }
  {
     \str_set:Nn \l_eisenheim_input_str { #1 }
     % CALLS basicMACRO
     \eisenheim_basicmacro:V \l_eisenheim_input_str
  }
\ExplSyntaxOff

\begin{document}
    
\basicMacro{N}
    
\anotherUseMacro{N} 

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .