twig-sys 3.11.1

FFI bindings and native library for Twig (the Djot/Markdown/HTML/XML document engine). Used by the `twig-doc` crate.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
//! Languages registered at runtime: a format twig did not compile in, carried
//! by a table of functions that parse into the node table (`ast/table.zig`)
//! and print from it. `docs/proposals/runtime-languages.md` is the argument;
//! this file is its in-process carrier.
//!
//! ── What a registration becomes ────────────────────────────────────────────
//! A row like any other. `register` hands back a `Format` whose value is the
//! language's C wire code, `base` and up, and from then on `format.entryFor`
//! and `format.targetEntryFor` return real `Entry`/`TargetEntry` rows for it:
//! `parse` and `parseToAst` call the language and decode its table, `renderHtml`
//! is the shared printer over that table with core's labels, and a language
//! that prints has `serializeCanonical` and `serializeFromAst`. So the CLI, the
//! C ABI, the editor and the diagnostics reach a runtime language through the
//! code they already have, and none of them switches on where a row came from.
//!
//! A row's functions are plain function pointers with no closure, so each slot
//! has its own set, instantiated at comptime over the slot index (`Row`). That
//! is what bounds the registry at `capacity`, and why a slot never moves.
//!
//! ── What a runtime language may declare ────────────────────────────────────
//! The read and write tiers: `parse`, and optionally `print`. Not `author` —
//! a `Syntax` from outside the library, and the editor gestures it unlocks,
//! wait for the checks that make an unseen spelling table safe to edit with
//! (the proposal's Status names them). A language that declares it is refused
//! by name rather than registered as something less than it said.
//!
//! ── Load is the validation moment ──────────────────────────────────────────
//! A compiled format has its test suite; a runtime one has what it declares,
//! and `register` holds it to that before the row exists: the description is
//! well-formed and claims no name or extension a row already has; then
//! `contract.all`, the checks the harness runs over every compiled row —
//! every sample parses to a table `ast/table.zig` accepts, and a language that
//! prints reparses every sample's print to an equal tree; and the fidelity
//! probe runs over it,
//! so `diagnostics` measures what a conversion into it loses rather than being
//! told. After load, core still validates every table it receives — a parse
//! that fails later is an error naming the language, never a crash.
//!
//! ── Concurrency ────────────────────────────────────────────────────────────
//! Append-only. A registration fills its slot under `lock` and publishes it by
//! bumping `count` with release ordering; a reader that sees the count sees
//! the slot whole, and a slot never changes once published. There is no
//! unregistration.

const std = @import("std");
const Allocator = std.mem.Allocator;
const Writer = std.Io.Writer;

const AST = @import("ast/ast.zig");
const Document = @import("document.zig");
const format = @import("format.zig");
const Format = format.Format;
const Target = format.Target;
const node_table = @import("ast/table.zig");
const diagnostics = @import("diagnostics.zig");
const contract = @import("contract.zig");
const Html = @import("languages/html/html.zig");

/// The first `Format`/`Target` value — and C wire code — a registration is
/// given. The C ABI's `TWIG_FORMAT_RUNTIME_BASE` is the same number, checked
/// at comptime there.
pub const base: u16 = 4096;

/// How many languages one process may register.
pub const capacity = 64;

/// What a language's functions may fail with. `LanguageFailed` carries its
/// reason in the `diag` writer it was handed.
pub const Error = error{ LanguageFailed, OutOfMemory };

/// The functions a runtime language supplies. Both exchange the node table
/// as JSON text (`ast/table.zig`), allocated with the allocator they are
/// given; `row` is the name the language registered under, so one table of
/// functions can serve several registrations.
pub const Language = struct {
    context: ?*anyopaque = null,
    /// `source` to the node table of its parse.
    parse: *const fn (context: ?*anyopaque, allocator: Allocator, row: []const u8, source: []const u8, diag: *Writer) Error![]u8,
    /// A node table to source. Its rows carry no positions when the tree
    /// was never parsed from anything — a conversion from another format.
    /// `null` for a language that only reads.
    print: ?*const fn (context: ?*anyopaque, allocator: Allocator, row: []const u8, table: []const u8, diag: *Writer) Error![]u8 = null,
};

