diff --git a/Cargo.toml b/Cargo.toml index efadb5d..db7c844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" console_error_panic_hook = "0.1.7" struct_scalpel = "0.1.1" +serde-wasm-bindgen = "0.6.5" diff --git a/pkg/ludus.js b/pkg/ludus.js index ef36b49..1d6a133 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -32,10 +32,18 @@ async function handle_messages (e) { outbox = [] break } + case "Error": { + console.log("Main: ludus errored with => ", msg.data) + running = false + ready = false + outbox = [] + break + } // TODO: do more than report these case "Console": { - console.log("Main: ludus says => ", msg.data) - ludus_console = ludus_console + msg.data + let new_lines = msg.data.join("\n"); + console.log("Main: ludus says => ", new_lines) + ludus_console = ludus_console + new_lines break } case "Commands": { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index ae53166..0d303b9 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure347_externref_shim: (a: number, b: number, c: any) => void; - readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure305_externref_shim: (a: number, b: number, c: any) => void; + readonly closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 65e5ee7..6c2a561 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure347_externref_shim(arg0, arg1, arg2); + wasm.closure305_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure329_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7945 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7885 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 306, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 81f0fa6..825f63d 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9a87573..2d5f3c8 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure347_externref_shim: (a: number, b: number, c: any) => void; -export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure305_externref_shim: (a: number, b: number, c: any) => void; +export const closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/io.rs b/src/io.rs index c6107b8..d278d62 100644 --- a/src/io.rs +++ b/src/io.rs @@ -18,62 +18,54 @@ extern "C" { fn log(s: String); } -type Commands = Value; // expect a list of values -type Url = Value; // expect a string representing a URL -type FinalValue = Result; +type Url = Value; // expect a string +type Commands = Value; // expect a list of command tuples -fn make_json_payload(verb: &'static str, data: String) -> String { - format!("{{\"verb\":\"{verb}\",\"data\":{data}}}") -} - -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "verb", content = "data")] pub enum MsgOut { Console(Value), Commands(Commands), Fetch(Url), - Complete(FinalValue), + Complete(Value), + Error(String), Ready } -impl std::fmt::Display for MsgOut { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_json()) - } -} -impl MsgOut { - pub fn to_json(&self) -> String { - match self { - MsgOut::Complete(value) => match value { - Ok(value) => { - make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) - }, - Err(_) => make_json_payload("Complete", "\"null\"".to_string()) - }, - MsgOut::Commands(commands) => { - let commands = commands.as_list(); - let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); - let vals_json = format!("[{vals_json}]"); - make_json_payload("Commands", vals_json) - } - MsgOut::Fetch(value) => { - // TODO: do parsing here? - // Right now, defer to fetch - let url = value.to_json().unwrap(); - make_json_payload("Fetch", url) - } - MsgOut::Console(lines) => { - let lines = lines.as_list(); - let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); - let json_lines = format!("\"{json_lines}\""); - make_json_payload("Console", json_lines) - } - MsgOut::Ready => { - make_json_payload("Ready", "\"null\"".to_string()) - } - } - } -} +// impl MsgOut { +// pub fn to_json(&self) -> String { +// match self { +// MsgOut::Complete(value) => match value { +// Ok(value) => { +// make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) +// }, +// Err(_) => make_json_payload("Complete", "\"null\"".to_string()) +// }, +// MsgOut::Commands(commands) => { +// let commands = commands.as_list(); +// let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); +// let vals_json = format!("[{vals_json}]"); +// make_json_payload("Commands", vals_json) +// } +// MsgOut::Fetch(value) => { +// // TODO: do parsing here? +// // Right now, defer to fetch +// let url = value.to_json().unwrap(); +// make_json_payload("Fetch", url) +// } +// MsgOut::Console(lines) => { +// let lines = lines.as_list(); +// let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); +// let json_lines = format!("\"{json_lines}\""); +// make_json_payload("Console", json_lines) +// } +// MsgOut::Ready => { +// make_json_payload("Ready", "\"null\"".to_string()) +// } +// } +// } +// } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "verb", content = "data")] @@ -124,18 +116,12 @@ impl MsgIn { pub async fn send_err_to_ludus_console(msg: String) { log(msg.clone()); - let console_msg = Value::string(msg); - let mut console_vector = Vector::new(); - console_vector.push_back(console_msg); - let console_list = Value::list(console_vector); - let console = MsgOut::Console(console_list); - let completion = MsgOut::Complete(Err(Panic::Str(""))); - do_io(vec![MsgOut::Ready, console, completion]).await; + do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await; } pub async fn do_io (msgs: Vec) -> Vec { - let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); - let inbox = io (outbox).await; + let json = serde_json::to_string(&msgs).unwrap(); + let inbox = io (json).await; // if our request dies, make sure we return back to the event loop let inbox = match inbox { Ok(msgs) => msgs, diff --git a/src/main.rs b/src/main.rs index 94dad4d..f4be844 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ use rudus::value::Value; use std::env; -use struct_scalpel::print_dissection_info; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - print_dissection_info::(); } diff --git a/src/value.rs b/src/value.rs index 8c4f66d..2cdb658 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,10 @@ use crate::base::BaseFn; use crate::chunk::Chunk; use imbl::{HashMap, Vector}; +use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; -use struct_scalpel::Dissectible; +use wasm_bindgen::JsValue; #[derive(Clone, Debug)] pub enum LFn { @@ -130,6 +131,19 @@ impl std::fmt::Display for Key { } } +impl Serialize for Key { + fn serialize(&self, srlzr: S) -> Result + where + S: Serializer, + { + match self { + Key::Keyword(s) => srlzr.serialize_str(s), + Key::Interned(s) => srlzr.serialize_str(s), + Key::String(s) => srlzr.serialize_str(s.as_str()), + } + } +} + impl Key { pub fn to_value(&self) -> Value { match self { @@ -149,7 +163,7 @@ impl Key { } } -#[derive(Clone, Debug, Dissectible)] +#[derive(Clone, Debug)] pub enum Value { Nothing, Nil, @@ -247,6 +261,51 @@ impl std::fmt::Display for Value { } } +impl Serialize for Value { + fn serialize(&self, srlzr: S) -> Result + where + S: Serializer, + { + use Value::*; + match self { + Nil => srlzr.serialize_none(), + True => srlzr.serialize_bool(true), + False => srlzr.serialize_bool(false), + Number(n) => srlzr.serialize_f64(*n), + Interned(s) => srlzr.serialize_str(s), + Keyword(k) => srlzr.serialize_str(k), + String(s) => srlzr.serialize_str(s.as_str()), + Tuple(t) => { + let mut seq = srlzr.serialize_seq(Some(t.len()))?; + for e in t.iter() { + seq.serialize_element(e)?; + } + seq.end() + } + List(l) => { + let mut seq = srlzr.serialize_seq(Some(l.len()))?; + for e in l.iter() { + seq.serialize_element(e)?; + } + seq.end() + } + Dict(d) => { + let mut map = srlzr.serialize_map(Some(d.len()))?; + for (k, v) in d.iter() { + map.serialize_entry(k, v)?; + } + map.end() + } + Box(b) => { + let boxed = b.borrow(); + (*boxed).serialize(srlzr) + } + Fn(..) | BaseFn(..) | Partial(..) => unreachable!(), + Process | Nothing => unreachable!(), + } + } +} + impl Value { pub fn show(&self) -> String { use Value::*; @@ -292,57 +351,71 @@ impl Value { } } - pub fn to_json(&self) -> Option { - use Value::*; - match self { - True | False | Number(..) => Some(self.show()), - String(string) => Some(string.escape_default().to_string()), - Interned(str) => Some(str.escape_default().to_string()), - Keyword(str) => Some(format!("\"{str}\"")), - List(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Tuple(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Dict(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some((key, value)) = members.next() { - let json = value.to_json()?; - joined = format!("\"{key}\":{json}") - } - for (key, value) in members { - let json = value.to_json()?; - joined = format!("{joined},\"{key}\": {json}"); - } - Some(format!("{{{joined}}}")) - } - not_serializable => { - println!("Cannot convert to json:"); - dbg!(not_serializable); - None - } - } - } + // pub fn to_js(&self) -> JsValue { + // use Value::*; + // match self { + // Nil => JsValue::NULL, + // True => JsValue::TRUE, + // False => JsValue::FALSE, + // Number(n) => JsValue::from_f64(*n), + // Interned(s) => JsValue::from_str(s), + // String(s) => JsValue::from_str(s.as_str()), + // Keyword(k) => JsValue::from_str(k), + // _ => todo!(), + // } + // } + + // pub fn to_json(&self) -> Option { + // use Value::*; + // match self { + // True | False | Number(..) => Some(self.show()), + // String(string) => Some(string.escape_default().to_string()), + // Interned(str) => Some(str.escape_default().to_string()), + // Keyword(str) => Some(format!("\"{str}\"")), + // List(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some(member) = members.next() { + // joined = member.to_json()?; + // } + // for member in members { + // let json = member.to_json()?; + // joined = format!("{joined},{json}"); + // } + // Some(format!("[{joined}]")) + // } + // Tuple(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some(member) = members.next() { + // joined = member.to_json()?; + // } + // for member in members { + // let json = member.to_json()?; + // joined = format!("{joined},{json}"); + // } + // Some(format!("[{joined}]")) + // } + // Dict(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some((key, value)) = members.next() { + // let json = value.to_json()?; + // joined = format!("\"{key}\":{json}") + // } + // for (key, value) in members { + // let json = value.to_json()?; + // joined = format!("{joined},\"{key}\": {json}"); + // } + // Some(format!("{{{joined}}}")) + // } + // not_serializable => { + // println!("Cannot convert to json:"); + // dbg!(not_serializable); + // None + // } + // } + // } pub fn stringify(&self) -> String { use Value::*; diff --git a/src/world.rs b/src/world.rs index 988255e..ec868b2 100644 --- a/src/world.rs +++ b/src/world.rs @@ -417,7 +417,11 @@ impl World { // TODO: if we have a panic, actually add the panic message to the console let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); - outbox.push(MsgOut::Complete(result)); + let result_msg = match result { + Ok(value) => MsgOut::Complete(Value::string(value.show())), + Err(_msg) => MsgOut::Error("Ludus panicked!".to_string()) + }; + outbox.push(result_msg); outbox } @@ -470,7 +474,7 @@ impl World { self.maybe_do_io().await; if self.kill_signal { let mut outbox = self.flush_buffers(); - outbox.push(MsgOut::Complete(Err(Panic::Str("ludus killed by user")))); + outbox.push(MsgOut::Error("Ludus killed by user".to_string())); do_io(outbox).await; return; }