1use wasm_bindgen::prelude::*;
23use serde::Serialize;
24use serde_wasm_bindgen as swb;
25
26use std::collections::BTreeSet;
27use std::sync::{Arc, Mutex};
28
29use core_mumu::{Interpreter, Value};
30use core_mumu::parser::core::driver::parse_tokens;
31use core_mumu::parser::lexer::tokenize;
32use core_mumu::parser::types::{FunctionValue, IteratorKind};
33
34#[wasm_bindgen(start)]
36pub fn wasm_start() {
37 console_error_panic_hook::set_once();
38}
39
40#[derive(Serialize)]
42#[serde(tag = "kind", rename_all = "snake_case")]
43enum JsValueView {
44 Int { value: i32 },
45 Long { value: i64 },
46 Float { value: f64 },
47 Bool { value: bool },
48 String { value: String },
49 IntArray { value: Vec<i32> },
50 FloatArray { value: Vec<f64> },
51 BoolArray { value: Vec<bool> },
52 StrArray { value: Vec<String> },
53 Int2DArray { value: Vec<Vec<i32>> },
54 Float2DArray { value: Vec<Vec<f64>> },
55 MixedArray { value: Vec<JsValueView> },
56 KeyedArray { value: Vec<(String, JsValueView)> },
57 Function,
58 Iterator,
59 Stream { label: String },
60 Tensor,
61 Regex { pattern: String, flags: String },
62 Placeholder,
63 Undefined,
64}
65
66fn to_js_view(v: &Value) -> JsValueView {
68 use Value::*;
69 match v {
70 Int(n) => JsValueView::Int { value: *n },
71 Long(l) => JsValueView::Long { value: *l },
72 Float(f) => JsValueView::Float { value: *f },
73 Bool(b) => JsValueView::Bool { value: *b },
74 SingleString(s) => JsValueView::String { value: s.clone() },
75 IntArray(xs) => JsValueView::IntArray { value: xs.clone() },
76 FloatArray(xs) => JsValueView::FloatArray { value: xs.clone() },
77 BoolArray(xs) => JsValueView::BoolArray { value: xs.clone() },
78 StrArray(xs) => JsValueView::StrArray { value: xs.clone() },
79 Int2DArray(rows) => JsValueView::Int2DArray { value: rows.clone() },
80 Float2DArray(rows) => JsValueView::Float2DArray { value: rows.clone() },
81 MixedArray(items) => JsValueView::MixedArray {
82 value: items.iter().map(to_js_view).collect(),
83 },
84 KeyedArray(map) => JsValueView::KeyedArray {
85 value: map
86 .iter()
87 .map(|(k, v)| (k.clone(), to_js_view(v)))
88 .collect(),
89 },
90 Function(_) => JsValueView::Function,
91 Iterator(_) => JsValueView::Iterator,
92 Stream(h) => JsValueView::Stream {
93 label: h.label.clone(),
94 },
95 Tensor(_) => JsValueView::Tensor,
96 Regex(rx) => JsValueView::Regex {
97 pattern: rx.pattern.clone(),
98 flags: rx.flags.clone(),
99 },
100 Ref(cell) => to_js_view(&cell.lock().unwrap()),
101 Placeholder => JsValueView::Placeholder,
102 Undefined => JsValueView::Undefined,
103 }
104}
105
106fn short_type_name(v: &Value) -> &'static str {
108 use Value::*;
109 match v {
110 Int(_) => "int",
111 IntArray(_) => "int_array",
112 Int2DArray(_) => "int2d_array",
113 Float(_) => "float",
114 FloatArray(_) => "float_array",
115 Float2DArray(_) => "float2d_array",
116 Long(_) => "long",
117 Bool(_) => "bool",
118 BoolArray(_) => "bool_array",
119 Undefined => "undefined",
120 Placeholder => "placeholder",
121 SingleString(_) => "string",
122 StrArray(_) => "str_array",
123 KeyedArray(_) => "keyed_array",
124 Function(_) => "function",
125 Stream(_) => "stream",
126 Iterator(_) => "iterator",
127 Tensor(_) => "tensor",
128 MixedArray(_) => "mixed_array",
129 Ref(_) => "ref",
130 Regex(_) => "regex",
131 }
132}
133
134#[derive(Serialize)]
135struct ExecOk {
136 ok: bool,
137 value: JsValueView,
138 value_string: String,
139 value_type: String,
140 prints: Vec<String>,
141}
142
143#[derive(Serialize)]
144struct ExecErr {
145 ok: bool,
146 error: String,
147}
148
149#[wasm_bindgen]
151pub struct WasmMumu {
152 interp: Interpreter,
153 verbose: bool,
154 drained_cursor: usize, }
156
157fn bootstrap_interpreter(verbose: bool) -> Interpreter {
158 let mut i = Interpreter::new();
159 i.set_verbose(verbose);
160
161 core_mumu::modules::compose::register_compose_and_pipe(&mut i);
163 core_mumu::modules::sput::register_sput(&mut i);
164 core_mumu::modules::slog::register_slog(&mut i);
165 core_mumu::modules::step::register_step(&mut i);
166 core_mumu::modules::r#type::register_type(&mut i);
167 core_mumu::modules::check::register_check(&mut i);
168
169 i.disallow_extend();
171
172 mumuarray::register_all(&mut i);
174 mumumath::register_all(&mut i);
175
176 register_web_std(&mut i);
178
179 i
180}
181
182fn register_web_std(interp: &mut Interpreter) {
184 macro_rules! reg {
185 ($name:expr, $f:expr) => {{
186 let func = Arc::new(Mutex::new($f));
187 interp.register_dynamic_function($name, func);
188 interp.set_variable(
189 $name,
190 Value::Function(Box::new(FunctionValue::Named($name.into()))),
191 );
192 }};
193 }
194
195 fn to_printable(v: &Value) -> String {
196 match v {
197 Value::SingleString(s) => s.clone(),
198 Value::Int(i) => i.to_string(),
199 Value::Long(l) => l.to_string(),
200 Value::Float(f) => f.to_string(),
201 Value::Bool(b) => b.to_string(),
202 _ => format!("{:?}", v),
203 }
204 }
205
206 fn drain_iterator(
208 interp: &mut Interpreter,
209 it: &core_mumu::parser::types::IteratorHandle,
210 newline: bool,
211 ) -> Result<(), String> {
212 match &it.kind {
213 IteratorKind::Core(state_arc) => {
214 let mut guard = state_arc
215 .lock()
216 .map_err(|_| "std (wasm): iterator lock error".to_string())?;
217 while !guard.done && guard.current < guard.end {
218 let item = guard.current;
219 guard.current += 1;
220 if guard.current >= guard.end {
221 guard.done = true;
222 }
223 drop(guard);
224 let s = item.to_string();
225 if newline {
226 interp.push_print(&(s.clone() + "\n"));
227 } else {
228 interp.push_print(&s);
229 }
230 guard = state_arc
231 .lock()
232 .map_err(|_| "std (wasm): iterator lock error".to_string())?;
233 }
234 Ok(())
235 }
236 IteratorKind::Plugin(plugin_arc) => {
237 let mut plugin = plugin_arc
238 .lock()
239 .map_err(|_| "std (wasm): plugin iterator lock error".to_string())?;
240 loop {
241 match plugin.next_value() {
242 Ok(val) => {
243 let s = to_printable(&val);
244 if newline {
245 interp.push_print(&(s.clone() + "\n"));
246 } else {
247 interp.push_print(&s);
248 }
249 }
250 Err(e) if e == "NO_MORE_DATA" => break,
251 Err(e) => return Err(e),
252 }
253 }
254 Ok(())
255 }
256 }
257 }
258
259 fn std_put_bridge(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
261 if args.len() != 1 {
262 return Err(format!(
263 "std:put => expected exactly 1 argument, got {}",
264 args.len()
265 ));
266 }
267 let val = args.remove(0);
268 match &val {
269 Value::Iterator(handle) => {
270 drain_iterator(interp, handle, false)?;
271 Ok(val)
272 }
273 other => {
274 let s = to_printable(other);
275 interp.push_print(&s);
276 Ok(val)
277 }
278 }
279 }
280
281 fn std_log_bridge(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
283 if args.len() != 1 {
284 return Err(format!(
285 "std:log => expected exactly 1 argument, got {}",
286 args.len()
287 ));
288 }
289 let val = args.remove(0);
290 match &val {
291 Value::Iterator(handle) => {
292 drain_iterator(interp, handle, true)?;
293 Ok(val)
294 }
295 other => {
296 let s = to_printable(other);
297 interp.push_print(&(s + "\n"));
298 Ok(val)
299 }
300 }
301 }
302
303 fn std_deep_bridge(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
305 if args.len() != 1 {
306 return Err(format!(
307 "std:deep => expected exactly 1 argument, got {}",
308 args.len()
309 ));
310 }
311 let val = args.remove(0);
312 let s = core_mumu::value_to_string(&val);
313 interp.push_print(&(s.clone() + "\n"));
314 Ok(val)
315 }
316
317 reg!("std:put", std_put_bridge);
318 reg!("std:log", std_log_bridge);
319 reg!("std:deep", std_deep_bridge);
320}
321
322impl WasmMumu {
323 fn collect_symbols(&self) -> Vec<String> {
325 let mut set: BTreeSet<String> = BTreeSet::new();
326
327 for k in self.interp.get_dynamic_functions_for_clone().keys() {
329 set.insert(k.clone());
330 }
331 for k in self.interp.get_variables().keys() {
333 set.insert(k.clone());
334 }
335
336 set.into_iter().collect()
337 }
338}
339
340#[wasm_bindgen]
341impl WasmMumu {
342 #[wasm_bindgen(constructor)]
343 pub fn new(verbose: bool) -> WasmMumu {
344 WasmMumu {
345 interp: bootstrap_interpreter(verbose),
346 verbose,
347 drained_cursor: 0,
348 }
349 }
350
351 #[wasm_bindgen]
353 pub fn reset(&mut self) {
354 self.interp = bootstrap_interpreter(self.verbose);
355 self.drained_cursor = 0;
356 }
357
358 #[wasm_bindgen]
361 pub fn exec(&mut self, source: &str) -> JsValue {
362 let res = (|| -> Result<ExecOk, ExecErr> {
363 let tokens = tokenize(source, self.interp.is_verbose())
364 .map_err(|e| ExecErr {
365 ok: false,
366 error: e.to_string(),
367 })?;
368 let ast = parse_tokens(&tokens, self.interp.is_verbose())
369 .map_err(|e| ExecErr {
370 ok: false,
371 error: e.to_string(),
372 })?;
373
374 let mut last = Value::Bool(true);
375 for stmt in &ast {
376 last = self
377 .interp
378 .exec_statement(stmt)
379 .map_err(|e| ExecErr { ok: false, error: e })?;
380 }
381
382 let view = to_js_view(&last);
383 let value_string = core_mumu::value_to_string(&last);
384 let value_type = short_type_name(&last).to_string();
385 let prints = self.interp.get_prints().clone();
386
387 Ok(ExecOk {
388 ok: true,
389 value: view,
390 value_string,
391 value_type,
392 prints,
393 })
394 })();
395
396 match res {
397 Ok(ok) => swb::to_value(&ok).unwrap(),
398 Err(err) => swb::to_value(&err).unwrap(),
399 }
400 }
401
402 #[wasm_bindgen]
405 pub fn poll(&mut self) -> u32 {
406 self.interp.poll_all() as u32
407 }
408
409 #[wasm_bindgen]
411 pub fn symbols(&self) -> JsValue {
412 let v = self.collect_symbols();
413 swb::to_value(&v).unwrap()
414 }
415
416 #[wasm_bindgen]
418 pub fn complete(&self, prefix: &str) -> JsValue {
419 let lower = prefix.to_ascii_lowercase();
420 let mut out: Vec<String> = self
421 .collect_symbols()
422 .into_iter()
423 .filter(|s| s.to_ascii_lowercase().starts_with(&lower))
424 .collect();
425 out.sort();
426 out.dedup();
427 swb::to_value(&out).unwrap()
428 }
429
430 #[wasm_bindgen]
433 pub fn drain_prints(&mut self) -> JsValue {
434 let all = self.interp.get_prints().clone();
435 let start = if self.drained_cursor <= all.len() {
436 self.drained_cursor
437 } else {
438 0
439 };
440 let new: Vec<String> = all[start..].to_vec();
441 self.drained_cursor = all.len();
442 swb::to_value(&new).unwrap()
443 }
444
445 #[wasm_bindgen]
447 pub fn set_verbose(&mut self, v: bool) {
448 self.verbose = v;
449 self.interp.set_verbose(v);
450 }
451
452 #[wasm_bindgen]
454 pub fn version(&self) -> String {
455 "wasm-mumu/0.1".to_string()
457 }
458}