/// What a language says about itself — the `describe` document of the
/// proposal, read by `Description.parse`.
pub const Description = struct {
    name: []const u8,
    /// Lowercase and dot-less, as `Entry.extensions` are.
    extensions: []const []const u8 = &.{},
    aliases: []const []const u8 = &.{},
    /// Whether the language prints — the write tier. It must supply `print`.
    write: bool = false,
    /// Small documents the load check holds the language to. At least one.
    samples: []const []const u8,

    /// Read a `describe` document:
    ///
    ///     {"name": "org", "extensions": ["org"], "aliases": [],
    ///      "caps": {"read": true, "write": true}, "samples": ["* x\n"]}
    ///
    /// Strings borrow from the parsed JSON, which `arena` owns. Unknown keys
    /// are ignored; `caps.author`, `syntax` and `dialects` are refused by name,
    /// since a language declaring them expects what this tier cannot give.
    pub fn parse(arena: Allocator, text: []const u8, diag: *Writer) (error{InvalidLanguage} || Allocator.Error)!Description {
        const value = std.json.parseFromSliceLeaky(std.json.Value, arena, text, .{}) catch |err| switch (err) {
            error.OutOfMemory => return error.OutOfMemory,
            else => return refuse(diag, "the description is not a JSON document", .{}),
        };
        return fromValue(arena, value, diag);
    }

    /// `parse` over an already-parsed value — the description as it sits in
    /// a `describe` response on the helper wire. Strings borrow from `value`.
    pub fn fromValue(arena: Allocator, value: std.json.Value, diag: *Writer) (error{InvalidLanguage} || Allocator.Error)!Description {
        const obj = switch (value) {
            .object => |o| o,
            else => return refuse(diag, "the description is a JSON object", .{}),
        };
        for ([_][]const u8{ "syntax", "dialects" }) |key| {
            if (obj.get(key) != null) return refuse(diag, "\"{s}\" is not open to a runtime language yet: it reads and writes, and does not author", .{key});
        }
        var write = false;
        if (obj.get("caps")) |caps_value| {
            const caps = switch (caps_value) {
                .object => |o| o,
                else => return refuse(diag, "\"caps\" is an object of booleans", .{}),
            };
            if (try flag(caps, "author", diag)) return refuse(diag, "caps.author is not open to a runtime language yet: it reads and writes, and does not author", .{});
            if (caps.get("read")) |_| if (!try flag(caps, "read", diag)) return refuse(diag, "caps.read is every language's", .{});
            write = try flag(caps, "write", diag);
        }
        return .{
            .name = switch (obj.get("name") orelse .null) {
                .string => |s| s,
                else => return refuse(diag, "\"name\" is a string", .{}),
            },
            .extensions = try strings(arena, obj, "extensions", diag),
            .aliases = try strings(arena, obj, "aliases", diag),
            .write = write,
            .samples = try strings(arena, obj, "samples", diag),
        };
    }

    fn flag(obj: std.json.ObjectMap, key: []const u8, diag: *Writer) error{InvalidLanguage}!bool {
        return switch (obj.get(key) orelse return false) {
            .bool => |b| b,
            else => refuse(diag, "caps.{s} is a boolean", .{key}),
        };
    }

    fn strings(arena: Allocator, obj: std.json.ObjectMap, key: []const u8, diag: *Writer) (error{InvalidLanguage} || Allocator.Error)![]const []const u8 {
        const items = switch (obj.get(key) orelse return &.{}) {
            .array => |a| a.items,
            else => return refuse(diag, "\"{s}\" is an array of strings", .{key}),
        };
        const out = try arena.alloc([]const u8, items.len);
        for (items, out) |item, *o| o.* = switch (item) {
            .string => |s| s,
            else => return refuse(diag, "\"{s}\" is an array of strings", .{key}),
        };
        return out;
    }
};

