3

I am trying to do basic comparison of values but am uncertain how to do it correctly (using LaTeX3 functionality only, so without xifthen).

From the documentation, these are the only generic if functions:

\if:w
\if_bool:N
\if_box_empty:N
\if_case:w
\if_catcode:w
\if_charcode:w
\if_cs_exist:N
\if_cs_exist:w
\if_dim:w 
\if_eof:w 
\if_false:
\if_hbox:N
\if_int_compare:w 
\if_int_odd:w 
\if_meaning:w 
\if_mode_horizontal:
\if_mode_inner: 
\if_mode_math:
\if_mode_vertical:
\if_predicate:w 
\if_true: 
\if_vbox:N

There is also this one:

\IfValueTF

to check for argument presence.

I am wondering though how to just compare arbitrary value types to each other. The reason for this is because the input values might be anything, and it should be testable against a specific value of interest.

For example, I would like to be able to do this:

\ifeq \something 10 % integer
\ifeq \something true % boolean
\ifeq \something 10.12 % float
\ifeq \something foo=bar % prop list
\ifeq \something [1,2,3] % seq

where \something is the same arbitrary variable, so it would end up essentially being:

\ifeq 5 10
\ifeq 5 true
\ifeq 5 10.12
\ifeq 5 foo=bar
\ifeq 5 [1,2,3]
% or
\ifeq foo 10
\ifeq foo true
\ifeq foo 10.12
\ifeq foo foo=bar
\ifeq foo [1,2,3]
% or
\ifeq a=b 10
\ifeq a=b true
\ifeq a=b 10.12
\ifeq a=b foo=bar
\ifeq a=b [1,2,3]
% etc.

Wondering how to go about doing this sort of typechecking. The MWE I have so far is:

\documentclass[a4paper]{article}

\usepackage{expl3}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\gettype}{m}{
  ... return int % if int
  ... return clist % if clist
  ... return proplist % if prop list
  ... return seq % if seq
  ... return bool % if boolean
  ...
}

\NewDocumentCommand{\ifeq}{mmmO{}}{
  % int case
  \if \gettype{#1} == int {
    \if \gettype{#2} != int {
      \#4 % false branch
    }
    \else {
      % comparison for each type perhaps, not sure
      \if #1 == #2 {
        \#3
      }
    }
  }

  % i'd handle all these cases somehow once i understand
  % boolean case
  % float case
  % prop list case
  % seq case
  % clist case
  % tl case
}

\begin{document}

\ifeq{5}{10}
\ifeq{5}{true}
\ifeq{5}{10.12}
\ifeq{5}{foo=bar}
\ifeq{5}{[1,2,3]}
% or
\ifeq{foo}{10}
\ifeq{foo}{true}
\ifeq{foo}{10.12}
\ifeq{foo}{foo=bar}
\ifeq{foo}{[1,2,3]}
% or
\ifeq{a=b}{10}
\ifeq{a=b}{true}
\ifeq{a=b}{10.12}
\ifeq{a=b}{foo=bar}
\ifeq{a=b}{[1,2,3]}
% etc.

\end{document}
  • I fear you're want to use type-casting in LaTeX, which is not really possible. – user31729 Mar 26 '18 at 18:15
  • Not really good code but you could test the given argument with \regex_match:nnTF to check for the type of input and only if the pattern matches execute the correct comparison, else return false. – Skillmon likes topanswers.xyz Mar 26 '18 at 18:35
3

The following is just a proposal. I don't think this should ever be used, but it shows how one could implement some checks on which type of input there is and defining variables of certain type.

It implements this for the types tl, str, int, and fp. To further extent it, one would have to define a regular expression which should be matched for the new data type and a comparing function which has to be added to the str_case:nn.

\documentclass[]{article}

\usepackage{xparse}

\ExplSyntaxOn
\makeatletter
% regexes for comparison
\regex_const:Nn \c_lance_pollard_tl_regex { \A.*\Z }% could be anything
\regex_const:Nn \c_lance_pollard_str_regex { \A\c[OS].*\Z }% could be anything
\regex_const:Nn \c_lance_pollard_int_regex { \A[+\-]?\d+\Z }
\regex_const:Nn \c_lance_pollard_fp_regex { \A[+\-]?\d+\.?\d*\Z }

