use fusevm::{Op, RustSugar};
use crate::compiler::{ext, CompileError, Compiler};
use crate::parser::Word;
pub const COMPILE_COMMAND: &str = "__rust_compile";
fn emit(body_b64: &str, line: usize) -> String {
format!("{COMPILE_COMMAND} {body_b64} {line}")
}
pub const SUGAR: RustSugar = RustSugar {
keyword: "rust",
line_comments: &["#"],
block_comment: None,
newline_boundary: true,
emit,
};
pub fn desugar(src: &str) -> String {
SUGAR.desugar(src)
}
pub fn is_exported(name: &str) -> bool {
fusevm::ffi::is_registered(name)
}
pub fn register(body_b64: &str) -> Result<(), String> {
fusevm::ffi::compile_and_register(body_b64)
}
pub fn call(name: &str, args: &[fusevm::Value]) -> Result<fusevm::Value, String> {
fusevm::ffi::try_call(name, args)
.unwrap_or_else(|| Err(format!("invalid command name \"{name}\"")))
}
impl Compiler {
pub(crate) fn cmd_rust_compile(&mut self, args: &[Word]) -> Result<(), CompileError> {
let [body, line] = args else {
return self.error("wrong # args: should be \"__rust_compile base64 line\"");
};
let body = self.literal_of(body, "the block body")?.to_string();
if let Some(line) = line.as_literal().and_then(|l| l.parse::<usize>().ok()) {
self.line = line;
self.command_line = line;
}
if let Err(e) = register(&body) {
return self.error(e);
}
self.push_empty();
Ok(())
}
pub(crate) fn call_ffi(&mut self, name: &str, args: &[Word]) -> Result<(), CompileError> {
let count = u8::try_from(args.len() + 1).map_err(|_| {
self.err(format!(
"more than 254 arguments to the rust function \"{name}\""
))
})?;
self.push_text(name);
for arg in args {
self.word(arg)?;
}
self.emit(Op::Extended(ext::FFI_CALL, count), -(args.len() as i32));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_block_becomes_a_command_and_the_rust_leaves_the_source() {
let src = "rust {\n pub extern \"C\" fn add(a: i64, b: i64) -> i64 { a + b }\n}\nputs [add 2 3]\n";
let out = desugar(src);
assert!(out.contains(COMPILE_COMMAND), "{out}");
assert!(!out.contains("pub extern"), "the Rust body leaked: {out}");
assert!(out.contains("puts [add 2 3]"), "{out}");
assert_eq!(out.lines().count(), src.lines().count());
}
#[test]
fn a_script_without_a_block_is_returned_as_it_was() {
let src = "set x 1\nputs $x\n";
assert_eq!(desugar(src), src);
}
#[test]
fn the_keyword_in_a_comment_is_left_alone() {
let src = "# rust { not a block }\nset x 1\n";
assert_eq!(desugar(src), src);
}
#[test]
fn the_keyword_as_a_word_is_left_alone() {
let src = "puts rust\nset rust 1\n";
assert_eq!(desugar(src), src);
}
}