fn refuse(diag: *Writer, comptime fmt: []const u8, args: anytype) error{InvalidLanguage} {
    diag.print(fmt, args) catch {};
    return error.InvalidLanguage;
}

// ── the registry ────────────────────────────────────────────────────────────

const Slot = struct {
    language: Language,
    description: Description,
    entry: format.Entry,
    target: format.TargetEntry,
    /// What a conversion into this language keeps, measured at load. `null`
    /// for a language that does not print, which has nothing to measure.
    measured: ?diagnostics.Measured,

    fn parseDocument(self: *const Slot, allocator: Allocator, source: []const u8) anyerror!Document {
        var diag: Writer.Allocating = .init(allocator);
        defer diag.deinit();
        const text = self.language.parse(self.language.context, allocator, self.description.name, source, &diag.writer) catch |err| {
            fail("{s}: parse failed: {s}", .{ self.description.name, diag.written() });
            return err;
        };
        defer allocator.free(text);
        var problem: node_table.Problem = .{};
        return node_table.decode(allocator, source, text, &problem) catch |err| {
            if (err == error.InvalidTable) failTable(self.description.name, problem);
            return err;
        };
    }

    fn printTable(self: *const Slot, allocator: Allocator, text: []const u8) anyerror![]u8 {
        const print = self.language.print orelse return error.UnsupportedFormat;
        var diag: Writer.Allocating = .init(allocator);
        defer diag.deinit();
        return print(self.language.context, allocator, self.description.name, text, &diag.writer) catch |err| {
            fail("{s}: print failed: {s}", .{ self.description.name, diag.written() });
            return err;
        };
    }
};

var slots: [capacity]Slot = undefined;
var count = std.atomic.Value(u32).init(0);
/// The value of the row `register` is filling, while its load check runs;
/// zero otherwise.
var loading = std.atomic.Value(u16).init(0);
var lock: std.atomic.Mutex = .unlocked;

/// The published slots.
fn published() []const Slot {
    return slots[0..count.load(.acquire)];
}

fn slotOf(value: u16) ?*const Slot {
    if (value < base) return null;
    const i = value - base;
    if (i >= count.load(.acquire)) return null;
    return &slots[i];
}

/// Whether `fmt` names a registered language — false for a compiled row, and
/// for a value in the runtime range that no registration has been given.
pub fn isRegistered(fmt: Format) bool {
    return slotOf(@intFromEnum(fmt)) != null;
}

/// The `Format` a wire code names, if a language holds it.
pub fn formatFromCode(code: i64) ?Format {
    const value = std.math.cast(u16, code) orelse return null;
    _ = slotOf(value) orelse return null;
    return @enumFromInt(value);
}

pub fn entryFor(fmt: Format) ?*const format.Entry {
    return if (slotOf(@intFromEnum(fmt))) |s| &s.entry else null;
}

pub fn targetEntryFor(t: Target) ?*const format.TargetEntry {
    return if (slotOf(@intFromEnum(t))) |s| &s.target else null;
}

/// The name a runtime `Format` or `Target` value was registered under, or
/// `"unregistered"` for a value in the range no language holds.
pub fn nameOf(value: u16) []const u8 {
    if (slotOf(value)) |s| return s.description.name;
    // A row being loaded has its name before it is published, so the load
    // check's messages can say whose samples they are about.
    if (loading.load(.acquire) == value) return slots[value - base].description.name;
    return "unregistered";
}

/// Every registered row, in registration order.
pub fn entries() []const Slot {
    return published();
}

pub fn byName(name: []const u8) ?Format {
    for (published()) |*s| {
        if (std.mem.eql(u8, s.description.name, name)) return s.entry.id;
        for (s.description.aliases) |a| if (std.mem.eql(u8, a, name)) return s.entry.id;
    }
    return null;
}

