In the process of answering expl3 switch for booleans: Is there a \bool_case:Nnn?, I tried to make sense of the various \xx_case: functions Expl3 provides. Here, for example, is \tl_case:Npn:

\cs_new:Npn \tl_case:Nnn #1#2#3
    \__tl_case:Nw #1 #2 #1 {#3} \q_recursion_stop
\cs_new:Npn \__tl_case:Nw #1#2#3
    \tl_if_eq:NNTF #1 #2
      { \__tl_case_end:nw {#3} }
      { \__tl_case:Nw #1 }
\cs_new_eq:NN \__tl_case_end:nw \__prg_case_end:nw

Working from the inside-out, I’ve figured out this much:

The command \__prg_case_end:nw {<code>} <tokens> \q_recursion_stop consumes all ⟨tokens⟩ until \q_recursion_stop, leaving ⟨code⟩ behind.

Within the internal function \__tl_case:Nw, we test for equality of the ⟨test token-list variable⟩ to the particular ⟨token-list variable case⟩: if they’re equal, the function terminates returning the associated ⟨code case⟩; otherwise the function yields itself, which then applies to the next {<token-list variable case>} {<code case>} pair.

What I’m having trouble understanding is how \tl_case:Nnn sets up this recursion. In particular, I know \tex_romannumeral:D (\romannumeral) does something funny related to expansion, but what’s it doing in this context?

(Would it be easier to explain or implement this sort of function in terms of the recursion functionality documented in l3quark §§ 4 & 6?)

  • Well put. Exactly what I was confused by.
    – A.Ellett
    Jun 27 '13 at 15:22

To explain what is happening, we need to consider a couple of things: the \romannumeral business and how we can deal with an unknown number of case tests. (I've also looked the the \romannumeral trick in my blog.)

The \romannumeral primitive expands the input stream until it finds an integer: this may be a literal value followed by a non-numerical token or may be for example an integer stored in a register. The 'trick' is that it produces no output for negative or zero integer values. Thus what we need to do is let it expand the input, making sure our 'real' output never gets left to be read as a number, and to tidy up using a zero or negative value. Thus \romannumeral really shows up in two places: at the start of the test, where it ensures that the test will be expanded, and at the end, where we need to terminate. We'll come back to the latter point below.

The case tests themselves work one at a time with a loop. In the example, we have a token list test, but the same principal applies to the other case functions. In each loop, the \__xx_case: function is used, grabbing

  1. The search value
  2. The current test case
  3. The code to use if the test is true

The test is carried out, and if it fails there is a loop. To terminate the loop, the code is set up so that the final test case is checking the search value against itself, which is always true and inserts the 'else' code. (Technically, the end case inserts #1 twice and compares the two, which will be true unless #1 is variable at point of use, which can only happen with for example \pdfuniformdeviate 99 inside \str_case:nnn: very unusual!)

If the test is true, \__prg_case_end:nw (with the 'correct' name) is inserted. It is define as

> \__prg_case_end:nw=\long macro:
#1#2\q_recursion_stop ->\c_zero #1.

Thus it grabs the code we want to run as #1 and remaining cases as #2. It then inserts \c_zero before the code we want to run. The \c_zero is a token \chardefed as 0, and thus it is what \romannumeral is looking for. It's also zero, so the \romannumeral produces no output. The expansion therefore ends, and whatever code was set up with the true case is ready to go. (If none of the cases were true, the code will be that for the 'else' path.)

  • You could expand on the 'else' case: the \xx_case: function inserts its first argument as an extra case, with the else code as the coresponding code. This will always match... except for \str_case_x:nnn { \pdfuniformdeviate 99 } { {1} {one} {2} {two} } {other}, which breaks horribly (this only happens for \str_case_x, and is documented IIRC). Jun 27 '13 at 18:32
  • Thanks, @BrunoLeFloch; I’d actually caught that when trying to make \bool_case work, hence the \c_true_bool. Jun 27 '13 at 19:54

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.