nodejs/rust_ffi.rs
1//! JavaScript wiring for inline Rust FFI (`rust { ... }` blocks).
2//!
3//! The heavy lifting lives in fusevm: [`fusevm::RustSugar`] scans and rewrites
4//! the block at the source level, and [`fusevm::ffi`] compiles/loads/marshals
5//! it. This module only supplies the JS-flavored [`fusevm::RustSugar`] config
6//! and the desugar entry the parser calls. The emitted `__rust_compile(...)`
7//! call and every exported bareword are resolved in [`crate::host::call_named`].
8
9use fusevm::RustSugar;
10
11/// Emit the JS statement a `rust { ... }` block desugars to: a call to the
12/// `__rust_compile` builtin carrying the base64-encoded block body and its line.
13/// base64's alphabet (`A-Za-z0-9+/=`) needs no escaping inside the double-quoted
14/// JS string literal.
15fn emit(b64: &str, line: usize) -> String {
16 format!("__rust_compile(\"{b64}\", {line})")
17}
18
19/// JavaScript desugar config: C-family braces with `//` and `/* */` comments.
20/// `newline_boundary` is `true` so a top-level `rust { ... }` on its own line is
21/// recognized — `rust {` is never valid JS otherwise, so this only ever matches
22/// an intended FFI block. The desugar runs on raw source BEFORE lexing, so the
23/// block is replaced in place by a `__rust_compile(...)` expression statement.
24pub const SUGAR: RustSugar = RustSugar {
25 keyword: "rust",
26 line_comments: &["//"],
27 block_comment: Some(("/*", "*/")),
28 newline_boundary: true,
29 emit,
30};
31
32/// Rewrite every top-level `rust { ... }` block in JS source into a
33/// `__rust_compile(...)` call, before lexing. No-op when the source has no
34/// `rust` token.
35pub fn desugar(src: &str) -> String {
36 SUGAR.desugar(src)
37}
38
39#[cfg(test)]
40mod tests {
41 #[test]
42 fn desugars_top_level_block() {
43 let src =
44 "rust { pub extern \"C\" fn add(a: i64, b: i64) -> i64 { a + b } }\nconsole.log(add(2, 3))\n";
45 let out = super::desugar(src);
46 assert!(out.contains("__rust_compile("), "no builtin call: {out}");
47 assert!(!out.contains("pub extern"), "Rust body leaked: {out}");
48 assert!(
49 out.contains("console.log(add(2, 3))"),
50 "trailing code lost: {out}"
51 );
52 }
53
54 #[test]
55 fn leaves_ordinary_js_untouched() {
56 let src = "const x = \"hi\".length;\nconsole.log(x);\n";
57 assert_eq!(super::desugar(src), src);
58 }
59}