pub fn byExtension(ext: []const u8) ?Format {
    for (published()) |*s| {
        for (s.description.extensions) |known| if (std.ascii.eqlIgnoreCase(known, ext)) return s.entry.id;
    }
    return null;
}

/// What a conversion into `t` keeps, as the load-time probe measured it.
pub fn measured(t: Target) ?*const diagnostics.Measured {
    const s = slotOf(@intFromEnum(t)) orelse return null;
    return if (s.measured) |*m| m else null;
}

// ── why the last call failed ────────────────────────────────────────────────

threadlocal var failure_buf: [1024]u8 = undefined;
threadlocal var failure_len: usize = 0;

/// Why the most recent failed call into a runtime language on this thread
/// failed: the language's own message, or the table rule its output broke.
/// The row functions are `anyerror`-shaped and cannot carry a message, so
/// the CLI and the C ABI read it here after one returns an error.
pub fn lastFailure() []const u8 {
    return failure_buf[0..failure_len];
}

fn fail(comptime fmt: []const u8, args: anytype) void {
    const out = std.fmt.bufPrint(&failure_buf, fmt, args) catch failure_buf[0..];
    failure_len = out.len;
}

fn failTable(name: []const u8, problem: node_table.Problem) void {
    var w: Writer = .fixed(&failure_buf);
    w.print("{s}: the table is refused: ", .{name}) catch {};
    problem.render(&w) catch {};
    failure_len = w.end;
}

// ── the per-slot rows ───────────────────────────────────────────────────────

fn Row(comptime i: usize) type {
    return struct {
        fn parse(ctx: *const anyopaque, allocator: Allocator, source: []const u8) anyerror!format.ParsedDoc {
            const s = &slots[i];
            return .{ .format = s.entry.id, .config = format.ParseConfig.from(ctx).*, .doc = try s.parseDocument(allocator, source) };
        }

        fn parseToAst(_: *const anyopaque, allocator: Allocator, source: []const u8) anyerror!Document {
            return slots[i].parseDocument(allocator, source);
        }

        /// The shared printer, with the labels the table carried or core
        /// indexed from it — the path every compiled format but djot and
        /// Markdown takes, and those two only because they number footnotes
        /// against labels of their own.
        fn renderHtml(allocator: Allocator, doc: *const format.ParsedDoc, writer: *Writer) anyerror!void {
            try Html.serialize(allocator, doc.ast(), writer, &doc.doc.labels);
        }

        /// The parse's own table without its positions — a print has no
        /// source to hold them against — and with the spelling and labels
        /// the parse recorded, which is what `serializeCanonical` keeps
        /// that `serializeFromAst` rebuilds.
        fn serializeCanonical(allocator: Allocator, doc: *const format.ParsedDoc) anyerror![]u8 {
            const text = try node_table.encodeAlloc(allocator, &doc.doc, .{ .pretty = false, .positions = false });
            defer allocator.free(text);
            return slots[i].printTable(allocator, text);
        }

        fn serializeFromAst(allocator: Allocator, ast: *const AST) anyerror![]u8 {
            const text = try node_table.encodeAstAlloc(allocator, ast, .{ .pretty = false });
            defer allocator.free(text);
            return slots[i].printTable(allocator, text);
        }
    };
}

const RowFns = struct {
    parse: *const fn (*const anyopaque, Allocator, []const u8) anyerror!format.ParsedDoc,
    parseToAst: *const fn (*const anyopaque, Allocator, []const u8) anyerror!Document,
    renderHtml: *const fn (Allocator, *const format.ParsedDoc, *Writer) anyerror!void,
    serializeCanonical: *const fn (Allocator, *const format.ParsedDoc) anyerror![]u8,
    serializeFromAst: *const fn (Allocator, *const AST) anyerror![]u8,
};

const rows: [capacity]RowFns = blk: {
    var out: [capacity]RowFns = undefined;
    for (&out, 0..) |*r, i| {
        const R = Row(i);
        r.* = .{
            .parse = R.parse,
            .parseToAst = R.parseToAst,
            .renderHtml = R.renderHtml,
            .serializeCanonical = R.serializeCanonical,
            .serializeFromAst = R.serializeFromAst,
        };
    }
    break :blk out;
};

