1

This is a follow-up question connected to this: Non-breakable space after isolated one letter

After testing proposed solution on plain text, the finalizer works. However, after using it in document containing sectioning commands (\title, but even others) ConTeXt process crashes.

MWE:

\startluacode
function userdata.prevent_single_letter(head)
    local cur = head
    while cur do
        if cur.id == node.id("glyph") then
            if cur.prev.id == node.id("glue") and cur.next.id == node.id("glue") then
                local p = node.new("penalty")
                p.penalty = 10000

                -- This is for debugging only, but then you have to
                -- remove the last node.insert_after line:
                --local w = node.new("whatsit","pdf_literal")
                --w.data = "q 1 0 1 RG 1 0 1 rg 0 0 m 0 5 l 2 5 l 2 0 l b Q"
                --node.insert_after(head,cur,w)
                --node.insert_after(head,w,p)

                node.insert_after(head,cur,p)
            end
        end
        cur = cur.next
    end
    return head, true
end
\stopluacode

\startluacode
nodes.tasks.appendaction("processors", "before", "userdata.prevent_single_letter")
\stopluacode

\starttext
\title[title:poznamky]{Notes z Con\TeX{}t}

Filling text filling text filling text filling text filling text filling text filling text fil V text

\stoptext

Even more trubling is, that I am unable to get any meaningfull error message other than the issue is caused by \title. The only readable error message I am getting from console:

 hpack filter: error: [\directlua]:6: attempt to index a nil value (field 'prev')

I have no idea what is hpack_filter. In order to next time be able to debug issues I am hitting upon a little better, what reference for ConTeXt should I use?

2
  • 1
    Read LuaTeX reference manual, which is shipped with ConTeXt distribution (don't know if shipped with TeXlive). hpack_filter is described p. 173. Note that there are more than 50 manuals shipped with ConTeXt, so which one you should read depend on your issue.
    – sztruks
    Commented Jul 30, 2020 at 15:49
  • 1
    Replace the conditional if cur.prev.id == ... by if cur.prev and cur.next and cur.prev.id == .... The problem here is that the prev field does not exist but it tries to index it anyway to get the id field which is not allowed. Commented Jul 31, 2020 at 1:19

1 Answer 1

2

The problem comes from the fact that the ConTeXt processors run both on pre_linebreak_filter and on hpack_filter. In pre_linebreak_filter the problem will not show up because the first node is always local_par. In hpack_filter, however, the first node is very likely a glyph which is a problem because there will be no other node preceding it, i.e. the prev field is nil. Right now the code just queries the id property of the prev field but since the prev field doesn't exist in the first place, this fails. We can work around this by harnessing a property of the and logical and (and or) operator which is called short-circuit evaluation.

More information on those callbacks in ConTeXt can be found in the manual MkIV Hybrid Technology, in particular the tables in Chapter 9 “Callbacks” (source).

On short-circuit evaluation in Lua

The common idiom in Lua is to guard this access using a statement like

if cur.prev and cur.prev.id == ... then

When the left-hand side operand evaluates to false, the right-hand side operand is not even evaluated and the composite expression results in false. This is because false and <anything> will always be false by Boolean logic, irregardless of the value of <anything>.

In Lua this idiom extends even further, because the language assigns a “truthiness” to all values. The values nil and false are always considered “false-y” whereas everything else is considered “true-y” (in particular the integer values 0 and 1 both evaluate “true-y” even though you might expect 0 to be “false-y” as it is in C-like languages). On top of that, a logical statement always returns the last operand that was evaluated. This allows for constructs like

userdata = userdata or {}

which you see a lot in the ConTeXt core Lua code. What this does is that it evaluates userdata and if this is nil (which is considered “false-y”) it evaluates the {} (which is considered “true-y”) and returns the empty table from the expression which is then assigned to userdata. This is a great way to quickly write a guarded variable initialization which does nothing when the variable is already initialized.

MWE

In the present case, we can actually fuse the two nested if statements together by using the short-circuit evaluation property of and. This makes the code quite a bit more compact and you might gain an infinitesimal performance improvement. What's probably more relevant for performance is caching the IDs of the glyph and glue nodes, because these never change.

\startluacode
local glyph_id = node.id("glyph")
local glue_id = node.id("glue")

function userdata.prevent_single_letter(head)
    local cur = head
    while cur do
        if cur.id == glyph_id and
           cur.prev and
           cur.next and
           cur.prev.id == glue_id and
           cur.next.id == glue_id
        then
            local p = node.new("penalty")
            p.penalty = 10000
            node.insert_after(head,cur,p)
        end
        cur = cur.next
    end
    return head, true
end
\stopluacode

\startluacode
nodes.tasks.appendaction("processors", "before", "userdata.prevent_single_letter")
\stopluacode

\starttext
\title[title:poznamky]{Notes z Con\TeX{}t}

Filling text filling text filling text filling text filling text filling text filling text fil V text

\stoptext
3
  • Thank you for informative addition how to program in Lua. Even though I have peeked through Lua Reference, these things I did not know. Only thing that confuses me a little is function node.insert_after and its arguments. I am looking at its description in LuaTeX reference. The argument head is first node in (current) node list? But if I undersand it correctly, you are assigning current node into head and them using it in node.insert_after(head,cur,p). Maybe I just dont know what head arg. stands for. I admit that last time I was reading LuaTeX ref. i didnt made it far Commented Jul 31, 2020 at 7:46
  • 1
    @TomášKruliš Well, that's complicated. In fact, head is only a pointer to the head of the node list, so when assigning it to cur I'm only copying the pointer and not the actual head. Hence, when incrementing cur, head will stay at the head of the list such that in the call node.insert_after(head,cur,p) both head and cur will point to the head and the current node, respectively. Commented Jul 31, 2020 at 8:58
  • 1
    @TomášKruliš I've found the source of the problem and updated the answer text. Commented Aug 1, 2020 at 0:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .