muon_wasm/lib.rs
1//! A `wasm32-unknown-unknown` wrapper exposing [`muon::parse`] to JavaScript.
2//!
3//! The muon crate itself is `no_std`; this thin shim links `std` only for the
4//! allocator that `wasm32-unknown-unknown` provides, so no `wasm-bindgen` (or
5//! any post-processing tool) is needed — the `.wasm` is loaded directly.
6//!
7//! ## ABI
8//!
9//! Strings cross the boundary as `(ptr, len)` into wasm linear memory:
10//!
11//! - `muon_alloc(len) -> ptr` — reserve `len` bytes for JS to write UTF-8 into.
12//! - `muon_parse(ptr, len) -> out` — parse those bytes; returns a pointer to a
13//! length-prefixed result: a little-endian `u32` byte length followed by that
14//! many bytes of UTF-8 JSON. On success it is the tagged tree dump
15//! (`muon::Seq::to_json`); on failure, `{"error":{"offset":N,"kind":"…"}}`.
16//! - `muon_free(ptr, len)` — release a block obtained from `muon_alloc`.
17
18/// Reserve `len` bytes of linear memory and hand ownership to the caller.
19#[no_mangle]
20pub extern "C" fn muon_alloc(len: usize) -> *mut u8 {
21 let mut buf = Vec::<u8>::with_capacity(len);
22 let ptr = buf.as_mut_ptr();
23 core::mem::forget(buf);
24 ptr
25}
26
27/// Release a block previously returned by [`muon_alloc`] (same `len`).
28///
29/// # Safety
30/// `ptr`/`len` must come from a prior `muon_alloc(len)` and not be freed twice.
31#[no_mangle]
32pub unsafe extern "C" fn muon_free(ptr: *mut u8, len: usize) {
33 drop(Vec::from_raw_parts(ptr, 0, len));
34}
35
36/// Parse `len` UTF-8 bytes at `ptr`; return a length-prefixed JSON result.
37///
38/// # Safety
39/// `ptr`/`len` must describe a valid, initialized block of linear memory (e.g.
40/// one from [`muon_alloc`] that JS has filled). The returned block is owned by
41/// the caller and must be released with [`muon_free`].
42#[no_mangle]
43pub unsafe extern "C" fn muon_parse(ptr: *const u8, len: usize) -> *mut u8 {
44 let input = core::slice::from_raw_parts(ptr, len);
45 let json = match core::str::from_utf8(input) {
46 Ok(src) => match muon::parse(src) {
47 Ok(seq) => seq.to_json(),
48 Err(e) => format!(
49 r#"{{"error":{{"offset":{},"kind":"{:?}"}}}}"#,
50 e.offset, e.kind
51 ),
52 },
53 Err(_) => r#"{"error":{"offset":0,"kind":"NotUtf8"}}"#.to_string(),
54 };
55
56 // Frame as: [u32 little-endian length][UTF-8 bytes].
57 let bytes = json.into_bytes();
58 let mut out = Vec::with_capacity(4 + bytes.len());
59 out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
60 out.extend_from_slice(&bytes);
61 let p = out.as_mut_ptr();
62 core::mem::forget(out);
63 p
64}