// ── registration ────────────────────────────────────────────────────────────

pub const RegisterError = error{ InvalidLanguage, RegistryFull, OutOfMemory };

/// Names a runtime language may not take: the CLI's `-o` words, which would
/// shadow it on the command line.
const reserved_names = [_][]const u8{ "ast", "table", "canonical" };

/// Register `language` under `description`, run the load check over it, and
/// return the `Format` it answers to. `gpa` owns the copies of the description
/// for the life of the process. On refusal nothing is registered, and `diag`
/// says why.
pub fn register(gpa: Allocator, language: Language, description: Description, diag: *Writer) RegisterError!Format {
    while (!lock.tryLock()) std.atomic.spinLoopHint();
    defer lock.unlock();

    const i = count.load(.acquire);
    try checkDescription(description, language, diag);
    if (i >= capacity) return refuseFull(diag);

    const owned = try own(gpa, description);
    const id: Format = @enumFromInt(base + i);
    const fns = rows[i];
    slots[i] = .{
        .language = language,
        .description = owned,
        .entry = .{
            .id = id,
            .samples = owned.samples,
            .extensions = owned.extensions,
            .aliases = owned.aliases,
            .parse = fns.parse,
            .parseToAst = fns.parseToAst,
            .renderHtml = fns.renderHtml,
            .serializeCanonical = if (owned.write) fns.serializeCanonical else null,
        },
        .target = .{
            .id = @enumFromInt(base + i),
            .reads_back_as = id,
            .serializeFromAst = if (owned.write) fns.serializeFromAst else null,
        },
        .measured = null,
    };

    // The slot is filled and not yet published: the checks below call the
    // row's own functions, which read it, while no other reader can see it.
    loading.store(base + @as(u16, @intCast(i)), .release);
    defer loading.store(0, .release);
    try checkContract(gpa, &slots[i], diag);
    // A print that fails on a probe is measured as dropping it, not as a
    // reason to refuse the language: it declared the write tier, not the
    // whole vocabulary.
    if (owned.write) slots[i].measured = try diagnostics.measure(gpa, fns.serializeFromAst, fns.parseToAst);
    count.store(i + 1, .release);
    return id;
}

fn refuseFull(diag: *Writer) error{RegistryFull} {
    diag.print("the registry holds {d} languages and is full", .{capacity}) catch {};
    return error.RegistryFull;
}

fn isIdentifier(s: []const u8) bool {
    if (s.len == 0 or s.len > 64) return false;
    if (!std.ascii.isLower(s[0])) return false;
    for (s) |c| if (!(std.ascii.isLower(c) or std.ascii.isDigit(c) or c == '-' or c == '_')) return false;
    return true;
}

/// Whether a compiled row or a registered one already answers to `name`.
fn nameTaken(name: []const u8) bool {
    if (format.parseFormatName(name) != null or format.parseTargetName(name) != null) return true;
    for (reserved_names) |r| if (std.mem.eql(u8, r, name)) return true;
    return false;
}

