Skip to main content

monkey_wasm/
lib.rs

1mod utils;
2
3use crate::utils::set_panic_hook;
4use compiler::compiler::Compiler;
5use compiler::snapshot::{read_bytecode, write_bytecode};
6use compiler::snapshot_layout::describe_bytecode;
7use parser::parse as parser_pase;
8use parser::parse_ast_json_string;
9use wasm_bindgen::prelude::*;
10use wasm_bindgen::throw_str;
11
12const PLAYGROUND_GC_INSTRUCTION_BUDGET: usize = 10_000;
13
14// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
15// allocator.
16#[cfg(feature = "wee_alloc")]
17#[global_allocator]
18static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
19
20#[wasm_bindgen]
21pub fn parse(input: &str) -> String {
22    set_panic_hook();
23    match parse_ast_json_string(input) {
24        Ok(node) => node.to_string(),
25        Err(e) => throw_str(format!("parse error: {}", e[0]).as_str()),
26    }
27}
28
29#[wasm_bindgen]
30pub fn compile(input: &str) -> String {
31    set_panic_hook();
32
33    let program = match parser_pase(input) {
34        Ok(ast) => ast,
35        Err(e) => throw_str(format!("parse error: {}", e[0]).as_str()),
36    };
37    let mut compiler = Compiler::new();
38    match compiler.compile(&program) {
39        Ok(bytecode) => return bytecode.instructions.string(),
40        Err(e) => throw_str(format!("compile error: {}", e).as_str()),
41    }
42}
43
44#[wasm_bindgen]
45pub fn compile_detail(input: &str) -> String {
46    set_panic_hook();
47
48    let program = match parser_pase(input) {
49        Ok(ast) => ast,
50        Err(e) => throw_str(format!("parse error: {}", e[0]).as_str()),
51    };
52    let mut compiler = Compiler::new();
53    match compiler.compile(&program) {
54        Ok(bytecode) => return bytecode.string(),
55        Err(e) => throw_str(format!("compile error: {}", e).as_str()),
56    }
57}
58
59#[wasm_bindgen]
60pub fn compile_with_debug(input: &str) -> String {
61    set_panic_hook();
62
63    let program = match parser_pase(input) {
64        Ok(ast) => ast,
65        Err(e) => throw_str(format!("parse error: {}", e[0]).as_str()),
66    };
67    let mut compiler = Compiler::new();
68    match compiler.compile(&program) {
69        Ok(bytecode) => match serde_json::to_string(&bytecode.debug_view()) {
70            Ok(json) => json,
71            Err(e) => throw_str(format!("json error: {}", e).as_str()),
72        },
73        Err(e) => throw_str(format!("compile error: {}", e).as_str()),
74    }
75}
76
77/// Execute Monkey source on the cycle-collecting VM and return a tagged JSON envelope.
78///
79/// User parse, compile, runtime, and execution-limit failures are data in the envelope,
80/// not JavaScript exceptions. This keeps the playground's Run GC path deterministic.
81#[wasm_bindgen]
82pub fn run_gc_with_report(input: &str) -> String {
83    set_panic_hook();
84
85    let envelope = match gc::run_source_with_report(input, PLAYGROUND_GC_INSTRUCTION_BUDGET) {
86        Ok(success) => serde_json::json!({
87            "status": "ok",
88            "result": success.result,
89            "report": success.report,
90        }),
91        Err(error) => serde_json::json!({
92            "status": "error",
93            "stage": error.stage,
94            "message": error.message,
95            "span": error.span,
96        }),
97    };
98
99    serde_json::to_string(&envelope).expect("GC run envelope serialization should not fail")
100}
101
102/// Compile Monkey source into a `.mbc` snapshot and return a tagged JSON envelope
103/// with the raw bytes (lowercase hex) plus a byte-range annotation of the container
104/// layout for the playground inspector.
105///
106/// User parse and compile failures are data in the envelope, not JavaScript
107/// exceptions, mirroring [`run_gc_with_report`].
108#[wasm_bindgen]
109pub fn compile_to_snapshot(input: &str, strip_debug: bool) -> String {
110    set_panic_hook();
111
112    let envelope = match snapshot_envelope(input, strip_debug) {
113        Ok(envelope) => envelope,
114        Err((stage, message)) => serde_json::json!({
115            "status": "error",
116            "stage": stage,
117            "message": message,
118        }),
119    };
120    serde_json::to_string(&envelope).expect("snapshot envelope serialization should not fail")
121}
122
123fn snapshot_envelope(
124    input: &str,
125    strip_debug: bool,
126) -> Result<serde_json::Value, (&'static str, String)> {
127    let program = parser_pase(input).map_err(|errors| {
128        let message = errors
129            .first()
130            .cloned()
131            .unwrap_or_else(|| "unknown parse error".to_string());
132        ("parse", message)
133    })?;
134    let mut compiler = Compiler::new();
135    let bytecode = compiler
136        .compile(&program)
137        .map_err(|message| ("compile", message))?;
138    let bytes = write_bytecode(&bytecode, strip_debug)
139        .map_err(|error| ("snapshot", format!("{:?}", error)))?;
140    let layout = describe_bytecode(&bytes).map_err(|error| ("snapshot", format!("{:?}", error)))?;
141    Ok(serde_json::json!({
142        "status": "ok",
143        "bytesHex": hex_encode(&bytes),
144        "layout": layout,
145    }))
146}
147
148fn hex_encode(bytes: &[u8]) -> String {
149    use std::fmt::Write;
150
151    let mut out = String::with_capacity(bytes.len() * 2);
152    for byte in bytes {
153        write!(out, "{:02x}", byte).expect("writing to a String cannot fail");
154    }
155    out
156}
157
158/// Execute `.mbc` snapshot bytes on the cycle-collecting VM — the browser twin of
159/// `monkey-gc run foo.mbc`, sharing its execution path (`gc::run_bytecode`).
160///
161/// The buffer is untrusted input: it goes through the validating snapshot reader
162/// before the VM. Failures are data in the envelope — stage `snapshot` when the
163/// bytes are rejected, `runtime` when the VM errors (the span is only present
164/// when the snapshot kept its debug info).
165#[wasm_bindgen]
166pub fn run_snapshot(bytes: &[u8]) -> String {
167    set_panic_hook();
168
169    let envelope = match read_bytecode(bytes) {
170        Ok(bytecode) => match gc::run_bytecode(bytecode, PLAYGROUND_GC_INSTRUCTION_BUDGET) {
171            Ok(result) => serde_json::json!({
172                "status": "ok",
173                "result": result,
174            }),
175            Err(error) => serde_json::json!({
176                "status": "error",
177                "stage": "runtime",
178                "message": error.message,
179                "span": error.span,
180            }),
181        },
182        Err(error) => serde_json::json!({
183            "status": "error",
184            "stage": "snapshot",
185            "message": format!("{:?}", error),
186            "span": null,
187        }),
188    };
189    serde_json::to_string(&envelope).expect("snapshot run envelope serialization should not fail")
190}