twig-sys 2.2.3

FFI bindings and native library for Twig (the Djot/Markdown/HTML/XML document engine). Used by the `twig-doc` crate.
Documentation
//! Event = the flat intermediate representation between scanning
//! (`block.zig` + `inline.zig`) and tree-building (`parser.zig`). Mirrors
//! djot.js's `src/event.ts` (`{startpos, endpos, annot}`) plus the full
//! vocabulary of `annot` strings block.ts/inline.ts/attributes.ts actually
//! emit and parse.ts actually dispatches on — there is no single upstream
//! file enumerating them; they are literal string keys scattered across each
//! file's `addMatch`/`addEvent` calls and parse.ts's `handlers` table. This
//! file is the result of collecting all of them into one exhaustive enum.
//!
//! A whole document parses to one flat `[]Event` array. Container structure
//! is carried by *paired* annotations (`.emph_open` / `.emph_close`, matching
//! djot.js's `"+emph"` / `"-emph"` string pairs) rather than nesting; a
//! generic stack-based builder in `parser.zig` turns matched pairs into a
//! tree — see that file's module doc comment for why a flat stream (rather
//! than building the tree directly during scanning) matters here: forward
//! references/footnotes and tight/loose list determination both need to see
//! the *whole* stream before they can be resolved.
//!
//! One deliberate deviation from djot.js: where it packs extra data onto an
//! annot string via `|`-joined suffixes (`"+list|-|*"` for ambiguous bullet
//! styles), `Event` instead carries that data in a typed side field
//! (`list_styles`) — there's no need to serialize through a string when
//! producer and consumer are both this same codebase.

const std = @import("std");

pub const Event = struct {
    start: u32,
    end: u32,
    annot: Annotation,
    /// Only meaningful when `annot` is `.list_open` or `.list_item_open`:
    /// the candidate marker styles remaining after narrowing. djot.js calls
    /// this "ambiguous style" resolution — see `block.zig`'s `getListStyles`.
    list_styles: ListStyleCandidates = .{},
};

pub const EventList = std.ArrayList(Event);

/// A list item marker's possible interpretations. `getListStyles` in
/// `block.zig` produces these from marker text; at most two ever arise (a
/// lone `i.`/`I.` is ambiguous between roman numeral one and the first
/// letter of the alphabet — djot.js's `getListStyles` returns exactly those
/// two candidates in that order, roman first, so it wins if never narrowed).
pub const ListMarkerStyle = union(enum) {
    dash, // "-"
    plus, // "+"
    star, // "*"
    colon, // ":" (definition list)
    dash_task, // "- [ ]" / "- [x]"
    plus_task, // "+ [ ]" / "+ [x]"
    star_task, // "* [ ]" / "* [x]"
    ordered: struct { numbering: Numbering, delim: Delim },

    pub const Numbering = enum { decimal, lower_alpha, upper_alpha, lower_roman, upper_roman };
    /// Which punctuation wraps the number/letter: `1.`, `1)`, or `(1)`.
    pub const Delim = enum { period, paren_after, paren_both };

    pub fn eql(a: ListMarkerStyle, b: ListMarkerStyle) bool {
        if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false;
        return switch (a) {
            .ordered => |av| av.numbering == b.ordered.numbering and av.delim == b.ordered.delim,
            else => true,
        };
    }

    pub fn isTask(self: ListMarkerStyle) bool {
        return switch (self) {
            .dash_task, .plus_task, .star_task => true,
            else => false,
        };
    }
};

/// At most two candidate styles for one marker (see `ListMarkerStyle` doc).
pub const ListStyleCandidates = struct {
    items: [2]ListMarkerStyle = undefined,
    len: u8 = 0,

    pub fn single(s: ListMarkerStyle) ListStyleCandidates {
        return .{ .items = .{ s, s }, .len = 1 };
    }

    pub fn two(a: ListMarkerStyle, b: ListMarkerStyle) ListStyleCandidates {
        return .{ .items = .{ a, b }, .len = 2 };
    }

    pub fn slice(self: *const ListStyleCandidates) []const ListMarkerStyle {
        return self.items[0..self.len];
    }

    pub fn isEmpty(self: ListStyleCandidates) bool {
        return self.len == 0;
    }

    pub fn contains(self: ListStyleCandidates, s: ListMarkerStyle) bool {
        for (self.slice()) |c| {
            if (c.eql(s)) return true;
        }
        return false;
    }

    /// Intersect two candidate sets (narrowing, per djot.js's continuation
    /// matching on a `list` container's `extra.styles`), preserving `self`'s
    /// order — so a still-ambiguous result keeps its original tie-break
    /// preference (roman before alpha).
    pub fn intersect(self: ListStyleCandidates, other: ListStyleCandidates) ListStyleCandidates {
        var result: ListStyleCandidates = .{};
        for (self.slice()) |c| {
            if (other.contains(c)) {
                result.items[result.len] = c;
                result.len += 1;
            }
        }
        return result;
    }
};