fn checkDescription(d: Description, language: Language, diag: *Writer) error{InvalidLanguage}!void {
    if (!isIdentifier(d.name)) return refuse(diag, "the name \"{s}\" is not a lowercase identifier", .{d.name});
    if (nameTaken(d.name)) return refuse(diag, "the name \"{s}\" is already a format's", .{d.name});
    for (d.aliases) |a| {
        if (!isIdentifier(a)) return refuse(diag, "{s}: the alias \"{s}\" is not a lowercase identifier", .{ d.name, a });
        if (nameTaken(a) or std.mem.eql(u8, a, d.name)) return refuse(diag, "{s}: the alias \"{s}\" is already a format's", .{ d.name, a });
    }
    for (d.extensions) |e| {
        if (e.len == 0 or std.mem.indexOfScalar(u8, e, '.') != null) return refuse(diag, "{s}: the extension \"{s}\" is written without its dot", .{ d.name, e });
        for (e) |c| if (std.ascii.isUpper(c)) return refuse(diag, "{s}: the extension \"{s}\" is written in lowercase", .{ d.name, e });
        var probe_path: [80]u8 = undefined;
        const path = std.fmt.bufPrint(&probe_path, "x.{s}", .{e}) catch return refuse(diag, "{s}: the extension \"{s}\" is too long", .{ d.name, e });
        if (format.detectFromExtension(path)) |owner| return refuse(diag, "{s}: the extension \"{s}\" is {s}'s", .{ d.name, e, owner.name() });
    }
    if (d.samples.len == 0) return refuse(diag, "{s}: a language declares at least one sample, which is the whole of what the load check holds it to", .{d.name});
    if (d.write and language.print == null) return refuse(diag, "{s}: caps.write needs a print function", .{d.name});
}

fn own(gpa: Allocator, d: Description) Allocator.Error!Description {
    return .{
        .name = try gpa.dupe(u8, d.name),
        .extensions = try ownAll(gpa, d.extensions),
        .aliases = try ownAll(gpa, d.aliases),
        .write = d.write,
        .samples = try ownAll(gpa, d.samples),
    };
}

fn ownAll(gpa: Allocator, items: []const []const u8) Allocator.Error![]const []const u8 {
    const out = try gpa.alloc([]const u8, items.len);
    for (items, out) |item, *o| o.* = try gpa.dupe(u8, item);
    return out;
}

/// The engine contract — `contract.all`, the checks every compiled format's
/// harness runs: every sample parses to a table core accepts, and a language
/// that prints reparses each print to an equal tree. The renderer, claim,
/// move and gesture checks apply to a row that authors, which a runtime one
/// does not yet.
fn checkContract(gpa: Allocator, slot: *const Slot, diag: *Writer) RegisterError!void {
    var report: contract.Report = .{};
    contract.all(gpa, &slot.entry, &report) catch |err| switch (err) {
        error.OutOfMemory => return error.OutOfMemory,
        error.ContractBroken => return refuse(diag, "{s}", .{report.message()}),
    };
}

// ── tests ───────────────────────────────────────────────────────────────────
//
// The registry is process-global and append-only, so the tests below share
// it: each registers under a name of its own and never assumes a count.

const testing = std.testing;

/// A language whose "parse" is djot's, written out as a table, and whose
/// "print" is djot's serializer over the decoded table — the compiled row
/// driven through the runtime contract, which is what a twin is.
const DjotTwin = struct {
    fn parse(_: ?*anyopaque, allocator: Allocator, _: []const u8, source: []const u8, _: *Writer) Error![]u8 {
        var doc = @import("languages/djot/djot.zig").parse(allocator, source) catch return error.LanguageFailed;
        defer doc.deinit();
        return node_table.encodeAlloc(allocator, &doc, .{ .pretty = false });
    }

    fn print(_: ?*anyopaque, allocator: Allocator, _: []const u8, text: []const u8, diag: *Writer) Error![]u8 {
        var doc = node_table.decodeBare(allocator, text, null) catch |err| switch (err) {
            error.OutOfMemory => return error.OutOfMemory,
            else => {
                diag.writeAll("the table did not decode") catch {};
                return error.LanguageFailed;
            },
        };
        defer doc.deinit();
        // The Document-aware print, as the compiled row's canonical path:
        // the table carried the parse's labels, which say that a heading's
        // target was implicit and is not to be written out.
        return @import("languages/djot/serializer.zig").serializeAlloc(allocator, &doc) catch error.LanguageFailed;
    }
};

