fig-sys 3.4.0

FFI bindings and native library for fig (the comment-preserving JSON/YAML/TOML/… config engine). Used by the `fig` crate.
Documentation
const toml = @This();
const AST = @import("../../ast/ast.zig");
const Document = @import("../../document.zig");
const lang = @import("../manifest.zig");

pub const Parser = @import("parser.zig");
pub const Tokenizer = @import("tokenizer.zig");
pub const Printer = @import("printer.zig");

pub const Type = enum {
    /// TOML 1.0.0 (stable, finalized).
    TOML_1_0,
    /// TOML 1.1.0 (draft): newlines + trailing commas in inline tables,
    /// seconds-optional times, `\e` and `\xHH` string escapes.
    TOML_1_1,
};

pub const Language = struct {
    pub const Type = toml.Type;
    pub const Parser = toml.Parser;
    pub const Printer = toml.Printer;
    pub const default_type: toml.Type = .TOML_1_1;
    pub fn parse(parser: *toml.Parser, input: []const u8, format: toml.Type) !Document {
        return toml.Parser.parse(parser.allocator, input, format);
    }
    pub const print = toml.Printer.print;
    pub const printNode = toml.Printer.printNode;
    /// Parse straight to a core AST with no `Document` around it — what
    /// `deserialize.zig` maps onto a Zig type. Optional `Language` decl,
    /// required exactly of a language with a `deserializable` dialect row.
    pub const parseAbstract = toml.Parser.parseAbstract;

    pub const name = "toml";
    pub const extensions: []const []const u8 = &.{"toml"};
    pub const caps: lang.Caps = .{
        .read = true,
        .edit = true,
        .serialize = true,
        // The four datetimes and `inf`/`nan` floats are native; `null` is
        // not — TOML is the one typed format without one, which makes a
        // `null` the one value the lossy path drops outright rather than
        // degrades. Enum/char literals and plist's date/data are enveloped.
        .lossless = .{
            .offset_datetime = true,
            .local_datetime = true,
            .local_date = true,
            .local_time = true,
            .number_special = true,
        },
    };

    /// What `languages/harness.zig` round-trips and edits: a root key, a
    /// `[table]` (the section shape the parser records regions for), an
    /// array, and an inline table.
    pub const samples: []const []const u8 = &.{
        "a = 1\nb = [1, 2]\n\n[s]\nk = \"v\"\nt = { x = 1 }\n",
    };

    pub const dialects: []const lang.Dialect(@This()) = &.{.{
        .name = "toml",
        .abi_value = 4,
        // First of the `key = value` grammars. fig, INI and dotenv all accept
        // plain TOML-shaped content, so TOML gets first claim on it and they
        // win only what TOML rejects.
        .sniff_rank = 5,
        .deserializable = true,
        .splice = .literal,
        .empty_doc_seed = "",
        .specs = &.{
            .{ .name = "1.0", .dialect = .TOML_1_0 },
            .{ .name = "1.0.0", .dialect = .TOML_1_0 },
            .{ .name = "1.1", .dialect = .TOML_1_1 },
            .{ .name = "1.1.0", .dialect = .TOML_1_1 },
        },
        .embed = .{
            .fence_tag = "toml",
            .frontmatter = "---toml",
            .script_mime = "application/toml",
            .code_class = "language-toml",
        },
    }};

    /// 1.0 and 1.1 differ in what the PARSER accepts (newlines and trailing
    /// commas in inline tables, seconds-optional times, `\e`/`\xHH` escapes),
    /// not in what the editor writes, so both dialects answer identically.
    pub fn syntax(t: toml.Type) lang.Syntax {
        _ = t;
        return .{
            .comments = .hash,
            // TOML spells an entry `key = value`, and every path where that
            // matters is delegated to `toml/editor_helper.zig` — so the
            // generic engine never writes a TOML separator. See
            // `Syntax.kv_sep`.
            .kv_sep = null,
            // The dotted-key formats keep the flow `{}` chain as the
            // idiomatic intermediate form — `fig fmt` canonicalizes
            // `a = { b = { c = v }}` back to `a.b.c = v`.
            .empty_map_literal = "{}",
            // A non-flow TOML sequence is an array-of-tables (use
            // `appendTableToArray`), and TOML has no block scalar array — so
            // the generic block-sequence edits refuse with `NotAnInlineArray`.
            .block_seq_editable = false,
            // A section format: a `[table]`, `[[array]]` or dotted table is
            // assembled from scattered lines, which `parser.zig` records in
            // `Document.node_regions`. Its refusals say "table".
            .section_noun = .table,
        };
    }

    // ── Editing hooks ────────────────────────────────────────────────────────
    //
    // Operations this format takes over from the generic splice engine.
    // `Editor` dispatches on PRESENCE — `@hasDecl(Language, "insertKey")` — so
    // declaring one here is the whole of opting in, and every operation not
    // named below runs the generic implementation. Each signature is fixed by
    // the `editor.Editor` method of the same name; see its doc comment.
    //
    // The logic lives in `editor_helper.zig` (which holds this format's editor
    // tests too), not here: this block is the DECLARATION of which operations
    // are overridden, so a reader can see a format's whole answer in one struct
    // without opening the helper.
    const edit = @import("editor_helper.zig");

    /// TOML splits a logical table across scattered `[header]` and dotted-key
    /// lines, so a new entry has to land at the end of the intended table's own
    /// header region — never after a sub-table header, which would silently
    /// reparent it.
    pub const insertKey = edit.tomlInsertKey;

    // No delete/replace/move/reorder guards: the engine's section rule covers
    // them. A block table is a section node (`Document.node_regions`), and a
    // section node cannot be line-spliced — `deleteKey`, `replaceValAtPath`,
    // `moveKey` and `reorderKeys` refuse it in this format's vocabulary
    // (`CannotDeleteTable`, …) and point at the whole-container ops.

    /// A table's name is written once per `[header]`/dotted line that mentions
    /// it, but only the first has a key node — so the generic one-span splice
    /// renames that mention and leaves the rest behind, splitting the table.
    /// Routes a block table to `renameContainer`'s multi-mention rewrite.
    pub const replaceKeyAtPath = edit.tomlReplaceKey;

    // ── Whole-container ops ──────────────────────────────────────────────────
    //
    // `deleteContainer`, `moveContainer` and `reorderContainers` are GENERIC:
    // `editor.zig` derives a table's scattered regions from `Document.
    // node_regions` and needs nothing from here. The three below are the ops
    // that have to SPELL something TOML — a `[header]` line, or every mention
    // of a table's name — and stay hooks; `Editor` dispatches on `@hasDecl`
    // for them exactly as for the hooks above.

    /// A new `[path]` table, spliced past the parent's whole subtree so no
    /// existing key is reparented.
    pub const insertContainer = edit.insertTable;

    /// TOML alone needs a rename op: the renamed segment appears in every
    /// descendant header (`[a.b]`, `[a.b.c]`, `[[a.b]]`) and every dotted line
    /// that spells it, not just its own key node.
    pub const renameContainer = edit.renameTable;

    /// A new `[[header]]` element on the end of an array-of-tables, past every
    /// line of the current last element's subtree.
    pub const appendContainerToSeq = edit.appendTableToArray;
};

// Test discovery for the TOML module: importing `toml.zig` (from root.zig) pulls
// in every TOML submodule's tests, so the module owns its own test surface rather
// than root.zig enumerating each file. `editor_helper.zig` holds the TOML editor
// tests; conformance is gated by a build option and stays in root.zig.
test {
    _ = @import("tokenizer.zig");
    _ = @import("parser.zig");
    _ = @import("printer.zig");
    _ = @import("editor_helper.zig");
}