# How does \discretionary work?

In poking around in someone else's package, I found the \discretionary command being used quite a lot. I wanted to hook into the code being used in one of its arguments, but found that my code got executed rather a lot more than I was expecting. My conjecture is that \discretionary behaves a little like \mathchoice: each argument is processed into a box, one of those boxes is typeset into the document and the others are thrown away.

Is that the case?

(What I'm trying to do is step a counter when a particular one of the arguments is used. As this is an assignment, it would appear that the counter is stepped every time \discretionary is used. In similar circumstances, I've used a trick using the aux file to see which box is actually processed. I was wondering if I could avoid that here.)

• you are, as you predict, doomed (unless you are using luatex and inspect the node lists from the line breaking) Dec 16, 2019 at 17:25
• not only is it pre-evaluated, normally the branches are not boxes but just character nodes so you would not be able to add \write nodes Dec 16, 2019 at 17:28

If you use luatex you could traverse the nodes in the post_linebreak_filter callback, but looking at classic TeX...

You mention \mathchoice but the situation with \mathchoice is easier, the syntax for \mathchoice is

\mathchoice{a}{b}{c}{d}


and each of a,b,c,d is set in a box but only one of the boxes is copied to the final page and so by placing non immediate writes in each box, you can see from the output which is used.

 \mathchoice{\write20{A}a}{\write20{B}b}{\write20{C}c}{\write20{D}d}


only one of A B C D will be written showing which box is used.

The situation with discretionary is superficially similar

 \discretionary{pre-break text}{post-break text}{no-break text}


but the arguments are not normally boxed. You can use a box, but normally they are just characters and they are restricted to characters, boxes and kerns, you can not have write nodes.

So you can have immediate printing that does not generate a node:

aaa \discretionary{\message{1}a}{\message{2}b}{\message{3}c} xxx

aaa \discretionary{\immediate\write20{a}a}{\immediate\write20{b}a}{\immediate\write20{c}a} xxx


But neither of these forms gives you any information as all three arguments are evaluated so you get 1 2 3 and a b c respectively.

You could try a non-immediate \write but then you get

! Improper discretionary list.
l.9 aaa \discretionary{\write20{a}a}


So you could put the write node in a box

aaa \discretionary{\hbox{\write20{A}a}}{\hbox{\write20{B}b}}{\hbox{\write20{C}c}} xxx


is error free and just outputs C as no line break happened, however this means that the horizontal list contains \hbox{c} not c which will disturb any ligatures or inter-letter kerns.

(No help to what you're ultimately trying to accomplish, but just to answer the question literally as asked.)

Recall that \discretionary is supposed to be used as

\discretionary{⟨pre-break text⟩}{⟨post-break text⟩}{⟨no-break text⟩}


where the ⟨no-break text⟩ is what is printed by default, but if a line needs to be broken then the current line will end with ⟨pre-break text⟩ and the next line will start with ⟨post-break text⟩. This is explained in The TeXbook page 95 (Chapter 14: How TeX Breaks Paragraphs into Lines).

How it works internally is that TeX inserts a "disc" node into the current list. For example, if you type

Here you find a st\discretionary{ra-}{ng}{rang}e word.

then TeX's internal list will look like (click on the image for proper resolution):

(note the kerns and “fi” ligature too—got the above using LuaTeX's pre_linebreak_filter as here). To be more precise, what the disc node points to are themselves lists of course:

In general, these three lists will contain only contain char_node, kern_node, hlist_node, vlist_node, rule_node, and ligature_node items.

Building these lists happens when TeX works on the \discretionary command; later during line-breaking TeX will first use the link (the no-break text) of the disc node in its first (no-hyphenation) pass, then (if nothing satisfactory was found within \pretolerance) use the pre and post links of the disc node in the second pass of line-breaking.

To go into even more low-level detail:

When TeX encounters \discretionary, if it's in vertical mode, then (just as when encountering a letter or \hskip etc) it first backs up and starts a new paragraph (thus switches to horizontal mode). (See p. 283, "Chapter 24: Summary of Vertical Mode" of the TeXbook, or §1090 of the program.) So we can assume that TeX is in horizontal or math mode when it encounters a \discretionary.

So when it encounters \discretionary, it does the following:

1. Append a new disc node to the current list.
2. Push to the save stack, and start a new save level, keeping track that the current level is of type discretionary. (Rather than hbox, align, etc -- there are 16 kinds of groups; §269 of the program.)
3. Scan the left brace.
4. Go into restricted horizontal mode (with space factor 1000, the default).
5. Then, until the right brace is encountered, continue processing things as usual (as if in restricted horizontal mode).
6. When the right brace is encountered,
• Prune the list that has been built up so far, to contain only char, kern, hlist, vlist, rule and ligature nodes. (Else say "Improper discretionary list".)
• If the pre-break text was being scanned, make the disc node's pre_break field point to it, and start scanning again (from step 3 above).
• If the post-break text was being scanned, make the disc node's post_break field point to it, and start scanning again (from step 3 above).
• If the no-break text was being scanned, make the disc node's link field point to it. (Here for math mode, this one should be empty.)

All this is in §1114–§1121 of the program, or page 287 (Chapter 25: Summary of Horizontal Mode) of The TeXbook:

\discretionary⟨general text⟩⟨general text⟩⟨general text⟩

The three general texts are processed in restricted horizontal mode. They should contain only fixed-width things; hence they aren't really very general in this case. More precisely, the horizontal list formed by each discretionary general text must consist only of characters, ligatures, kerns, boxes, and rules; there should be no glue or penalty items, etc. This command appends a discretionary item to the current list; see Chapter 14 for the meaning of a discretionary item. The space factor is not changed.

• +1: Amazing level of detail. Feb 2, 2020 at 8:48