5

This is a follow-up question to LaTeX3 conditional with grouping fails to compile. I would like to write a token list conditional with expl3 that is fully expandable in order to use it as a predicate conditional. Motivated by Joseph Wright's comment I thought of a recursive implementation, but I cannot figure out how to put it into the expl3 syntax.

For the sake of this question let's implement a function, which checks whether its argument is an integer, i.e. only composed of digits. It is straightforward to save the argument in a local token list and then run an appropriate test. However, the resulting code is not expandable due to the assignment. Without an assignment a possible solution might look as follows.

\prg_new_conditional:Npnn \is_integer:n #1 { p, T, F, TF }
{
  \tl_if_empty:nTF { #1 }
  {
    % We are done if the token list is empty
    \prg_return_true:
  }{
    \exp_args:NNx \tl_if_in:nnTF { 0123456789 } { \tl_head:n { #1 } }
    {
      % Call \is_integer:n with \tl_tail:n { #1 }
    }{
      \prg_return_false:
    }
  }
}

Is there any way how to call \is_integer:n again in the indicated line? From my understanding the desired implementation would be expandable, as for any given input the token replacement done by TeX would eventually terminate. Of course, alternative approaches with the same result are appreciated.

As a side remark, I am aware of already existing implementations e.g. in the xstring package. However, I need to make adjustments for my own needs and cannot use such solutions.

2
  • The number can only be positive?
    – egreg
    Commented May 11, 2015 at 20:08
  • For simplicity, yes. Once this works, more general cases should be feasable.
    – ranguwud
    Commented May 11, 2015 at 20:10

2 Answers 2

3

The old trick for checking if a token list <tl> consists only of digits is to use \romannumeral-<tl>, which will return nothing in that case.

As far as I know there's no public interface for the trick in expl3, only \__int_to_roman:w:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\prg_new_conditional:Npnn \is_integer:n #1 { p, T, F, TF }
 {
  \tl_if_blank:oTF { \__int_to_roman:w -0#1 }
   {
    \prg_return_true:
   }
   {
    \prg_return_false:
   }
 }

\NewDocumentCommand{\isinteger}{m}
 {
  #1~is \bool_if:nF { \is_integer_p:n {#1} } {~not}~an~integer
 }
\ExplSyntaxOff

\begin{document}

\isinteger{42}

\isinteger{2ab1}

\end{document}

enter image description here

14
  • Thanks a lot for your suggestion. However, to my understanding this is not flexible to obtain more sophisticated checks. As I stated in my question, the check for integers is just a mere example. If I want to check for more complex patterns, I won't be able to rely on already implemented functions such as \__int_to_roman:w.
    – ranguwud
    Commented May 11, 2015 at 20:29
  • @ranguwud More complex patterns require unexpandable functions, or LuaTeX
    – egreg
    Commented May 11, 2015 at 20:36
  • 2
    @ranguwud \prg_new_conditional:Npnn isn't your problem it is defining your test by expansion for the example you gave , \romannumeral gives an expandable test, for other tests, it depends. In general it may not be possible. Commented May 11, 2015 at 21:13
  • 1
    @Gaussler I fixed it; note that only positive integers can be tested this way.
    – egreg
    Commented Aug 12, 2015 at 17:01
  • 1
    @gusbrs There has been some recent discussion, no decision yet.
    – egreg
    Commented Jul 28, 2021 at 7:19
1

To answer my own question, using only expandable macros and avoiding the use of \prg_new_conditional:Npnn the following example just works fine and is flexible in the way that all used tests can be adapted to specific problems and do not rely on predefined code like \__int_to_roman:w suggested by egreg.

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn

\bool_new:N \true_bool
\bool_new:N \false_bool

\bool_gset_true:N \true_bool
\bool_gset_false:N \false_bool

\prg_new_conditional:Npnn \is_digit:N #1 { TF }
{
  \bool_if:nTF
  {
    \token_if_eq_charcode_p:NN 0 #1 ||
    \token_if_eq_charcode_p:NN 1 #1 ||
    \token_if_eq_charcode_p:NN 2 #1 ||
    \token_if_eq_charcode_p:NN 3 #1 ||
    \token_if_eq_charcode_p:NN 4 #1 ||
    \token_if_eq_charcode_p:NN 5 #1 ||
    \token_if_eq_charcode_p:NN 6 #1 ||
    \token_if_eq_charcode_p:NN 7 #1 ||
    \token_if_eq_charcode_p:NN 8 #1 ||
    \token_if_eq_charcode_p:NN 9 #1
  }{
    \prg_return_true:
  }{
    \prg_return_false:
  }
}

\cs_new:Npn \is_integer_p:n #1
{
  \tl_if_empty:nTF { #1 }
  {
    % We are done if the token list is empty
    \bool_if_p:n { \true_bool }
  }{
    \exp_args:Nf \is_digit:NTF { \tl_head:n { #1 } }
    {
      \exp_args:Nf \is_integer_p:n { \tl_tail:n { #1 } }
    }{
      \bool_if_p:n { \false_bool }
    }
  }
}

\bool_if:nTF { \is_integer_p:n { 1234 } } { true } { false }

\ExplSyntaxOff
\end{document}

Interestingly, trying the same with \prg_new_conditional:Npnn doesn't seem to be possible. The following throws errors, although I don't understand why. I would assume that both implementations result in equivalent code, but apparently this isn't true.

\prg_new_conditional:Npnn \is_integer_prg:n #1 { p }
{
  \tl_if_empty:nTF { #1 }
  {
    % We are done if the token list is empty
    \prg_return_true:
  }{
    \exp_args:Nf \is_digit:NTF { \tl_head:n { #1 } }
    {
      \exp_args:Nf \is_integer_prg_p:n { \tl_tail:n { #1 } }
    }{
      \prg_return_false:
    }
  }
}

Maybe somebody can clarify, why the \prg_new_conditional:Npnn solution doesn't work.


Edit: Eventually I figured out how to do the trick with \prg_new_conditional:Npnn. The problem in my previous attempt was that a predicate conditional is no legal "return value" of \prg_new_conditional:Npnn. This means that

\prg_new_conditional:Npnn \foo: { p }
{
  \int_compare_p:n { 1 = 2 }
}

fails, while

\prg_new_conditional:Npnn \foo: { p }
{
  \int_compare:nTF { 1 = 2 } { \prg_return_true: } { \prg_return_false: }
}

works, which of course makes sense in hindsight. So the above attempt can be corrected to the following working example:

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn

\prg_new_conditional:Npnn \is_digit:N #1 { TF }
{
  \bool_if:nTF
  {
    \token_if_eq_charcode_p:NN 0 #1 ||
    \token_if_eq_charcode_p:NN 1 #1 ||
    \token_if_eq_charcode_p:NN 2 #1 ||
    \token_if_eq_charcode_p:NN 3 #1 ||
    \token_if_eq_charcode_p:NN 4 #1 ||
    \token_if_eq_charcode_p:NN 5 #1 ||
    \token_if_eq_charcode_p:NN 6 #1 ||
    \token_if_eq_charcode_p:NN 7 #1 ||
    \token_if_eq_charcode_p:NN 8 #1 ||
    \token_if_eq_charcode_p:NN 9 #1
  }{
    \prg_return_true:
  }{
    \prg_return_false:
  }
}

\prg_new_conditional:Npnn \is_integer:n #1 { p, TF }
{
  \tl_if_empty:nTF { #1 }
  {
    % We are done if the token list is empty
    \prg_return_true:
  }{
    \exp_args:Nf \is_digit:NTF { \tl_head:n { #1 } }
    {
      \exp_args:Nf \is_integer:nTF { \tl_tail:n { #1 } }
      {
        \prg_return_true:
      }{
        \prg_return_false:
      }
    }{
      \prg_return_false:
    }
  }
}

\bool_if:nTF { \is_integer_p:n { 1234 } } { true } { false },~
\bool_if:nTF { \is_integer_p:n { 12ab } } { true } { false }

\ExplSyntaxOff
\end{document}

As expected, this results in the text true, false in the document. Of course, as pointed out by Manuel, the use of \exp_args:Nf can be avoided by defining appropriate variants of \is_digit:NTF and \is_integer:NTF. Marking as solved here.

1
  • You should use \cs_generate_variant:Nn when possible.
    – Manuel
    Commented May 12, 2015 at 14:05

You must log in to answer this question.

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