test "runtime: a registered language is a row every consumer reaches" {
    var diag: Writer.Allocating = .init(testing.allocator);
    defer diag.deinit();
    const fmt = register(std.heap.page_allocator, .{ .parse = DjotTwin.parse, .print = DjotTwin.print }, .{
        .name = "djot-twin",
        .extensions = &.{"djtwin"},
        .aliases = &.{"djt"},
        .write = true,
        .samples = &.{ "# Title\n\nSome _emphasis_ and a [link](/u).\n", "- a\n- b\n" },
    }, &diag.writer) catch |err| {
        std.debug.print("\nrefused: {s}\n", .{diag.written()});
        return err;
    };

    try testing.expect(isRegistered(fmt));
    try testing.expectEqualStrings("djot-twin", fmt.name());
    try testing.expectEqual(fmt, format.parseFormatName("djot-twin").?);
    try testing.expectEqual(fmt, format.parseFormatName("djt").?);
    try testing.expectEqual(fmt, format.detectFromExtension("notes.DJTWIN").?);
    try testing.expectEqual(@intFromEnum(fmt), @intFromEnum(format.targetFor(fmt)));

    const src = "Hello *world*.\n";
    const cfg: format.ParseConfig = .{};
    var doc = try format.entryFor(fmt).parse(&cfg, testing.allocator, src);
    defer doc.deinit();
    try testing.expectEqual(fmt, doc.format);
    var djot = try @import("languages/djot/djot.zig").parse(testing.allocator, src);
    defer djot.deinit();
    try testing.expect(djot.ast.eql(doc.doc.ast));

    const html = try format.renderHtmlAlloc(testing.allocator, &doc);
    defer testing.allocator.free(html);
    try testing.expectEqualStrings("<p>Hello <strong>world</strong>.</p>\n", html);

    const again = try format.serializeCanonicalAlloc(testing.allocator, &doc);
    defer testing.allocator.free(again);
    try testing.expectEqualStrings(src, again);

    // Into the runtime target from a compiled parse, and out of it.
    var md = try format.entryFor(.markdown).parse(&cfg, testing.allocator, "# T\n\n*x*\n");
    defer md.deinit();
    const as_twin = try format.serializeFromAstAlloc(testing.allocator, md.ast(), format.targetFor(fmt));
    defer testing.allocator.free(as_twin);
    try testing.expectEqualStrings("# T\n\n_x_\n", as_twin);

    // The probe measured it, and it measures what djot's table declares.
    const t = format.targetFor(fmt);
    try testing.expectEqual(diagnostics.Fidelity.faithful, diagnostics.fidelity(t, .{ .heading = .{ .level = 2 } }));
    try testing.expectEqual(diagnostics.fidelity(.djot, .{ .inline_mark = .superscript }), diagnostics.fidelity(t, .{ .inline_mark = .superscript }));

    // An editor opens over it, and has nothing to author with.
    var editor = try @import("ast/editor.zig").Editor.init(testing.allocator, src, &cfg, format.entryFor(fmt).parseToAst, format.entryFor(fmt).syntax);
    defer editor.deinit();
    try testing.expect(!format.entryFor(fmt).syntax.authorable());
}

fn failingParse(_: ?*anyopaque, allocator: Allocator, _: []const u8, source: []const u8, diag: *Writer) Error![]u8 {
    if (std.mem.startsWith(u8, source, "bad")) {
        diag.writeAll("this language does not read \"bad\"") catch {};
        return error.LanguageFailed;
    }
    // A table whose paragraph claims a span past the source.
    if (std.mem.startsWith(u8, source, "wide")) return allocator.dupe(u8, "{\"nodes\":[{\"kind\":\"doc\",\"span\":[0,1]},{\"kind\":\"para\",\"parent\":0,\"span\":[0,99]}]}");
    return std.fmt.allocPrint(allocator, "{{\"nodes\":[{{\"kind\":\"doc\",\"span\":[0,{d}]}}]}}", .{source.len});
}

