Skip to main content

larvae_worm/
lib.rs

1/*!
2The guest side of the larvae worm ABI.
3
4A worm is a `wasm32` module that larvae loads and calls. wasm has no strings.
5Thus all data crosses as an offset and a length into the linear memory of the
6module. This crate owns that protocol, so a worm author does not write it:
7
8```ignore
9larvae_worm::frontend!(|source: &str, config: &str| -> anyhow::Result<String> {
10    luaux::compile_configured(source, Backend::Vide, &Config::parse(config)?)
11});
12```
13
14The macro is not the only entry point. [`abi`] is public and documented. Thus
15a worm with an unusual design can export the raw functions itself, without a
16copy of the macro.
17
18# The ABI
19
20A worm exports `memory`, plus:
21
22| export | signature |
23|---|---|
24| `larvae_alloc` | `(len: u32) -> ptr` |
25| `larvae_dealloc` | `(ptr, len: u32)` |
26| `larvae_transform` | `(src_ptr, src_len, cfg_ptr, cfg_len) -> *header` |
27| `larvae_init` | `(cfg_ptr, cfg_len, rules_ptr, rules_len)` |
28| `larvae_visit` | `(rule, epoch, node_id)` |
29
30`larvae_transform` returns a pointer to a three word header,
31`[out_ptr, out_len, ok]`. `ok` is 1 when the bytes are output and 0 when they
32are an error message. The header lives in a static, so the host does not free
33it. The host calls `larvae_dealloc(out_ptr, out_len)` when it has read the
34payload out.
35*/
36
37#![deny(missing_docs)]
38
39/// The ABI revision this crate implements. It must match `api` in `worm.toml`.
40pub const ABI_VERSION: u32 = 1;
41
42pub mod abi;
43#[cfg(feature = "native")]
44pub mod native;
45pub mod node;
46
47pub use node::Node;
48
49/**
50Define a front-end worm. It takes source text and returns transformed source.
51
52The closure takes the contents of the file and the `[config.<name>]` table of
53the worm, serialized again as TOML. It returns the transformed source. Each
54error type that implements [`Display`](core::fmt::Display) works, so
55`anyhow::Result<String>` is valid.
56
57```ignore
58larvae_worm::frontend!(|source: &str, _config: &str| -> Result<String, String> {
59    Ok(source.replace("<>", "{}"))
60});
61```
62
63The macro expands to the three exports in the module docs. Use it once per worm.
64*/
65#[macro_export]
66macro_rules! frontend {
67    ($handler:expr) => {
68        /// Allocate `len` bytes for the host to write into
69        #[unsafe(no_mangle)]
70        pub extern "C" fn larvae_alloc(len: u32) -> *mut u8 {
71            $crate::abi::alloc(len)
72        }
73
74        /// Release a buffer that the host does not need anymore
75        #[unsafe(no_mangle)]
76        pub extern "C" fn larvae_dealloc(ptr: *mut u8, len: u32) {
77            // SAFETY: the host passes back only pointers that larvae_alloc
78            // returned, with the length of the allocation
79            unsafe { $crate::abi::dealloc(ptr, len) }
80        }
81
82        /// Transform `src` under `cfg` and return a pointer to the result header
83        #[unsafe(no_mangle)]
84        pub extern "C" fn larvae_transform(
85            src_ptr: *const u8,
86            src_len: u32,
87            cfg_ptr: *const u8,
88            cfg_len: u32,
89        ) -> *const u32 {
90            // SAFETY: larvae_alloc allocated both spans, and the host wrote
91            // them and knows their lengths
92            unsafe { $crate::abi::dispatch(src_ptr, src_len, cfg_ptr, cfg_len, $handler) }
93        }
94    };
95}
96
97/**
98Define the rule half of a worm.
99
100Each rule is a name and a handler. larvae calls a rule only on the nodes that
101match the `filter` you declared in `worm.toml`. Thus undeclared kinds do not
102cross the boundary.
103
104```ignore
105larvae_worm::rules! {
106    "strip_debug" => |node: larvae_worm::Node| {
107        if node.kind() == "CallExpr" && node.text().starts_with("dprint") {
108            node.remove();
109        }
110    },
111}
112```
113
114Combine this macro with [`frontend!`](crate::frontend) when a worm holds both roles.
115*/
116#[macro_export]
117macro_rules! rules {
118    ($($name:literal => $handler:expr),+ $(,)?) => {
119        /// Rule ids are indexes into the order that is declared here
120        #[unsafe(no_mangle)]
121        pub extern "C" fn larvae_visit(rule: u32, epoch: u64, id: u32) {
122            let node = $crate::Node::from_raw(epoch, id);
123            let mut which = 0u32;
124
125            $(
126                if rule == which {
127                    let _ = $name;
128                    let handler = $handler;
129                    handler(node);
130                    return;
131                }
132
133                which += 1;
134            )+
135
136            let _ = which;
137        }
138    };
139}