\NewDocumentCommand\newsomething{ m }
  {
    % #1 : type
    \str_if_eq:nnTF { #1 } { str }
      { \newsomethingVerbatim { #1 } }
      { \newsomethingNormal { #1 } }
  }
\NewDocumentCommand\newsomethingNormal { m m +m }
  {
    % #1 : type
    % #2 : name
    % #3 : value
    \lance_pollard_new_something:nnn { #1 } { #2 } { #3 }
  }
\NewDocumentCommand\newsomethingVerbatim { m m +v }
  {
    % #1 : type
    % #2 : name
    % #3 : value
    \lance_pollard_new_something:nnn { #1 } { #2 } { #3 }
  }
\cs_new:Nn \lance_pollard_new_something:nnn
  {
    % #1 : type
    % #2 : name
    % #3 : value
    \cs_if_exist:cTF { c_lance_pollard_#1_regex }
      {
        \exp_args:Nc \regex_match:NnTF { c_lance_pollard_#1_regex } { #3 }
          {
            \tl_set:cn { l_lance_pollard_#2_type_tl } { #1 }
            \use:c { #1_new:c  } { l_lance_pollard_#2_val_#1 }
            \use:c { #1_set:cn } { l_lance_pollard_#2_val_#1 } { #3 }
          }
          {
            \msg_error:nnnnn { lance_pollard } { badly~formatted }
              { #3 } { #2 } { #1 }
          }
      }
      {
        \msg_error:nnn { lance_pollard } { type~not~setup } { #1 }
      }
  }
\NewDocumentCommand\ifsomethingeq { m }
  {
    % #1 : name
    \exp_args:Nx \str_if_eq:nnTF
      { \tl_use:c { l_lance_pollard_#1_type_tl } }
      { str }
      { \ifsomethingeqVerbatim { #1 } }
      { \ifsomethingeqNormal { #1 } }
  }
\NewDocumentCommand\ifsomethingeqNormal { m +m +m +m }
  {
    % #1 : name which was defined with \newsomething
    % #2 : value to compare with
    % #3 : T
    % #4 : F
    \lance_pollard_if_something_eq:nnnn { #1 } { #2 } { #3 } { #4 }
  }
\NewDocumentCommand\ifsomethingeqVerbatim { m +v +m +m }
  {
    % #1 : name which was defined with \newsomething
    % #2 : value to compare with
    % #3 : T
    % #4 : F
    \lance_pollard_if_something_eq:nnnn { #1 } { #2 } { #3 } { #4 }
  }
\cs_new:Nn \lance_pollard_if_something_eq:nnnn
  {
    % #1 : name which was defined with \newsomething
    % #2 : value to compare with
    % #3 : T
    % #4 : F
    \lance_pollard_if_something_eq:xnnTF
      { \tl_use:c { l_lance_pollard_#1_type_tl } } { #1 } { #2 }
      { #3 }
      { #4 }
  }
\cs_new:Nn \lance_pollard_if_something_eq:nnnTF
  {
    % #1 : type
    % #2 : name
    % #3 : value to compare with
    % #4 : T
    % #5 : F
    \lance_pollard_if_type:nnTF { #1 } { #3 }
      {
        \exp_args:Nnc 
          \lance_pollard_cmp_type:nNnTF { #1 }
            { l_lance_pollard_#2_val_#1 } { #3 }
            { #4 }
            { #5 }
      }
      { #5 }
  }
\cs_generate_variant:Nn \lance_pollard_if_something_eq:nnnTF { xnnTF }
\cs_new:Nn \lance_pollard_if_type:nnTF
  {
    % #1 : type
    % #2 : value to compare with
    % #3 : T
    % #4 : F
    \cs_if_exist:cTF { c_lance_pollard_#1_regex }
      {
        \exp_args:Nc \regex_match:NnTF { c_lance_pollard_#1_regex } { #2 }
          { #3 }
          { #4 }
      }
      {
        \msg_error:nnn { lance_pollard } { type~not~setup } { #1 }
        #4
      }
  }
\cs_new:Nn \lance_pollard_cmp_type:nNnTF
  {
    % #1 : type
    % #2 : name of macro storing value
    % #3 : value to compare with
    % #4 : T
    % #5 : F
    \str_case:nn { #1 }
      {
        { tl  } { \exp_args:NV \tl_if_eq:nnTF #2 { #3 } { #4 } { #5 } }
        { int } { \lance_pollard_cmp_int:nnTF { #2 } { #3 } { #4 } { #5 } }
        { fp  } { \lance_pollard_cmp_fp:nnTF { #2 } { #3 } { #4 } { #5 } }
        { str } { \str_if_eq:VnTF #2 { #3 } { #4 } { #5 } }
      }
  }
\cs_new:Nn \lance_pollard_cmp_int:nnTF
  {
    \int_compare:nNnTF { #1 } = { #2 }
      { #3 }
      { #4 }
  }
\cs_new:Nn \lance_pollard_cmp_fp:nnTF
  {
    \fp_compare:nNnTF { #1 } = { #2 }
      { #3 }
      { #4 }
  }
\msg_new:nnn { lance_pollard } { type~not~setup }
  {
    The~used~type~'#1'~was~not~setup~for~use~with~\newsomething
    and~\ifsomethingeq.
  }
\msg_new:nnn { lance_pollard } { badly~formatted }
  {
    The~initial~value~'#1'~for~'#2'~doesn't~match~the~defined~format~of~the~
    type~'#3'.
  }
\makeatother
\ExplSyntaxOff

\newsomething{fp}{myfp}{10.1}
\newsomething{int}{myint}{10}
\newsomething{tl}{mytl}{foobar}
\newsomething{str}{mystr}|$^_}{ern|

\begin{document}
\ifsomethingeq{myfp}{10.1}{equal}{not equal}

\ifsomethingeq{myint}{10}{equal}{not equal}

\ifsomethingeq{mytl}{foobar}{equal}{not equal}

\ifsomethingeq{mystr}|$^_}{ern|{equal}{not equal}

\ifsomethingeq{myfp}{foobar}{equal}{not equal}

\ifsomethingeq{myint}{foobar}{equal}{not equal}

\ifsomethingeq{mytl}{bazbang}{equal}{not equal}

\ifsomethingeq{mystr}{foobar}{equal}{not equal}
\end{document}
  • In a str only characters with category code 12 or 10 can appear. – egreg Mar 26 '18 at 21:05
  • It would be nice to have an API where you could do either \mycmd{foo=1} or \mycmd{foo=[1,2,3]}, then just check "if it's an integer, make it into a clist" to normalize. guess it's all just regexps. – Lance Pollard Mar 26 '18 at 21:07
  • @egreg, you're right, my bad... – Skillmon likes topanswers.xyz Mar 26 '18 at 21:08
  • @Skillmon Probably \A(\cO.|\s)*\Z to take care of space tokens. – egreg Mar 26 '18 at 21:10
  • @LancePollard sorry, except for correcting issues/errors, I don't have any interest in develop this any further. Feel free to use my code in any way you want, but I personally wouldn't use it. It just makes things more complicated, imho. Also clists can store anything, not only integers. – Skillmon likes topanswers.xyz Mar 26 '18 at 21:15
7

There is no build in 'guess the data format' command in either xparse or expl3. In general, we expect input to be well-defined, whilst actually doing error checking on data types can be somewhat tricky.

Ultimately, all literal input in TeX is a token list. So we can always test if they are exactly the same: this is a code-level function

\tl_if_eq:nnTF { foo } {#1}
  { True code }
  { False code }

Much of the time, you will likely want to do a string test instead

\str_if_eq:nnTF { foo } {#1}
  { True code }
  { False code }

which differs in that it is catcode-agnostic.

Beyond this, you would need to define appropriate checking functions. The issue of course is what those might be. For example, +-+-\numexpr 1 0\relax is from a TeX point-of-view a perfectly reasonable number. Life gets even more 'interesting' with something like [1,2,3], which is not a a sequence at all: it's a comma list. Several expl3 data structures are constructed in a form that cannot be entered in a document, so they cannot be simply tested in the way asked.

Most likely, there are better approaches to the problem(s) at hand, but they depend upon the exact use case.

  • Interesting, I would be interested to see how bool and int are implemented as datatypes then, it seems like the literal string true would be what is checked, but I thought also maybe there is a special token list like xparse's -NoValue- for an empty argument. Maybe it's like ~~TRUE~~ or something. Maybe there is a null/undefined value as well like NULL. Haven't seen it in the docs yet. – Lance Pollard Mar 26 '18 at 20:50
  • 1
    @LancePollard It's all in source3 (texdoc source to see it). At the code level, bools are currently implemented as the chars 0 and 1: the previous version had 00 and 01, and before that there was the TeX \iffalse and \iftrue primitives. For int, we've always used the TeX \count registers. However, the point of expl3 is that you don't rely on the implementation: we aim to document the way things are supposed to be used. – Joseph Wright Mar 26 '18 at 20:56
  • well, you still have the \numexpr problem beacuse TeX will fetch the 1 and leave 0\relax if I do \count200=+-+-\numexpr 1 0\relax ... :) – user4686 Apr 24 '18 at 15:55
  • perhaps state that -(1) is not legal in \numexpr. That's not widely known: \count200=\numexpr-(1)\relax --> Missing number, treated as zero. – user4686 Apr 24 '18 at 15:57
  • @jfbu Not sure what you mean about \numexpr: spaces are ignored so \numexpr 1 0 is the same as \numexpr10. – Joseph Wright Apr 24 '18 at 16:22

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.