Skip to main content

twig_sys/
lib.rs

1//! Low-level FFI bindings for Twig's C ABI, plus the build script that links
2//! the native `libtwig.a`.
3//!
4//! This crate is the `-sys` layer: it exposes the raw `extern "C"` functions,
5//! the `#[repr(C)]` type mirrors, and the status/format constants, and nothing
6//! else. The safe, ergonomic API lives in the `twig-doc` crate, which depends
7//! on this one. Direct use is `unsafe` and unstable across ABI-version bumps —
8//! see [`TWIG_ABI_VERSION`].
9#![allow(non_camel_case_types)]
10
11use std::os::raw::{c_char, c_int};
12
13/// The C ABI contract version this binding is written against; see
14/// `twig_abi_version`. Must match the value baked into the linked library
15/// (asserted at runtime by the `abi_version_matches` test in `lib.rs`).
16pub const TWIG_ABI_VERSION: u32 = 3;
17
18// Freeze the canonical 64-bit layout of every `#[repr(C)]` mirror below so it
19// can never silently drift from the Zig `extern struct` it shadows. These are
20// the same offsets asserted on the Zig side in `src/c_abi.zig`; a change on
21// either side that isn't matched on the other fails to compile. Gated on 64-bit
22// (the offsets are the LP64/LLP64 layout); 32-bit targets pack tighter but the
23// C ABI still keeps both languages in agreement.
24#[cfg(target_pointer_width = "64")]
25const _: () = {
26    use std::mem::offset_of;
27    use std::mem::size_of;
28
29    assert!(size_of::<TwigSpan>() == 16);
30    assert!(offset_of!(TwigSpan, start) == 0);
31    assert!(offset_of!(TwigSpan, end) == 8);
32
33    assert!(size_of::<TwigQueryMatch>() == 56);
34    assert!(offset_of!(TwigQueryMatch, node_id) == 0);
35    assert!(offset_of!(TwigQueryMatch, span) == 8);
36    assert!(offset_of!(TwigQueryMatch, content_span) == 24);
37    assert!(offset_of!(TwigQueryMatch, has_content_span) == 40);
38    assert!(offset_of!(TwigQueryMatch, kind) == 48);
39
40    assert!(size_of::<TwigChange>() == 32);
41    assert!(offset_of!(TwigChange, old_span) == 0);
42    assert!(offset_of!(TwigChange, new_span) == 16);
43
44    assert!(size_of::<TwigFlatNode>() == 136);
45    assert!(offset_of!(TwigFlatNode, id) == 0);
46    assert!(offset_of!(TwigFlatNode, parent) == 4);
47    assert!(offset_of!(TwigFlatNode, first_child) == 8);
48    assert!(offset_of!(TwigFlatNode, next_sibling) == 12);
49    assert!(offset_of!(TwigFlatNode, span) == 16);
50    assert!(offset_of!(TwigFlatNode, content_span) == 32);
51    assert!(offset_of!(TwigFlatNode, has_content_span) == 48);
52    assert!(offset_of!(TwigFlatNode, level) == 52);
53    assert!(offset_of!(TwigFlatNode, kind) == 56);
54    assert!(offset_of!(TwigFlatNode, text_ptr) == 64);
55    assert!(offset_of!(TwigFlatNode, text_len) == 72);
56    assert!(offset_of!(TwigFlatNode, destination_ptr) == 80);
57    assert!(offset_of!(TwigFlatNode, destination_len) == 88);
58    assert!(offset_of!(TwigFlatNode, head) == 96);
59    assert!(offset_of!(TwigFlatNode, alignment) == 100);
60    assert!(offset_of!(TwigFlatNode, name_ptr) == 104);
61    assert!(offset_of!(TwigFlatNode, name_len) == 112);
62    assert!(offset_of!(TwigFlatNode, attrs_ptr) == 120);
63    assert!(offset_of!(TwigFlatNode, attrs_len) == 128);
64
65    assert!(size_of::<TwigKeyVal>() == 32);
66    assert!(offset_of!(TwigKeyVal, key) == 0);
67    assert!(offset_of!(TwigKeyVal, key_len) == 8);
68    assert!(offset_of!(TwigKeyVal, value) == 16);
69    assert!(offset_of!(TwigKeyVal, value_len) == 24);
70};
71
72#[repr(transparent)]
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub struct TwigStatus(pub c_int);
75
76#[allow(dead_code)]
77impl TwigStatus {
78    pub const OK: c_int = 0;
79    pub const INVALID_ARGUMENT: c_int = 1;
80    pub const PARSE_ERROR: c_int = 2;
81    pub const OUT_OF_MEMORY: c_int = 3;
82    pub const UNSUPPORTED_FORMAT: c_int = 4;
83    pub const NOT_FOUND: c_int = 5;
84    pub const AMBIGUOUS: c_int = 6;
85    pub const NOT_EDITABLE: c_int = 7;
86    pub const EDIT_CONFLICT: c_int = 8;
87    pub const UNSAFE_METADATA: c_int = 9;
88    pub const INTERNAL_ERROR: c_int = 255;
89}
90
91#[repr(C)]
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum TwigFormat {
94    Djot = 1,
95    Markdown = 2,
96    Xml = 3,
97    Html = 4,
98}
99
100/// Markdown extension flags for the `md_flags` bitmask of `twig_parse_ext` and
101/// `twig_editor_create_ext`.
102pub const TWIG_MD_DIRECTIVES: u32 = 1 << 0;
103pub const TWIG_MD_MATH: u32 = 1 << 1;
104pub const TWIG_MD_HTML_ELEMENTS: u32 = 1 << 2;
105
106pub enum TwigDocument {}
107
108pub enum TwigEditor {}
109
110pub enum TwigBuilder {}
111
112/// C ABI mirror of Zig's `TwigKeyVal` — one `(key, value)` attribute pair. Used
113/// on the read path ([`TwigFlatNode::attrs_ptr`], borrowed) and the write path
114/// (`twig_builder_set_attrs`, copied). A NULL `value` is a bare attribute.
115#[repr(C)]
116#[derive(Clone, Copy, Debug)]
117pub struct TwigKeyVal {
118    pub key: *const u8,
119    pub key_len: usize,
120    pub value: *const u8,
121    pub value_len: usize,
122}
123
124/// C ABI mirror of Zig's `TwigSpan` — a byte range `[start, end)`.
125#[repr(C)]
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct TwigSpan {
128    pub start: usize,
129    pub end: usize,
130}
131
132/// C ABI mirror of Zig's `TwigQueryMatch` — one node returned by
133/// `twig_document_query`. `content_span` is only meaningful when
134/// `has_content_span` is non-zero. `kind` is a NUL-terminated node-kind name
135/// in static, library-owned storage (never freed).
136#[repr(C)]
137#[derive(Clone, Copy, Debug)]
138pub struct TwigQueryMatch {
139    pub node_id: u32,
140    pub span: TwigSpan,
141    pub content_span: TwigSpan,
142    pub has_content_span: c_int,
143    pub kind: *const c_char,
144}
145
146/// The sentinel `node_id` for "no such node" in a [`TwigFlatNode`] link field.
147pub const TWIG_NO_NODE: u32 = u32::MAX;
148
149/// C ABI mirror of Zig's `TwigChange` — the byte effect of an edit. `old_span`
150/// is the replaced range in the pre-edit source; `new_span` is the range the
151/// replacement occupies in the post-edit source (same start).
152#[repr(C)]
153#[derive(Clone, Copy, Debug)]
154pub struct TwigChange {
155    pub old_span: TwigSpan,
156    pub new_span: TwigSpan,
157}
158
159/// C ABI mirror of Zig's `TwigFlatNode` — one node of the editor's flat tree
160/// snapshot. Link fields (`parent`/`first_child`/`next_sibling`) are ids or
161/// [`TWIG_NO_NODE`]. `content_span` is meaningful only when `has_content_span`.
162/// `kind` is static, library-owned storage; `text`/`destination` pointers
163/// borrow the current parse's payloads (NULL when the kind carries none).
164/// `head`/`alignment` carry a `row`/`cell` payload, each `-1` for a kind that
165/// has none (see [`TWIG_HEAD_NONE`] / [`TWIG_ALIGN_NONE`]). `name_ptr` is a
166/// generic element's tag name (NULL for every other kind); `attrs_ptr` points at
167/// `attrs_len` [`TwigKeyVal`]s (NULL/0 when the node has no attributes). Both
168/// borrow the current parse's payloads with the same lifetime as `text_ptr`.
169#[repr(C)]
170#[derive(Clone, Copy, Debug)]
171pub struct TwigFlatNode {
172    pub id: u32,
173    pub parent: u32,
174    pub first_child: u32,
175    pub next_sibling: u32,
176    pub span: TwigSpan,
177    pub content_span: TwigSpan,
178    pub has_content_span: c_int,
179    pub level: u32,
180    pub kind: *const c_char,
181    pub text_ptr: *const u8,
182    pub text_len: usize,
183    pub destination_ptr: *const u8,
184    pub destination_len: usize,
185    pub head: c_int,
186    pub alignment: c_int,
187    pub name_ptr: *const u8,
188    pub name_len: usize,
189    pub attrs_ptr: *const TwigKeyVal,
190    pub attrs_len: usize,
191}
192
193/// `TwigFlatNode::head` for a node that is neither a `row` nor a `cell`.
194pub const TWIG_HEAD_NONE: c_int = -1;
195
196/// `TwigFlatNode::alignment` codes; `NONE` means the node isn't a `cell`.
197/// `NONE` is part of the ABI contract even though `Alignment::from_c` folds it
198/// into its catch-all, so spell it out rather than leaving the -1 a mystery.
199#[allow(dead_code)]
200pub const TWIG_ALIGN_NONE: c_int = -1;
201pub const TWIG_ALIGN_DEFAULT: c_int = 0;
202pub const TWIG_ALIGN_LEFT: c_int = 1;
203pub const TWIG_ALIGN_RIGHT: c_int = 2;
204pub const TWIG_ALIGN_CENTER: c_int = 3;
205
206unsafe extern "C" {
207    pub fn twig_abi_version() -> u32;
208    pub fn twig_version() -> u32;
209    pub fn twig_version_string() -> *const c_char;
210    pub fn twig_parse(
211        input: *const u8,
212        input_len: usize,
213        format: c_int,
214        out_doc: *mut *mut TwigDocument,
215    ) -> TwigStatus;
216    pub fn twig_parse_ext(
217        input: *const u8,
218        input_len: usize,
219        format: c_int,
220        md_flags: u32,
221        out_doc: *mut *mut TwigDocument,
222    ) -> TwigStatus;
223    pub fn twig_document_destroy(doc: *mut TwigDocument);
224    pub fn twig_document_render_html(
225        doc: *mut TwigDocument,
226        out_ptr: *mut *const u8,
227        out_len: *mut usize,
228    ) -> TwigStatus;
229    pub fn twig_document_serialize(
230        doc: *mut TwigDocument,
231        format: c_int,
232        out_ptr: *mut *const u8,
233        out_len: *mut usize,
234    ) -> TwigStatus;
235    pub fn twig_document_ast_json(
236        doc: *mut TwigDocument,
237        out_ptr: *mut *const u8,
238        out_len: *mut usize,
239    ) -> TwigStatus;
240    pub fn twig_document_query(
241        doc: *mut TwigDocument,
242        selector: *const u8,
243        selector_len: usize,
244        out_ptr: *mut *const TwigQueryMatch,
245        out_len: *mut usize,
246    ) -> TwigStatus;
247
248    pub fn twig_editor_create(
249        input: *const u8,
250        input_len: usize,
251        format: c_int,
252        out_editor: *mut *mut TwigEditor,
253    ) -> TwigStatus;
254    pub fn twig_editor_create_ext(
255        input: *const u8,
256        input_len: usize,
257        format: c_int,
258        md_flags: u32,
259        out_editor: *mut *mut TwigEditor,
260    ) -> TwigStatus;
261    pub fn twig_editor_destroy(editor: *mut TwigEditor);
262    pub fn twig_editor_replace(
263        editor: *mut TwigEditor,
264        locator: *const u8,
265        locator_len: usize,
266        text: *const u8,
267        text_len: usize,
268    ) -> TwigStatus;
269    pub fn twig_editor_replace_content(
270        editor: *mut TwigEditor,
271        locator: *const u8,
272        locator_len: usize,
273        text: *const u8,
274        text_len: usize,
275    ) -> TwigStatus;
276    pub fn twig_editor_insert_before(
277        editor: *mut TwigEditor,
278        locator: *const u8,
279        locator_len: usize,
280        text: *const u8,
281        text_len: usize,
282    ) -> TwigStatus;
283    pub fn twig_editor_insert_after(
284        editor: *mut TwigEditor,
285        locator: *const u8,
286        locator_len: usize,
287        text: *const u8,
288        text_len: usize,
289    ) -> TwigStatus;
290    pub fn twig_editor_insert_child(
291        editor: *mut TwigEditor,
292        locator: *const u8,
293        locator_len: usize,
294        child_index: usize,
295        text: *const u8,
296        text_len: usize,
297    ) -> TwigStatus;
298    pub fn twig_editor_delete(
299        editor: *mut TwigEditor,
300        locator: *const u8,
301        locator_len: usize,
302    ) -> TwigStatus;
303    pub fn twig_editor_delete_smart(
304        editor: *mut TwigEditor,
305        locator: *const u8,
306        locator_len: usize,
307    ) -> TwigStatus;
308    pub fn twig_editor_unwrap(
309        editor: *mut TwigEditor,
310        locator: *const u8,
311        locator_len: usize,
312    ) -> TwigStatus;
313    pub fn twig_editor_filter(
314        editor: *mut TwigEditor,
315        drop: *const u8,
316        drop_len: usize,
317        keep: *const u8,
318        keep_len: usize,
319        unwrap_kept: c_int,
320    ) -> TwigStatus;
321    pub fn twig_editor_source(
322        editor: *mut TwigEditor,
323        out_ptr: *mut *const u8,
324        out_len: *mut usize,
325    ) -> TwigStatus;
326    pub fn twig_editor_ast_json(
327        editor: *mut TwigEditor,
328        out_ptr: *mut *const u8,
329        out_len: *mut usize,
330    ) -> TwigStatus;
331    pub fn twig_editor_query(
332        editor: *mut TwigEditor,
333        selector: *const u8,
334        selector_len: usize,
335        out_ptr: *mut *const TwigQueryMatch,
336        out_len: *mut usize,
337    ) -> TwigStatus;
338    pub fn twig_editor_edit_range(
339        editor: *mut TwigEditor,
340        start: usize,
341        end: usize,
342        text: *const u8,
343        text_len: usize,
344        out_change: *mut TwigChange,
345    ) -> TwigStatus;
346    pub fn twig_editor_last_change(
347        editor: *mut TwigEditor,
348        out_change: *mut TwigChange,
349    ) -> TwigStatus;
350    pub fn twig_editor_undo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
351    pub fn twig_editor_redo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
352    pub fn twig_editor_coalesce_last(editor: *mut TwigEditor) -> TwigStatus;
353    pub fn twig_editor_revision(editor: *mut TwigEditor) -> u64;
354    pub fn twig_editor_dirty_range(editor: *mut TwigEditor, out_span: *mut TwigSpan)
355        -> TwigStatus;
356    pub fn twig_editor_clear_dirty(editor: *mut TwigEditor) -> TwigStatus;
357    pub fn twig_editor_set_caret_blob(
358        editor: *mut TwigEditor,
359        blob_ptr: *const u8,
360        blob_len: usize,
361    ) -> TwigStatus;
362    pub fn twig_editor_caret_blob(
363        editor: *mut TwigEditor,
364        out_ptr: *mut *const u8,
365        out_len: *mut usize,
366    ) -> TwigStatus;
367    pub fn twig_editor_nodes(
368        editor: *mut TwigEditor,
369        out_ptr: *mut *const TwigFlatNode,
370        out_len: *mut usize,
371    ) -> TwigStatus;
372    pub fn twig_editor_child_spans(
373        editor: *mut TwigEditor,
374        node_id: u32,
375        out_ptr: *mut *const TwigQueryMatch,
376        out_len: *mut usize,
377    ) -> TwigStatus;
378    pub fn twig_editor_subtree(
379        editor: *mut TwigEditor,
380        node_id: u32,
381        out_ptr: *mut *const TwigFlatNode,
382        out_len: *mut usize,
383    ) -> TwigStatus;
384    pub fn twig_editor_node_at(
385        editor: *mut TwigEditor,
386        offset: usize,
387        out_match: *mut TwigQueryMatch,
388    ) -> TwigStatus;
389    pub fn twig_editor_nodes_at(
390        editor: *mut TwigEditor,
391        offset: usize,
392        out_ptr: *mut *const TwigQueryMatch,
393        out_len: *mut usize,
394    ) -> TwigStatus;
395    pub fn twig_editor_wrap_range(
396        editor: *mut TwigEditor,
397        start: usize,
398        end: usize,
399        kind: c_int,
400        out_change: *mut TwigChange,
401    ) -> TwigStatus;
402    pub fn twig_editor_toggle_inline(
403        editor: *mut TwigEditor,
404        start: usize,
405        end: usize,
406        kind: c_int,
407        out_change: *mut TwigChange,
408    ) -> TwigStatus;
409    pub fn twig_editor_set_block(
410        editor: *mut TwigEditor,
411        offset: usize,
412        block_kind: c_int,
413        level: u32,
414        out_change: *mut TwigChange,
415    ) -> TwigStatus;
416    pub fn twig_editor_toggle_block_container(
417        editor: *mut TwigEditor,
418        start: usize,
419        end: usize,
420        container_kind: c_int,
421        out_change: *mut TwigChange,
422    ) -> TwigStatus;
423    pub fn twig_editor_insert_link(
424        editor: *mut TwigEditor,
425        start: usize,
426        end: usize,
427        destination: *const u8,
428        destination_len: usize,
429        out_change: *mut TwigChange,
430    ) -> TwigStatus;
431
432    pub fn twig_builder_create(out_builder: *mut *mut TwigBuilder) -> TwigStatus;
433    pub fn twig_builder_destroy(builder: *mut TwigBuilder);
434    pub fn twig_builder_add(builder: *mut TwigBuilder, kind: c_int, out_id: *mut u32) -> TwigStatus;
435    pub fn twig_builder_add_text(
436        builder: *mut TwigBuilder,
437        kind: c_int,
438        text: *const u8,
439        text_len: usize,
440        out_id: *mut u32,
441    ) -> TwigStatus;
442    pub fn twig_builder_add_heading(builder: *mut TwigBuilder, level: u32, out_id: *mut u32) -> TwigStatus;
443    pub fn twig_builder_add_code_block(
444        builder: *mut TwigBuilder,
445        lang: *const u8,
446        lang_len: usize,
447        has_lang: c_int,
448        text: *const u8,
449        text_len: usize,
450        out_id: *mut u32,
451    ) -> TwigStatus;
452    pub fn twig_builder_add_raw_block(
453        builder: *mut TwigBuilder,
454        format: *const u8,
455        format_len: usize,
456        text: *const u8,
457        text_len: usize,
458        out_id: *mut u32,
459    ) -> TwigStatus;
460    pub fn twig_builder_add_metadata(
461        builder: *mut TwigBuilder,
462        lang: *const u8,
463        lang_len: usize,
464        text: *const u8,
465        text_len: usize,
466        out_id: *mut u32,
467    ) -> TwigStatus;
468    pub fn twig_builder_add_raw_inline(
469        builder: *mut TwigBuilder,
470        format: *const u8,
471        format_len: usize,
472        text: *const u8,
473        text_len: usize,
474        out_id: *mut u32,
475    ) -> TwigStatus;
476    pub fn twig_builder_add_smart_punctuation(
477        builder: *mut TwigBuilder,
478        punct_kind: c_int,
479        text: *const u8,
480        text_len: usize,
481        out_id: *mut u32,
482    ) -> TwigStatus;
483    pub fn twig_builder_add_link(
484        builder: *mut TwigBuilder,
485        destination: *const u8,
486        destination_len: usize,
487        has_destination: c_int,
488        reference: *const u8,
489        reference_len: usize,
490        has_reference: c_int,
491        out_id: *mut u32,
492    ) -> TwigStatus;
493    pub fn twig_builder_add_image(
494        builder: *mut TwigBuilder,
495        destination: *const u8,
496        destination_len: usize,
497        has_destination: c_int,
498        reference: *const u8,
499        reference_len: usize,
500        has_reference: c_int,
501        out_id: *mut u32,
502    ) -> TwigStatus;
503    pub fn twig_builder_add_directive(
504        builder: *mut TwigBuilder,
505        form: c_int,
506        name: *const u8,
507        name_len: usize,
508        out_id: *mut u32,
509    ) -> TwigStatus;
510    pub fn twig_builder_add_element(
511        builder: *mut TwigBuilder,
512        name: *const u8,
513        name_len: usize,
514        out_id: *mut u32,
515    ) -> TwigStatus;
516    pub fn twig_builder_add_processing_instruction(
517        builder: *mut TwigBuilder,
518        target: *const u8,
519        target_len: usize,
520        data: *const u8,
521        data_len: usize,
522        out_id: *mut u32,
523    ) -> TwigStatus;
524    pub fn twig_builder_add_footnote(
525        builder: *mut TwigBuilder,
526        label: *const u8,
527        label_len: usize,
528        out_id: *mut u32,
529    ) -> TwigStatus;
530    pub fn twig_builder_add_reference(
531        builder: *mut TwigBuilder,
532        label: *const u8,
533        label_len: usize,
534        destination: *const u8,
535        destination_len: usize,
536        out_id: *mut u32,
537    ) -> TwigStatus;
538    pub fn twig_builder_add_bullet_list(
539        builder: *mut TwigBuilder,
540        style: c_int,
541        tight: c_int,
542        out_id: *mut u32,
543    ) -> TwigStatus;
544    pub fn twig_builder_add_ordered_list(
545        builder: *mut TwigBuilder,
546        numbering: c_int,
547        delim: c_int,
548        tight: c_int,
549        start: u32,
550        has_start: c_int,
551        out_id: *mut u32,
552    ) -> TwigStatus;
553    pub fn twig_builder_add_task_list(builder: *mut TwigBuilder, tight: c_int, out_id: *mut u32) -> TwigStatus;
554    pub fn twig_builder_add_task_list_item(builder: *mut TwigBuilder, checked: c_int, out_id: *mut u32) -> TwigStatus;
555    pub fn twig_builder_add_row(builder: *mut TwigBuilder, head: c_int, out_id: *mut u32) -> TwigStatus;
556    pub fn twig_builder_add_cell(builder: *mut TwigBuilder, head: c_int, alignment: c_int, out_id: *mut u32) -> TwigStatus;
557    pub fn twig_builder_set_children(
558        builder: *mut TwigBuilder,
559        parent: u32,
560        ids: *const u32,
561        ids_len: usize,
562    ) -> TwigStatus;
563    pub fn twig_builder_set_attrs(
564        builder: *mut TwigBuilder,
565        id: u32,
566        kvs: *const TwigKeyVal,
567        kvs_len: usize,
568    ) -> TwigStatus;
569    pub fn twig_builder_render_html(
570        builder: *mut TwigBuilder,
571        root: u32,
572        out_ptr: *mut *const u8,
573        out_len: *mut usize,
574    ) -> TwigStatus;
575    pub fn twig_builder_serialize(
576        builder: *mut TwigBuilder,
577        root: u32,
578        format: c_int,
579        out_ptr: *mut *const u8,
580        out_len: *mut usize,
581    ) -> TwigStatus;
582    pub fn twig_builder_ast_json(
583        builder: *mut TwigBuilder,
584        root: u32,
585        out_ptr: *mut *const u8,
586        out_len: *mut usize,
587    ) -> TwigStatus;
588    pub fn twig_builder_query(
589        builder: *mut TwigBuilder,
590        root: u32,
591        selector: *const u8,
592        selector_len: usize,
593        out_ptr: *mut *const TwigQueryMatch,
594        out_len: *mut usize,
595    ) -> TwigStatus;
596}