use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize}; use crate::value::Value; use crate::vm::Panic; use imbl::Vector; use std::rc::Rc; #[wasm_bindgen(module = "/pkg/worker.js")] extern "C" { #[wasm_bindgen(catch)] async fn io (output: String) -> Result; } #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: String); } type Lines = Value; // expect a list of values type Commands = Value; // expect a list of values type Url = Value; // expect a string representing a URL type FinalValue = Result; fn make_json_payload(verb: &'static str, data: String) -> String { format!("{{\"verb\":\"{verb}\",\"data\":{data}}}") } #[derive(Debug, Clone, PartialEq)] pub enum MsgOut { Console(Lines), Commands(Commands), SlurpRequest(Url), Complete(FinalValue), } 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) => { log(format!("value is: {}", value.show())); 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::SlurpRequest(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.stringify()).collect::>().join("\\n"); let json_lines = format!("\"{json_lines}\""); make_json_payload("Console", json_lines) } } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "verb", content = "data")] pub enum MsgIn { Input(String), Fetch(String, String, String), Kill, Keyboard(Vec), } impl std::fmt::Display for MsgIn { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { MsgIn::Input(str) => write!(f, "Input: {str}"), MsgIn::Kill => write!(f, "Kill"), _ => todo!() } } } impl MsgIn { pub fn to_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), MsgIn::Fetch(url, status, string) => { let url = Value::string(url); let status = Value::keyword(status); let string = Value::string(string); let result_tuple = Value::tuple(vec![status, string]); Value::tuple(vec![url, result_tuple]) } MsgIn::Kill => Value::Nothing, MsgIn::Keyboard(downkeys) => { let mut vector = Vector::new(); for key in downkeys { vector.push_back(Value::String(Rc::new(key))); } Value::List(Box::new(vector)) } } } } 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; // if our request dies, make sure we return back to the event loop let inbox = match inbox { Ok(msgs) => msgs, Err(_) => return vec![] }; let inbox = inbox.as_string().expect("response should be a string"); log(format!("got a message: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { log("ludus received messages".to_string()); for msg in inbox.iter() { log(format!("{}", msg)); } } inbox }