From 4dd47dd56c0cb708f23e2182f55ae20156606a6b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 11:38:45 -0400 Subject: [PATCH] save work --- may_2025_thoughts.md | 221 ++++++++++++++++++++++++++----------------- src/vm.rs | 26 ++--- 2 files changed, 141 insertions(+), 106 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index f9b32b1..323f00a 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -142,12 +142,12 @@ A few thoughts: * That will get me a lot of the way there. What's left after that which might be challenging? - [x] string interpolation - [x] splats - - [ ] splatterns + - [x] splatterns - [x] string patterns - [x] partial application - - [ ] tail calls - - [ ] stack traces in panics - - [ ] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough." + - [x] tail calls + - [-] stack traces in panics + - [-] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough." * After that, we're in integration hell: taking this thing and putting it together for Computer Class 1. Other things that I want (e.g., `test` forms) are for later on. * There's then a whole host of things I'll need to get done for CC2: - some kind of actual parsing strategy (that's good enough for "Dissociated Press"/Markov chains) @@ -243,41 +243,41 @@ To reiterate the punch list that *I would have needed for Computer Class 1*: * [x] jump instructions need 16 bits of operand - Whew, that took longer than I expected * [x] splatterns - - [ ] validator should ensure splatterns are the longest patterns in a form -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others + - [-] validator should ensure splatterns are the longest patterns in a form +* [-] improve validator + - [-] Tuples may not be longer than n members + - [-] Loops may not have splatterns + - [-] Identify others * [x] add guards to loop forms * [x] check loop forms against function calls: do they still work the way we want them to? * [x] tail call elimination * [x] stack traces in panics -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics -* [ ] getting to prelude - - [ ] `base` should load into Prelude - - [ ] prelude should run properly - - [ ] prelude should be loaded into every context -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases +* [-] actually good error messages + - [-] parsing + - [-] my memory is that validator messages are already good? + - [-] panics, esp. no match panics +* [-] getting to prelude + - [-] `base` should load into Prelude + - [-] prelude should run properly + - [-] prelude should be loaded into every context +* [-] packaging things up + - [-] add a `to_json` method for values + - [-] teach Rudus to speak our protocols (stdout and turtle graphics) + - [-] there should be a Rust function that takes Ludus source and returns valid Ludus status json + - [-] compile Rust to WASM + - [-] wire Rust-based WASM into JS + - [-] FINALLY, test Rudus against Ludus test cases So this is the work of the week of June 16, maybe? Just trying to get a sense of what needs to happen for CC2: -* [ ] Actor model (objects, Spacewar!) -* [ ] Animation hooked into the web frontend (Spacewar!) -* [ ] Text input (Spacewar!) - - [ ] Makey makey for alternate input? -* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) -* [ ] Finding corpuses for Dissociated Press +* [x] Actor model (objects, Spacewar!) +* [-] Animation hooked into the web frontend (Spacewar!) +* [-] Text input (Spacewar!) + - [-] Makey makey for alternate input? +* [-] Saving and loading data into Ludus (perceptrons, dissociated press) +* [-] Finding corpuses for Dissociated Press ### Final touches on semantics, or lots of bugs #### 2025-06-19 @@ -309,32 +309,32 @@ So this is my near-term TODO: - [x] `base` should load into Prelude - [x] write a mock prelude with a few key functions from real prelude - [x] a prelude should be loaded into every context - - [ ] the full prelude should run properly -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases + - [?] the full prelude should run properly +* [x] packaging things up + - [x] add a `to_json` method for values + - [x] teach Rudus to speak our protocols (stdout and turtle graphics) + - [x] there should be a Rust function that takes Ludus source and returns valid Ludus status json + - [x] compile Rust to WASM + - [x] wire Rust-based WASM into JS + - [-] FINALLY, test Rudus against Ludus test cases And then: quality of life improvements: -* [ ] refactor messes - - [ ] The compiler should abstract over some of the very titchy bytecode instruction code - - [ ] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules - - [ ] Identify code smells - - [ ] Fix some of them -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others - - [ ] Splats in functions must be the same arity, and greater than any explicit arity -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics - * [ ] panics should be able to refernce the line number where they fail - * [ ] that suggests that we need a mapping from bytecodes to AST nodes +* [-] refactor messes + - [x] The compiler should abstract over some of the very titchy bytecode instruction code + - [x] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules + - [x] Identify code smells + - [x] Fix some of them +* [-] improve validator + - [-] Tuples may not be longer than n members + - [-] Loops may not have splatterns + - [-] Identify others + - [-] Splats in functions must be the same arity, and greater than any explicit arity +* [-] actually good error messages + - [-] parsing + - [-] my memory is that validator messages are already good? + - [-] panics, esp. no match panics + * [-] panics should be able to refernce the line number where they fail + * [-] that suggests that we need a mapping from bytecodes to AST nodes * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. ### Bugs discovered while trying to compile prelude @@ -381,15 +381,15 @@ So here's a short punch list of things to do in that register: * [x] Hook validator back in to both source AND prelude code - [x] Validator should know about the environment for global/prelude function - [x] Run validator on current prelude to fix current known errors -* [ ] Do what it takes to compile this interpreter into Ludus's JS environment - - [ ] JSONify Ludus values - - [ ] Write a function that's source code to JSON result - - [ ] Expose this to a WASM compiler - - [ ] Patch this into a JS file - - [ ] Automate this build process -* [ ] Start testing against the cases in `ludus-test` -* [ ] Systematically debug prelude - - [ ] Bring it in function by function, testing each in turn +* [x] Do what it takes to compile this interpreter into Ludus's JS environment + - [x] JSONify Ludus values + - [x] Write a function that's source code to JSON result + - [x] Expose this to a WASM compiler + - [x] Patch this into a JS file + - [-] Automate this build process +* [-] Start testing against the cases in `ludus-test` +* [-] Systematically debug prelude + - [-] Bring it in function by function, testing each in turn *** I've started working on systematically going through the Prelude. @@ -486,19 +486,19 @@ I may be surprised, though. Currently fixing little bugs in prelude. Here's a list of things that need doing: -* [ ] Escape characters in strings: \n, \t, and \{, \}. -* [ ] `doc!` needs to print the patterns of a function. -* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. -* [ ] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.) +* [-] Escape characters in strings: \n, \t, and \{, \}. +* [-] `doc!` needs to print the patterns of a function. +* [-] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. +* [-] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.) - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. - I need this fixed for optimization reasons. - I _think_ I just fixed this by fixing tail position tracking in collections - - [ ] test this + - [-] test this - I did not fix it. * [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard? -* [ ] This is in the service of getting turtle graphics working +* [x] This is in the service of getting turtle graphics working * Other forms in the language need help: - * [ ] repeat needs its stack discipline updated, it currently crashes the compiler + * xx] repeat needs its stack discipline updated, it currently crashes the compiler ### More closure problems #### 2025-06-23 @@ -738,13 +738,13 @@ println!("line {line_no}: {}", lines[line_no - 1]); #### 2025-06-25 * Web workers * My javascript wrapper needs to execute WASM in its own thread (ugh) - - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? + - [-] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? * Top priorities: - - [ ] Get a node package out - - [ ] Stand up actors + threads, etc. - - [ ] How to model keyboard input from p5? - * [ ] Model after the p5 keyboard input API - * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) + - [-] Get a node package out + - [-] Stand up actors + threads, etc. + - [-] How to model keyboard input from p5? + * [-] Model after the p5 keyboard input API + * [-] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) - Assets: * We don't (for now) need to worry about serialization formats, since we're not doing perceptrons * We do need to read from URLs, which need in a *.ludus.dev. @@ -827,11 +827,11 @@ But everything else? Seems pretty straightforward. I've implemented what I decribe above. It works! I'm low-key astonished. It perfectly handles an infinitely recurring process! What the fuck. Anyway, things left to do: -* [ ] `receive` forms are the big one: they require threading through the whole interpreter +* [x] `receive` forms are the big one: they require threading through the whole interpreter * [x] implement the missing process functions at the end of prelude -* [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 -* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum -* [ ] develop a design for how to deal with asynchronous io with js +* [-] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [-] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [-] develop a design for how to deal with asynchronous io with js ```ludus fn agent/get (pid) -> { @@ -870,7 +870,7 @@ Two things that pop out to me: ### Rethinking reception #### 2025-06-27 -So one thing that's stuck with me is that in Elixir, `receive` isn't a special form: it's a function that takes a block. +So one thing that's stuck with me is that in Elixir, ~`receive` isn't a special form: it's a function that takes a block~ (**EDIT**: it is indeed a special form in Elixir, and it has to be on in Ludus.). It may be a macro, but it's still mostly normalish, and doesn't invovle compiler shenanigans. So, this is what I want to write: @@ -955,7 +955,7 @@ So the flushing would need to happen in the receiver. A few things that are wrong right now: -* [ ] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. +* [-] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. Minimal failing case: ```ludus @@ -965,16 +965,19 @@ match (nil) with { } } ``` -* [ ] The amount of sugar needed to get to `receive` without making a special form is too great. +* [x] The amount of sugar needed to get to `receive` without making a special form is too great. In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc. -~* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~ -* [ ] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. +_This is now implemented._ -* [ ] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. +~* [-] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~ +* [-] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. -* [ ] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought. +* [-] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. + +* [x] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought. + - That is not what was happening. The mailbox I haven't got to a minimal case, but here's what's not working: ```ludus @@ -1098,3 +1101,47 @@ Here's some pseudobytecode to get us to where we need to be: 090 jump to 025 (not really in bytecode; this will be unrolled) 100 receive end +#### a short time later +Well, that worked! The real issue was the jump back if we're out of messages. + +That leaves the following list: +* [a] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [a] develop a design for how to deal with asynchronous io with js +* [a] I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. +* [a] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. + - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. +* [a] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. +* My javascript wrapper needs to execute WASM in its own thread (ugh) + - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? +* Top priorities: + - [-] Get a node package out + - [x] Stand up actors + threads, etc. + - [ ] How to model keyboard input from p5? + * [ ] Model after the p5 keyboard input API + * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) +* [a] Escape characters in strings: \n, \t, and \{, \}. +* [ ] `doc!` needs to print the patterns of a function. +* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. +* [ ] Automate this build process +* [ ] Start testing against the cases in `ludus-test` +* [ ] Systematically debug prelude + - [ ] Bring it in function by function, testing each in turn +* [ ] Animation hooked into the web frontend (Spacewar!) +* [ ] Text input (Spacewar!) + - [ ] Makey makey for alternate input? +* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) +* [ ] Finding corpuses for Dissociated Press +* [ ] improve validator + - [ ] Tuples may not be longer than n members + - [ ] Loops may not have splatterns + - [ ] Identify others + - [ ] Splats in functions must be the same arity, and greater than any explicit arity +* [ ] actually good error messages + - [ ] parsing + - [ ] my memory is that validator messages are already good? + - [ ] panics, esp. no match panics + * [ ] panics should be able to refernce the line number where they fail + * [ ] that suggests that we need a mapping from bytecodes to AST nodes + * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. + diff --git a/src/vm.rs b/src/vm.rs index ac02ebb..e6e0b9b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1222,30 +1222,18 @@ impl Creature { 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; - self.ip -= 2; - } + LoadMessage => match self.mbx.get(self.msg_idx) { + Some(msg) => self.push(msg.clone()), + None => { + self.msg_idx = 0; + self.r#yield = true; + self.ip -= 2; } - } + }, MatchMessage => { let matched = self.mbx.remove(self.msg_idx).unwrap(); - println!( - "matched in {}: @idx {}, msg {matched}", - self.pid, self.msg_idx - ); } ClearMessage => { - println!("clearing messages in {}", self.pid); self.msg_idx = 0; } }