TransWikia.com

ConTeXt: Non-breakable space finalizer crashes with title

TeX - LaTeX Asked on October 4, 2021

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 ConTeX{}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?

One Answer

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 ConTeX{}t}

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

stoptext

Correct answer by Henri Menke on October 4, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP