use std::sync::Arc;
use async_trait::async_trait;
use serde_json::{json, Value};
use crate::error::Result;
use crate::tools::{Tool, ToolContext};
pub struct CompileRustlite;
crate::tool_params! {
struct Args: lenient {
source: req_str = "Rustlite source code to compile",
function: opt_str = "Function name to call after compilation (default: 'handle')",
args: opt_i64_array = "i32 arguments to pass to the function",
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Tool for CompileRustlite {
fn name(&self) -> &str {
"compile_rustlite"
}
fn description(&self) -> &str {
"Compile-check rustlite (Rust-subset) source to wasm WITHOUT touching the \
display — your compile-in-the-loop tool. Use it after each addition while \
building a cartridge: it returns either a clean compile (then run_cartridge \
/ create_and_publish_app) or, on failure, `{ error: \"compilation failed\", \
code: \"LHxxxx\", detail, location: \"line N, col M\", snippet: \"<the \
offending source line with a ^ caret under the error>\", hint }` — READ \
the location + snippet, fix that exact spot, and recompile before adding \
more. rustlite supports structs, enums, fns, match (incl. ranges), \
if/else, while/for/loop, arrays (read), const, recursion — but NO traits, \
generics, references, heap types (Vec/String building/Box), or array \
writes. If `function` is given it is also called and its i32 result \
returned; for a plain compile-check just pass `source`."
}
fn input_schema(&self) -> Value {
Args::schema()
}
async fn execute(&self, args: Value, _ctx: Option<Arc<ToolContext>>) -> Result<Value> {
let p = Args::lenient(&args);
let source = p.source;
let function = p.function.as_deref().unwrap_or("handle").to_string();
let fn_args: Vec<i32> = p
.args
.map(|v| v.into_iter().map(|n| n as i32).collect())
.unwrap_or_default();
if source.is_empty() {
return Ok(json!({ "error": "source is required" }));
}
let wasm_bytes = match crate::rustlite::compile(&source) {
Ok(bytes) => bytes,
Err(err) => {
let mut report = crate::builtins::compile_failure_report(&err, &source);
report["exports"] = json!([]);
return Ok(report);
}
};
#[cfg(target_arch = "wasm32")]
{
use crate::rustlite::loader::Cartridge;
let cartridge = match Cartridge::load(&wasm_bytes).await {
Ok(c) => c,
Err(err) => {
return Ok(json!({
"error": "load failed",
"detail": err.to_string(),
"wasm_size": wasm_bytes.len()
}));
}
};
let exports = cartridge.exports();
match cartridge.call_i32(&function, &fn_args) {
Ok(result) => Ok(json!({
"result": result,
"function": function,
"exports": exports,
"wasm_size": wasm_bytes.len()
})),
Err(err) => Ok(json!({
"error": "execution failed",
"detail": err.to_string(),
"exports": exports,
"wasm_size": wasm_bytes.len()
})),
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let _ = (&function, &fn_args);
Ok(json!({
"compiled": true,
"wasm_size": wasm_bytes.len(),
"note": "execution requires a browser — compiled successfully but cannot instantiate on native"
}))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_is_byte_identical_to_the_frozen_original() {
let frozen = json!({
"type": "object",
"properties": {
"source": {
"type": "string",
"description": "Rustlite source code to compile"
},
"function": {
"type": "string",
"description": "Function name to call after compilation (default: 'handle')"
},
"args": {
"type": "array",
"items": { "type": "integer" },
"description": "i32 arguments to pass to the function"
}
},
"required": ["source"]
});
assert_eq!(Args::schema().to_string(), frozen.to_string());
}
#[test]
fn lenient_matches_the_old_inline_extraction() {
let p = Args::lenient(&serde_json::json!({}));
assert_eq!((p.source.as_str(), p.function, p.args), ("", None, None));
let p = Args::lenient(&serde_json::json!({
"source": "fn handle() -> i32 { 1 }",
"function": 7, "args": [1, -2, "3", 4.5], }));
assert_eq!(p.function.as_deref().unwrap_or("handle"), "handle");
let fn_args: Vec<i32> = p
.args
.map(|v| v.into_iter().map(|n| n as i32).collect())
.unwrap_or_default();
assert_eq!(fn_args, vec![1, -2]);
}
}