diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 169f8b4..6ce6794 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1247,10 +1247,10 @@ fn send { } } -fn spawn! { - "Spawns a new process running the function passed in." - (f as :fn) -> base :process (:spawn, f) -} +& fn spawn! { +& "Spawns a new process running the function passed in." +& (f as :fn) -> base :process (:spawn, f) +& } fn yield! { "Forces a process to yield." @@ -1303,7 +1303,7 @@ 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 () -> request_fetch! (pid, url)) + spawn! request_fetch! (pid, url) receive { (:reply, response) -> response } @@ -1312,7 +1312,7 @@ fn fetch { fn input_reader! { (pid as :keyword) -> { - if not (unbox (input)) + if do input > unbox > not then { yield! () input_reader! (pid) @@ -1328,7 +1328,7 @@ fn read_input { "Waits until there is input in the input buffer, and returns it once there is." () -> { let pid = self () - spawn! (fn () -> input_reader! (pid)) + spawn! input_reader! (pid) receive { (:reply, response) -> response } @@ -1339,7 +1339,7 @@ fn read_input { & completed actor functions self send - spawn! + & spawn! yield! sleep! alive? diff --git a/pkg/ludus.js b/pkg/ludus.js index 29195fb..0e0b05c 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -13,18 +13,6 @@ let ready = false let io_interval_id = null let keys_down = new Set(); -function reset_ludus () { - outbox = [] - ludus_console = "" - ludus_commands = [] - ludus_result = null - code = null - running = false - ready = false - io_interval_id = null - keys_down = new Set() -} - worker.onmessage = handle_messages async function handle_messages (e) { @@ -99,17 +87,28 @@ function io_poller () { } function start_io_polling () { - io_interval_id = setInterval(io_poller, 10) + io_interval_id = setInterval(io_poller, 100) } // runs a ludus script; does not return the result // the result must be explicitly polled with `result` export function run (source) { if (running || ready) { + console.log("Main: received bouncy `run` call"); return "TODO: handle this? should not be running" } + // start the vm worker.postMessage([{verb: "Run", data: source}]) - reset_ludus() + // reset all my state + outbox = [] + ludus_console = "" + ludus_commands = [] + ludus_result = null + code = null + running = true + ready = false + keys_down = new Set(); + // start the polling loop loop start_io_polling() } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..b37dd12 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8087 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 48d0857..fb31310 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ diff --git a/pkg/worker.js b/pkg/worker.js index ac860a3..fc935f0 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -18,7 +18,7 @@ export function io (out) { resolve(JSON.stringify(e.data)) } // cancel the response if it takes too long - setTimeout(() => reject("io took too long"), 500) + setTimeout(() => reject("io took too long"), 1000) }) } diff --git a/src/ast.rs b/src/ast.rs index 0e0e680..3554833 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -51,6 +51,7 @@ pub enum Ast { WhenClause(Box>, Box>), Match(Box>, Vec>), Receive(Vec>), + Spawn(Box>), MatchClause( Box>, Box>>, @@ -210,6 +211,7 @@ impl Ast { .join(" > ") ) } + Spawn(body) => format!("spawn! {}", body.0.show()), Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), Splat(word) => format!("...{}", word), Splattern(pattern) => format!("...{}", pattern.0.show()), @@ -380,6 +382,7 @@ impl fmt::Display for Ast { .join(" > ") ) } + Spawn(body) => write!(f, "spawn: {}", body.0), Repeat(_times, _body) => todo!(), Splat(word) => { write!(f, "splat: {}", word) diff --git a/src/chunk.rs b/src/chunk.rs index c2b2159..085e040 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -43,7 +43,7 @@ impl Chunk { | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage - | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => { + | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => { console_log!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 624a0a8..6a0ced7 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1059,6 +1059,54 @@ impl Compiler { self.emit_op(Op::Load); self.stack_depth += 1; } + Spawn(body) => { + // we compile this identically to a function + // but it's a single, nullary fn + // no match at the beginning, just go + let name = "_spawned"; + let mut compiler = Compiler::new( + body, + self.input, + self.src, + self.depth + 1, + self.chunk.env.clone(), + self.debug, + ); + compiler.tail_pos = true; + compiler.visit(body); + compiler.store(); + compiler.scope_depth -= 1; + while let Some(binding) = compiler.bindings.last() { + if binding.depth > compiler.scope_depth { + compiler.bindings.pop(); + } else { + break; + } + } + compiler.pop_n(compiler.stack_depth); + compiler.stack_depth = 0; + compiler.emit_op(Op::Return); + let arities = vec![0]; + let chunks = vec![compiler.chunk]; + let lfn = crate::value::LFn::Defined { + name, + doc: None, + arities, + chunks, + splat: 0, + closed: RefCell::new(vec![]), + }; + + let the_fn = Value::Fn(Rc::new(lfn)); + + self.emit_constant(the_fn); + for upvalue in compiler.upvalues { + self.resolve_binding(upvalue); + self.emit_op(Op::SetUpvalue); + self.stack_depth -= 1; + } + self.emit_op(Op::Spawn); + } Receive(clauses) => { let tail_pos = self.tail_pos; self.emit_op(Op::ClearMessage); diff --git a/src/io.rs b/src/io.rs index 261f7e4..6054a2f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -82,10 +82,13 @@ pub async fn send_err_to_ludus_console(msg: String) { pub async fn do_io (msgs: Vec) -> Vec { let json = serde_json::to_string(&msgs).unwrap(); - let inbox = io (json).await; + let inbox = io(json).await; let inbox = match inbox { Ok(msgs) => msgs, - Err(_) => return vec![] + Err(errs) => { + console_log!("error receiving messages in io; {:?}", errs); + return vec![]; + } }; let inbox = inbox.as_string().expect("response should be a string"); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); diff --git a/src/lexer.rs b/src/lexer.rs index c9090d6..a16d4f0 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -76,7 +76,7 @@ pub fn lexer( "nil" => Token::Nil, // todo: hard code these as type constructors "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => { + | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" | "spawn!" => { Token::Reserved(word) } _ => Token::Word(word), diff --git a/src/op.rs b/src/op.rs index d2e37ff..55ec459 100644 --- a/src/op.rs +++ b/src/op.rs @@ -106,6 +106,7 @@ pub enum Op { MatchMessage, ClearMessage, SendMethod, + Spawn, LoadScrutinee, } @@ -208,6 +209,7 @@ impl std::fmt::Display for Op { MatchMessage => "match_message", ClearMessage => "clear_message", SendMethod => "send_method", + Spawn => "spawn", LoadScrutinee => "load_scrutinee", }; diff --git a/src/parser.rs b/src/parser.rs index 0d355cd..072eff3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -512,6 +512,10 @@ where .then(block.clone()) .map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span())); + let spawn = just(Token::Reserved("spawn!")) + .ignore_then(nonbinding.clone()) + .map_with(|body, e| (Spawn(Box::new(body)), e.span())); + let fn_guarded = tuple_pattern .clone() .then_ignore(just(Token::Reserved("if"))) @@ -590,6 +594,7 @@ where .or(conditional) .or(block) .or(repeat) + .or(spawn) .or(r#loop) .labelled("nonbinding expression"), ); diff --git a/src/validator.rs b/src/validator.rs index 6af0d09..fdd73cf 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -164,6 +164,7 @@ impl<'a> Validator<'a> { let root = self.ast; match root { Error => unreachable!(), + Spawn(body) => self.visit(body), Word(name) | Splat(name) => { if !self.resolved(name) { self.err(format!("unbound name `{name}`")) diff --git a/src/vm.rs b/src/vm.rs index dc79555..3898e83 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1260,6 +1260,12 @@ impl Creature { LoadScrutinee => { self.scrutinee = Some(self.peek().clone()); } + Spawn => { + let f = self.pop(); + let proc = Creature::spawn(f, self.zoo.clone(), self.debug); + let id = self.zoo.borrow_mut().put(proc); + self.push(Value::Keyword(id)); + } } } }