fn expectRefusal(description: Description, language: Language, want: []const u8) !void {
    var diag: Writer.Allocating = .init(testing.allocator);
    defer diag.deinit();
    _ = register(std.heap.page_allocator, language, description, &diag.writer) catch |err| {
        try testing.expectEqual(error.InvalidLanguage, err);
        if (std.mem.indexOf(u8, diag.written(), want) == null) {
            std.debug.print("\nwanted \"{s}\" in \"{s}\"\n", .{ want, diag.written() });
            return error.TestUnexpectedResult;
        }
        return;
    };
    return error.TestUnexpectedResult;
}

test "runtime: load refuses what the description or the samples get wrong" {
    const lang: Language = .{ .parse = failingParse };
    try expectRefusal(.{ .name = "Org", .samples = &.{"x"} }, lang, "lowercase identifier");
    try expectRefusal(.{ .name = "markdown", .samples = &.{"x"} }, lang, "already a format's");
    try expectRefusal(.{ .name = "table", .samples = &.{"x"} }, lang, "already a format's");
    try expectRefusal(.{ .name = "orgish", .aliases = &.{"md"}, .samples = &.{"x"} }, lang, "alias \"md\"");
    try expectRefusal(.{ .name = "orgish", .extensions = &.{"md"}, .samples = &.{"x"} }, lang, "is markdown's");
    try expectRefusal(.{ .name = "orgish", .extensions = &.{".org"}, .samples = &.{"x"} }, lang, "without its dot");
    try expectRefusal(.{ .name = "orgish", .samples = &.{} }, lang, "at least one sample");
    try expectRefusal(.{ .name = "orgish", .write = true, .samples = &.{"x"} }, lang, "needs a print");
    try expectRefusal(.{ .name = "orgish", .samples = &.{ "x", "bad" } }, lang, "orgish: sample 1 does not parse: orgish: parse failed: this language does not read \"bad\"");
    try expectRefusal(.{ .name = "orgish", .samples = &.{"wide"} }, lang, "row 1: span: ends past the source");
    // Nothing above registered anything.
    try testing.expectEqual(@as(?Format, null), format.parseFormatName("orgish"));

    // And a read-tier language that passes is a row with no writer.
    var diag: Writer.Allocating = .init(testing.allocator);
    defer diag.deinit();
    const fmt = try register(std.heap.page_allocator, lang, .{ .name = "blank-reader", .samples = &.{"x"} }, &diag.writer);
    try testing.expect(format.entryFor(fmt).serializeCanonical == null);
    try testing.expect(format.targetEntryFor(format.targetFor(fmt)).serializeFromAst == null);
    try testing.expect(measured(format.targetFor(fmt)) == null);
    // A later parse that breaks the table is an error with a reason, not a crash.
    const cfg: format.ParseConfig = .{};
    try testing.expectError(error.InvalidTable, format.entryFor(fmt).parse(&cfg, testing.allocator, "wide"));
    try testing.expect(std.mem.indexOf(u8, lastFailure(), "blank-reader: the table is refused: row 1: span") != null);
}

test "runtime: a description reads from its describe document" {
    var arena: std.heap.ArenaAllocator = .init(testing.allocator);
    defer arena.deinit();
    var diag: Writer.Allocating = .init(testing.allocator);
    defer diag.deinit();
    const d = try Description.parse(arena.allocator(),
        \\{"name":"org","extensions":["org"],"caps":{"read":true,"write":true},"samples":["* x\n"],"future":1}
    , &diag.writer);
    try testing.expectEqualStrings("org", d.name);
    try testing.expectEqualStrings("org", d.extensions[0]);
    try testing.expect(d.write);
    try testing.expectEqual(@as(usize, 0), d.aliases.len);

    try testing.expectError(error.InvalidLanguage, Description.parse(arena.allocator(),
        \\{"name":"org","caps":{"author":true},"samples":["x"]}
    , &diag.writer));
    try testing.expect(std.mem.indexOf(u8, diag.written(), "caps.author is not open") != null);
    try testing.expectError(error.InvalidLanguage, Description.parse(arena.allocator(),
        \\{"name":"org","syntax":{},"samples":["x"]}
    , &diag.writer));
}