4

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.

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

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

  • 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 May 11 '15 at 20:29
  • @ranguwud More complex patterns require unexpandable functions, or LuaTeX – egreg May 11 '15 at 20:36
  • Actually I don't think this is true. If I choose not to use \prg_new_conditional:Npnn but to directly define \is_integer_p:n, then I should be able to call \is_integer_p:n recursively and end up with flexible and fully expandable code. Tell me if I am mistaken in that point! So my question would rather be: Can I achieve the same using \prg_new_conditional:Npnn? If not, how can I properly return true and false in my self-defined macro \is_integer_p:n? – ranguwud May 11 '15 at 20:46
  • 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. – David Carlisle May 11 '15 at 21:13
  • 1
    @Gaussler I fixed it; note that only positive integers can be tested this way. – egreg Aug 12 '15 at 17:01
0

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.

  • You should use \cs_generate_variant:Nn when possible. – Manuel May 12 '15 at 14:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.