Weeknotes 2025 W46: It’s terminal

November 10​–​16, 2025

Quick bits:


Shower thoughts:


It occurred to me that the lowercase letters a and g on the Berlin S-Bahn digital signage are mirror versions of each other:

 XXXX    XXX
X   X       X
 XXXX    XXXX
    X   X   X
 XXX     XXXX

 XXX     XXXX
    X   X   X
 XXXX    XXXX
X   X       X
 XXXX    XXX

Duolingo has been getting weird lately. I’m doing Spanish on there, and the last few days, I’ve gotten sentences like the following:

I don’t mind the occasional odd sentence, but it’s now so bizarre that it is hard to translate such Spanish sentences to English because I keep second-guessing whether it really means what I think it means.

Learning a new language involves inferring meaning from the context, but if the sentences are about fat pigs writing letters in French, then that becomes really quite difficult.

Duolingo is perhaps not a great way to learn languages.


This week, I have been struggling with a specific Zig problem: crashes would cause the process to freeze, not printing anything and not exiting.

To reproduce, I created this source file, blah.zig:

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.DebugAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const ptr = try allocator.create(u32);
    allocator.destroy(ptr);
    allocator.destroy(ptr);
}

I then built an executable (with zig build-exe blah.zig) and executed it (with ./blah), which just… hangs:

% ./blah
 

To be clear: that second allocator.destroy(ptr) is intentionally wrong. That line frees an allocation that has already been freed. Still, it at least shouldn’t hang.

What is expected is something like the following:

% ./blah
Segmentation fault at address 0x104fc1428
[snip]/debug_allocator.zig:875:23: 0x104ebebec in free (blah)
            if (bucket.canary != config.canary) @panic("Invalid free");
                      ^
[snip]/Allocator.zig:147:25: 0x104ebd747 in destroy__anon_19268 (blah)
    return a.vtable.free(a.ptr, memory, alignment, ret_addr);
                        ^
[snip]/blah.zig:10:22: 0x104ebd4b3 in main (blah)
    allocator.destroy(ptr);
                     ^
[snip]/start.zig:627:37: 0x104ebd9c3 in main (blah)
            const result = root.main() catch |err| {
                                    ^
???:?:?: 0x184df2b97 in ??? (???)
???:?:?: 0x0 in ??? (???)
fish: Job 1, './blah' terminated by signal SIGABRT (Abort)

You might be asking yourself now: how did I manage to get that backtrace if Zig hangs instead of terminating? Well, as it turns out, running the executable only hangs when executed in a Zed terminal, but not when executed in ghostty.

It turns out this is not even a problem with Zig. In fact, even the exit 1 command makes the Zed terminal hang:

% exit 1
 

So I filed Zed issue #42414.

I hope Zed recovers.

I hope it’s not… terminal.


I’ve shifted my focus back from my static-site generator to Deng. One thing that certainly has been missing is error reporting for the VM. It was already present for the compiler (including the tokenizer and parser), but that was easy. Error reporting for the VM is not nearly as simple.

The way I’ve solved this is to make compiler emit a byte array that contains not just the bytecode, but also:

This byte array starts with a header that contains the lengths of the bytecode and the source map, so that all three parts (bytecode + source map + template) can be extracted. While this encoding is a little space-inefficient, it does work well enough.

Mapping instructions to tokens is unfortunately not easy. Here is one of those runtime error messages:

6:20: Found `null` value, which is not printable

    <title>{{ page.title }}</title>
                         ^^

The write instruction is emitted when the compiler sees the }} token. That instruction takes the topmost value off the VM stack and writes it to the output stream.

What I really want, though, is an indication of the entire expression that caused the error — page.title in this case, not the }} token:

    <title>{{ page.title }}</title>
              ^^^^^^^^^^

I don’t yet know how to achieve this. Further research and experimentation is needed to improve these error messages.


At the recommendation of E——, I started using Herb for syntax highlighting and linting my HTML+ERB files.

It works just fine with Nanoc, though I had to rename the .erb layouts to have the .html.erb extension. Not a single change needed in the Rules file, even!1

I am facing Herb issue #854, however, which makes a few of my layouts unparseable. Hopefully that’ll get fixed soon.

Thanks for Herb, though, I learned about the autocomplete attribute, and adjusted all the input fields — not that there are that many — accordingly. Hurray for convenience!


Entertainment:


Toots and skeets:

Links:

Tech links:


  1. Nanoc is great and I would absolutely make a donation to its creator if I weren’t the creator myself, you know? ↩︎

  2. Warhammer 40,000: Rogue Trader (Owlcat Games, 2023), published by Owlcat Games. ↩︎

You can reply to this weeknotes entry by email. I’d love to hear your thoughts!
If you like what I write, stick your email address below and subscribe. I send out my weeknotes every Sunday morning. Alternatively, subscribe to the web feed.