From 48342ba4eafe9d3602258a5253d2abea2603827c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 20:41:29 -0400 Subject: [PATCH] make progress, I guess --- assets/test_prelude.ld | 2 -- may_2025_thoughts.md | 10 ++++++++ sandbox.ld | 34 ++++++++++----------------- src/ast.rs | 5 ++-- src/chunk.rs | 2 +- src/compiler.rs | 50 +++++++++++++++++++++++++++++++++++++-- src/op.rs | 8 +++++++ src/validator.rs | 7 +++++- src/vm.rs | 53 +++++++++++++++++++++++++++++++----------- 9 files changed, 127 insertions(+), 44 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 5e24eef..71b29a9 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1261,13 +1261,11 @@ fn sleep! { #{ self send - msgs spawn! yield! sleep! alive? flush! - flush_i! link! diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index bc7eb26..33c43cb 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1065,3 +1065,13 @@ yield! () fn id (x) -> x receive(id) ``` + +#### some time later +I've started work on `receive`. +I've got all the stuff wired up, and it seems to all work (and was pretty straightforward!). +EXCEPT: I've got a difficult off-by-one error. +The problem is being in a receive in a tight loop/tail call, where the ip doesn't advance past the tail call back to the top of the function. +Jumping back to the beginning of the loop advances the message counter by one. +Basically, after the first time we've matched, we keep skipping item 0. +So: what I need to do is to figure out the right order of operations for. +This is just stepwise logic, and some titchy state management. diff --git a/sandbox.ld b/sandbox.ld index 8886bdd..1427760 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,35 +1,25 @@ -fn receive (receiver) -> { - print! ("receiving in", self (), "with msgs", msgs()) - if empty? (msgs ()) - then {yield! (); receive (receiver)} - else do msgs () > first > receiver -} - -fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with { +fn foo (val) -> receive { (:report) -> { print! ("LUDUS SAYS ==> value is {val}") - flush! () - foo? (val) + foo (val) } (:set, x) -> { print! ("LUDUS SAYS ==> foo! was {val}, now is {x}") - flush! () - foo? (x) + foo (x) } (:get, pid) -> { print! ("LUDUS SAYS ==> value is {val}") send (pid, (:response, val)) - flush! () - foo? (val) + foo (val) } - x -> print! ("LUDUS SAYS ==> no match, got {x}") -}) +} -let foo = spawn! (fn () -> foo? (42)) -print! (foo) -send (foo, (:set, 23)) +let fooer = spawn! (fn () -> foo (42)) +print! (fooer) +send (fooer, (:set, 23)) yield! () -send (foo, (:get, self ())) +send (fooer, (:get, self ())) yield! () -fn id (x) -> x -receive(id) + +flush! () + diff --git a/src/ast.rs b/src/ast.rs index e15dbf5..628dbd6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,4 +1,3 @@ -use crate::parser::StringMatcher; use crate::spans::*; use std::fmt; @@ -164,7 +163,7 @@ impl Ast { .collect::>() .join(" ") ), - When(clauses) => format!( + When(clauses) | Receive(clauses) => format!( "when {{\n {}\n}}", clauses .iter() @@ -321,7 +320,7 @@ impl fmt::Display for Ast { .collect::>() .join("\n") ), - When(clauses) => write!( + When(clauses) | Receive(clauses) => write!( f, "When: [{}]", clauses diff --git a/src/chunk.rs b/src/chunk.rs index 7dfba57..22d7e3b 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -37,7 +37,7 @@ impl Chunk { | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue => { + | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index a59581a..0a161b5 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -750,7 +750,7 @@ impl Compiler { self.patch_jump(jump_idx, self.len() - jump_idx - 3); } Splattern(patt) => self.visit(patt), - InterpolatedPattern(parts, _) => { + InterpolatedPattern(parts) => { // println!("An interpolated pattern of {} parts", parts.len()); let mut pattern = "".to_string(); let mut words = vec![]; @@ -1038,6 +1038,52 @@ impl Compiler { self.emit_op(Op::Load); self.stack_depth += 1; } + Receive(clauses) => { + let tail_pos = self.tail_pos; + let receive_begin = self.len(); + self.emit_op(Op::LoadMessage); + self.stack_depth += 1; + let stack_depth = self.stack_depth; + let mut jump_idxes = vec![]; + let mut clauses = clauses.iter(); + while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() { + self.tail_pos = false; + let mut no_match_jumps = vec![]; + self.enter_scope(); + // TODO: should this be self.reset_match()? + self.match_depth = 0; + self.visit(pattern); + no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch)); + if guard.is_some() { + let guard_expr: &'static Spanned = + Box::leak(Box::new(guard.clone().unwrap())); + self.visit(guard_expr); + no_match_jumps.push(self.stub_jump(Op::JumpIfFalse)); + } + self.tail_pos = tail_pos; + self.emit_op(Op::MatchMessage); + self.visit(body); + self.store(); + self.leave_scope(); + self.pop_n(self.stack_depth - stack_depth); + jump_idxes.push(self.stub_jump(Op::Jump)); + for idx in no_match_jumps { + self.patch_jump(idx, self.len() - idx - 3); + } + } + // TODO: get the next message + self.emit_op(Op::NextMessage); + // TODO: jump back to the "get a message" instruction + let jump_back = self.stub_jump(Op::JumpBack); + self.patch_jump(jump_back, self.len() - receive_begin - 3); + + for idx in jump_idxes { + self.patch_jump(idx, self.len() - idx - 3); + } + self.pop_n(self.stack_depth - stack_depth); + self.emit_op(Op::Load); + self.stack_depth += 1; + } MatchClause(..) => unreachable!(), Fn(name, body, doc) => { let is_anon = name.is_empty(); @@ -1258,7 +1304,7 @@ impl Compiler { let jump_back = self.stub_jump(Op::JumpBack); // set jump points self.patch_jump(jump_back, self.len() - repeat_begin - 2); - self.patch_jump(jiz_idx, self.len() - repeat_begin - 4); + self.patch_jump(jiz_idx, self.len() - jiz_idx - 3); self.pop(); self.emit_constant(Value::Nil); self.tail_pos = tail_pos; diff --git a/src/op.rs b/src/op.rs index bfe0252..278a6a4 100644 --- a/src/op.rs +++ b/src/op.rs @@ -89,6 +89,10 @@ pub enum Op { GetUpvalue, Msg, + + LoadMessage, + NextMessage, + MatchMessage, // Inc, // Dec, // Gt, @@ -220,6 +224,10 @@ impl std::fmt::Display for Op { SetUpvalue => "set_upvalue", GetUpvalue => "get_upvalue", + + LoadMessage => "load_message", + NextMessage => "next_message", + MatchMessage => "clear_message", }; write!(f, "{rep}") } diff --git a/src/validator.rs b/src/validator.rs index 7833365..22a2e62 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -364,6 +364,11 @@ impl<'a> Validator<'a> { self.visit(clause); } } + Receive(clauses) => { + for clause in clauses { + self.visit(clause); + } + } FnDeclaration(name) => { let tailpos = self.status.tail_position; self.status.tail_position = false; @@ -520,7 +525,7 @@ impl<'a> Validator<'a> { self.bind(name.to_string()); } }, - InterpolatedPattern(parts, _) => { + InterpolatedPattern(parts) => { for (part, span) in parts { if let StringPart::Word(name) = part { self.span = span; diff --git a/src/vm.rs b/src/vm.rs index 118b6a1..c496947 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -93,6 +93,7 @@ pub struct Creature { last_code: usize, pub pid: &'static str, pub mbx: VecDeque, + msg_idx: usize, pub reductions: usize, pub zoo: Rc>, pub r#yield: bool, @@ -141,6 +142,7 @@ impl Creature { mbx: VecDeque::new(), reductions: 0, r#yield: false, + msg_idx: 0, } } @@ -274,18 +276,6 @@ impl Creature { }; match *msg { "self" => self.push(Value::Keyword(self.pid)), - "msgs" => { - let msgs = self.mbx.iter().cloned().collect::>(); - let msgs = Vector::from(msgs); - println!( - "delivering messages: {}", - msgs.iter() - .map(|x| x.show()) - .collect::>() - .join(" | ") - ); - self.push(Value::List(Box::new(msgs))); - } "send" => { let Value::Keyword(pid) = args[1] else { return self.panic("malformed pid"); @@ -331,9 +321,18 @@ impl Creature { } "link" => todo!(), "flush" => { + let msgs = self.mbx.iter().cloned().collect::>(); + let msgs = Vector::from(msgs); + println!( + "delivering messages: {}", + msgs.iter() + .map(|x| x.show()) + .collect::>() + .join(" | ") + ); self.mbx = VecDeque::new(); println!("flushing messages in {}", self.pid); - self.push(Value::Keyword("ok")); + self.push(Value::List(Box::new(msgs))); } "sleep" => { println!("sleeping {} for {}", self.pid, args[1]); @@ -1220,6 +1219,34 @@ impl Creature { unreachable!(); } } + NextMessage => { + self.msg_idx += 1; + } + LoadMessage => { + println!("loading message {} in {}", self.msg_idx, self.pid); + match self.mbx.get(self.msg_idx) { + Some(msg) => { + println!("loaded message: {msg}"); + self.push(msg.clone()) + } + None => { + println!("no more messages in {}", self.pid); + self.msg_idx = 0; + self.r#yield = true; + } + } + } + MatchMessage => { + self.msg_idx = 0; + let matched = self.mbx.remove(self.msg_idx).unwrap(); + println!( + "matched in {}: @idx {}, msg {matched}", + self.pid, self.msg_idx + ); + } + ClearMessage => { + self.msg_idx = 0; + } } } }