Skip to main content

fig_sys/
lib.rs

1//! Low-level FFI bindings for fig's C ABI, plus the build script that compiles
2//! and links the native `libfig.a`.
3//!
4//! This is the `-sys` layer: raw `extern "C"` functions, `#[repr(C)]` type
5//! mirrors, and status/format constants — nothing else. The safe, ergonomic
6//! API (and serde/derive integration) lives in the `fig` crate, which depends
7//! on this one. Direct use is `unsafe` and unstable across ABI-version bumps.
8#![allow(non_camel_case_types)]
9
10use std::os::raw::{c_char, c_int, c_void};
11
12/// A fig C ABI status code, as a transparent wrapper over the raw `c_int` the
13/// ABI returns — deliberately *not* a Rust `enum`.
14///
15/// A fieldless `#[repr(C)]` enum returned by value from an `extern "C"` function
16/// is undefined behavior the instant the callee returns a discriminant the enum
17/// does not list, and fig's status set is allowed to grow after 1.0. The newtype
18/// preserves any code unchanged. Compare against the associated constants —
19/// each is a `FigStatus`, so `status == FigStatus::OK` and a `match` arm both
20/// work — and route unrecognized values through a fallback (the `fig` crate
21/// does this in its `Error::from_status`).
22#[repr(transparent)]
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct FigStatus(pub c_int);
25
26impl FigStatus {
27    pub const OK: FigStatus = FigStatus(0);
28    pub const INVALID_ARGUMENT: FigStatus = FigStatus(1);
29    pub const PARSE_ERROR: FigStatus = FigStatus(2);
30    pub const OUT_OF_MEMORY: FigStatus = FigStatus(3);
31    pub const UNSUPPORTED_FORMAT: FigStatus = FigStatus(4);
32    pub const NOT_FOUND: FigStatus = FigStatus(5);
33    /// The operation is not defined for these arguments, though each is
34    /// individually valid. Added in core 2.7.0; see `fig_embed_retype`.
35    pub const UNSUPPORTED_OPERATION: FigStatus = FigStatus(6);
36    pub const INTERNAL_ERROR: FigStatus = FigStatus(255);
37}
38
39#[repr(C)]
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum FigFormat {
42    Json = 1,
43    Jsonc = 2,
44    Yaml = 3,
45    Toml = 4,
46    Zon = 5,
47    // 6 was generic XML through core 2.x; retired with the format in core
48    // 3.0 and never reused, which is why JSON5 keeps its gap.
49    Json5 = 7,
50    // The native `fig` authoring dialect. Appended, same reasoning as JSON5.
51    Fig = 8,
52    Ini = 9,
53    Dotenv = 10,
54    Properties = 11,
55    Plist = 12,
56    Nestedtext = 13,
57}
58
59/// Mirror of fig.h's `FIG_FORMAT_RUNTIME_BASE`: a format integer at or above
60/// this names a language registered at runtime, assigned per process and
61/// never a `FigFormat` variant. Every compiled-in variant above is below it.
62pub const FIG_FORMAT_RUNTIME_BASE: c_int = 4096;
63
64pub enum FigDocument {}
65
66pub type FigNodeId = u32;
67
68/// Sentinel for "no such node", matching `FIG_NODE_NONE` in `fig.h`.
69pub const FIG_NODE_NONE: FigNodeId = 0xFFFF_FFFF;
70
71#[repr(C)]
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73#[allow(dead_code)]
74pub enum FigNodeKind {
75    Invalid = -1,
76    Null = 0,
77    Bool = 1,
78    Int = 2,
79    Float = 3,
80    String = 4,
81    Sequence = 5,
82    Mapping = 6,
83    Keyvalue = 7,
84    Alias = 8,
85    /// A format-specific scalar; `fig_node_extended` says which.
86    Extended = 9,
87}
88
89impl FigNodeKind {
90    /// Map the raw `c_int` returned by `fig_node_kind` onto a `FigNodeKind`.
91    /// Unknown / future kinds collapse to [`FigNodeKind::Invalid`] rather than
92    /// being reinterpreted as an out-of-range enum value — which, for a value
93    /// returned by an `extern "C"` function into a Rust enum, is undefined
94    /// behavior. This is the only place a raw kind crosses into the enum.
95    pub fn from_c(raw: c_int) -> Self {
96        match raw {
97            0 => FigNodeKind::Null,
98            1 => FigNodeKind::Bool,
99            2 => FigNodeKind::Int,
100            3 => FigNodeKind::Float,
101            4 => FigNodeKind::String,
102            5 => FigNodeKind::Sequence,
103            6 => FigNodeKind::Mapping,
104            7 => FigNodeKind::Keyvalue,
105            8 => FigNodeKind::Alias,
106            9 => FigNodeKind::Extended,
107            _ => FigNodeKind::Invalid,
108        }
109    }
110}
111
112/// A caller-allocated parse diagnostic. Mirrors `FigError` in `fig.h`. Lead with
113/// `size = size_of::<FigError>()`: the library writes only the fields `size`
114/// covers, so it can gain fields in a later release without breaking this layout.
115/// `byte_offset`/`line`/`column` are 0 when unknown. A runtime language's parse
116/// failure fills `byte_offset` with the offset its parser reported; the compiled
117/// formats leave all three 0 for now. `message` is NUL-terminated and truncated
118/// to fit; `message_len` excludes the NUL.
119#[repr(C)]
120#[derive(Clone, Copy)]
121pub struct FigError {
122    pub size: u32,
123    pub code: c_int,
124    pub byte_offset: usize,
125    pub line: u32,
126    pub column: u32,
127    pub message_len: usize,
128    pub message: [u8; 256],
129}
130
131impl FigError {
132    /// A zeroed struct with `size` set, ready to pass to `fig_parse_ex`.
133    pub fn new() -> Self {
134        FigError {
135            size: std::mem::size_of::<FigError>() as u32,
136            code: 0,
137            byte_offset: 0,
138            line: 0,
139            column: 0,
140            message_len: 0,
141            message: [0; 256],
142        }
143    }
144}
145
146unsafe extern "C" {
147    pub fn fig_version() -> u32;
148    pub fn fig_version_string() -> *const std::os::raw::c_char;
149    pub fn fig_format_capabilities(format: c_int) -> u32;
150
151    // Declared for ABI-mirror completeness; the binding parses via `fig_parse_ex`
152    // (richer errors), so this plain entry point is not called from Rust.
153    #[allow(dead_code)]
154    pub fn fig_parse(
155        input: *const u8,
156        input_len: usize,
157        format: c_int,
158        out_doc: *mut *mut FigDocument,
159    ) -> FigStatus;
160
161    pub fn fig_parse_ex(
162        input: *const u8,
163        input_len: usize,
164        format: c_int,
165        out_doc: *mut *mut FigDocument,
166        out_err: *mut FigError,
167    ) -> FigStatus;
168
169    pub fn fig_document_destroy(doc: *mut FigDocument);
170
171    pub fn fig_document_serialize(
172        doc: *mut FigDocument,
173        format: c_int,
174        options: *const FigSerializeOptions,
175        out_ptr: *mut *const u8,
176        out_len: *mut usize,
177    ) -> FigStatus;
178}
179
180// Read traversal — consumed by `Document::to_value` and the serde deserializer.
181unsafe extern "C" {
182    pub fn fig_document_root(doc: *const FigDocument) -> FigNodeId;
183    // Returns the raw kind as `c_int`, not `FigNodeKind`: decoding it directly
184    // into the enum would be UB if the core returned an unlisted value. Callers
185    // go through `FigNodeKind::from_c`.
186    pub fn fig_node_kind(doc: *const FigDocument, node: FigNodeId) -> c_int;
187    pub fn fig_node_first_child(doc: *const FigDocument, node: FigNodeId) -> FigNodeId;
188    pub fn fig_node_next_sibling(doc: *const FigDocument, node: FigNodeId) -> FigNodeId;
189    pub fn fig_node_child_count(doc: *const FigDocument, node: FigNodeId) -> usize;
190    pub fn fig_keyvalue_key(doc: *const FigDocument, node: FigNodeId) -> FigNodeId;
191    pub fn fig_keyvalue_value(doc: *const FigDocument, node: FigNodeId) -> FigNodeId;
192
193    pub fn fig_node_bool(doc: *const FigDocument, node: FigNodeId, out: *mut bool) -> bool;
194    pub fn fig_node_number(
195        doc: *const FigDocument,
196        node: FigNodeId,
197        out_ptr: *mut *const u8,
198        out_len: *mut usize,
199    ) -> bool;
200    pub fn fig_node_string(
201        doc: *const FigDocument,
202        node: FigNodeId,
203        out_ptr: *mut *const u8,
204        out_len: *mut usize,
205    ) -> bool;
206    pub fn fig_node_extended(
207        doc: *const FigDocument,
208        node: FigNodeId,
209        out_kind: *mut c_int,
210        out_ptr: *mut *const u8,
211        out_len: *mut usize,
212    ) -> bool;
213}
214
215// ---- value construction + serialization ----
216
217pub enum FigValue {}
218
219/// A `key: value` entry for `fig_value_map`. Mirrors `FigKeyValue` in `fig.h`.
220#[repr(C)]
221#[derive(Clone, Copy, Debug)]
222pub struct FigKeyValue {
223    pub key: FigNodeId,
224    pub value: FigNodeId,
225}
226
227/// Output style for `fig_value_serialize_opts`/`fig_document_serialize`. Mirrors
228/// `FigSerializeOptions` in `fig.h`. ALL trailing fields are declared explicitly
229/// (not left to struct padding): with `size = size_of` the core reads every byte
230/// up to `size`, so an undeclared `strip_comments`/`lossless` would otherwise be
231/// read out of uninitialized padding.
232#[repr(C)]
233#[derive(Clone, Copy, Debug)]
234pub struct FigSerializeOptions {
235    /// Set to `size_of::<FigSerializeOptions>()`. Version tag for the struct:
236    /// the core reads a field only when `size` covers it, so fields can be
237    /// appended without breaking this layout. See `FigSerializeOptions` in `fig.h`.
238    pub size: u32,
239    pub pretty: u8,
240    pub indent: u8,
241    pub strip_comments: u8,
242    pub lossless: u8,
243    pub width: u16,
244    /// fig-format fragments only: nonzero renders a container root as inline
245    /// flow (`[a, b]` / `{ k = v }`). Set by the editors' splice path (see
246    /// `value_text`); not exposed on the public `SerializeOptions` — inline is
247    /// a property of *where* the text goes, not a caller style preference.
248    pub flow: u8,
249    /// Nonzero renders the value as the editor takes it spliced into a
250    /// document (plist's bare element, a NestedText scalar's plain text).
251    /// Set by the editors' splice path; not exposed on the public
252    /// `SerializeOptions`.
253    pub splice: u8,
254}
255
256/// One lossy event from `fig_*_diagnose`, pulled by index via `fig_*_warning`.
257/// Caller-allocated; lead with `size = size_of::<FigWarning>()` (same policy as
258/// `FigSerializeOptions`/`FigError`). `path`/`note` are NOT NUL-terminated and
259/// borrow the producing handle's diagnostics arena (valid until the next
260/// diagnose on it or its destroy) — copy them out before then. Mirrors
261/// `FigWarning` in `fig.h`.
262#[repr(C)]
263#[derive(Clone, Copy)]
264pub struct FigWarning {
265    pub size: u32,
266    pub code: c_int,
267    pub cause: c_int,
268    pub path: *const u8,
269    pub path_len: usize,
270    pub note: *const u8,
271    pub note_len: usize,
272}
273
274impl FigWarning {
275    /// A zeroed struct with `size` set, ready to pass to a `fig_*_warning` call.
276    pub fn new() -> Self {
277        FigWarning {
278            size: std::mem::size_of::<FigWarning>() as u32,
279            code: 0,
280            cause: 0,
281            path: std::ptr::null(),
282            path_len: 0,
283            note: std::ptr::null(),
284            note_len: 0,
285        }
286    }
287}
288
289unsafe extern "C" {
290    pub fn fig_value_create(out_value: *mut *mut FigValue) -> FigStatus;
291    pub fn fig_value_destroy(value: *mut FigValue);
292
293    pub fn fig_value_null(value: *mut FigValue, out_id: *mut FigNodeId) -> FigStatus;
294    pub fn fig_value_bool(value: *mut FigValue, b: bool, out_id: *mut FigNodeId) -> FigStatus;
295    pub fn fig_value_int(value: *mut FigValue, n: i64, out_id: *mut FigNodeId) -> FigStatus;
296    pub fn fig_value_uint(value: *mut FigValue, n: u64, out_id: *mut FigNodeId) -> FigStatus;
297    pub fn fig_value_number(
298        value: *mut FigValue,
299        raw: *const u8,
300        raw_len: usize,
301        is_float: bool,
302        out_id: *mut FigNodeId,
303    ) -> FigStatus;
304    pub fn fig_value_string(
305        value: *mut FigValue,
306        ptr: *const u8,
307        len: usize,
308        out_id: *mut FigNodeId,
309    ) -> FigStatus;
310    pub fn fig_value_extended(
311        value: *mut FigValue,
312        kind: c_int,
313        text: *const u8,
314        text_len: usize,
315        out_id: *mut FigNodeId,
316    ) -> FigStatus;
317    pub fn fig_value_seq(
318        value: *mut FigValue,
319        items: *const FigNodeId,
320        items_len: usize,
321        out_id: *mut FigNodeId,
322    ) -> FigStatus;
323    pub fn fig_value_map(
324        value: *mut FigValue,
325        entries: *const FigKeyValue,
326        entries_len: usize,
327        out_id: *mut FigNodeId,
328    ) -> FigStatus;
329    // ABI-mirror decl; the binding always serializes through the `_opts` form.
330    #[allow(dead_code)]
331    pub fn fig_value_serialize(
332        value: *mut FigValue,
333        root: FigNodeId,
334        format: c_int,
335        out_ptr: *mut *const u8,
336        out_len: *mut usize,
337    ) -> FigStatus;
338    pub fn fig_value_serialize_opts(
339        value: *mut FigValue,
340        root: FigNodeId,
341        format: c_int,
342        options: *const FigSerializeOptions,
343        out_ptr: *mut *const u8,
344        out_len: *mut usize,
345    ) -> FigStatus;
346}
347
348// ---- serialization diagnostics ----
349
350unsafe extern "C" {
351    pub fn fig_document_diagnose(
352        doc: *mut FigDocument,
353        format: c_int,
354        options: *const FigSerializeOptions,
355        out_count: *mut usize,
356    ) -> FigStatus;
357    pub fn fig_document_warning(
358        doc: *mut FigDocument,
359        index: usize,
360        out: *mut FigWarning,
361    ) -> FigStatus;
362    pub fn fig_value_diagnose(
363        value: *mut FigValue,
364        root: FigNodeId,
365        format: c_int,
366        options: *const FigSerializeOptions,
367        out_count: *mut usize,
368    ) -> FigStatus;
369    pub fn fig_value_warning(value: *mut FigValue, index: usize, out: *mut FigWarning)
370    -> FigStatus;
371}
372
373// ---- editing (write path) ----
374
375pub enum FigEditor {}
376pub enum FigEmbed {}
377
378/// One step of a path: `kind == 0` selects mapping key `key_ptr[0..key_len]`;
379/// `kind == 1` selects sequence element `index`. Mirrors `FigPathSegment` in
380/// `fig.h`.
381#[repr(C)]
382#[derive(Clone, Copy, Debug)]
383pub struct FigPathSegment {
384    pub kind: c_int,
385    pub key_ptr: *const u8,
386    pub key_len: usize,
387    pub index: usize,
388}
389
390/// A borrowed UTF-8 string slice (`ptr[0..len]`) passed across the C ABI.
391/// Mirrors `FigStr` in `fig.h`; used for the key list of `*_reorder_keys`.
392#[repr(C)]
393#[derive(Clone, Copy, Debug)]
394pub struct FigStr {
395    pub ptr: *const u8,
396    pub len: usize,
397}
398
399// `FigSpan`/`FigRegion`/`fig_embed_extract` mirror the low-level embed C ABI.
400// The Rust-facing consumer is `Embed` (which uses `fig_embed_*`); these are
401// declared for parity with the header and for any future low-level wrapper.
402#[repr(C)]
403#[derive(Clone, Copy, Debug, Default)]
404#[allow(dead_code)]
405pub struct FigSpan {
406    pub start: usize,
407    pub end: usize,
408}
409
410#[repr(C)]
411#[derive(Clone, Copy, Debug, Default)]
412#[allow(dead_code)]
413pub struct FigRegion {
414    /// Size-version tag: set to `size_of::<FigRegion>()` before
415    /// `fig_embed_extract` so the library only writes the fields this layout
416    /// covers. A zero `size` (e.g. from `Default`) makes the library write
417    /// nothing — always set it explicitly.
418    pub size: u32,
419    pub open_fence: FigSpan,
420    pub content: FigSpan,
421    pub close_fence: FigSpan,
422    pub body: FigSpan,
423    /// `[0, open_fence.start)` and `[close_fence.end, input_len)` — the host
424    /// text on each side of the block. With the three region spans they tile
425    /// the input exactly, so a rebuild loses nothing. Added in core 2.7.0; an older
426    /// `size` leaves them unwritten.
427    pub body_before: FigSpan,
428    pub body_after: FigSpan,
429}
430
431/// The container half of an embed selector; every `fig_embed_*` entry point
432/// that selects a region takes one of these beside a `FigFormat`. The four
433/// parametric containers read the format; the three presets pin their own
434/// and ignore it. Mirrors `FigEmbedContainer` in `fig.h` (ABI 2).
435#[repr(C)]
436#[derive(Clone, Copy, Debug, Eq, PartialEq)]
437#[allow(dead_code)]
438pub enum FigEmbedContainer {
439    MdFrontmatter = 0,
440    Fenced = 1,
441    HtmlScript = 2,
442    HtmlCode = 3,
443    SemicolonsJson = 4,
444    PlusToml = 5,
445    EndmatterYaml = 6,
446}
447
448unsafe extern "C" {
449    pub fn fig_editor_create(
450        input: *const u8,
451        input_len: usize,
452        format: c_int,
453        out_editor: *mut *mut FigEditor,
454    ) -> FigStatus;
455    pub fn fig_editor_destroy(editor: *mut FigEditor);
456
457    pub fn fig_editor_replace_val(
458        editor: *mut FigEditor,
459        path: *const FigPathSegment,
460        path_len: usize,
461        repl: *const u8,
462        repl_len: usize,
463    ) -> FigStatus;
464    pub fn fig_editor_replace_key(
465        editor: *mut FigEditor,
466        path: *const FigPathSegment,
467        path_len: usize,
468        repl: *const u8,
469        repl_len: usize,
470    ) -> FigStatus;
471    pub fn fig_editor_replace_named_key(
472        editor: *mut FigEditor,
473        path: *const FigPathSegment,
474        path_len: usize,
475        name: *const u8,
476        name_len: usize,
477    ) -> FigStatus;
478    pub fn fig_editor_set(
479        editor: *mut FigEditor,
480        path: *const FigPathSegment,
481        path_len: usize,
482        val: *const u8,
483        val_len: usize,
484    ) -> FigStatus;
485    pub fn fig_editor_add_leading_comment(
486        editor: *mut FigEditor,
487        path: *const FigPathSegment,
488        path_len: usize,
489        text: *const u8,
490        text_len: usize,
491    ) -> FigStatus;
492    pub fn fig_editor_set_trailing_comment(
493        editor: *mut FigEditor,
494        path: *const FigPathSegment,
495        path_len: usize,
496        text: *const u8,
497        text_len: usize,
498    ) -> FigStatus;
499    pub fn fig_editor_delete_leading_comments(
500        editor: *mut FigEditor,
501        path: *const FigPathSegment,
502        path_len: usize,
503    ) -> FigStatus;
504    pub fn fig_editor_delete_trailing_comment(
505        editor: *mut FigEditor,
506        path: *const FigPathSegment,
507        path_len: usize,
508    ) -> FigStatus;
509    pub fn fig_editor_get_leading_comment(
510        editor: *mut FigEditor,
511        path: *const FigPathSegment,
512        path_len: usize,
513        out_ptr: *mut *const u8,
514        out_len: *mut usize,
515    ) -> FigStatus;
516    pub fn fig_editor_get_trailing_comment(
517        editor: *mut FigEditor,
518        path: *const FigPathSegment,
519        path_len: usize,
520        out_ptr: *mut *const u8,
521        out_len: *mut usize,
522    ) -> FigStatus;
523    pub fn fig_editor_add_dangling_comment(
524        editor: *mut FigEditor,
525        path: *const FigPathSegment,
526        path_len: usize,
527        text: *const u8,
528        text_len: usize,
529    ) -> FigStatus;
530    pub fn fig_editor_delete_dangling_comments(
531        editor: *mut FigEditor,
532        path: *const FigPathSegment,
533        path_len: usize,
534    ) -> FigStatus;
535    pub fn fig_editor_get_dangling_comment(
536        editor: *mut FigEditor,
537        path: *const FigPathSegment,
538        path_len: usize,
539        out_ptr: *mut *const u8,
540        out_len: *mut usize,
541    ) -> FigStatus;
542    pub fn fig_editor_comment_out(
543        editor: *mut FigEditor,
544        path: *const FigPathSegment,
545        path_len: usize,
546    ) -> FigStatus;
547    pub fn fig_editor_uncomment_leading(
548        editor: *mut FigEditor,
549        path: *const FigPathSegment,
550        path_len: usize,
551        first_line: usize,
552        line_count: usize,
553    ) -> FigStatus;
554    pub fn fig_editor_uncomment_dangling(
555        editor: *mut FigEditor,
556        path: *const FigPathSegment,
557        path_len: usize,
558        first_line: usize,
559        line_count: usize,
560    ) -> FigStatus;
561    pub fn fig_editor_insert_key(
562        editor: *mut FigEditor,
563        path: *const FigPathSegment,
564        path_len: usize,
565        key: *const u8,
566        key_len: usize,
567        val: *const u8,
568        val_len: usize,
569    ) -> FigStatus;
570    pub fn fig_editor_insert_named_key(
571        editor: *mut FigEditor,
572        path: *const FigPathSegment,
573        path_len: usize,
574        name: *const u8,
575        name_len: usize,
576        val: *const u8,
577        val_len: usize,
578    ) -> FigStatus;
579    pub fn fig_editor_delete_key(
580        editor: *mut FigEditor,
581        path: *const FigPathSegment,
582        path_len: usize,
583    ) -> FigStatus;
584    pub fn fig_editor_append_seq(
585        editor: *mut FigEditor,
586        path: *const FigPathSegment,
587        path_len: usize,
588        val: *const u8,
589        val_len: usize,
590    ) -> FigStatus;
591    pub fn fig_editor_prepend_seq(
592        editor: *mut FigEditor,
593        path: *const FigPathSegment,
594        path_len: usize,
595        val: *const u8,
596        val_len: usize,
597    ) -> FigStatus;
598    pub fn fig_editor_remove_seq_item(
599        editor: *mut FigEditor,
600        path: *const FigPathSegment,
601        path_len: usize,
602        index: usize,
603    ) -> FigStatus;
604    pub fn fig_editor_move_key(
605        editor: *mut FigEditor,
606        src_path: *const FigPathSegment,
607        src_path_len: usize,
608        dest_path: *const FigPathSegment,
609        dest_path_len: usize,
610    ) -> FigStatus;
611    pub fn fig_editor_reorder_keys(
612        editor: *mut FigEditor,
613        path: *const FigPathSegment,
614        path_len: usize,
615        keys: *const FigStr,
616        keys_len: usize,
617    ) -> FigStatus;
618    pub fn fig_editor_move_item(
619        editor: *mut FigEditor,
620        path: *const FigPathSegment,
621        path_len: usize,
622        from: usize,
623        to: usize,
624    ) -> FigStatus;
625    pub fn fig_editor_reorder_items(
626        editor: *mut FigEditor,
627        path: *const FigPathSegment,
628        path_len: usize,
629        indices: *const usize,
630        indices_len: usize,
631    ) -> FigStatus;
632    pub fn fig_editor_set_sequence(
633        editor: *mut FigEditor,
634        path: *const FigPathSegment,
635        path_len: usize,
636        items: *const FigStr,
637        items_len: usize,
638    ) -> FigStatus;
639    // Whole-container ops, for containers scattered through the source (a TOML
640    // `[header]` table, an INI `[section]`, a fig block container). The key ops
641    // above cannot address one, and refuse with `INVALID_ARGUMENT` at such a
642    // path. A format that does not support the op answers `UNSUPPORTED_FORMAT`.
643    pub fn fig_editor_delete_container(
644        editor: *mut FigEditor,
645        path: *const FigPathSegment,
646        path_len: usize,
647    ) -> FigStatus;
648    pub fn fig_editor_insert_container(
649        editor: *mut FigEditor,
650        path: *const FigPathSegment,
651        path_len: usize,
652        body: *const u8,
653        body_len: usize,
654    ) -> FigStatus;
655    pub fn fig_editor_rename_container(
656        editor: *mut FigEditor,
657        path: *const FigPathSegment,
658        path_len: usize,
659        new_leaf: *const u8,
660        new_leaf_len: usize,
661    ) -> FigStatus;
662    /// A NULL `dest_path` means "to the end of the document" — distinct from a
663    /// zero-length path, which every other entry point reads as the root.
664    pub fn fig_editor_move_container(
665        editor: *mut FigEditor,
666        src_path: *const FigPathSegment,
667        src_path_len: usize,
668        dest_path: *const FigPathSegment,
669        dest_path_len: usize,
670    ) -> FigStatus;
671    pub fn fig_editor_reorder_containers(
672        editor: *mut FigEditor,
673        order: *const FigStr,
674        order_len: usize,
675    ) -> FigStatus;
676    pub fn fig_editor_append_container_to_seq(
677        editor: *mut FigEditor,
678        path: *const FigPathSegment,
679        path_len: usize,
680        body: *const u8,
681        body_len: usize,
682    ) -> FigStatus;
683    pub fn fig_editor_source(
684        editor: *const FigEditor,
685        out_ptr: *mut *const u8,
686        out_len: *mut usize,
687    ) -> FigStatus;
688
689    pub fn fig_embed_extract(
690        input: *const u8,
691        input_len: usize,
692        container: c_int,
693        format: c_int,
694        out_region: *mut FigRegion,
695    ) -> FigStatus;
696
697    pub fn fig_embed_detect(
698        input: *const u8,
699        input_len: usize,
700        out_container: *mut c_int,
701        out_format: *mut c_int,
702    ) -> FigStatus;
703
704    /// Re-house an embedded region under a different archetype's fences. On
705    /// `OK` the result is an OWNED buffer — release it with `fig_free`, passing
706    /// back the exact `out_len`. Added in core 2.7.0.
707    pub fn fig_embed_retype(
708        input: *const u8,
709        input_len: usize,
710        from_container: c_int,
711        from_format: c_int,
712        to_container: c_int,
713        to_format: c_int,
714        content: *const u8,
715        content_len: usize,
716        out_ptr: *mut *mut u8,
717        out_len: *mut usize,
718    ) -> FigStatus;
719
720    /// Sized free for a buffer fig allocated (`fig_embed_retype`'s result) or
721    /// that the caller obtained from `fig_alloc`. `len` must be exact.
722    pub fn fig_free(ptr: *mut u8, len: usize);
723
724    pub fn fig_embed_open(
725        input: *const u8,
726        input_len: usize,
727        container: c_int,
728        format: c_int,
729        out_embed: *mut *mut FigEmbed,
730    ) -> FigStatus;
731    pub fn fig_embed_open_or_init(
732        input: *const u8,
733        input_len: usize,
734        container: c_int,
735        format: c_int,
736        out_embed: *mut *mut FigEmbed,
737    ) -> FigStatus;
738    pub fn fig_embed_destroy(fm: *mut FigEmbed);
739
740    pub fn fig_embed_replace_val(
741        fm: *mut FigEmbed,
742        path: *const FigPathSegment,
743        path_len: usize,
744        repl: *const u8,
745        repl_len: usize,
746    ) -> FigStatus;
747    pub fn fig_embed_replace_key(
748        fm: *mut FigEmbed,
749        path: *const FigPathSegment,
750        path_len: usize,
751        repl: *const u8,
752        repl_len: usize,
753    ) -> FigStatus;
754    pub fn fig_embed_replace_named_key(
755        fm: *mut FigEmbed,
756        path: *const FigPathSegment,
757        path_len: usize,
758        name: *const u8,
759        name_len: usize,
760    ) -> FigStatus;
761    pub fn fig_embed_set(
762        fm: *mut FigEmbed,
763        path: *const FigPathSegment,
764        path_len: usize,
765        val: *const u8,
766        val_len: usize,
767    ) -> FigStatus;
768    pub fn fig_embed_add_leading_comment(
769        fm: *mut FigEmbed,
770        path: *const FigPathSegment,
771        path_len: usize,
772        text: *const u8,
773        text_len: usize,
774    ) -> FigStatus;
775    pub fn fig_embed_set_trailing_comment(
776        fm: *mut FigEmbed,
777        path: *const FigPathSegment,
778        path_len: usize,
779        text: *const u8,
780        text_len: usize,
781    ) -> FigStatus;
782    pub fn fig_embed_delete_leading_comments(
783        fm: *mut FigEmbed,
784        path: *const FigPathSegment,
785        path_len: usize,
786    ) -> FigStatus;
787    pub fn fig_embed_delete_trailing_comment(
788        fm: *mut FigEmbed,
789        path: *const FigPathSegment,
790        path_len: usize,
791    ) -> FigStatus;
792    pub fn fig_embed_get_leading_comment(
793        fm: *mut FigEmbed,
794        path: *const FigPathSegment,
795        path_len: usize,
796        out_ptr: *mut *const u8,
797        out_len: *mut usize,
798    ) -> FigStatus;
799    pub fn fig_embed_get_trailing_comment(
800        fm: *mut FigEmbed,
801        path: *const FigPathSegment,
802        path_len: usize,
803        out_ptr: *mut *const u8,
804        out_len: *mut usize,
805    ) -> FigStatus;
806    pub fn fig_embed_add_dangling_comment(
807        fm: *mut FigEmbed,
808        path: *const FigPathSegment,
809        path_len: usize,
810        text: *const u8,
811        text_len: usize,
812    ) -> FigStatus;
813    pub fn fig_embed_delete_dangling_comments(
814        fm: *mut FigEmbed,
815        path: *const FigPathSegment,
816        path_len: usize,
817    ) -> FigStatus;
818    pub fn fig_embed_get_dangling_comment(
819        fm: *mut FigEmbed,
820        path: *const FigPathSegment,
821        path_len: usize,
822        out_ptr: *mut *const u8,
823        out_len: *mut usize,
824    ) -> FigStatus;
825    pub fn fig_embed_comment_out(
826        fm: *mut FigEmbed,
827        path: *const FigPathSegment,
828        path_len: usize,
829    ) -> FigStatus;
830    pub fn fig_embed_uncomment_leading(
831        fm: *mut FigEmbed,
832        path: *const FigPathSegment,
833        path_len: usize,
834        first_line: usize,
835        line_count: usize,
836    ) -> FigStatus;
837    pub fn fig_embed_uncomment_dangling(
838        fm: *mut FigEmbed,
839        path: *const FigPathSegment,
840        path_len: usize,
841        first_line: usize,
842        line_count: usize,
843    ) -> FigStatus;
844    pub fn fig_embed_insert_key(
845        fm: *mut FigEmbed,
846        path: *const FigPathSegment,
847        path_len: usize,
848        key: *const u8,
849        key_len: usize,
850        val: *const u8,
851        val_len: usize,
852    ) -> FigStatus;
853    pub fn fig_embed_insert_named_key(
854        embed: *mut FigEmbed,
855        path: *const FigPathSegment,
856        path_len: usize,
857        name: *const u8,
858        name_len: usize,
859        val: *const u8,
860        val_len: usize,
861    ) -> FigStatus;
862    pub fn fig_embed_delete_key(
863        fm: *mut FigEmbed,
864        path: *const FigPathSegment,
865        path_len: usize,
866    ) -> FigStatus;
867    pub fn fig_embed_append_seq(
868        fm: *mut FigEmbed,
869        path: *const FigPathSegment,
870        path_len: usize,
871        val: *const u8,
872        val_len: usize,
873    ) -> FigStatus;
874    pub fn fig_embed_prepend_seq(
875        fm: *mut FigEmbed,
876        path: *const FigPathSegment,
877        path_len: usize,
878        val: *const u8,
879        val_len: usize,
880    ) -> FigStatus;
881    pub fn fig_embed_remove_seq_item(
882        fm: *mut FigEmbed,
883        path: *const FigPathSegment,
884        path_len: usize,
885        index: usize,
886    ) -> FigStatus;
887    pub fn fig_embed_move_key(
888        fm: *mut FigEmbed,
889        src_path: *const FigPathSegment,
890        src_path_len: usize,
891        dest_path: *const FigPathSegment,
892        dest_path_len: usize,
893    ) -> FigStatus;
894    pub fn fig_embed_reorder_keys(
895        fm: *mut FigEmbed,
896        path: *const FigPathSegment,
897        path_len: usize,
898        keys: *const FigStr,
899        keys_len: usize,
900    ) -> FigStatus;
901    pub fn fig_embed_move_item(
902        fm: *mut FigEmbed,
903        path: *const FigPathSegment,
904        path_len: usize,
905        from: usize,
906        to: usize,
907    ) -> FigStatus;
908    pub fn fig_embed_reorder_items(
909        fm: *mut FigEmbed,
910        path: *const FigPathSegment,
911        path_len: usize,
912        indices: *const usize,
913        indices_len: usize,
914    ) -> FigStatus;
915    pub fn fig_embed_set_sequence(
916        fm: *mut FigEmbed,
917        path: *const FigPathSegment,
918        path_len: usize,
919        items: *const FigStr,
920        items_len: usize,
921    ) -> FigStatus;
922    pub fn fig_embed_replace_body(fm: *mut FigEmbed, body: *const u8, body_len: usize)
923    -> FigStatus;
924    pub fn fig_embed_render(
925        fm: *mut FigEmbed,
926        out_ptr: *mut *const u8,
927        out_len: *mut usize,
928    ) -> FigStatus;
929}
930
931// ── Runtime languages ──────────────────────────────────────────────────────
932//
933// Mirrors of fig.h's "Runtime languages" section: the vtable a host fills to
934// register a language, and the node table its parse returns and its print
935// receives. Field for field with the header; `zig build abi-check` runs a
936// C-hosted language through the same structs, and the `fig` crate's tests run
937// a Rust-hosted one through these.
938
939/// Mirror of `FIG_LANGUAGE_VTABLE_VERSION`.
940pub const FIG_LANGUAGE_VTABLE_VERSION: u32 = 2;
941/// Mirror of `FIG_OFFSET_NONE` / `FIG_LEN_NONE`: an absent optional span or
942/// string.
943pub const FIG_OFFSET_NONE: usize = usize::MAX;
944pub const FIG_LEN_NONE: usize = usize::MAX;
945/// Mirror of `FIG_ROW_NONE`: the root's `parent`.
946pub const FIG_ROW_NONE: u32 = u32::MAX;
947/// Mirror of `FIG_EXT_NONE`.
948pub const FIG_EXT_NONE: c_int = -1;
949/// Mirror of `FIG_DEPTH_NONE`: a vtable's `max_mapping_depth` when unbounded.
950pub const FIG_DEPTH_NONE: c_int = -1;
951
952pub const FIG_MENTION_HEADER: c_int = 0;
953pub const FIG_MENTION_ENTRY: c_int = 1;
954pub const FIG_COMMENT_LEADING: c_int = 0;
955pub const FIG_COMMENT_TRAILING: c_int = 1;
956pub const FIG_COMMENT_DANGLING: c_int = 2;
957pub const FIG_COMMENT_LINE: c_int = 0;
958pub const FIG_COMMENT_BLOCK: c_int = 1;
959
960impl FigStr {
961    /// The absent optional string.
962    pub const NONE: FigStr = FigStr {
963        ptr: std::ptr::null(),
964        len: FIG_LEN_NONE,
965    };
966}
967
968impl FigSpan {
969    /// The absent optional span.
970    pub const NONE: FigSpan = FigSpan {
971        start: FIG_OFFSET_NONE,
972        end: FIG_OFFSET_NONE,
973    };
974}
975
976/// Mirror of `FigNodeRow`.
977#[repr(C)]
978#[derive(Clone, Copy, Debug)]
979pub struct FigNodeRow {
980    pub kind: c_int,
981    pub ext_kind: c_int,
982    pub parent: u32,
983    pub span: FigSpan,
984    pub text: FigStr,
985    pub anchor: FigStr,
986    pub anchor_span: FigSpan,
987    pub tag: FigStr,
988    pub tag_span: FigSpan,
989    pub marker: FigSpan,
990    pub sep: FigSpan,
991}
992
993/// Mirror of `FigRegionRow`.
994#[repr(C)]
995#[derive(Clone, Copy, Debug)]
996pub struct FigRegionRow {
997    pub node: u32,
998    pub start: usize,
999    pub end: usize,
1000}
1001
1002/// Mirror of `FigMentionRow`.
1003#[repr(C)]
1004#[derive(Clone, Copy, Debug)]
1005pub struct FigMentionRow {
1006    pub node: u32,
1007    pub span: FigSpan,
1008    pub kind: c_int,
1009}
1010
1011/// Mirror of `FigCommentRow`.
1012#[repr(C)]
1013#[derive(Clone, Copy, Debug)]
1014pub struct FigCommentRow {
1015    pub node: u32,
1016    pub slot: c_int,
1017    pub style: c_int,
1018    pub text: FigStr,
1019}
1020
1021/// Mirror of `FigDirectiveRow`.
1022#[repr(C)]
1023#[derive(Clone, Copy, Debug)]
1024pub struct FigDirectiveRow {
1025    pub handle: FigStr,
1026    pub prefix: FigStr,
1027}
1028
1029/// Mirror of `FigNodeTable`.
1030#[repr(C)]
1031#[derive(Clone, Copy, Debug)]
1032pub struct FigNodeTable {
1033    /// fig's `sizeof(FigNodeTable)` on a table fig hands over; write and
1034    /// read only the fields it covers.
1035    pub size: u32,
1036    /// The stride of `rows`: `size_of::<FigNodeRow>()` in a table `parse`
1037    /// returns; read the rows `print` is handed at the stride it says.
1038    pub row_size: u32,
1039    pub rows: *const FigNodeRow,
1040    pub row_count: usize,
1041    pub regions: *const FigRegionRow,
1042    pub region_count: usize,
1043    pub mentions: *const FigMentionRow,
1044    pub mention_count: usize,
1045    pub comments: *const FigCommentRow,
1046    pub comment_count: usize,
1047    pub directives: *const FigDirectiveRow,
1048    pub directive_count: usize,
1049    pub owner: *mut c_void,
1050}
1051
1052/// Mirror of `FigPrintOptions`.
1053#[repr(C)]
1054#[derive(Clone, Copy, Debug)]
1055pub struct FigPrintOptions {
1056    /// fig's `sizeof`: read a field only when it covers it.
1057    pub size: u32,
1058    pub pretty: bool,
1059    pub strip_comments: bool,
1060    pub indent: u8,
1061    pub width: u16,
1062    pub splice: bool,
1063    pub flow: bool,
1064}
1065
1066/// Mirror of `FigCommentDelimiter`.
1067#[repr(C)]
1068#[derive(Clone, Copy, Debug)]
1069pub struct FigCommentDelimiter {
1070    pub open: *const c_char,
1071    pub close: *const c_char,
1072    pub forbidden: *const c_char,
1073}
1074
1075/// Mirror of `FigComments`.
1076#[repr(C)]
1077#[derive(Clone, Copy, Debug)]
1078pub struct FigComments {
1079    pub style: c_int,
1080    pub line: FigCommentDelimiter,
1081    pub trailing: FigCommentDelimiter,
1082}
1083
1084/// Mirror of `FigSectionHeader`.
1085#[repr(C)]
1086#[derive(Clone, Copy, Debug)]
1087pub struct FigSectionHeader {
1088    pub open: *const c_char,
1089    pub close: *const c_char,
1090    pub seq_open: *const c_char,
1091    pub seq_close: *const c_char,
1092    pub sep: *const c_char,
1093    pub skip_index: bool,
1094}
1095
1096/// Mirror of `FigClosedContainers`.
1097#[repr(C)]
1098#[derive(Clone, Copy, Debug)]
1099pub struct FigClosedContainers {
1100    pub map_open: *const c_char,
1101    pub map_close: *const c_char,
1102    pub seq_open: *const c_char,
1103    pub seq_close: *const c_char,
1104}
1105
1106/// Mirror of `FigSyntax`.
1107#[repr(C)]
1108#[derive(Clone, Copy, Debug)]
1109pub struct FigSyntax {
1110    /// `size_of::<FigSyntax>()`.
1111    pub size: u32,
1112    pub comments: FigComments,
1113    pub kv_sep: *const c_char,
1114    pub flow_kv_sep_from_siblings: bool,
1115    pub flow_map_pad: *const c_char,
1116    pub key_style: c_int,
1117    pub key_sigil: u8,
1118    pub empty_map_literal: *const c_char,
1119    pub block_seq_editable: bool,
1120    pub flow_containers: bool,
1121    pub indent_unit: *const c_char,
1122    pub seq_item_marker: *const c_char,
1123    pub closed_containers: FigClosedContainers,
1124    pub single_line_block_mapping: bool,
1125    pub bare_document_mapping: bool,
1126    pub flow_map_open: *const c_char,
1127    pub flow_map_close: *const c_char,
1128    pub structural_indent: bool,
1129    pub section_noun: c_int,
1130    pub section_header: FigSectionHeader,
1131    pub merge_key: *const c_char,
1132}
1133
1134/// `FigLanguageVTable::lossless`: the language takes the `$fig` envelope.
1135pub const FIG_LOSSLESS_ENVELOPE: u32 = 1 << 0;
1136/// With the envelope, a null the format holds natively.
1137pub const FIG_NATIVE_NULL: u32 = 1 << 1;
1138/// With the envelope, the `FigExtKind` `k` held natively.
1139pub const fn fig_native_ext(k: u32) -> u32 {
1140    1 << (2 + k)
1141}
1142
1143/// Mirror of `FigDialectDesc`.
1144#[repr(C)]
1145#[derive(Clone, Copy, Debug)]
1146pub struct FigDialectDesc {
1147    pub name: *const c_char,
1148    pub extensions: *const *const c_char,
1149    pub splice: c_int,
1150    pub empty_doc_seed: *const c_char,
1151    pub syntax: *const FigSyntax,
1152}
1153
1154pub type FigParseFn = unsafe extern "C" fn(
1155    ctx: *mut c_void,
1156    dialect: *const c_char,
1157    input: FigStr,
1158    out: *mut FigNodeTable,
1159    err: *mut FigError,
1160) -> c_int;
1161pub type FigPrintFn = unsafe extern "C" fn(
1162    ctx: *mut c_void,
1163    dialect: *const c_char,
1164    table: *const FigNodeTable,
1165    options: *const FigPrintOptions,
1166    out: *mut FigStr,
1167    err: *mut FigError,
1168) -> c_int;
1169pub type FigFreeTableFn = unsafe extern "C" fn(ctx: *mut c_void, table: *mut FigNodeTable);
1170pub type FigFreeBytesFn = unsafe extern "C" fn(ctx: *mut c_void, bytes: FigStr);
1171/// Mirror of `FigRenderRequest`: everything a renderer is told. fig writes
1172/// it, and a renderer reads a field only when `size` covers it.
1173#[repr(C)]
1174#[derive(Clone, Copy, Debug)]
1175pub struct FigRenderRequest {
1176    /// fig's `sizeof`: read a field only when it covers it.
1177    pub size: u32,
1178    pub dialect: *const c_char,
1179    pub indent: FigStr,
1180    pub key: FigStr,
1181    pub value: FigStr,
1182    pub literal: *const c_char,
1183    pub old_key: FigStr,
1184    pub parent_key: FigStr,
1185    pub parent_tag: FigStr,
1186}
1187
1188/// Every `render_*` slot's shape.
1189pub type FigRenderFn = unsafe extern "C" fn(
1190    ctx: *mut c_void,
1191    request: *const FigRenderRequest,
1192    out: *mut FigStr,
1193    err: *mut FigError,
1194) -> c_int;
1195
1196/// Mirror of `FigLanguageVTable`. The five `render_*` slots and `print` are
1197/// `Option`, which is the null pointer on the C side.
1198#[repr(C)]
1199#[derive(Clone, Copy)]
1200pub struct FigLanguageVTable {
1201    pub version: u32,
1202    /// `size_of::<FigLanguageVTable>()`.
1203    pub size: u32,
1204    pub ctx: *mut c_void,
1205    pub name: *const c_char,
1206    pub caps: u32,
1207    pub max_mapping_depth: c_int,
1208    /// `FIG_LOSSLESS_ENVELOPE` and `FIG_NATIVE_*` bits; 0 for no envelope.
1209    pub lossless: u32,
1210    pub syntax: *const FigSyntax,
1211    pub dialects: *const FigDialectDesc,
1212    pub dialect_count: usize,
1213    /// `size_of::<FigDialectDesc>()`.
1214    pub dialect_size: usize,
1215    pub samples: *const FigStr,
1216    pub sample_count: usize,
1217    pub parse: FigParseFn,
1218    pub print: Option<FigPrintFn>,
1219    pub free_table: FigFreeTableFn,
1220    pub free_bytes: FigFreeBytesFn,
1221    pub render_value: Option<FigRenderFn>,
1222    pub render_entry: Option<FigRenderFn>,
1223    pub render_item: Option<FigRenderFn>,
1224    pub render_tail: Option<FigRenderFn>,
1225    pub render_key: Option<FigRenderFn>,
1226}
1227
1228unsafe extern "C" {
1229    pub fn fig_language_vtable_version() -> u32;
1230    pub fn fig_language_register(
1231        vt: *const FigLanguageVTable,
1232        out_format: *mut c_int,
1233        out_err: *mut FigError,
1234    ) -> FigStatus;
1235    pub fn fig_format_by_name(name: *const c_char) -> c_int;
1236}