4

I am needing to make some modifications to the following code in order to better fit my use case, but am really struggling to learn and understand some of its language. A token like \tl is simply too short to return any meaningful results in a search, and I don't even know what it stands for to attempt finding it by that.

Here is the code:

% code to create chapters, verses, and cross references
\ExplSyntaxOn

\int_new:N \crossref_int
\int_new:N \vs_int
\tl_new:N \crossref_tl

% insert a cross reference
\NewDocumentCommand {\crossref} {m}
  {
    \int_compare:nNnT { \crossref_int } > { 25 }
      {
        \int_set:Nn \crossref_int { 0 }
      }
    \int_incr:N \crossref_int
% NOT PRESENTLY REQUIRING LETTERED SUPERSCRIPTS
%    \textsuperscript{ \emph { \int_to_alph:n { \crossref_int } } }
    \tl_if_empty:NF \crossref_tl
      {
        \tl_gput_right:Nn \crossref_tl { ~ }
      }
    \tl_gput_right:Nx \crossref_tl
      {
% NOT PRESENTLY REQUIRING LETTERED SUPERSCRIPTS
%        \exp_not:N \textsuperscript
%          {
%            \exp_not:N \emph { \int_to_alph:n { \crossref_int } }
%          }
        \, #1
      }
  }

%Sample output
% insert chapter marker
\NewDocumentCommand {\ch} {m}
  {
    \int_gset:Nn \vs_int {1}
    \lettrine [findent=0.5em,nindent=0em] { #1 } {}
  }

% output cross references from previous verse and insert verse marker
\NewDocumentCommand {\vs} {m}
  {
    \tl_if_empty:NF \crossref_tl
      {
        \sidebar
          {
            \textbf { \int_use:N \vs_int } \, \tl_use:N \crossref_tl
          }
      }
    \int_gset:Nn \vs_int { #1 }
    \tl_gclear:N \crossref_tl
    \textsuperscript { #1 \, }
  }

% output any remaining cross references
\AtEndDocument
  {
    \tl_if_empty:NF \crossref_tl
      {
        \sidebar
          {
            \textbf { \int_use:N \vs_int } \, \tl_use:N \crossref_tl
          }
      }
  }

\ExplSyntaxOff

Somewhere in this code, cross-references are presumably placed in the margin, but several things are not presently working in that process that I will need to fix, including the fact that this is working only for even-numbered pages, it is not making the reference numbers bold (and mysteriously, when I tried adding \emph within the code, the cross-references all landed in the text columns instead of in the margin), and I would like to be able to send the references for the second text column to the bottom of the margin--if this is possible.

Thank you to those who may be able to help me understand this code!

13

There is no \tl. I think you might be getting a bit confused because you're seeing expl3 syntax (thus the \ExplSyntaxOn\ExplSyntaxOff surrounding the code. The key documentation for this can be found by typing texdoc expl3 for a short overview and texdoc interface3 for a long detailed but not necessarily easy to follow account. Alan Xiang's LaTeX3 tutorial is a more gentle introduction and a good starting point.

But let's go through your code a bit at a time to try to understand it:

\ExplSyntaxOn

This is the signal that we're in expl3 mode. In this mode, command names can contain _ and : so what you thought was \tl, was actually a number of different command names that began with \tl but had more to them than that. Also, all spaces in this mode are ignored (if you need a literal space, ~ will give that, while \nobreakspace can be used if you need a non-breaking space.

\int_new:N \crossref_int
\int_new:N \vs_int
\tl_new:N \crossref_tl

Here we're declaring a few new variables¹. The standard naming for commands is to use \scope_module_description_type⟩ to name these. Alas, this code doesn't follow that convention except for putting the type at the end. We also see some commands. The expl 3 convention for commands is \module_description:arg-spec⟩ which is followed internally at least. That : is helpful for noticing when something is to be a command vs a variable which can help with at least one aspect of TeX programming’s inscrutability. All expl3 commands will have :, even if they don’t take arguments. Here we see an arg-spec of N which refers to a single token which here is a LaTeX control sequence name. So what we've done here is to define two new integer variables and one token list (that's the \tl you're seeing all over the place).

% insert a cross reference
\NewDocumentCommand {\crossref} {m}

Here we define a new document command, \crossref with a single mandatory argument. This is roughly equivalent to \newcommand*{\crossref}[1]{...} You can get the full story on \NewDocumentCommand by typing texdoc xparse at a command line.

  {
    \int_compare:nNnT { \crossref_int } > { 25 }
      {
        \int_set:Nn \crossref_int { 0 }
      }

Now we've actually begun the command definition. We're doing an integer comparison. The arg spec here is nNnT. The ns refer to arguments which are delimited with braces. The N is a single token again, although this time its the > and the T is a special case of n which refers to a block of code to be executed if the comparison is true (there's also F and both could be used in a comparison, so, e.g., \int_compare:nNnTF which take two blocks of code after the comparison code to execute the true and false branches.

So in this case, if \crossref_int is greater than 25, we reset it to 0. Since, as we'll see later, we're using it to generate alphabetic indices, this makes perfect sense.

    \int_incr:N \crossref_int

And here, we increment the value of \crossref_int.

% NOT PRESENTLY REQUIRING LETTERED SUPERSCRIPTS
%    \textsuperscript{ \emph { \int_to_alph:n { \crossref_int } } }
    \tl_if_empty:NF \crossref_tl
      {
        \tl_gput_right:Nn \crossref_tl { ~ }
      }

Now here's where the token lists come into play. Token lists are, as there name indicates, lists of tokens. In this case, we look at the value of \crossref_tl and see if its empty. If it's not (note the F arg-spec on \tl_if_empty), then we call \tl_gput_right: to add a space to the right. The g in gput means global and this is one of the places where following the conventions of expl3 would be useful. One of the limitations of TeX is that if you do both global and local manipulations on its register variables, you can end up with a memory leak. Knuth's recommendation for this was to adopt a convention of, e.g., only doing global operations on even-numbered registers and local on odd.²

    \tl_gput_right:Nx \crossref_tl
      {
% NOT PRESENTLY REQUIRING LETTERED SUPERSCRIPTS
%        \exp_not:N \textsuperscript
%          {
%            \exp_not:N \emph { \int_to_alph:n { \crossref_int } }
%          }
        \, #1
      }
  }

And then there's the main work of this command. Putting a new token list into the right side of the \crossref_tl token list. Note that are arg list here is Nx the x is saying that we want to expand the contents of the list before appending it. This would allow the commented code to be executed before its added to the token list which is what we would want since we care about the value of \crossref_int at the time the command is executed. If we had not done that then we would end up with a token list of, e.g.,

d note 1 d note 2 d note 3 d note 4

instead of the desired

a note 1 b note 2 c note 3 d note 4

%Sample output
% insert chapter marker
\NewDocumentCommand {\ch} {m}
  {
    \int_gset:Nn \vs_int {1}
    \lettrine [findent=0.5em,nindent=0em] { #1 } {}
  }

This one is pretty straightforward. We define \ch to take a single mandatory argument and we use the lettrine class³ to set the chapter number in drop cap style. We also (globally) set our counter \vs_int to 1.

% output cross references from previous verse and insert verse marker
\NewDocumentCommand {\vs} {m}
  {
    \tl_if_empty:NF \crossref_tl
      {
        \sidebar
          {
            \textbf { \int_use:N \vs_int } \, \tl_use:N \crossref_tl % ❶
          }
      }
    \int_gset:Nn \vs_int { #1 } % ❷
    \tl_gclear:N \crossref_tl % ❸
    \textsuperscript { #1 \, } % ❹
  }

So here, we define \vs also with a single mandatory argument, the verse number, which is set as a superscript before the text of the verse ❹. But before that, we will output the cross-references, if any. We look in \crossref_tl to see if its empty (\tl_if_empty) and if it's not (thus the F in the arg spec), we use \sidebar to set the value of \vs_int in boldface (we have to use \int_use:N to get the value for this purpose) and then the contents of the \crossref_tl token list (again, \tl_use:N is necessary to get that value ❶). Then we set the value of \vs_int to the number passed in the argument to \vs ❷ and clear the \crossref_tl token list ❸.

% output any remaining cross references
\AtEndDocument
  {
    \tl_if_empty:NF \crossref_tl
      {
        \sidebar
          {
            \textbf { \int_use:N \vs_int } \, \tl_use:N \crossref_tl
          }
      }
  }

The final bit of code is to make sure that the cross references that remain are output at the end of the document. This is done using the \AtEndDocument hook (although the newer style would be to instead write \AddToHook{enddocument} (LaTeX has added extensive “hook” mechanisms for class and package writers. See texdoc source2e for the gory details). The code for setting this is essentially identical to the code that was used in defining \vs so I'll not say anything more about that.

\ExplSyntaxOff

And then we turn off the expl syntax so that the rest of our document will be fine.

As for your original question, I'm guessing the problem that you're trying to resolve lies in the definition of \sidebar, wherever that comes from.


  1. TeX does not exactly have variables. There are 256 registers of various types (counters–\counts in plain TeX-speak which hold integers, lengths (plain TeX calls these \dimens, glues (plain TeX calls these \skips and they're essentially lengths which can stretch or shrink), token lists, boxes, muskips, inserts, read and write streams and math font families. Most of these are limited to 256 registers with some registers reserved (e.g., \count0 is the page number). Most “variables” in TeX are simply macros, which makes life a bit difficult and expl3 attempts to impose some order on things although there is no enforcement of this order as we'll see).

  2. Or maybe it's the other way around. I'm going on memories from 1986 here.

  3. This is a good example of the correct use of lettrine. Since a drop cap doesn't have any semantic value it really shouldn't be set directly in the text but should be part of other code. It's possible to, without too much effort, use lettrine’s capabilities to automatically set the first letter of the first paragraph after \chapter without any additional markup.

5
  • Thank you very much! Great answer. While I'm still very interested in learning the "guts" of the macro language, and have now read your response several times trying to grasp it all, it turns out that I did not need to do anything with this portion of the code to solve my margin layout issue. I simply needed to add \sidebarmargin{left} to the section where that sidebar was defined--just as you suspected. Oddly, when I tried \sidebarmargin{inner}, the cross-references were centered on all but the 2nd page. LaTeX has many idiosyncrasies! Just happy to learn more and pass one more hurdle!
    – Polyhat
    Jun 2 '21 at 6:09
  • Oh, and I now understand that \tl stands for "token list." That was a significant part of my question which you answered.
    – Polyhat
    Jun 2 '21 at 6:11
  • @Polyhat In short, \tl_if_empty:NF is a single command, and there is no independent \tl.
    – L. F.
    Jun 2 '21 at 10:38
  • This is a great answer. Jun 2 '21 at 16:49
  • @L.F. Yes, but at the same time, the acronym tl does in fact stand for "token list."
    – zwol
    Jun 2 '21 at 17:56

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.