use serde to serialize the things

This commit is contained in:
Scott Richmond 2025-07-03 20:22:11 -04:00
parent 9f9f59b33b
commit c6709bb2e8
10 changed files with 193 additions and 123 deletions

View File

@ -18,3 +18,4 @@ serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0" serde_json = "1.0"
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
struct_scalpel = "0.1.1" struct_scalpel = "0.1.1"
serde-wasm-bindgen = "0.6.5"

View File

@ -32,10 +32,18 @@ async function handle_messages (e) {
outbox = [] outbox = []
break break
} }
case "Error": {
console.log("Main: ludus errored with => ", msg.data)
running = false
ready = false
outbox = []
break
}
// TODO: do more than report these // TODO: do more than report these
case "Console": { case "Console": {
console.log("Main: ludus says => ", msg.data) let new_lines = msg.data.join("\n");
ludus_console = ludus_console + msg.data console.log("Main: ludus says => ", new_lines)
ludus_console = ludus_console + new_lines
break break
} }
case "Commands": { case "Commands": {

4
pkg/rudus.d.ts vendored
View File

@ -14,8 +14,8 @@ export interface InitOutput {
readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export_6: WebAssembly.Table; readonly __wbindgen_export_6: WebAssembly.Table;
readonly closure347_externref_shim: (a: number, b: number, c: any) => void; readonly closure305_externref_shim: (a: number, b: number, c: any) => void;
readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly closure329_externref_shim: (a: number, b: number, c: any, d: any) => void;
readonly __wbindgen_start: () => void; readonly __wbindgen_start: () => void;
} }

View File

@ -240,13 +240,13 @@ function _assertNum(n) {
function __wbg_adapter_20(arg0, arg1, arg2) { function __wbg_adapter_20(arg0, arg1, arg2) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure347_externref_shim(arg0, arg1, arg2); wasm.closure305_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_50(arg0, arg1, arg2, arg3) { function __wbg_adapter_50(arg0, arg1, arg2, arg3) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); wasm.closure329_externref_shim(arg0, arg1, arg2, arg3);
} }
async function __wbg_load(module, imports) { async function __wbg_load(module, imports) {
@ -425,8 +425,8 @@ function __wbg_get_imports() {
_assertBoolean(ret); _assertBoolean(ret);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper7945 = function() { return logError(function (arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper7885 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_20); const ret = makeMutClosure(arg0, arg1, 306, __wbg_adapter_20);
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {

Binary file not shown.

View File

@ -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_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_export_6: WebAssembly.Table; export const __wbindgen_export_6: WebAssembly.Table;
export const closure347_externref_shim: (a: number, b: number, c: any) => void; export const closure305_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 closure329_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void; export const __wbindgen_start: () => void;

View File

@ -18,62 +18,54 @@ extern "C" {
fn log(s: String); fn log(s: String);
} }
type Commands = Value; // expect a list of values type Url = Value; // expect a string
type Url = Value; // expect a string representing a URL type Commands = Value; // expect a list of command tuples
type FinalValue = Result<Value, Panic>;
fn make_json_payload(verb: &'static str, data: String) -> String { #[derive(Debug, Clone, PartialEq, Serialize)]
format!("{{\"verb\":\"{verb}\",\"data\":{data}}}") #[serde(tag = "verb", content = "data")]
}
#[derive(Debug, Clone, PartialEq)]
pub enum MsgOut { pub enum MsgOut {
Console(Value), Console(Value),
Commands(Commands), Commands(Commands),
Fetch(Url), Fetch(Url),
Complete(FinalValue), Complete(Value),
Error(String),
Ready 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 { // impl MsgOut {
pub fn to_json(&self) -> String { // pub fn to_json(&self) -> String {
match self { // match self {
MsgOut::Complete(value) => match value { // MsgOut::Complete(value) => match value {
Ok(value) => { // Ok(value) => {
make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) // make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap())
}, // },
Err(_) => make_json_payload("Complete", "\"null\"".to_string()) // Err(_) => make_json_payload("Complete", "\"null\"".to_string())
}, // },
MsgOut::Commands(commands) => { // MsgOut::Commands(commands) => {
let commands = commands.as_list(); // let commands = commands.as_list();
let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::<Vec<_>>().join(","); // let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::<Vec<_>>().join(",");
let vals_json = format!("[{vals_json}]"); // let vals_json = format!("[{vals_json}]");
make_json_payload("Commands", vals_json) // make_json_payload("Commands", vals_json)
} // }
MsgOut::Fetch(value) => { // MsgOut::Fetch(value) => {
// TODO: do parsing here? // // TODO: do parsing here?
// Right now, defer to fetch // // Right now, defer to fetch
let url = value.to_json().unwrap(); // let url = value.to_json().unwrap();
make_json_payload("Fetch", url) // make_json_payload("Fetch", url)
} // }
MsgOut::Console(lines) => { // MsgOut::Console(lines) => {
let lines = lines.as_list(); // let lines = lines.as_list();
let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::<Vec<_>>().join("\\n"); // let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::<Vec<_>>().join("\\n");
let json_lines = format!("\"{json_lines}\""); // let json_lines = format!("\"{json_lines}\"");
make_json_payload("Console", json_lines) // make_json_payload("Console", json_lines)
} // }
MsgOut::Ready => { // MsgOut::Ready => {
make_json_payload("Ready", "\"null\"".to_string()) // make_json_payload("Ready", "\"null\"".to_string())
} // }
} // }
} // }
} // }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "verb", content = "data")] #[serde(tag = "verb", content = "data")]
@ -124,18 +116,12 @@ impl MsgIn {
pub async fn send_err_to_ludus_console(msg: String) { pub async fn send_err_to_ludus_console(msg: String) {
log(msg.clone()); log(msg.clone());
let console_msg = Value::string(msg); do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await;
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;
} }
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> { pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::<Vec<_>>().join(",")); let json = serde_json::to_string(&msgs).unwrap();
let inbox = io (outbox).await; let inbox = io (json).await;
// if our request dies, make sure we return back to the event loop // if our request dies, make sure we return back to the event loop
let inbox = match inbox { let inbox = match inbox {
Ok(msgs) => msgs, Ok(msgs) => msgs,

View File

@ -1,8 +1,6 @@
use rudus::value::Value; use rudus::value::Value;
use std::env; use std::env;
use struct_scalpel::print_dissection_info;
pub fn main() { pub fn main() {
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
print_dissection_info::<Value>();
} }

View File

@ -1,9 +1,10 @@
use crate::base::BaseFn; use crate::base::BaseFn;
use crate::chunk::Chunk; use crate::chunk::Chunk;
use imbl::{HashMap, Vector}; use imbl::{HashMap, Vector};
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use struct_scalpel::Dissectible; use wasm_bindgen::JsValue;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum LFn { pub enum LFn {
@ -130,6 +131,19 @@ impl std::fmt::Display for Key {
} }
} }
impl Serialize for Key {
fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
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 { impl Key {
pub fn to_value(&self) -> Value { pub fn to_value(&self) -> Value {
match self { match self {
@ -149,7 +163,7 @@ impl Key {
} }
} }
#[derive(Clone, Debug, Dissectible)] #[derive(Clone, Debug)]
pub enum Value { pub enum Value {
Nothing, Nothing,
Nil, Nil,
@ -247,6 +261,51 @@ impl std::fmt::Display for Value {
} }
} }
impl Serialize for Value {
fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
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 { impl Value {
pub fn show(&self) -> String { pub fn show(&self) -> String {
use Value::*; use Value::*;
@ -292,57 +351,71 @@ impl Value {
} }
} }
pub fn to_json(&self) -> Option<String> { // pub fn to_js(&self) -> JsValue {
use Value::*; // use Value::*;
match self { // match self {
True | False | Number(..) => Some(self.show()), // Nil => JsValue::NULL,
String(string) => Some(string.escape_default().to_string()), // True => JsValue::TRUE,
Interned(str) => Some(str.escape_default().to_string()), // False => JsValue::FALSE,
Keyword(str) => Some(format!("\"{str}\"")), // Number(n) => JsValue::from_f64(*n),
List(members) => { // Interned(s) => JsValue::from_str(s),
let mut joined = "".to_string(); // String(s) => JsValue::from_str(s.as_str()),
let mut members = members.iter(); // Keyword(k) => JsValue::from_str(k),
if let Some(member) = members.next() { // _ => todo!(),
joined = member.to_json()?; // }
} // }
for member in members {
let json = member.to_json()?; // pub fn to_json(&self) -> Option<String> {
joined = format!("{joined},{json}"); // use Value::*;
} // match self {
Some(format!("[{joined}]")) // True | False | Number(..) => Some(self.show()),
} // String(string) => Some(string.escape_default().to_string()),
Tuple(members) => { // Interned(str) => Some(str.escape_default().to_string()),
let mut joined = "".to_string(); // Keyword(str) => Some(format!("\"{str}\"")),
let mut members = members.iter(); // List(members) => {
if let Some(member) = members.next() { // let mut joined = "".to_string();
joined = member.to_json()?; // let mut members = members.iter();
} // if let Some(member) = members.next() {
for member in members { // joined = member.to_json()?;
let json = member.to_json()?; // }
joined = format!("{joined},{json}"); // for member in members {
} // let json = member.to_json()?;
Some(format!("[{joined}]")) // joined = format!("{joined},{json}");
} // }
Dict(members) => { // Some(format!("[{joined}]"))
let mut joined = "".to_string(); // }
let mut members = members.iter(); // Tuple(members) => {
if let Some((key, value)) = members.next() { // let mut joined = "".to_string();
let json = value.to_json()?; // let mut members = members.iter();
joined = format!("\"{key}\":{json}") // if let Some(member) = members.next() {
} // joined = member.to_json()?;
for (key, value) in members { // }
let json = value.to_json()?; // for member in members {
joined = format!("{joined},\"{key}\": {json}"); // let json = member.to_json()?;
} // joined = format!("{joined},{json}");
Some(format!("{{{joined}}}")) // }
} // Some(format!("[{joined}]"))
not_serializable => { // }
println!("Cannot convert to json:"); // Dict(members) => {
dbg!(not_serializable); // let mut joined = "".to_string();
None // 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 { pub fn stringify(&self) -> String {
use Value::*; use Value::*;

View File

@ -417,7 +417,11 @@ impl World {
// TODO: if we have a panic, actually add the panic message to the console // TODO: if we have a panic, actually add the panic message to the console
let result = self.active_result().clone().unwrap(); let result = self.active_result().clone().unwrap();
self.result = Some(result.clone()); 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 outbox
} }
@ -470,7 +474,7 @@ impl World {
self.maybe_do_io().await; self.maybe_do_io().await;
if self.kill_signal { if self.kill_signal {
let mut outbox = self.flush_buffers(); 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; do_io(outbox).await;
return; return;
} }