/// Every distinct event annotation the scanners emit and the parser
/// dispatches on. `_open`/`_close` pairs are djot.js's `"+x"`/`"-x"` strings;
/// everything else is a leaf (no matching close — its span alone carries the
/// meaning, sliced directly out of the source by whoever handles it).
///
/// Two annotations are deliberately asymmetric, mirroring djot.js exactly
/// rather than forcing a push/pop shape that doesn't fit: `.destination_open`
/// and `.reference_open` don't open a new container (the container was
/// already pushed by a preceding `.linktext_open`/`.imagetext_open` and is
/// still open) — they only toggle the parser's literal-text-accumulation
/// mode. Their `_close` counterparts are what actually finalize the node.
pub const Annotation = enum {
    // ── Containers ──────────────────────────────────────────────────────
    reference_definition_open,
    reference_definition_close,
    emph_open,
    emph_close,
    strong_open,
    strong_close,
    span_open,
    span_close,
    mark_open,
    mark_close,
    superscript_open,
    superscript_close,
    subscript_open,
    subscript_close,
    delete_open,
    delete_close,
    insert_open,
    insert_close,
    double_quoted_open,
    double_quoted_close,
    single_quoted_open,
    single_quoted_close,
    attributes_open,
    attributes_close,
    block_attributes_open,
    block_attributes_close,
    linktext_open,
    linktext_close,
    imagetext_open,
    imagetext_close,
    /// See the module doc comment: doesn't push a container.
    destination_open,
    destination_close,
    /// See the module doc comment: doesn't push a container.
    reference_open,
    reference_close,
    verbatim_open,
    verbatim_close,
    display_math_open,
    display_math_close,
    inline_math_open,
    inline_math_close,
    url_open,
    url_close,
    email_open,
    email_close,
    para_open,
    para_close,
    heading_open,
    heading_close,
    list_open,
    list_close,
    list_item_open,
    list_item_close,
    block_quote_open,
    block_quote_close,
    table_open,
    table_close,
    row_open,
    row_close,
    cell_open,
    cell_close,
    caption_open,
    caption_close,
    footnote_open,
    footnote_close,
    code_block_open,
    code_block_close,
    div_open,
    div_close,

    // ── Leaves ──────────────────────────────────────────────────────────
    str,
    soft_break,
    /// The backslash itself, immediately before an escaped char/newline/space.
    escape,
    hard_break,
    non_breaking_space,
    /// A `:name:` symbol shortcode (span covers the surrounding colons).
    symb,
    /// `[^label]` used inline (span covers the whole thing, incl. brackets).
    footnote_reference,
    reference_key,
    reference_value,
    checkbox_checked,
    checkbox_unchecked,
    note_label,
    code_language,
    raw_format,
    /// Reused for both a fenced div's `{.class}`-less bare class name and the
    /// attribute parser's `.class` token — same annotation, same meaning.
    class,
    thematic_break,
    left_single_quote,
    right_single_quote,
    left_double_quote,
    right_double_quote,
    ellipses,
    en_dash,
    em_dash,
    /// Recorded so `parser.zig` can determine tight/loose lists; carries no
    /// other meaning.
    blankline,
    separator_default,
    separator_left,
    separator_right,
    separator_center,
    /// The `!` before an image's `[`.
    image_marker,
    /// An explicit `{` open/close marker for a brace-delimited construct
    /// (insert/mark/delete). Consumed internally during inline scanning;
    /// `parser.zig` has no handler for it (a harmless no-op if seen).
    open_marker,

    // ── Attribute-parser leaves (attributes.zig) ───────────────────────
    attr_space,
    attr_id_marker,
    id,
    attr_class_marker,
    key,
    attr_equal_marker,
    attr_quote_marker,
    value,
    /// `%...%` comment inside an attribute block. No parser.zig handler —
    /// comments are discarded, same as upstream.
    comment,
};