diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index adba258..9e762fd 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1,6 +1,9 @@ &&& buffers: shared memory with Rust +& use types that are all either empty or any box console = [] box input = "" +box fetch_outbox = "" +box fetch_inbox = () & the very base: know something's type fn type { @@ -1244,7 +1247,7 @@ fn alive? { } fn link! { - "Creates a 'hard link' between two processes: if either one dies, they both do." + "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:enforce`, which causes a panic in one when the other dies. The default is `:report`." (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) (pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2) @@ -1260,7 +1263,43 @@ fn sleep! { (ms as :number) -> base :process (:sleep, ms) } +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch! { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> { + print! ("spawning fetch request in", self ()) + request_fetch! (pid, url) + }) + let out = receive { + (:reply, response) -> response + } + print! ("received response: {out}") + out + } +} + #{ + & completed actor functions self send spawn! @@ -1269,11 +1308,18 @@ fn sleep! { alive? flush! + & wip actor functions link! - + + & shared memory w/ rust console input + fetch_outbox + fetch_inbox + & a fetch fn + fetch! + abs abs add diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 4445ca5..2f5fe50 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1193,8 +1193,8 @@ We've got one bug to address in Firefox before I continue: After that: * [ ] implement other verbs beside `console`: - [x] `command` - - [ ] `input` - * [ ] js->rust->ludus buffer (in Rust code) + - [x] `input` + * [x] js->rust->ludus buffer (in Rust code) * [ ] ludus abstractions around this buffer (in Ludus code) - [ ] `fetch`--request & response * [ ] request: ludus->rust->js->net diff --git a/pkg/ludus.js b/pkg/ludus.js index e93e3ac..de045a1 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -13,7 +13,7 @@ let io_interval_id = null worker.onmessage = handle_messages -function handle_messages (e) { +async function handle_messages (e) { let msgs try { msgs = JSON.parse(e.data) @@ -42,6 +42,13 @@ function handle_messages (e) { } break } + case "Fetch": { + console.log("Main: ludus requests => ", msg.data) + const res = await fetch(msg.data, {mode: "cors"}) + const text = await res.text() + console.log("Main: js responds => ", text) + outbox.push({verb: "Fetch", data: [msg.data, res.status, text]}) + } } } } @@ -67,6 +74,7 @@ function start_io_polling () { export function run (source) { if (running) "TODO: handle this? should not be running" running = true + result = null code = source outbox = [{verb: "Run", data: source}] start_io_polling() diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0ee0693..329ab57 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 closure342_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_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 cc24b0e..671a2fc 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_22(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure342_externref_shim(arg0, arg1, arg2); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -325,6 +325,9 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)); + }, arguments) }; imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -343,7 +346,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -422,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7647 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 343, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper7695 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index d7ca997..a764218 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 f0ff0f2..e177a90 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 closure342_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_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 d2f19e7..e6e2b2c 100644 --- a/src/io.rs +++ b/src/io.rs @@ -31,7 +31,7 @@ fn make_json_payload(verb: &'static str, data: String) -> String { pub enum MsgOut { Console(Lines), Commands(Commands), - SlurpRequest(Url), + Fetch(Url), Complete(FinalValue), } @@ -46,7 +46,6 @@ impl MsgOut { 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()) @@ -57,7 +56,7 @@ impl MsgOut { let vals_json = format!("[{vals_json}]"); make_json_payload("Commands", vals_json) } - MsgOut::SlurpRequest(value) => { + MsgOut::Fetch(value) => { // TODO: do parsing here? // Right now, defer to fetch let url = value.to_json().unwrap(); @@ -77,7 +76,7 @@ impl MsgOut { #[serde(tag = "verb", content = "data")] pub enum MsgIn { Input(String), - Fetch(String, String, String), + Fetch(String, f64, String), Kill, Keyboard(Vec), } @@ -87,6 +86,7 @@ impl std::fmt::Display for MsgIn { match self { MsgIn::Input(str) => write!(f, "Input: {str}"), MsgIn::Kill => write!(f, "Kill"), + MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"), _ => todo!() } } @@ -96,11 +96,15 @@ impl MsgIn { pub fn to_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), - MsgIn::Fetch(url, status, string) => { + MsgIn::Fetch(url, status_f64, string) => { let url = Value::string(url); - let status = Value::keyword(status); - let string = Value::string(string); - let result_tuple = Value::tuple(vec![status, string]); + let status = Value::Number(status_f64); + let text = Value::string(string); + let result_tuple = if status_f64 == 200.0 { + Value::tuple(vec![Value::keyword("ok".to_string()), text]) + } else { + Value::tuple(vec![Value::keyword("err".to_string()), status]) + }; Value::tuple(vec![url, result_tuple]) } MsgIn::Kill => Value::Nothing, diff --git a/src/lib.rs b/src/lib.rs index b6ffe67..64fe9f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,9 +60,9 @@ fn prelude() -> HashMap<&'static str, Value> { .into_output_errors(); if !parse_errors.is_empty() { - println!("ERROR PARSING PRELUDE:"); - println!("{:?}", parse_errors); - panic!(); + log("ERROR PARSING PRELUDE:"); + log(format!("{:?}", parse_errors).as_str()); + panic!("parsing errors in prelude"); } let parsed = parsed.unwrap(); @@ -77,8 +77,9 @@ fn prelude() -> HashMap<&'static str, Value> { validator.validate(); if !validator.errors.is_empty() { - println!("VALIDATION ERRORS IN PRLUDE:"); - report_invalidation(validator.errors); + log("VALIDATION ERRORS IN PRLUDE:"); + // report_invalidation(validator.errors); + log(format!("{:?}", validator.errors).as_str()); panic!("validator errors in prelude"); } diff --git a/src/value.rs b/src/value.rs index 9618c67..f63b35c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -398,6 +398,21 @@ impl Value { } } + pub fn as_string(&self) -> Rc { + match self { + Value::String(str) => str.clone(), + Value::Interned(str) => Rc::new(str.to_string()), + _ => unreachable!("expected value to be a string"), + } + } + + pub fn as_tuple(&self) -> Rc> { + match self { + Value::Tuple(members) => members.clone(), + _ => unreachable!("expected value to be a tuple"), + } + } + pub fn string(str: String) -> Value { Value::String(Rc::new(str)) } diff --git a/src/world.rs b/src/world.rs index cbbf094..15a8379 100644 --- a/src/world.rs +++ b/src/world.rs @@ -254,8 +254,8 @@ impl Zoo { pub struct Buffers { console: Value, commands: Value, - // fetch_outbox: Value, - // fetch_inbox: Value, + fetch_out: Value, + fetch_in: Value, input: Value, } @@ -264,8 +264,8 @@ impl Buffers { Buffers { console: prelude.get("console").unwrap().clone(), commands: prelude.get("turtle_commands").unwrap().clone(), - // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), - // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), + fetch_out: prelude.get("fetch_outbox").unwrap().clone(), + fetch_in: prelude.get("fetch_inbox").unwrap().clone(), input: prelude.get("input").unwrap().clone(), } } @@ -282,12 +282,13 @@ impl Buffers { self.commands.as_box() } - // pub fn fetch_outbox (&self) -> Rc> { - // self.fetch_outbox.as_box() - // } - // pub fn fetch_inbox (&self) -> Rc> { - // self.fetch_inbox.as_box() - // } + pub fn fetch_out (&self) -> Rc> { + self.fetch_out.as_box() + } + + pub fn fetch_in (&self) -> Rc> { + self.fetch_in.as_box() + } } @@ -369,16 +370,34 @@ impl World { if let Some(commands) = self.flush_commands() { outbox.push(commands); } + if let Some(fetch) = self.make_fetch_happen() { + outbox.push(fetch); + } outbox } + fn make_fetch_happen(&self) -> Option { + let out = self.buffers.fetch_out(); + let working = RefCell::new(Value::Interned("")); + out.swap(&working); + let working = working.borrow(); + if working.as_string().is_empty() { + None + } else { + Some(MsgOut::Fetch(working.clone())) + } + } + fn flush_console(&self) -> Option { let console = self.buffers.console(); let working_copy = RefCell::new(Value::new_list()); console.swap(&working_copy); let working_value = working_copy.borrow(); - if working_value.as_list().is_empty() { return None; } - Some(MsgOut::Console(working_value.clone())) + if working_value.as_list().is_empty() { + None + } else { + Some(MsgOut::Console(working_value.clone())) + } } fn flush_commands(&self) -> Option { @@ -422,11 +441,17 @@ impl World { input.swap(&working); } + fn fetch_reply(&mut self, reply: Value) { + let inbox_rc = self.buffers.fetch_in(); + inbox_rc.replace(reply); + } + fn fill_buffers(&mut self, inbox: Vec) { for msg in inbox { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, + MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()), _ => todo!() } }