Compare commits

..

142 Commits
main ... actors

Author SHA1 Message Date
Scott Richmond
b5528ced8f start work on actor model 2025-06-26 01:28:33 -04:00
Scott Richmond
0c17b64fd7 maybe get git right? ugh 2025-06-25 23:21:22 -04:00
Scott Richmond
97547b1f7f fix blasted merge conflicts 2025-06-25 23:18:59 -04:00
Scott Richmond
e86f077247 cleanup before next text publish 2025-06-25 22:58:29 -04:00
Scott Richmond
44f7ce7b06 maybe figure out the wasm thing? 2025-06-25 22:56:39 -04:00
Scott Richmond
b78063ba07 0.1.2 2025-06-25 17:46:27 -04:00
Scott Richmond
c647cc0ea5 bring pkg directory under git 2025-06-25 17:46:00 -04:00
Scott Richmond
80eb81c6a8 things and stuff 2025-06-25 17:43:30 -04:00
Scott Richmond
333f5c9518 start splitting rudus into lib & main 2025-06-25 15:41:44 -04:00
Scott Richmond
3e84aa3f14 run is now String -> String, outputting a json result 2025-06-25 15:41:30 -04:00
Scott Richmond
34cd3a2187 unfuck stash 2025-06-25 13:38:29 -04:00
Scott Richmond
db109999d3 meet with mnl redux 2025-06-25 13:36:02 -04:00
Scott Richmond
87e58364e0 meet with mnl 2025-06-25 13:34:59 -04:00
Scott Richmond
4f80c500a2 so turtle graphics appears to work? 2025-06-24 15:58:14 -04:00
Scott Richmond
d9f0b44bed so many things: DRY out VM, fix repeat tail calls, etc. 2025-06-24 15:50:02 -04:00
Scott Richmond
0290bb3bf2 update (fix?) partial function application stack discipline 2025-06-23 21:22:28 -04:00
Scott Richmond
772c56a6df fix upvalue resolution for forward-declared functions 2025-06-23 20:26:26 -04:00
Scott Richmond
42a5f599f7 fix upvalue resolution for forward-declared functions 2025-06-23 20:06:40 -04:00
Scott Richmond
f2bae26e1c fix upvalue resolution, hopefully for real this time 2025-06-23 19:17:53 -04:00
Scott Richmond
987cc172c1 moar debugging: find issues with upvalues 2025-06-23 18:59:12 -04:00
Scott Richmond
24f57c1529 actually fix splatted dict pattern stack discipline 2025-06-23 17:58:10 -04:00
Scott Richmond
49e46d045b fix repeat stack discipline 2025-06-23 17:37:46 -04:00
Scott Richmond
6954857fdd fix dict pattern stack discipline 2025-06-23 17:33:45 -04:00
Scott Richmond
f09caabfcb wip: sorting out diect pattern stack discipline 2025-06-23 00:38:51 -04:00
Scott Richmond
e4b385d2fb fix tail position in collection forms 2025-06-23 00:27:50 -04:00
Scott Richmond
f576868a7a update stack discipline in dict patterns 2025-06-23 00:07:04 -04:00
Scott Richmond
f9e4b4623b additional fixes in tuple and dict patterns 2025-06-22 23:58:55 -04:00
Scott Richmond
bf97a34771 add turtle graphics, fix jump len error in tuple pattern 2025-06-22 23:57:11 -04:00
Scott Richmond
480e05f561 keep adding prelude; fix when stack discipline 2025-06-22 22:46:07 -04:00
Scott Richmond
9d798e5e58 fix interpolated string stack discipline 2025-06-22 21:15:17 -04:00
Scott Richmond
f0cf43b486 update block stack work 2025-06-22 20:44:07 -04:00
Scott Richmond
e2c9619fa6 update block stack work 2025-06-22 20:43:51 -04:00
Scott Richmond
f35cdd0e8d fix and & or 2025-06-22 20:26:38 -04:00
Scott Richmond
c00e1275fd fix and & or 2025-06-22 20:26:08 -04:00
Scott Richmond
e4a948ba94 maybe actually fix the loop stuff; lots of QOL improvements 2025-06-22 19:42:25 -04:00
Scott Richmond
86de66c4d2 keep grinding; quality of life improvements to aid in grinding 2025-06-22 17:03:50 -04:00
Scott Richmond
813a79a415 add a sandbox file to replace in-code Rust string 2025-06-22 14:40:27 -04:00
Scott Richmond
0b27944d11 improve bytecode readability by reporting patterns 2025-06-22 14:39:52 -04:00
Scott Richmond
f58a5f14b5 improve bytecode readability by reporting patterns 2025-06-22 14:38:29 -04:00
Scott Richmond
2f60de79a2 keep grinding on loop/recur/jif stack mismatch; add ast->code printer 2025-06-22 14:04:43 -04:00
Scott Richmond
398b140d79 grind on bugs 2025-06-22 01:24:52 -04:00
Scott Richmond
25a0c62dcf lots of bugs fixed--upvalues, bindings, stack manipulations, tail calls, etc. 2025-06-21 22:25:08 -04:00
Scott Richmond
583262f9e8 fix one-up closure resolution 2025-06-21 18:33:14 -04:00
Scott Richmond
ce11f1cd0f start work on getting prelude working; discover closure bug 2025-06-21 17:43:47 -04:00
Scott Richmond
3fe5365586 moar thoughts 2025-06-20 17:11:08 -04:00
Scott Richmond
23d9945c48 fix if alternative unconditional jump len 2025-06-20 17:09:41 -04:00
Scott Richmond
92d0915a71 prelude compiles: WAT 2025-06-20 16:02:48 -04:00
Scott Richmond
b3e0af41bb constants need 16 bits 2025-06-20 15:56:13 -04:00
Scott Richmond
121861cc8e fix function scoping bug 2025-06-20 15:35:09 -04:00
Scott Richmond
a2ae53f8e4 wire up stub:w prelude from external file 2025-06-20 14:43:14 -04:00
Scott Richmond
e06a24cf20 base and stub prelude wired up 2025-06-20 14:30:39 -04:00
Scott Richmond
1e3fcde57a work on prelude; update tailcall to deal properly with base fns 2025-06-20 14:29:31 -04:00
Scott Richmond
c73c7e0d6a load base into a compiler 2025-06-20 12:53:16 -04:00
Scott Richmond
ef134c0335 fix argument order in base fns 2025-06-20 12:49:31 -04:00
Scott Richmond
e580d68809 recursion works, so does mutual recursion; function call bugfixes 2025-06-20 12:32:15 -04:00
Scott Richmond
f4ade4938c notes and todos 2025-06-20 00:56:43 -04:00
Scott Richmond
fa587e38cd fix binding bug 2025-06-20 00:33:25 -04:00
Scott Richmond
8b004b45fa find a bug in function bindings; TCO is maybe complete?; things are in a shambles 2025-06-19 21:47:58 -04:00
Scott Richmond
7e4ddd3dc4 improve panic traces; tail calls work for simpel calls 2025-06-19 21:13:18 -04:00
Scott Richmond
2f5dab84a7 tail calls work now? also print stack trace on panic 2025-06-19 20:52:19 -04:00
Scott Richmond
94b7f98362 Merge branch 'bytecode' of alea.ludus.dev:scott/rudus into bytecode 2025-06-19 20:29:41 -04:00
Scott Richmond
1af75bc516 fix upvalue resolution, start work on TCO 2025-06-19 20:29:15 -04:00
Scott Richmond
807b2e6ce0 fix upvalue resolution 2025-06-19 20:26:16 -04:00
Scott Richmond
bf1e7e4072 rought draft of splatted fn args 2025-06-19 18:26:44 -04:00
Scott Richmond
442532ecd3 dict splatterns first draft 2025-06-19 15:56:23 -04:00
Scott Richmond
4dd4b8ff7e pseudocode splatted dict algo 2025-06-19 12:44:29 -04:00
Scott Richmond
35d7d3b1c8 first draft of tuple splatterns 2025-06-19 12:37:29 -04:00
Scott Richmond
a4d68fa02e first draft of list splatterns 2025-06-19 12:18:09 -04:00
Scott Richmond
2b95094eca first stab at splatted lists 2025-06-19 12:06:47 -04:00
Scott Richmond
4871dbd048 add match splatted list opcode 2025-06-19 11:54:26 -04:00
Scott Richmond
647f3d4463 update punch list 2025-06-19 11:48:50 -04:00
Scott Richmond
fb2488c850 rough draft of new version of loop w/ 16 bit jumps 2025-06-18 19:03:45 -04:00
Scott Richmond
f6bfe0975b update repeat w/ new jumps 2025-06-18 17:50:30 -04:00
Scott Richmond
ffe5d2ad61 panic on wrong number of args to functions 2025-06-18 16:47:53 -04:00
Scott Richmond
23298c8538 match now uses new jump; micro-optimize pop_n 2025-06-18 15:24:30 -04:00
Scott Richmond
77f1627132 update dict pattern for new jumps 2025-06-18 15:00:46 -04:00
Scott Richmond
6bd419125f update list pattern compiling to reflect new jump logic 2025-06-18 14:52:15 -04:00
Scott Richmond
a5f2e2a9bd tuple patterns now use jump_stub and patch_jump, with 16 bit jump values 2025-06-18 14:47:00 -04:00
Scott Richmond
a0ef6d2777 chip away at 16-bit jump instructions 2025-06-18 14:12:54 -04:00
Scott Richmond
d727096422 VM instructions now take 3 bytes 2025-06-18 14:12:10 -04:00
Scott Richmond
979afdbcb9 add jump fn for 16 bit jump 2025-06-18 13:15:57 -04:00
Scott Richmond
316e8a6a58 remove unused pattern.rs 2025-06-18 13:15:39 -04:00
Scott Richmond
ecd16e3aef update thoughts 2025-06-10 15:44:47 -04:00
Scott Richmond
e3b5b96dc2 test out 16 bit operand math 2025-06-06 00:11:08 -04:00
Scott Richmond
6ded94f7d0 oops: put working document under version control 2025-06-06 00:06:23 -04:00
Scott Richmond
0347d10db7 first draft of complex string matching, discover jump mistake 2025-06-05 23:26:42 -04:00
Scott Richmond
77faf67191 fix InterpolatedPattern compilation 2025-06-05 21:23:08 -04:00
Scott Richmond
f8adaa7971 first draft of partial application, is working in easy cases 2025-06-05 16:45:23 -04:00
Scott Richmond
dee9bcfc33 start work on partial application, fix/abstract binding resolution 2025-06-05 16:10:40 -04:00
Scott Richmond
f3bf55fe72 add dict splats 2025-06-05 13:24:32 -04:00
Scott Richmond
b557c487cc add list splats 2025-06-05 13:05:07 -04:00
Scott Richmond
b8b720b877 add do forms 2025-06-05 12:15:49 -04:00
Scott Richmond
23b8beb291 fix extra pop regression 2025-06-05 12:04:02 -04:00
Scott Richmond
5ae4742840 save work, working on function clause guards, extra pop instructions are showing up after function definitions 2025-06-04 19:02:19 -04:00
Scott Richmond
0f8645d2eb guards work in match forms 2025-06-04 18:55:40 -04:00
Scott Richmond
01ce043379 guards work in match forms 2025-06-04 18:51:07 -04:00
Scott Richmond
2ce2e2c2d3 let as last expr in block now returns rhs; clean up some comment cruft 2025-06-04 18:27:17 -04:00
Scott Richmond
61b1b7bf90 closures work?! 2025-06-04 17:53:38 -04:00
Scott Richmond
bf2b16c30f add a print builtin/opcode 2025-06-03 19:26:01 -04:00
Scott Richmond
c497b90a16 implement multiterm synthetic expressions 2025-06-03 19:13:40 -04:00
Scott Richmond
fc245348b4 start pulling base fns into bytecode interpreter 2025-06-03 18:54:33 -04:00
Scott Richmond
615fef6ebc function calls: first draft, worked out the obvious bugs 2025-06-03 17:30:00 -04:00
Scott Richmond
86992078e9 keywords and interned strings use &'static str instead of indexes into vecs 2025-06-03 16:23:37 -04:00
Scott Richmond
681176282c basic function calls, still with bugs! 2025-06-03 15:48:13 -04:00
Scott Richmond
22d1ceb3e8 vm.chunk -> vm.callframe.chunk 2025-06-02 13:34:23 -04:00
Scott Richmond
de3d7e834c maybe properly compile functions? need to start work on fn calls to test 2025-06-01 20:01:42 -04:00
Scott Richmond
b6f9b35b4c start major work on function compilation 2025-05-30 17:02:55 -04:00
Scott Richmond
34ab24c4c9 add as patterns 2025-05-30 14:57:51 -04:00
Scott Richmond
cda217f6ef add string interpolation 2025-05-30 11:44:32 -04:00
Scott Richmond
82ac6744ca or and and are now reserved words 2025-05-28 16:37:25 -04:00
Scott Richmond
182b14e5f4 add builtins, fix basic synthetic expressions 2025-05-27 14:15:12 -04:00
Scott Richmond
74cc0025d6 first draft loop/recur, seems to work? 2025-05-26 11:03:37 -04:00
Scott Richmond
db7eb5965d return register now an 8-member array 2025-05-26 09:16:47 -04:00
Scott Richmond
1e1593298d update repeat 2025-05-26 08:32:33 -04:00
Scott Richmond
04c96c9edb dict patterns prolly work? one-level nested patterns work 2025-05-25 19:14:34 -04:00
Scott Richmond
45d2e7e742 rough, not yet working, draft of dict patterns 2025-05-25 19:09:21 -04:00
Scott Richmond
cf51822128 add list patterns, fix dict stack mechanics 2025-05-25 16:30:20 -04:00
Scott Richmond
4f7ba56d1f properly compile when expressions? 2025-05-23 17:59:09 -04:00
Scott Richmond
ce1612a30c compiler: decouple stack & bindings resetting 2025-05-23 13:43:35 -04:00
Scott Richmond
1ef0da5dd1 compiler: decouple stack & bindings resetting 2025-05-23 13:42:29 -04:00
Scott Richmond
4a4b2b22ed oh god, so many changes. working on tuple matching 2025-05-23 00:09:35 -04:00
Scott Richmond
6c803cdf5a take some loop notes 2024-12-27 00:54:31 -05:00
Scott Richmond
a7ee8f8e57 vm::run is now a loop, not vm::interpret as a tailcall 2024-12-27 00:47:22 -05:00
Scott Richmond
8908630a21 add match_depth to vm 2024-12-27 00:22:01 -05:00
Scott Richmond
6f582bff06 refactor if/else to match in guard compilation 2024-12-26 23:48:38 -05:00
Scott Richmond
f5965fdb44 compile guards in match forms 2024-12-26 23:46:06 -05:00
Scott Richmond
cfe0b83192 fix block compilation; compile & run repeat 2024-12-26 23:33:57 -05:00
Scott Richmond
4fa2ce5e78 separate compiler & chunk 2024-12-26 19:03:09 -05:00
Scott Richmond
40d4f48878 notes and comments 2024-12-26 18:41:54 -05:00
Scott Richmond
ef0ac40dbe working & thinking 2024-12-24 12:35:44 -05:00
Scott Richmond
a4f12c8f7d continue work on compiling functions 2024-12-23 10:55:28 -05:00
Scott Richmond
9f4e630544 get lifetime out of Chunk, thus out of Value 2024-12-22 19:51:02 -05:00
Scott Richmond
be23ee6c44 get simple match forms done 2024-12-22 19:33:59 -05:00
Scott Richmond
d943185db8 do lots of work 2024-12-22 19:07:42 -05:00
Scott Richmond
d4342b0623 get binding & pretty debugging working 2024-12-18 01:28:23 -05:00
Scott Richmond
48754f92a4 do work 2024-12-17 23:45:39 -05:00
Scott Richmond
096d8d00bc add untracked from opening bytecode branch 2024-12-15 23:50:12 -05:00
Scott Richmond
9c3205d4c1 DRY out validator, simplify code 2024-12-15 23:49:43 -05:00
Scott Richmond
6c78cffe56 finish list of valid types 2024-12-15 23:49:27 -05:00
Scott Richmond
35fc591c76 make some progress: atoms and ifs 2024-12-15 23:28:57 -05:00
Scott Richmond
eff2ed90d5 some simple bytecodes! 2024-12-15 17:54:40 -05:00
Scott Richmond
86aea78c21 start working on a bytecode interpreter! 2024-12-15 16:37:51 -05:00
39 changed files with 11287 additions and 2011 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
/Cargo.lock
/node_modules

View File

@ -5,10 +5,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
ariadne = { git = "https://github.com/zesterer/ariadne" }
chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] }
imbl = "3.0.0"
struct_scalpel = "0.1.1"
ran = "2.0.1"
rust-embed = "8.5.0"
num-derive = "0.4.2"
num-traits = "0.2.19"
regex = "1.11.1"
wasm-bindgen = "0.2"
# struct_scalpel = "0.1.1"
# rust-embed = "8.5.0"
# boxing = "0.1.2"
# ordered-float = "4.5.0"
# index_vec = "0.1.4"

View File

@ -1,10 +1,10 @@
& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports
let base = base
& let base = base
& some forward declarations
& TODO: fix this so that we don't need (as many of) them
fn and
& fn and
fn append
fn apply_command
fn assoc
@ -25,7 +25,6 @@ fn turn/rad
fn unbox
fn update!
& the very base: know something's type
fn type {
"Returns a keyword representing the type of the value passed in."
(x) -> base :type (x)
@ -124,19 +123,19 @@ fn not {
(_) -> false
}
fn neq? {
"Returns true if none of the arguments have the same value."
(x) -> false
(x, y) -> not (eq? (x, y))
(x, y, ...zs) -> if eq? (x, y)
then false
else loop (y, zs) with {
(a, []) -> neq? (a, x)
(a, [b, ...cs]) -> if neq? (a, x)
then recur (b, cs)
else false
}
}
& fn neq? {
& "Returns true if none of the arguments have the same value."
& (x) -> false
& (x, y) -> not (eq? (x, y))
& (x, y, ...zs) -> if eq? (x, y)
& then false
& else loop (y, zs) with {
& (a, []) -> neq? (a, x)
& (a, [b, ...cs]) -> if neq? (a, x)
& then recur (b, cs)
& else false
& }
& }
& tuples: not a lot you can do with them functionally
fn tuple? {
@ -589,10 +588,10 @@ fn zero? {
fn gt? {
"Returns true if numbers are in decreasing order."
(x as :number) -> true
(x as :number, y as :number) -> base :gt (x, y)
(x as :number, y as :number) -> base :gt? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :gt (a, b)
(a, [b, ...cs]) -> if base :gt (a, b)
(a, [b]) -> base :gt? (a, b)
(a, [b, ...cs]) -> if base :gt? (a, b)
then recur (b, cs)
else false
}
@ -601,10 +600,10 @@ fn gt? {
fn gte? {
"Returns true if numbers are in decreasing or flat order."
(x as :number) -> true
(x as :number, y as :number) -> base :gte (x, y)
(x as :number, y as :number) -> base :gte? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :gte (a, b)
(a, [b, ...cs]) -> if base :gte (a, b)
(a, [b]) -> base :gte? (a, b)
(a, [b, ...cs]) -> if base :gte? (a, b)
then recur (b, cs)
else false
}
@ -613,10 +612,10 @@ fn gte? {
fn lt? {
"Returns true if numbers are in increasing order."
(x as :number) -> true
(x as :number, y as :number) -> base :lt (x, y)
(x as :number, y as :number) -> base :lt? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :lt (a, b)
(a, [b, ...cs]) -> if base :lt (a, b)
(a, [b]) -> base :lt? (a, b)
(a, [b, ...cs]) -> if base :lt? (a, b)
then recur (b, cs)
else false
}
@ -625,10 +624,10 @@ fn lt? {
fn lte? {
"Returns true if numbers are in increasing or flat order."
(x as :number) -> true
(x as :number, y as :number) -> base :lte (x, y)
(x as :number, y as :number) -> base :lte? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :lte (a, b)
(a, [b, ...cs]) -> if base :lte (a, b)
(a, [b]) -> base :lte? (a, b)
(a, [b, ...cs]) -> if base :lte? (a, b)
then recur (b, cs)
else false
}
@ -669,7 +668,7 @@ fn odd? {
fn min {
"Returns the number in its arguments that is closest to negative infinity."
(x as :number) -> x
(x as :number, y as :number) -> if lt? (x, y) then x else y
(x as :number, y as :number) -> if base :lt? (x, y) then x else y
(x, y, ...zs) -> fold (min, zs, min (x, y))
}
@ -698,7 +697,7 @@ fn at {
fn first {
"Returns the first element of a list or tuple."
(xs) if ordered? (xs) -> at (xs, 0)
(xs) if ordered? -> at (xs, 0)
}
fn second {
@ -738,22 +737,22 @@ fn keyword? {
& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc.
& TODO: make `and` and `or` special forms which lazily evaluate arguments
fn and {
"Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in."
() -> true
(x) -> bool (x)
(x, y) -> base :and (x, y)
(x, y, ...zs) -> fold (and, zs, base :and (x, y))
}
& & TODO: make `and` and `or` special forms which lazily evaluate arguments
& fn and {
& "Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in."
& () -> true
& (x) -> bool (x)
& (x, y) -> base :and (x, y)
& (x, y, ...zs) -> fold (and, zs, base :and (x, y))
& }
fn or {
"Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in."
() -> true
(x) -> bool (x)
(x, y) -> base :or (x, y)
(x, y, ...zs) -> fold (or, zs, base :or (x, y))
}
& fn or {
& "Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in."
& () -> true
& (x) -> bool (x)
& (x, y) -> base :or (x, y)
& (x, y, ...zs) -> fold (or, zs, base :or (x, y))
& }
fn assoc {
"Takes a dict, key, and value, and returns a new dict with the key set to value."
@ -1308,7 +1307,7 @@ box state = nil
#{
abs & math
add & math
and & bool
& and & bool
angle & math
any? & dicts lists strings sets tuples
append & lists sets
@ -1402,7 +1401,7 @@ box state = nil
ok & results
ok? & results
& omit & set
or & bool
& or & bool
ordered? & lists tuples strings
pc! & turtles
pd! & turtles

File diff suppressed because it is too large Load Diff

255
bytecode_thoughts.md Normal file
View File

@ -0,0 +1,255 @@
# Working notes on bytecode stuff
### 2024-12-15
So far, I've done the easy stuff: constants, and ifs.
There's still some easy stuff left:
* [ ] lists
* [ ] dicts
* [ ] when
* [ ] panic
So I'll do those next.
But then we've got two doozies: patterns and bindings, and tuples.
#### Tuples make things hard
In fact, it's tuples that make things hard.
The idea is that, when possible, tuples should be stored on the stack.
That makes them a different creature than anything else.
But the goal is to be able, in a function call, to just push a tuple onto the stack, and then match against it.
Because a tuple _isn't_ just another `Value`, that makes things challenging.
BUT: matching against all other `Values` should be straightforward enough?
I think that the way to do this is to reify patterns.
Rather than try to emit bytecodes to embody patterns, the patterns are some kind of data that get compiled and pushed onto a stack like keywords and interned strings and whatnot.
And then you can push a pattern onto the stack right behind a value, and then have a `match` opcode that pops them off.
Things get a bit gnarly since patterns can be nested. I'll start with the basic cases and run from there.
But when things get *very* gnarly is considering tuples on the stack.
How do you pop off a tuple?
Two thoughts:
1. Just put tuples on the heap. And treat function arguments/matching differently.
2. Have a "register" that stages values to be pattern matched.
##### Regarding the first option
I recall seeing somebody somewhere make a comment that trying to represent function arguments as tuples caused tons of pain.
I can see why that would be the case, from an implementation standpoint.
We should have _values_, and don't do fancy bookkeeping if we don't have to.
_Conceptually_, it makes a great deal of sense to think of tuples as being deeply the same as function invocation.
But _practically_, they are different things, especially with Rust underneath.
This feels like this cuts along the grain, and so this is what I will try.
I suspect that I'll end up specializing a lot around function arguments and calling, but that feels more tractable than the bookkeeping around stack-based tuples.
### 2024-12-17
Next thoughts: take some things systematically rather than choosing an approach first.
#### Things that always match
* Placeholder.
- I _think_ this is just a no-op. A `let` expression leaves its rhs pushed on the stack.
* Word: put something on the stack, and bind a name.
- This should follow the logic of locals as articulated in _Crafting Interpreters_.
In both of these cases, there's no conditional logic, simply a bind.
#### Things that never bind
* Atomic values: put the rhs on the stack, then do an equality check, and panic if it fails. Leave the thing on the stack.
#### Analysis
In terms of bytecode, I think one thing to do, in the simple case, is to do the following:
* `push` a `pattern` onto the stack
* `match`--pops the pattern and the value off the stack, and then applies the pattern to the value. It leaves the value on the stack, and pushes a special value onto the stack representing a match, or not.
- We'll probably want `match-1`, `match-2`, `match-3`, etc., opcodes for matching a value that's that far back in the stack. E.g., `match-1` matches against not the top element, but the `top - 1` element.
- This is _specifically_ for matching function arguments and `loop` forms.
* There are a few different things we might do from here:
- `panic_if_no_match`: panic if the last thing is a `no_match`, or just keep going if not.
- `jump_if_no_match`: in a `match` form or a function, we'll want to move to the next clause if there's no match, so jump to the next clause's `pattern` `push` code.
* Compound patterns are going to be more complex.
- I think, for example, what you're going to need to do is to get opcodes that work on our data structures, so, for example, when you have a `match_compound` opcode and you start digging into the pattern.
* Compound patterns are specifically _data structures_. So simple structures should be stack-allocated, and and complex structures should be pointers to something on the heap. Maybe?
#### A little note
For instructions that need more than 256 possibilities, we'll need to mush two `u8`s together into a `u16`. The one liner for this is:
```rust
let number = ((first as u16) << 8) | second as u16;
```
#### Oy, stacks and expressions
One thing that's giving me grief is when to pop and when to note on the value stack.
So, like, we need to make sure that a line of code leaves the stack exactly where it was before it ran, with the exception of binding forms: `let`, `fn`, `box`, etc. Those leave one (or more!) items on the stack.
In the simplest case, we have a line of code that's just a constant:
```
false
```
This should emit the bytecode instructions (more or less):
```
push false
pop
```
The push comes from the `false` value.
The pop comes from the end of a (nonbinding) line.
The problem is that there's no way (at all, in Ludus) to distinguish between an expression that's just a constant and a line that is a complete line of code that's an expression.
So if we have the following:
```
let foo = false
```
We want:
```
push false
```
Or, rather, given that `foo` is a word pattern, what we actually want is:
```
push false # constant
push pattern/word # load pattern
pop
pop # compare
push false # for the binding
```
But it's worth it here to explore Ludus's semantics.
It's the case that there are actually only three binding forms (for now): `let`, `fn`, and `box`.
Figuring out `let` will help a great deal.
Match also binds things, but at the very least, match doesn't bind with expressions on the rhs, but a single value.
Think, too about expressions: everything comes down to a single value (of course), even tuples (especially now that I'm separating function calls from tuple values (probably)).
So: anything that *isn't* a binding form should, before the `pop` from the end of a line, only leave a single value on the stack.
Which suggests that, as odd as it is, pushing a single `nil` onto the stack, just to pop it, might make sense.
Or, perhaps the thing to do is to peek: if the line in question is binding or not, then emit different bytecode.
That's probably the thing to do. Jesus, Scott.
And **another** thing worth internalizing: every single instruction that's not an explicit push or pop should leave the stack length unchanged.
So store and load need always to swap in a `nil`
### 2024-12-23
Compiling functions.
So I'm working through the functions chapter of _CI_, and there are a few things that I'm trying to wrap my head around.
First, I'm thinking that since we're not using raw pointers, we'll need some functional indirection to get our current byte.
So one of the hard things here is that, unlike with Lox, Ludus doesn't have fixed-arity functions. That means that the bindings for function calls can't be as dead simple as in Lox. More to the point, because we don't know everything statically, we'll need to do some dynamic magic.
The Bob Nystrom program uses three useful auxiliary constructs to make functions straightforward:
* `CallFrame`s, which know which function is being called, has their own instruction pointer, and an offset for the first stack slot that can be used by the function.
```c
typedef struct {
ObjFunction* function;
uint8_t* ip;
Value* slots;
} CallFrame;
```
Or the Rust equivalent:
```rust
struct CallFrame {
function: LFn,
ip: usize,
stack_root: usize,
}
```
* `Closure`s, which are actual objects that live alongside functions. They have a reference to a function and to an array of "upvalues"...
* `Upvalue`s, which are ways of pointing to values _below_ the `stack_root` of the call frame.
##### Digression: Prelude
I decided to skip the Prelude resolution in the compiler and only work with locals. But actually, closures, arguments, and the prelude are kind of the same problem: referring to values that aren't currently available on the stack.
We do, however, know at compile time the following:
* If a binding's target is on the stack, in a closure, or in the prelude.
* This does, however, require that the function arguments work in a different way.
The way to do this, I reckon, is this:
* Limit arguments (to, say, no more than 7).
* A `CallFrame` includes an arity field.
* It also includes an array of length 7.
* Each `match` operation in function arguments clones from the call frame, and the first instruction for any given body (i.e. once we've done the match) is to clear the arguments registers in the `CallFrame`, thus decrementing all the refcounts of all the heap-allocated objects.
* And the current strategy of scoping and popping in the current implementation of `match` will work just fine!
Meanwhile, we don't actually need upvalues, because bindings cannot change in Ludus. So instead of upvalues and their indirection, we can just emit a bunch of instructions to have a `values` field on a closure. The compiler, meanwhile, will know how to extract and emit instructions both to emit those values *and* to offer correct offsets.
The only part I haven't figured out quite yet is how to encode access to what's stored in a closure.
Also, I'm not certain we need the indirection of a closure object in Ludus. The function object itself can do the work, no?
And the compiler knows which function it's closing over, and we can emit a bunch of instructions to close stuff over easily, after compiling the function and putting it in the constants table. The way to do this is to yank the value to the top of the stack using normal name resolution procedures, and then use a two-byte operand, `Op::Close` + index of the function in the constants table.
##### End of digression.
And, because we know exactly is bound in a given closure, we can actually emit instructions to close over a given value easily.
#### A small optimization
The lifetimes make things complicated; but I'm not sure that I would want to actually manage them manually, given how much they make my head hurt with Rust. I do get the sense that we will, at some point, need some lifetimes. A `Chunk` right now is chunky, with lots of owned `vec`s.
Uncle Bob separates `Chunk`s and `Compiler`s, which, yes! But then we have a problem: all of the information to climb back to source code is in the `Compiler` and not in the `Chunk`. How to manage that encoding?
(Also the keyword and string intern tables should be global, and not only in a single compiler, since we're about to get nested compilers...)
### 2024-12-24
Other interesting optimizations abound:
* `add`, `sub`, `inc`, `dec`, `type`, and other extremely frequently used, simple functions can be compiled directly to built-in opcodes. We still need functions for them, with the same arities, for higher order function use.
- The special-case logic is in the `Synthetic` compiler branch, rather than anywhere else.
- It's probably best to disallow re-binding these names anywhere _except_ Prelude, where we'll want them shadowed.
- We can enforce this in `Validator` rather than `Compiler`.
* `or` and `and` are likewise built-in, but because they don't evaluate their arguments eagerly, that's another, different special case that's a series of eval, `jump_if_false`, eval, `jump_if_false`, instructions.
* More to the point, the difference between `or` and `and` here and the built-ins is that `or` and `and` are variadic, where I was originally thinking about `and` and co. as fixed-arity, with variadic behaviours defined by a shadowing/backing Ludus function. That isn't necessary, I don't think.
* Meanwhile, `and` and `or` will also, of necessity, have backing shadowing functions.
#### More on CallFrames and arg passing
* We don't actually need the arguments register! I was complicating things. The stack between the `stack_root` and the top will be _exactly_ the same as an arguments register would have been in my imagination. So we can determine the number of arguments passed in with `stack.len() - stack_root`, and we can access argument positions with `stack_root + n`, since the first argument is at `stack_root`.
- This has the added benefit of not having to do any dances to keep the refcount of any heap-allocated objects as low as possible. No extra `Clone`s here.
* In addition, we need two `check_arity` ops: one for fixed-arity clauses, and one for clauses with splatterns. Easily enough done. Remember: opcodes are for special cases!
#### Tail calls
* The way to implement tail calls is actually now really straightforward! The idea is to simply have a `TailCall` rather than a `Call` opcode. In place of creating a new stack frame and pushing it to the call stack on top of the old call frame, you pop the old call frame, then push the new one to the call stack.
* That does mean the `Compiler` will need to keep track of tail calls. This should be pretty straightforward, actually, and the logic is already there in `Validator`.
* The thing here is that the new stack frame simply requires the same return location as the old one it's replacing.
* That reminds me that there's an issue in terms of keeping track of not just the IP, but the chunk. In Lox, the IP is a pointer to a `u8`, which works great in C. But in Rust, we can't use a raw pointer like that, but an index into a `vec<u8>`. Which means the return location needs both a chunk and an index, not just a `u8` pointer:
```rust
struct StackFrame<'a> {
function: LFn,
stack_root: usize,
return: (&'a Chunk, usize),
}
```
(I hate that there's a lifetime here.)
This gives us a way to access everything we need: where to return to, the root of the stack, the chunk (function->chunk), the closures (function->closures).
### 2024-12-26
One particular concern here, which needs some work: recursion is challenging.
In particular, the issue is that if, as I have been planning, a function closes over all its values at the moment it is compiled, the only value type that requires updating is a function. A function can be declared but not yet defined, and then when another function that uses that function is defined, the closed-over value will be to the declaration but not the definition.
One way to handle this, I think is using `std::cell::OnceCell`. Rather than a `RefCell`, `OnceCell` has no runtime overhead. Instead, what happens is you effectively put a `None` in the cell. Then, once you have the value you want to put in there, you call `set` on the `OnceCell`, and it does what it needs to.
This allows for the closures to be closed over right after compilation.
### 2024-12-27
Working on `loop` and `recur`, rather than `fn`--this is the gentler slope.
And discovering that we actually need a `[Value; 15]` return register.
`recur` needs to push all the arguments to the stack, then yank them off into the return register, then pop back to the loop root, then push all the things back onto the stack, then jump to the beginning of the loop.
And that also means I need a different `Value` variant that's a true `Nothing`, not even `nil`, which will _never_ end up anywhere other than a placeholder value in the register and on the stack.
So, next steps:
1. Add `Value::Nothing` and fix all the compiler errors
2. Make the return register `[Value; 15]`, populated with `Value::Nothing`s at initialization.
3. Update `load` and `store` to work with the array rather than a single value.
4. Create `store_n` and `load_n` to work with multiple values.
5. Create a `Vm.arity` method that computes how many non-nothings were passed into the register.
6. Then, implement `recur`
7. And, then, fix up jump indexes in `loop`
8. Fix all the off-by-one errors in the jumps

777
may_2025_thoughts.md Normal file
View File

@ -0,0 +1,777 @@
# Catching back up
## May 2025
### Bugs
#### `match` is not popping things correctly
```
=== source code ===
let foo = match :foo with {
:foo -> 1
:bar -> 2
:baz -> 3
}
foo
=== chunk: test ===
IDX | CODE | INFO
0000: reset_match
0001: constant 0000: :foo
0003: match_constant 0000: :foo
0005: jump_if_no_match 0006
0007: constant 0001: 1
0009: store
0010: pop
0011: jump 0023
0013: match_constant 0002: :bar
0015: jump_if_no_match 0006
0017: constant 0003: 2
0019: store
0020: pop
0021: jump 0013
0023: match_constant 0004: :baz
0025: jump_if_no_match 0006
0027: constant 0005: 3
0029: store
0030: pop
0031: jump 0003
0033: panic_no_match
0034: load
0035: match_word
0036: panic_if_no_match
0037: push_binding 0000
0039: store
0040: pop
0041: pop
0042: load
=== vm run: test ===
0000: [] nil
0000: reset_match
0001: [] nil
0001: constant 0000: :foo
0003: [:10] nil
0003: match_constant 0000: :foo
0005: [:10] nil
0005: jump_if_no_match 0006
0007: [:10] nil
0007: constant 0001: 1
0009: [:10|1] nil
0009: store
0010: [:10|nil] 1
0010: pop
0011: [:10] 1
0011: jump 0023
0036: [:10] 1
0036: panic_if_no_match
0037: [:10] 1 <== Should "return" from match here
0037: push_binding 0000
0039: [:10|:10] 1
0039: store
0040: [:10|nil] :10
0040: pop
0041: [:10] :10
0041: pop
0042: [] :10
0042: load
:foo
```
Should return `1`.
Instruction `0037` is where it goes off the rails.
### Things left undone
Many. But things that were actively under development and in a state of unfinishedness:
1. Tuple patterns
2. Loops
3. Function calls
#### Tuple patterns
This is the blocking issue for loops, function calls, etc.
You need tuple pattern matching to get proper looping and function calls.
Here are some of the issues I'm having:
* Is it possible to represent tuples on the stack? Right now they're allocated on the heap, which isn't great for function calls.
* How to represent complex patterns? There are a few possibilities:
- Hard-coded into the bytecode (this is probably the thing to do?)
- Represented as a data structure, which itself would have to be allocated on the heap
- Some hybrid of the two:
* Easy scalar values are hard-coded: `nil`, `true`, `:foo`, `10` are all built into the bytecode + constants table
* Perhaps dict patterns will have to be data structures
#### Patterns, generally
On reflection, I think the easiest (perhaps not simplest!) way to go is to model the patterns as separate datatypes stored per-chunk in a vec.
The idea is that we push a value onto the stack, and then have a `match` instruction that takes an index into the pattern vec.
We don't even need to store all pattern types in that vec: constants (which already get stored in the constant vec), interpolations, and compound patterns.
`nil`, `false`, etc. are singletons and can be handled like (but not exactly as) the `nil` and `false` _values_.
This also means we can outsource the pattern matching mechanics to Rust, which means we don't have to fuss with "efficient compiling of pattern matching" titchiness.
This also has the benefit, while probably being fast _enough_, of reflecting the conceptual domain of Ludus, in which patterns and values are different DSLs within the language.
So: model it that way.
### Now that we've got tuple patterns
#### May 23, 2025
A few thoughts:
* Patterns aren't _things_, they're complex conditional forms. I had not really learned that; note that the "compiling pattern matching efficiently" paper is about how to optimize those conditionals. The tuple pattern compilation more closely resembles an `if` or `when` form.
* Tuple patterns break the nice stack-based semantics of binding. So do other patterns. That means I had to separate out bindings and the stack. I did this by introducing a representation of the stack into the compiler (it's just a stack-depth counter).
- This ended up being rather titchy. I think there's a lot of room to simplify things by avoiding manipulating this counter directly. My sense is that I should probably move a lot of the `emit_op` calls into methods that ensure all the bookkeeping happens automatically.
* `when` is much closer to `if` than `match`; remember that!
* Function calls should be different from tuple pattern matching. Tuples are currently (and maybe forever?) allocated on the heap. Function calls should *not* have to pass through the heap. The good news: `Arguments` is already a different AST node type than `Tuple`; we'll want an `ArgumentsPattern` pattern node type that's different from (and thus compiled differently than) `TuplePattern`. They'll be similar--the matching logic is the same, after all--but the arguments will be on the stack already, and won't need to be unpacked in the same way.
- One difficulty will be matching against different arities? But actually, we should compile these arities as different functions.
- Given splats, can we actually compile functions into different arities? Consider the following:
```
fn foo {
(x) -> & arity 1
(y, z) -> & arity 2
(x, y, 2) -> & arity 3
(...z) -> & arity 0+
}
```
`fn(1, 2, 3)` and `fn(1, 2, 4)` would invoke different "arities" of the function, 3 and 0+, respectively. I suspect the simpler thing really is to just compile each function as a singleton, and just keep track of the number of arguments you're matching.
* Before we get to function calls, `loop`/`recur` make sense as the starting ground. I had started that before, and there's some code in those branches of the compiler. But I ran into tuple pattern matching. That's now done, although actually, the `loop`/`recur` situation probably needs a rewrite from the ground up.
* Just remember: we're not aiming for *fast*, we're aiming for *fast enough*. And I don't have a ton of time. So the thing to do is to be as little clever as possible.
* I suspect the dominoes will fall reasonably quickly from here through the following:
- [x] `list` and `dict` patterns
- [x] updating `repeat`
- [x] standing up `loop`/`recur`
- [x] standing up functions
- [x] more complex synthetic expressions
- [x] `do` expressions
* 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] 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."
* 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)
- actors
- animation in the frontend
* In addition to a lot of this, I think I need some kind of testing solution. The Janet interpreter is pretty well-behaved.
### Now that we've got some additional opcodes and loop/recur working
#### 2025-05-27
The `loop` compilation is _almost_ the same as a function body. That said, the thing that's different is that we don't know the arity of the function that's called.
A few possibilities:
* Probably the best option: enforce a new requirement that splat patterns in function clauses *must* be longer than any explicit arity of the function. So, taking the above:
```
fn foo {
(x) -> & arity 1
(y, z) -> & arity 2
(x, y, 2) -> & arity 3
(...z) -> & arity 0+
}
```
This would give you a validation error that splats must be longer than any other arity.
Similarly, we could enforce this:
```
fn foo {
(x) -> & arity 1
(x, y) -> & arity 2
(x, ...) & arity n > 1; error! too short
(x, y, ...) & arity n > 2; ok!
}
```
The algorithm for compiling functions ends up being a little bit titchy, because we'll have to store multiple functions (i.e. chunks) with different arities.
Each arity gets a different chunk.
And the call function opcode comes with a second argument that specifies the number of arguments, 0 to 7.
(That means we only get a max of 7 arguments, unless I decide to give the call opcode two bytes, and make the call/return register much bigger.)
Todo, then:
* [x] reduce the call/return register to 7
* [x] implement single-arity compilation
* [x] implement single-arity calls, but with two bytes
* [x] compile multiple-arity functions
* [x] add closures
### Some thoughts while chattin w/ MNL
On `or` and `and`: these should be reserved words with special casing in the parser.
You can't pass them as functions, because that would change their semantics.
So they *look* like functions, but they don't *behave* like functions.
In Clojure, if you try `(def foo or)`, you get an error that you "can't take the value of a macro."
I'll need to change Ludus so that `or` and `and` expressions actually generate different AST nodes, and then compile them from there.
AND: done.
### Implementing functions & calls, finally
#### 2025-06-01
Just to explain where I am to myself:
* I have a rough draft (probably not yet fully functional) of function compilation in the compiler.
* Now I have to implement two op codes: `Call` and `Return`.
* I now need to implement call frames.
* A frame in _Crafting Interpreters_ has: a pointer to a Lox function object, an ip, and an index into the value stack that indicates the stack bottom for this function call. Taking each of these in turn:
* The lifetime for the pointer to the function:
- The pointer to the function object cannot, I think, be an explicit lifetime reference, since I don't think I know how to prove to the Rust borrow checker that the function will live long enough, especially since it's inside an `Rc`.
- That suggests that I actually need the whole `Value::Fn` struct and not just the inner `LFn` struct, so I can borrow it.
* The ip and stack bottom are just placeholders and don't change.
### Partially applied functions
#### 2025-06-05
Partially applied functions are a little complicated, because they involve both the compiler and the VM.
Maybe.
My sense is that, perhaps, the way to do them is actually to create a different value branch.
They ....
### Jumping! Numbers! And giving up the grind
#### 2025-06-05
Ok! So.
This won't be ready for next week.
That's clear enough now--even though I've made swell progress!
One thing I just discovered, which, well, it feels silly I haven't found this before.
Jump instructions, all of them, need 16 bits, not 8.
That means some fancy bit shifting, and likely some refactoring of the compiler & vm to make them easier to work with.
For reference, here's the algorithm for munging u8s and u16s:
```rust
let a: u16 = 14261;
let b_high: u8 = (a >> 8) as u8;
let b_low: u8 = a as u8;
let c: u16 = ((b_high as u16) << 8) + b_low as u16;
println!("{a} // {b_high}/{b_low} // {c}");
```
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
* [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
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
### Final touches on semantics, or lots of bugs
#### 2025-06-19
* Main code is fucking up bindings in functions
* Why?
* In the middle of doing TCO, looks like it works for `do` forms based on the bytecode, but I ran into these weird binding problems while trying to test that in the vm
* Once that's put to bed, I believe TCO fully works
_Edited to add: all the above is, I think, fixed._
* But then I need to test it actually works in its intended use case: recursive calls
* Which means I need to test recursive calls
* And, once that's done, I think I have a COMPLETE SEMANTICALLY CORRECT INTERPRETER.
* After that, jesus, it's time for base > prelude > test cases
#### Later
So this is my near-term TODO:
* [x] recursive calls
- [x] direct recursive calls
* [x] stash a function declaration before compiling the function, hang onto that declaration
* [x] update that declaration to a definition after compiling the function
- [x] mutual recursive
* [x] check to make sure a function has already been declared, hang onto that declaration
* [x] update that declaration to a definition after compiling the function...
* [x] but BEFORE we close over any upvalues, so the function will be an upvalue for itself
- I suspect this can be done not using anything other than an index into a chunk's `constants` vec--no fancy memory swapping or anything; and also--done in the compiler rather than the VM.
* [x] getting to prelude
- [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
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
* 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
#### 2025-06-20
Consider the following code:
```
fn one {
(x as :number) -> {
fn two () -> :number
two
}
(x as :bool) -> {
fn two () -> :bool
two
}
}
```
The second clause causes a panic in the compiler.
I'm not entirely sure what's going on.
That said, I'm pretty sure the root of it is that the fact that `two` was already bound in the first clause means that it's in the constants vector in that chunk under the name "two."
...
So that's fixed!
But:
Prelude functions are not properly closing over their upvalues.
Which is not right.
We get: `get_upvalue 000` and a panic: `index out of bounds: the len is 0 but the index is 0`.
The vec in question is the LFn::Defined.closed.
...
Just patched up the `if` alternative branch unconditional jump, which was jumping too far.
Now it really is just some pretty systematic testing of prelude functions, including the problems with upvalues, which I haven't yet been able to recreate.
### On proceeding from here
#### 2025-06-021
Rather than doing everything by hand right now, I think the way to go about things is to figure out how to do as much automated bugfixing as possible.
That means reprioritizing some things.
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
***
I've started working on systematically going through the Prelude.
I've found a closure error.
This is in `map`/`mapping`.
What's happening is that the inner function, `mapping`, is closing over values directly from a stack that no longer exists.
What we need to have happen is that if a function is closing over values _inside_ a function, it needs to capture not the upvalues directly from the stack, but _from the enclosing closure_.
I think I need to consult Uncle Bob Nystrom to get a sense of what to do here.
***
So I found the minimal test case:
```
let foo = {
let thing = :thing
let bar = :bar
let baz = :baz
fn quux () -> {
fn frobulate () -> (bar, baz)
}
}
foo ()
```
`frobulate` is closed over when `quux` is called, when the stack looks completely different than it does when `quux` is defined.
If you remove line 2, binding `thing`, then you don't get a panic, but when you call `foo () ()`, the result is `(fn quux, fn frobulate)`.
The problem is that `frobulate`'s upvalues are indexes into the stack, rather than having some relation to `quux`'s upvalues.
What needs to happen is that an enclosing function needs to capture, define, and pass down the upvalues for its enclosed functions.
I'm having an exact problem that Uncle Bob is describing at
https://craftinginterpreters.com/closures.html#flattening-upvalues.
I need to study and adapt this exact set of problems.
I believe I need to take the strategy he uses with closures being different from functions, etc.
So: rework the closures strategy here.
***
Closures strategy mostly unfucked.
Now I'm having some difficulty with bindings, again?
Current situation: still trying to get `map` and `fold` to work properly.
The bindings inside of non-trivial functions goes weird.
The scope depths are all out of whack.
And the stack positions on stuff are also totally weird.
One thing: the way we are now resolving upvalues means that nothing should ever reach back further in the stack than the stack base in the vm.
So now we'll do all bindings relative to the stack base.
UGH.
***
So now I have a bug that I'm dying to figure out.
But it's time to go to bed.
When `recur` is in the alternatve branch of an if, it thinks there's one more value on the stack than there really is.
To the best of my ability to tell, `if` has proper stack behaviour.
So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`.
To wit, the following code works just fine:
```
fn not {
(false) -> true
(nil) -> true
(_) -> false
}
let test = 2
loop ([1, 2, 3]) with {
([]) -> false
([x]) -> eq? (x, test)
([x, ...xs]) -> if not(eq? (x, test))
then recur (xs)
else true
}
```
But the following code does not:
```
let test = 2
loop ([1, 2, 3]) with {
([]) -> false
([x]) -> eq? (x, test)
([x, ...xs]) -> if eq? (x, test)
then true
else recur (xs)
}
```
Meanwhile, other `loop`/`recur` forms seem to work okay for me.
So: ugh.
### Grinding and grinding
#### 2025-06-22
Got 'er done.
Fixed `loop`/`recur` and many other stack shananigans.
I don't believe I've fixed everything yet.
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.)
- 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
- 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
* Other forms in the language need help:
* [ ] repeat needs its stack discipline updated, it currently crashes the compiler
### More closure problems
#### 2025-06-23
My solution to closures wasn't quite right.
I can't use Uncle Bob's strategy of the recursive call, since Rust's ownership semantics make this onerous at best.
My solution: introduce the concept of a "compiler depth," with 0 being the global scope.
If the compiler's at 0 depth, we can pull it out of the environment.
If the compiler's at a depth > 0, then we can ask the enclosing compiler to stash the upvalue.
And thus we get what we need.
But: some functions in prelude aren't properly getting their closures, and I don't know why, since they *are* getting them properly in user scripts.
Take `apply_command`.
Next step: sort out if any other functions aren't getting things closed over properly.
PROBLEM: forward-declared functions weren't at the top of the stack when `Op::SetUpvalue` was called.
So all of `apply_command`'s upvalues were being attached to the function declared before it (which was sitting right there at the top of the stack.)
SOLUTION: test to see if the function has been forward-declared, and if it has, bring it to the top fo the stack.
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
<<<<<<< HEAD
<<<<<<< Updated upstream
This is the thing I am about to do
||||||| Stash base
This is the thing I am about to do.
### I think the interpreter, uh, works?
#### 2025-06-24
I'm sure I'll find some small problems.
But right now the thing works.
At the moment, I'm thinking about how to get good error messages.
Panics are difficult.
And I'm worried about ariadne as the error reporting crate.
Since it writes to stdout, it has all kinds of escape codes.
I need a plain ass string, at least for the web frontend.
So.
Current task, however, is how to get reasonable panic error messages.
Let's simplify the problem.
First, let's get tracebacks and line numbers.
We use Chumsky spanned ASTs.
The span is just a range into an str.
What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback.
So instead of using Ariadne's fancy report builder, let's just do something more what we already have here:
```
Ludus panicked! no match
on line 1 in input,
calling: add
with arguments: ("foo")
expected match with one of:
()
(x as :number)
(x as :number, y as :number)
(x, y, ...zs)
((x1, y1), (x2, y2))
>>> add ("foo")
......^
```
We need:
* the location of the function call in terms of the line number
* the arguments used
* the patterns expected to match (special cases: `let` vs `match` vs `fn`)
That means, for bookkeeping, we need:
* In the compiler, line number
* In the VM, the arguments
* In the VM, the pattern AST node.
In Janet-Ludus, there are only a few types of panic:
* `fn-no-match`: no match against a function call
* `let-no-match`: no match against a `let` binding
* `match-no-match`: no match against a `match` form
* `generic-panic`: everything else
* `runtime-error`: an internal Ludus error
The first three are simply formatting differences.
There are no tracebacks.
Tracebacks should be easy enough, although there's some fiddly bits.
While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter.
And the traceback should look something like:
```
calling foo with (:bar, :baz)
at line 12 in input
calling bar with ()
at line 34 in prelude
calling baz with (1, 2, 3)
at line 12 in input
```
Which means, again: function names, ip->line conversion, and arguments.
The runtime needs a representation of the patterns in _any_ matching form.
The speed is so much greater now that I'm not so concerned about little optimizations.
So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec<String>`.)
So does a function, for `doc!`.
Same same re: `Vec<String>`.
A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured).
A VM also needs a register for the pattern.
So when there's a no match, we just yank the pattern and the scrutinee out of these registers.
This all seems very straightforward compared to the compiling & VM stuff.
Here's some stub code I wrote for dealing with ranges, source, line numbers:
```rust
let str = "foo bar baz\nquux frob\nthing thing thing";
let range = 0..4;
println!("{}", str.get(range).unwrap());
let lines: Vec<&str> = str.split_terminator("\n").collect();
println!("{:?}", lines);
let idx = 20;
let mut line_no = 1;
for i in 0..idx {
if str.chars().nth(i).unwrap() == '\n' {
line_no += 1;
}
}
println!("line {line_no}: {}", lines[line_no - 1]);
```
This is the thing I am about to do.
### I think the interpreter, uh, works?
#### 2025-06-24
I'm sure I'll find some small problems.
But right now the thing works.
At the moment, I'm thinking about how to get good error messages.
Panics are difficult.
And I'm worried about ariadne as the error reporting crate.
Since it writes to stdout, it has all kinds of escape codes.
I need a plain ass string, at least for the web frontend.
So.
Current task, however, is how to get reasonable panic error messages.
Let's simplify the problem.
First, let's get tracebacks and line numbers.
We use Chumsky spanned ASTs.
The span is just a range into an str.
What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback.
So instead of using Ariadne's fancy report builder, let's just do something more what we already have here:
```
Ludus panicked! no match
on line 1 in input,
calling: add
with arguments: ("foo")
expected match with one of:
()
(x as :number)
(x as :number, y as :number)
(x, y, ...zs)
((x1, y1), (x2, y2))
>>> add ("foo")
......^
```
We need:
* the location of the function call in terms of the line number
* the arguments used
* the patterns expected to match (special cases: `let` vs `match` vs `fn`)
That means, for bookkeeping, we need:
* In the compiler, line number
* In the VM, the arguments
* In the VM, the pattern AST node.
In Janet-Ludus, there are only a few types of panic:
* `fn-no-match`: no match against a function call
* `let-no-match`: no match against a `let` binding
* `match-no-match`: no match against a `match` form
* `generic-panic`: everything else
* `runtime-error`: an internal Ludus error
The first three are simply formatting differences.
There are no tracebacks.
Tracebacks should be easy enough, although there's some fiddly bits.
While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter.
And the traceback should look something like:
```
calling foo with (:bar, :baz)
at line 12 in input
calling bar with ()
at line 34 in prelude
calling baz with (1, 2, 3)
at line 12 in input
```
Which means, again: function names, ip->line conversion, and arguments.
The runtime needs a representation of the patterns in _any_ matching form.
The speed is so much greater now that I'm not so concerned about little optimizations.
So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec<String>`.)
So does a function, for `doc!`.
Same same re: `Vec<String>`.
A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured).
A VM also needs a register for the pattern.
So when there's a no match, we just yank the pattern and the scrutinee out of these registers.
This all seems very straightforward compared to the compiling & VM stuff.
Here's some stub code I wrote for dealing with ranges, source, line numbers:
```rust
let str = "foo bar baz\nquux frob\nthing thing thing";
let range = 0..4;
println!("{}", str.get(range).unwrap());
let lines: Vec<&str> = str.split_terminator("\n").collect();
println!("{:?}", lines);
let idx = 20;
let mut line_no = 1;
for i in 0..idx {
if str.chars().nth(i).unwrap() == '\n' {
line_no += 1;
}
}
println!("line {line_no}: {}", lines[line_no - 1]);
```
### Integration meeting with mnl
#### 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)?
* 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)
- 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.
* Users can create their own (public) repos and put stuff in there.
* We still want saving text output from web Ludus
* Later, with perceptrons & the book, we'll need additional solutions.
#### Integration hell
As predicted, Javascript is the devil.
wasm-pack has several targets:
* nodejs -> this should be what we want
* web -> this could be what we want
* bundler -> webpack confuses me
The simplest, shortest route should be to create a viable nodejs library.
It works.
I can wire up the wasm-pack output with a package.json, pull it down from npm, and it work.
However, because of course, vite, which svelte uses, doesn't like this.
We get an error that `TextEncoder is not a constructor`.
This, apparently, has something to do with the way that vite packages up node libraries?
See https://github.com/vitejs/vite/discussions/12826.
Web, in some ways, is even more straightforward.
It produces an ESM that just works in the browser.
And

1594
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "@ludus/rudus",
"version": "0.1.3",
"description": "A Rust-based Ludus bytecode interpreter.",
"type": "module",
"main": "pkg/ludus.js",
"directories": {},
"keywords": [],
"author": "Scott Richmond",
"license": "GPL-3.0",
"files": [
"pkg/rudus.js",
"pkg/ludus.js",
"pkg/rudus_bg.wasm",
"pkg/rudus_bg.wasm.d.ts",
"pkg/rudus.d.ts"
],
"devDependencies": {
}
}

3
pkg/README.md Normal file
View File

@ -0,0 +1,3 @@
# rudus
A Rust implementation of Ludus.

21
pkg/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
<title>Testing Ludus/WASM integration</title>
</head>
<body>
<script type="module">
import {run} from "./ludus.js";
window.ludus = run;
console.log(run(":foobar"));
</script>
<p>
Open the console. All the action's in there.
</p>
</body>
</html>

378
pkg/ludus.js Normal file
View File

@ -0,0 +1,378 @@
import init, {ludus} from "./rudus.js";
await init();
let res = null
let code = null
export function run (source) {
code = source
const output = ludus(source)
res = JSON.parse(output)
return res
}
export function stdout () {
if (!res) return ""
return res.io.stdout.data
}
export function turtle_commands () {
if (!res) return []
return res.io.turtle.data
}
export function result () {
return res
}
const turtle_init = {
position: [0, 0],
heading: 0,
pendown: true,
pencolor: "white",
penwidth: 1,
visible: true
}
const colors = {
black: [0, 0, 0, 255],
silver: [192, 192, 192, 255],
gray: [128, 128, 128, 255],
white: [255, 255, 255, 255],
maroon: [128, 0, 0, 255],
red: [255, 0, 0, 255],
purple: [128, 0, 128, 255],
fuchsia: [255, 0, 255, 255],
green: [0, 128, 0, 255],
lime: [0, 255, 0, 255],
olive: [128, 128, 0, 255],
yellow: [255, 255, 0, 255],
navy: [0, 0, 128, 255],
blue: [0, 0, 255, 255],
teal: [0, 128, 128, 255],
aqua: [0, 255, 255, 255],
}
function resolve_color (color) {
if (typeof color === 'string') return colors[color]
if (typeof color === 'number') return [color, color, color, 255]
if (Array.isArray(color)) return color
return [0, 0, 0, 255] // default to black?
}
let background_color = "black"
function add (v1, v2) {
const [x1, y1] = v1
const [x2, y2] = v2
return [x1 + x2, y1 + y2]
}
function mult (vector, scalar) {
const [x, y] = vector
return [x * scalar, y * scalar]
}
function unit_of (heading) {
const turns = -heading + 0.25
const radians = turn_to_rad(turns)
return [Math.cos(radians), Math.sin(radians)]
}
function command_to_state (prev_state, curr_command) {
const verb = curr_command[0]
switch (verb) {
case "goto": {
const [_, x, y] = curr_command
return {...prev_state, position: [x, y]}
}
case "home": {
return {...prev_state, position: [0, 0], heading: 0}
}
case "right": {
const [_, angle] = curr_command
const {heading} = prev_state
return {...prev_state, heading: heading + angle}
}
case "left": {
const [_, angle] = curr_command
const {heading} = prev_state
return {...prev_state, heading: heading - angle}
}
case "forward": {
const [_, steps] = curr_command
const {heading, position} = prev_state
const unit = unit_of(heading)
const move = mult(unit, steps)
return {...prev_state, position: add(position, move)}
}
case "back": {
const [_, steps] = curr_command
const {heading, position} = prev_state
const unit = unit_of(heading)
const move = mult(unit, -steps)
return {...prev_state, position: add(position, move)}
}
case "penup": {
return {...prev_state, pendown: false}
}
case "pendown": {
return {...prev_state, pendown: true}
}
case "penwidth": {
const [_, width] = curr_command
return {...prev_state, penwidth: width}
}
case "pencolor": {
const [_, color] = curr_command
return {...prev_state, pencolor: color}
}
case "setheading": {
const [_, heading] = curr_command
return {...prev_state, heading: heading}
}
case "loadstate": {
// console.log("LOADSTATE: ", curr_command)
const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
}
case "show": {
return {...prev_state, visible: true}
}
case "hide": {
return {...prev_state, visible: false}
}
case "background": {
background_color = curr_command[1]
return prev_state
}
}
}
function eq_vect (v1, v2) {
const [x1, y1] = v1
const [x2, y2] = v2
return (x1 === x2) && (y1 === y2)
}
function eq_color (c1, c2) {
if (c1 === c2) return true
const res1 = resolve_color(c1)
const res2 = resolve_color(c2)
for (let i = 0; i < res1.length; ++i) {
if (res1[i] !== res2[i]) return false
}
return true
}
function states_to_call (prev, curr) {
const calls = []
// whose state should we use?
// pen states will only differ on more than one property
// if we use `loadstate`
// my sense is `prev`, but that may change
if (prev.pendown && !eq_vect(prev.position, curr.position)) {
calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]])
}
if (!eq_color(curr.pencolor, prev.pencolor)) {
calls.push(["stroke", ...resolve_color(curr.pencolor)])
}
if (curr.penwidth !== prev.penwidth) {
calls.push(["strokeWeight", curr.penwidth])
}
return calls
}
const turtle_radius = 20
const turtle_angle = 0.385
let turtle_color = [255, 255, 255, 150]
function p5_call_root () {
return [
["background", ...resolve_color(background_color)],
["push"],
["rotate", Math.PI],
["scale", -1, 1],
["stroke", ...resolve_color(turtle_init.pencolor)],
]
}
function rotate (vector, heading) {
const radians = turn_to_rad(heading)
const [x, y] = vector
return [
(x * Math.cos (radians)) - (y * Math.sin (radians)),
(x * Math.sin (radians)) + (y * Math.cos (radians))
]
}
function turn_to_rad (heading) {
return (heading % 1) * 2 * Math.PI
}
function turn_to_deg (heading) {
return (heading % 1) * 360
}
function hex (n) {
return n.toString(16).padStart(2, "0")
}
function svg_render_line (prev, curr) {
if (!prev.pendown) return ""
if (eq_vect(prev.position, curr.position)) return ""
const {position: [x1, y1], pencolor, penwidth} = prev
const {position: [x2, y2]} = curr
const [r, g, b, a] = resolve_color(pencolor)
return `
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#${hex(r)}${hex(g)}${hex(b)}" stroke-linecap="square" stroke-opacity="${a/255}" stroke-width="${penwidth}"/>
`
}
function escape_svg (svg) {
return svg
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;")
}
export function extract_ludus (svg) {
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
return code
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, `"`)
.replace(/&apos;/g, `'`)
}
function svg_render_path (states) {
const path = []
for (let i = 1; i < states.length; ++i) {
const prev = states[i - 1]
const curr = states[i]
path.push(svg_render_line(prev, curr))
}
return path.join("")
}
function svg_render_turtle (state) {
if (!state.visible) return ""
const [fr, fg, fb, fa] = turtle_color
const fill_alpha = fa/255
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
const origin = [0, turtle_radius]
const [x1, y1] = origin
const [x2, y2] = rotate(origin, turtle_angle)
const [x3, y3] = rotate(origin, -turtle_angle)
const [pr, pg, pb, pa] = resolve_color(pencolor)
const pen_alpha = pa/255
const ink = pendown ? `<line x1="${x1}" y1="${y1}" x2="0" y2="0" stroke="#${hex(pr)}${hex(pg)}${hex(pb)}" stroke-linecap="round" stroke-opacity="${pen_alpha}" stroke-width="${penwidth}" />` : ""
return `
<g transform="translate(${x}, ${y})rotate(${-turn_to_deg(heading)})">
<polygon points="${x1} ${y1} ${x2} ${y2} ${x3} ${y3}" stroke="none" fill="#${hex(fr)}${hex(fg)}${hex(fb)})" fill-opacity="${fill_alpha}"/>
${ink}
</g>
`
}
export function svg (commands) {
// console.log(commands)
const states = [turtle_init]
commands.reduce((prev_state, command) => {
const new_state = command_to_state(prev_state, command)
states.push(new_state)
return new_state
}, turtle_init)
// console.log(states)
const {maxX, maxY, minX, minY} = states.reduce((accum, {position: [x, y]}) => {
accum.maxX = Math.max(accum.maxX, x)
accum.maxY = Math.max(accum.maxY, y)
accum.minX = Math.min(accum.minX, x)
accum.minY = Math.min(accum.minY, y)
return accum
}, {maxX: 0, maxY: 0, minX: 0, minY: 0})
const [r, g, b, a] = resolve_color(background_color)
if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150]
const view_width = (maxX - minX) * 1.2
const view_height = (maxY - minY) * 1.2
const margin = Math.max(view_width, view_height) * 0.1
const x_origin = minX - margin
const y_origin = -maxY - margin
const path = svg_render_path(states)
const turtle = svg_render_turtle(states[states.length - 1])
return `<?xml version="1.0" standalone="no"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="${x_origin} ${y_origin} ${view_width} ${view_height}" width="10in" height="8in">
<rect x="${x_origin - 5}" y="${y_origin - 5}" width="${view_width + 10}" height="${view_height + 10}" fill="#${hex(r)}${hex(g)}${hex(b)}" stroke-width="0" paint-order="fill" />
<g transform="scale(-1, 1) rotate(180)">
${path}
${turtle}
</g>
<ludus>
${escape_svg(code)}
</ludus>
</svg>
`
}
function p5_render_turtle (state, calls) {
if (!state.visible) return
calls.push(["push"])
const [r, g, b, a] = turtle_color
calls.push(["fill", r, g, b, a])
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
const origin = [0, turtle_radius]
const [x1, y1] = origin
const [x2, y2] = rotate(origin, turtle_angle)
const [x3, y3] = rotate(origin, -turtle_angle)
calls.push(["translate", x, y])
// need negative turtle rotation with the other p5 translations
calls.push(["rotate", -turn_to_rad(heading)])
calls.push(["noStroke"])
calls.push(["beginShape"])
calls.push(["vertex", x1, y1])
calls.push(["vertex", x2, y2])
calls.push(["vertex", x3, y3])
calls.push(["endShape"])
calls.push(["strokeWeight", penwidth])
calls.push(["stroke", ...resolve_color(pencolor)])
if (pendown) calls.push(["line", 0, 0, x1, y1])
calls.push(["pop"])
return calls
}
export function p5 (commands) {
const states = [turtle_init]
commands.reduce((prev_state, command) => {
const new_state = command_to_state(prev_state, command)
states.push(new_state)
return new_state
}, turtle_init)
// console.log(states)
const [r, g, b, _] = resolve_color(background_color)
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
const p5_calls = [...p5_call_root()]
for (let i = 1; i < states.length; ++i) {
const prev = states[i - 1]
const curr = states[i]
const calls = states_to_call(prev, curr)
for (const call of calls) {
p5_calls.push(call)
}
}
p5_calls[0] = ["background", ...resolve_color(background_color)]
p5_render_turtle(states[states.length - 1], p5_calls)
p5_calls.push(["pop"])
return p5_calls
}

15
pkg/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "rudus",
"type": "module",
"version": "0.0.1",
"files": [
"rudus_bg.wasm",
"rudus.js",
"rudus.d.ts"
],
"main": "rudus.js",
"types": "rudus.d.ts",
"sideEffects": [
"./snippets/*"
]
}

36
pkg/rudus.d.ts vendored Normal file
View File

@ -0,0 +1,36 @@
/* tslint:disable */
/* eslint-disable */
export function ludus(src: string): string;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly ludus: (a: number, b: number) => [number, number];
readonly __wbindgen_export_0: WebAssembly.Table;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

211
pkg/rudus.js Normal file
View File

@ -0,0 +1,211 @@
let wasm;
let WASM_VECTOR_LEN = 0;
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
/**
* @param {string} src
* @returns {string}
*/
export function ludus(src) {
let deferred2_0;
let deferred2_1;
try {
const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.ludus(ptr0, len0);
deferred2_0 = ret[0];
deferred2_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_init_externref_table = function() {
const table = wasm.__wbindgen_export_0;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
;
};
return imports;
}
function __wbg_init_memory(imports, memory) {
}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (typeof module !== 'undefined') {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
__wbg_init_memory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (typeof module_or_path !== 'undefined') {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
if (typeof module_or_path === 'undefined') {
module_or_path = new URL('rudus_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
__wbg_init_memory(imports);
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync };
export default __wbg_init;

BIN
pkg/rudus_bg.wasm Normal file

Binary file not shown.

9
pkg/rudus_bg.wasm.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const ludus: (a: number, b: number) => [number, number];
export const __wbindgen_export_0: WebAssembly.Table;
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_free: (a: number, b: number, c: number) => void;
export const __wbindgen_start: () => void;

5
pkg/test.js Normal file
View File

@ -0,0 +1,5 @@
import * as mod from "./ludus.js";
console.log(mod.run(`
:foobar
`));

4
sandbox.ld Normal file
View File

@ -0,0 +1,4 @@
repeat 1 {
fd! (100)
rt! (0.25)
}

917
sandbox_run.txt Normal file
View File

@ -0,0 +1,917 @@
{
box foos = []
fn foo! {
() -> update! (foos, append (_, :foo) )
}
fn foos! {
() -> repeat 4 {
{
foo! ()
}
}
}
foos! ()
unbox (foos)
}
closing over in type at 1: #{:list fn list/base...
closing over in eq? at 1: #{:list fn list/base...
closing over in eq? at 2: fn eq?
closing over in first at 1: #{:list fn list/base...
closing over in rest at 1: #{:list fn list/base...
closing over in inc at 1: #{:list fn list/base...
closing over in dec at 1: #{:list fn list/base...
closing over in count at 1: #{:list fn list/base...
closing over in any? at 1: fn empty?
closing over in any? at 2: fn not
closing over in list at 1: #{:list fn list/base...
closing over in append at 1: #{:list fn list/base...
closing over in fold at 1: fn fold
closing over in fold at 2: fn first
closing over in fold at 3: fn rest
closing over in foldr at 1: fn foldr
closing over in foldr at 2: fn first
closing over in foldr at 3: fn rest
closing over in map at 1: fn map
closing over in map at 2: fn append
closing over in map at 3: fn fold
closing over in filter at 1: fn filter
closing over in filter at 2: fn append
closing over in filter at 3: fn fold
closing over in keep at 1: fn some?
closing over in keep at 2: fn filter
closing over in concat at 1: #{:list fn list/base...
closing over in concat at 2: fn concat
closing over in concat at 3: fn fold
closing over in contains? at 1: fn first
closing over in contains? at 2: fn eq?
closing over in contains? at 3: fn rest
closing over in print! at 1: #{:list fn list/base...
closing over in show at 1: #{:list fn list/base...
closing over in report! at 1: fn print!
closing over in report! at 2: fn show
closing over in report! at 3: fn concat
closing over in doc! at 1: #{:list fn list/base...
closing over in doc! at 2: fn print!
closing over in string at 1: fn show
closing over in string at 2: fn string
closing over in string at 3: fn concat
closing over in join at 1: fn join
closing over in join at 2: fn concat
closing over in join at 3: fn fold
closing over in split at 1: #{:list fn list/base...
closing over in trim at 1: #{:list fn list/base...
closing over in upcase at 1: #{:list fn list/base...
closing over in downcase at 1: #{:list fn list/base...
closing over in chars at 1: #{:list fn list/base...
closing over in chars/safe at 1: #{:list fn list/base...
closing over in strip at 1: fn strip
closing over in words at 1: fn strip
closing over in words at 2: fn split
closing over in words at 3: fn empty?
closing over in words at 4: fn append
closing over in words at 5: fn fold
closing over in sentence at 1: fn join
closing over in to_number at 1: #{:list fn list/base...
closing over in unbox at 1: #{:list fn list/base...
closing over in store! at 1: #{:list fn list/base...
closing over in update! at 1: fn unbox
closing over in update! at 2: fn store!
closing over in add at 1: #{:list fn list/base...
closing over in add at 2: fn add
closing over in add at 3: fn fold
closing over in sub at 1: #{:list fn list/base...
closing over in sub at 2: fn sub
closing over in sub at 3: fn fold
closing over in mult at 1: #{:list fn list/base...
closing over in mult at 2: fn mult
closing over in mult at 3: fn fold
closing over in div at 1: #{:list fn list/base...
closing over in div at 2: fn mult
closing over in div at 3: fn fold
closing over in div at 4: fn div
closing over in div/0 at 1: #{:list fn list/base...
closing over in div/0 at 2: fn mult
closing over in div/0 at 3: fn fold
closing over in div/0 at 4: fn div/0
closing over in div/safe at 1: fn div
closing over in div/safe at 2: fn mult
closing over in div/safe at 3: fn fold
closing over in div/safe at 4: fn div/safe
closing over in inv at 1: fn div
closing over in inv/0 at 1: fn div/0
closing over in inv/safe at 1: fn div/safe
closing over in neg at 1: fn mult
closing over in gt? at 1: #{:list fn list/base...
closing over in gte? at 1: #{:list fn list/base...
closing over in lt? at 1: #{:list fn list/base...
closing over in lte? at 1: #{:list fn list/base...
closing over in between? at 1: fn gte?
closing over in between? at 2: fn lt?
closing over in neg? at 1: fn lt?
closing over in pos? at 1: fn gt?
closing over in abs at 1: fn neg?
closing over in abs at 2: fn mult
closing over in turn/deg at 1: fn mult
closing over in deg/turn at 1: fn div
closing over in turn/rad at 1: 6.283185307179586
closing over in turn/rad at 2: fn mult
closing over in rad/turn at 1: 6.283185307179586
closing over in rad/turn at 2: fn div
closing over in deg/rad at 1: 6.283185307179586
closing over in deg/rad at 2: fn div
closing over in deg/rad at 3: fn mult
closing over in rad/deg at 1: 6.283185307179586
closing over in rad/deg at 2: fn div
closing over in rad/deg at 3: fn mult
closing over in sin at 1: fn turn/rad
closing over in sin at 2: #{:list fn list/base...
closing over in sin at 3: fn deg/rad
closing over in cos at 1: fn turn/rad
closing over in cos at 2: #{:list fn list/base...
closing over in cos at 3: fn deg/rad
closing over in tan at 1: fn turn/rad
closing over in tan at 2: #{:list fn list/base...
closing over in tan at 3: fn deg/rad
closing over in rotate at 1: fn rotate
closing over in rotate at 2: fn cos
closing over in rotate at 3: fn mult
closing over in rotate at 4: fn sin
closing over in rotate at 5: fn sub
closing over in rotate at 6: fn add
closing over in atan/2 at 1: #{:list fn list/base...
closing over in atan/2 at 2: fn rad/turn
closing over in atan/2 at 3: fn atan/2
closing over in atan/2 at 4: fn rad/deg
closing over in angle at 1: fn atan/2
closing over in angle at 2: fn sub
closing over in mod at 1: #{:list fn list/base...
closing over in mod/0 at 1: #{:list fn list/base...
closing over in mod/safe at 1: #{:list fn list/base...
closing over in even? at 1: fn mod
closing over in even? at 2: fn eq?
closing over in odd? at 1: fn mod
closing over in odd? at 2: fn eq?
closing over in square at 1: fn mult
closing over in sqrt at 1: fn neg?
closing over in sqrt at 2: fn not
closing over in sqrt at 3: #{:list fn list/base...
closing over in sqrt/safe at 1: fn neg?
closing over in sqrt/safe at 2: fn not
closing over in sqrt/safe at 3: #{:list fn list/base...
closing over in sum_of_squares at 1: fn square
closing over in sum_of_squares at 2: fn add
closing over in sum_of_squares at 3: fn sum_of_squares
closing over in sum_of_squares at 4: fn fold
closing over in dist at 1: fn sum_of_squares
closing over in dist at 2: fn sqrt
closing over in dist at 3: fn dist
closing over in heading/vector at 1: fn neg
closing over in heading/vector at 2: fn add
closing over in heading/vector at 3: fn cos
closing over in heading/vector at 4: fn sin
closing over in floor at 1: #{:list fn list/base...
closing over in ceil at 1: #{:list fn list/base...
closing over in round at 1: #{:list fn list/base...
closing over in range at 1: #{:list fn list/base...
closing over in at at 1: #{:list fn list/base...
closing over in second at 1: fn ordered?
closing over in second at 2: fn at
closing over in last at 1: fn ordered?
closing over in last at 2: fn count
closing over in last at 3: fn dec
closing over in last at 4: fn at
closing over in slice at 1: fn slice
closing over in slice at 2: fn gte?
closing over in slice at 3: fn count
closing over in slice at 4: fn gt?
closing over in slice at 5: fn neg?
closing over in slice at 6: #{:list fn list/base...
closing over in butlast at 1: fn count
closing over in butlast at 2: fn dec
closing over in butlast at 3: fn slice
closing over in assoc at 1: #{:list fn list/base...
closing over in dissoc at 1: #{:list fn list/base...
closing over in get at 1: fn get
closing over in get at 2: #{:list fn list/base...
closing over in update at 1: fn get
closing over in update at 2: fn assoc
closing over in keys at 1: fn list
closing over in keys at 2: fn first
closing over in keys at 3: fn map
closing over in values at 1: fn list
closing over in values at 2: fn second
closing over in values at 3: fn map
closing over in has? at 1: fn has?
closing over in has? at 2: fn get
closing over in has? at 3: fn some?
closing over in dict at 1: fn assoc
closing over in dict at 2: fn fold
closing over in dict at 3: fn list
closing over in dict at 4: fn dict
closing over in each! at 1: fn each!
closing over in random at 1: #{:list fn list/base...
closing over in random at 2: fn random
closing over in random at 3: fn mult
closing over in random at 4: fn sub
closing over in random at 5: fn add
closing over in random at 6: fn count
closing over in random at 7: fn floor
closing over in random at 8: fn at
closing over in random at 9: fn keys
closing over in random at 10: fn get
closing over in random_int at 1: fn random
closing over in random_int at 2: fn floor
closing over in add_command! at 1: box { [] }
closing over in add_command! at 2: fn append
closing over in add_command! at 3: fn update!
closing over in add_command! at 4: box { #{:position (0...
closing over in add_command! at 5: fn unbox
closing over in add_command! at 6: fn apply_command
closing over in add_command! at 7: fn store!
closing over in forward! at 1: fn add_command!
closing over in back! at 1: fn add_command!
closing over in left! at 1: fn add_command!
closing over in right! at 1: fn add_command!
closing over in penup! at 1: fn add_command!
closing over in pendown! at 1: fn add_command!
closing over in pencolor! at 1: fn add_command!
closing over in penwidth! at 1: fn add_command!
closing over in background! at 1: fn add_command!
closing over in home! at 1: fn add_command!
closing over in clear! at 1: fn add_command!
closing over in goto! at 1: fn add_command!
closing over in goto! at 2: fn goto!
closing over in setheading! at 1: fn add_command!
closing over in showturtle! at 1: fn add_command!
closing over in hideturtle! at 1: fn add_command!
closing over in loadstate! at 1: fn add_command!
closing over in apply_command at 1: fn assoc
closing over in apply_command at 2: fn add
closing over in apply_command at 3: fn update
closing over in apply_command at 4: fn sub
closing over in apply_command at 5: fn heading/vector
closing over in apply_command at 6: fn mult
closing over in position at 1: box { #{:position (0...
closing over in position at 2: fn unbox
closing over in heading at 1: box { #{:position (0...
closing over in heading at 2: fn unbox
closing over in pendown? at 1: box { #{:position (0...
closing over in pendown? at 2: fn unbox
closing over in pencolor at 1: box { #{:position (0...
closing over in pencolor at 2: fn unbox
closing over in penwidth at 1: box { #{:position (0...
closing over in penwidth at 2: fn unbox
binding `foos` in sandbox
stack depth: 1; match depth: 0
at stack index: 0
new locals: foos@0//0
binding `foo!` in sandbox
stack depth: 2; match depth: 0
at stack index: 1
new locals: foos@0//0|foo!@1//0
***function clause matching: : ()
***calling function update! stack depth: 0
resolving binding `foos` in foo!
locals:
as enclosing upvalue 0
***calling function append stack depth: 1
resolving binding `append` in foo!
locals:
as enclosing upvalue 1
resolving binding `update!` in foo!
locals:
as enclosing upvalue 2
***after 2 args stack depth: 3
=== function chuncktion: foo!/0 ===
IDX | CODE | INFO
0000: reset_match
0001: ***function clause matching: : ()
0003: match
0004: jump 00000
0007: jump_if_no_match 00034
0010: ***calling function update! stack depth: 0
0012: resolving binding `foos` in foo!
locals:
0014: as enclosing upvalue 0
0016: get_upvalue 000
0018: ***calling function append stack depth: 1
0020: nothing
0021: constant 00000: :foo
0024: resolving binding `append` in foo!
locals:
0026: as enclosing upvalue 1
0028: get_upvalue 001
0030: partial 002
0032: resolving binding `update!` in foo!
locals:
0034: as enclosing upvalue 2
0036: get_upvalue 002
0038: ***after 2 args stack depth: 3
0040: tail_call 002
0042: store
0043: return
0044: panic_no_match
resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0
at locals position 0
resolving binding `append` in sandbox
locals: foos@0//0|foo!@1//0
as global
resolving binding `update!` in sandbox
locals: foos@0//0|foo!@1//0
as global
binding `foos!` in sandbox
stack depth: 3; match depth: 0
at stack index: 2
new locals: foos@0//0|foo!@1//0|foos!@2//0
***function clause matching: : ()
***calling function foo! stack depth: 1
resolving binding `foo!` in foos!
locals:
as enclosing upvalue 0
***after 0 args stack depth: 2
leaving scope 1
***leaving block before pop stack depth: 1
popping back from 1 to 1
=== function chuncktion: foos!/0 ===
IDX | CODE | INFO
0000: reset_match
0001: ***function clause matching: : ()
0003: match
0004: jump 00000
0007: jump_if_no_match 00042
0010: constant 00000: 4
0013: truncate
0014: jump 00001
0017: decrement
0018: duplicate
0019: jump_if_zero 00024
0022: ***calling function foo! stack depth: 1
0024: resolving binding `foo!` in foos!
locals:
0026: as enclosing upvalue 0
0028: get_upvalue 000
0030: ***after 0 args stack depth: 2
0032: tail_call 000
0034: store
0035: leaving scope 1
0037: ***leaving block before pop stack depth: 1
0039: popping back from 1 to 1
0041: load
0042: pop
0043: jump_back 00026
0046: pop
0047: constant 00001: nil
0050: store
0051: return
0052: panic_no_match
resolving binding `foo!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
at locals position 1
***calling function foos! stack depth: 3
resolving binding `foos!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
at locals position 2
***after 0 args stack depth: 4
***calling function unbox stack depth: 3
resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
at locals position 0
resolving binding `unbox` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
as global
***after 1 args stack depth: 5
leaving scope 0
releasing binding foos!@2//0
releasing binding foo!@1//0
releasing binding foos@0//0
***leaving block before pop stack depth: 3
popping back from 3 to 0
=== source code ===
box foos = []
fn foo! () -> update! (foos, append (_, :foo))
fn foos! () -> repeat 4 {
foo! ()
}
foos! ()
unbox (foos)
=== chunk: sandbox ===
IDX | CODE | INFO
0000: push_list
0001: push_box 082
0003: noop
0004: stack depth: 1; match depth: 0
0006: at stack index: 0
0008: new locals: foos@0//0
0010: constant 00000: fn foo!
0013: binding `foo!` in sandbox
0015: stack depth: 2; match depth: 0
0017: at stack index: 1
0019: new locals: foos@0//0|foo!@1//0
0021: resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0
0023: at locals position 0
0025: push_binding 000
0027: set_upvalue
0028: resolving binding `append` in sandbox
locals: foos@0//0|foo!@1//0
0030: as global
0032: constant 00001: :append
0035: push_global
0036: set_upvalue
0037: resolving binding `update!` in sandbox
locals: foos@0//0|foo!@1//0
0039: as global
0041: constant 00002: :update!
0044: push_global
0045: set_upvalue
0046: constant 00003: fn foos!
0049: binding `foos!` in sandbox
0051: stack depth: 3; match depth: 0
0053: at stack index: 2
0055: new locals: foos@0//0|foo!@1//0|foos!@2//0
0057: resolving binding `foo!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0059: at locals position 1
0061: push_binding 001
0063: set_upvalue
0064: ***calling function foos! stack depth: 3
0066: resolving binding `foos!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0068: at locals position 2
0070: push_binding 002
0072: ***after 0 args stack depth: 4
0074: call 000
0076: pop
0077: ***calling function unbox stack depth: 3
0079: resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0081: at locals position 0
0083: push_binding 000
0085: resolving binding `unbox` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0087: as global
0089: constant 00004: :unbox
0092: push_global
0093: ***after 1 args stack depth: 5
0095: call 001
0097: store
0098: leaving scope 0
0100: releasing binding foos!@2//0
0102: releasing binding foo!@1//0
0104: releasing binding foos@0//0
0106: ***leaving block before pop stack depth: 3
0108: popping back from 3 to 0
0110: pop_n 003
0112: load
=== vm run ===
0000: [] (_,_,_,_,_,_,_,_)
0000: push_list
0001: [->[]<-] (_,_,_,_,_,_,_,_)
0001: push_box 082
0002: [->box { [] }<-] (_,_,_,_,_,_,_,_)
0002: binding `foos` in sandbox
0004: [->box { [] }<-] (_,_,_,_,_,_,_,_)
0004: stack depth: 1; match depth: 0
0006: [->box { [] }<-] (_,_,_,_,_,_,_,_)
0006: at stack index: 0
0008: [->box { [] }<-] (_,_,_,_,_,_,_,_)
0008: new locals: foos@0//0
0010: [->box { [] }<-] (_,_,_,_,_,_,_,_)
0010: constant 00000: fn foo!
0013: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0013: binding `foo!` in sandbox
0015: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0015: stack depth: 2; match depth: 0
0017: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0017: at stack index: 1
0019: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0019: new locals: foos@0//0|foo!@1//0
0021: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0021: resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0
0023: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0023: at locals position 0
0025: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0025: push_binding 000
0027: [->box { [] }<-|fn foo!|box { [] }] (_,_,_,_,_,_,_,_)
0027: set_upvalue
closing over in foo! at 1: box { [] }
0028: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0028: resolving binding `append` in sandbox
locals: foos@0//0|foo!@1//0
0030: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0030: as global
0032: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0032: constant 00001: :append
0035: [->box { [] }<-|fn foo!|:append] (_,_,_,_,_,_,_,_)
0035: push_global
0036: [->box { [] }<-|fn foo!|fn append] (_,_,_,_,_,_,_,_)
0036: set_upvalue
closing over in foo! at 2: fn append
0037: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0037: resolving binding `update!` in sandbox
locals: foos@0//0|foo!@1//0
0039: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0039: as global
0041: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0041: constant 00002: :update!
0044: [->box { [] }<-|fn foo!|:update!] (_,_,_,_,_,_,_,_)
0044: push_global
0045: [->box { [] }<-|fn foo!|fn update!] (_,_,_,_,_,_,_,_)
0045: set_upvalue
closing over in foo! at 3: fn update!
0046: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_)
0046: constant 00003: fn foos!
0049: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0049: binding `foos!` in sandbox
0051: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0051: stack depth: 3; match depth: 0
0053: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0053: at stack index: 2
0055: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0055: new locals: foos@0//0|foo!@1//0|foos!@2//0
0057: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0057: resolving binding `foo!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0059: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0059: at locals position 1
0061: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0061: push_binding 001
0063: [->box { [] }<-|fn foo!|fn foos!|fn foo!] (_,_,_,_,_,_,_,_)
0063: set_upvalue
closing over in foos! at 1: fn foo!
0064: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0064: ***calling function foos! stack depth: 3
0066: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0066: resolving binding `foos!` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0068: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0068: at locals position 2
0070: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0070: push_binding 002
0072: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_)
0072: ***after 0 args stack depth: 4
0074: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_)
0074: call 000
=== calling into fn foos!/0 ===
0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0001: ***function clause matching: : ()
0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0003: match
0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0004: jump 00000
0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00042
0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0010: constant 00000: 4
0013: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0013: truncate
0014: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0014: jump 00001
0018: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0018: duplicate
0019: [box { [] }|fn foo!|fn foos!|->4<-|4] (_,_,_,_,_,_,_,_)
0019: jump_if_zero 00024
0022: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0022: ***calling function foo! stack depth: 1
0024: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0024: resolving binding `foo!` in foos!
locals:
0026: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0026: as enclosing upvalue 0
0028: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_)
0028: get_upvalue 000
0030: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_)
0030: ***after 0 args stack depth: 2
0032: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_)
0032: tail_call 000
=== tail call into fn foo!/0 from foos! ===
0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0001: ***function clause matching: : ()
0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0003: match
0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0004: jump 00000
0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00034
0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0010: ***calling function update! stack depth: 0
0012: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0012: resolving binding `foos` in foo!
locals:
0014: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0014: as enclosing upvalue 0
0016: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0016: get_upvalue 000
0018: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_)
0018: ***calling function append stack depth: 1
0020: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_)
0020: nothing
0021: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_] (_,_,_,_,_,_,_,_)
0021: constant 00000: :foo
0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
0024: resolving binding `append` in foo!
locals:
0026: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
0026: as enclosing upvalue 1
0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_)
0028: get_upvalue 001
0030: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo|fn append] (_,_,_,_,_,_,_,_)
0030: partial 002
0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0032: resolving binding `update!` in foo!
locals:
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0034: as enclosing upvalue 2
0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0036: get_upvalue 002
0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_)
0038: ***after 2 args stack depth: 3
0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_)
0040: tail_call 002
=== tail call into fn update!/2 from foo! ===
0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0001: match_depth 001
0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0003: constant 00000: :box
0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:box] (_,_,_,_,_,_,_,_)
0006: match_type
0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00012
0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0010: match_depth 000
0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0012: constant 00001: :fn
0015: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:fn] (_,_,_,_,_,_,_,_)
0015: match_type
0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0016: jump_if_no_match 00003
0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0019: jump 00000
0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0022: jump_if_no_match 00034
0025: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_)
0025: push_binding 000
0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }] (_,_,_,_,_,_,_,_)
0027: get_upvalue 000
0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }|fn unbox] (_,_,_,_,_,_,_,_)
0029: call 001
=== calling into fn unbox/1 ===
0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0001: match_depth 000
0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0003: constant 00000: :box
0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|:box] (_,_,_,_,_,_,_,_)
0006: match_type
0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00003
0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0010: jump 00000
0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0013: jump_if_no_match 00015
0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_)
0016: get_upvalue 000
0018: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
0018: constant 00001: :unbox
0021: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_)
0021: get_key
0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|fn unbox/base] (_,_,_,_,_,_,_,_)
0022: store
0023: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (fn unbox/base,_,_,_,_,_,_,_)
0023: push_binding 000
0025: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }] (fn unbox/base,_,_,_,_,_,_,_)
0025: load
0026: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }|fn unbox/base] (_,_,_,_,_,_,_,_)
0026: tail_call 001
=== tail call into fn unbox/base/1 from unbox ===
0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|[]] (_,_,_,_,_,_,_,_)
0028: store
0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] ([],_,_,_,_,_,_,_)
0029: pop
0030: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial] ([],_,_,_,_,_,_,_)
0030: return
== returning from fn unbox ==
0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
0031: reset_match
0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
0032: match
0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
0033: panic_if_no_match
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_)
0034: push_binding 002
0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]] (_,_,_,_,_,_,_,_)
0036: push_binding 001
0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]|fn append/partial] (_,_,_,_,_,_,_,_)
0038: call 001
=== calling into fn append/partial/1 ===
0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0001: match_depth 001
0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0003: constant 00000: :list
0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|:list] (_,_,_,_,_,_,_,_)
0006: match_type
0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00009
0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0010: match_depth 000
0012: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0012: match
0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0013: jump_if_no_match 00003
0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0016: jump 00000
0019: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0019: jump_if_no_match 00018
0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_)
0022: get_upvalue 000
0024: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
0024: constant 00001: :append
0027: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...|:append] (_,_,_,_,_,_,_,_)
0027: get_key
0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|fn append/base] (_,_,_,_,_,_,_,_)
0028: store
0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (fn append/base,_,_,_,_,_,_,_)
0029: push_binding 000
0031: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]] (fn append/base,_,_,_,_,_,_,_)
0031: push_binding 001
0033: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo] (fn append/base,_,_,_,_,_,_,_)
0033: load
0034: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo|fn append/base] (_,_,_,_,_,_,_,_)
0034: tail_call 002
=== tail call into fn append/base/2 from append ===
0036: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[:foo]] (_,_,_,_,_,_,_,_)
0036: store
0037: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] ([:foo],_,_,_,_,_,_,_)
0037: pop_n 002
0039: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]] ([:foo],_,_,_,_,_,_,_)
0039: return
== returning from fn append ==
0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
0040: reset_match
0041: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
0041: match
0042: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
0042: panic_if_no_match
0043: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_)
0043: push_binding 000
0045: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }] (_,_,_,_,_,_,_,_)
0045: push_binding 003
0047: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]] (_,_,_,_,_,_,_,_)
0047: get_upvalue 001
0049: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]|fn store!] (_,_,_,_,_,_,_,_)
0049: tail_call 002
=== tail call into fn store!/2 from update! ===
0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0001: match_depth 001
0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0003: constant 00000: :box
0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|:box] (_,_,_,_,_,_,_,_)
0006: match_type
0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00009
0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0010: match_depth 000
0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0012: match
0013: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0013: jump_if_no_match 00003
0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0016: jump 00000
0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0019: jump_if_no_match 00023
0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0022: get_upvalue 000
0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
0024: constant 00001: :store!
0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...|:store!] (_,_,_,_,_,_,_,_)
0027: get_key
0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_)
0028: store
0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (fn store!/base,_,_,_,_,_,_,_)
0029: push_binding 000
0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }] (fn store!/base,_,_,_,_,_,_,_)
0031: push_binding 001
0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]] (fn store!/base,_,_,_,_,_,_,_)
0033: load
0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_)
0034: call 002
=== calling into fn store!/base/2 ===
0036: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
0036: pop
0037: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0037: push_binding 001
0039: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
0039: store
0040: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_)
0040: load
0041: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_)
0041: store
0042: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_)
0042: pop_n 002
0044: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0044: return
== returning from fn store! ==
0076: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_)
0076: pop
0077: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0077: ***calling function unbox stack depth: 3
0079: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0079: resolving binding `foos` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0081: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0081: at locals position 0
0083: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_)
0083: push_binding 000
0085: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
0085: resolving binding `unbox` in sandbox
locals: foos@0//0|foo!@1//0|foos!@2//0
0087: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
0087: as global
0089: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_)
0089: constant 00004: :unbox
0092: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|:unbox] (_,_,_,_,_,_,_,_)
0092: push_global
0093: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_)
0093: ***after 1 args stack depth: 5
0095: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_)
0095: call 001
=== calling into fn unbox/1 ===
0000: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0001: match_depth 000
0003: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0003: constant 00000: :box
0006: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|:box] (_,_,_,_,_,_,_,_)
0006: match_type
0007: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0007: jump_if_no_match 00003
0010: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0010: jump 00000
0013: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0013: jump_if_no_match 00015
0016: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_)
0016: get_upvalue 000
0018: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_)
0018: constant 00001: :unbox
0021: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_)
0021: get_key
0022: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|fn unbox/base] (_,_,_,_,_,_,_,_)
0022: store
0023: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (fn unbox/base,_,_,_,_,_,_,_)
0023: push_binding 000
0025: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }] (fn unbox/base,_,_,_,_,_,_,_)
0025: load
0026: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }|fn unbox/base] (_,_,_,_,_,_,_,_)
0026: tail_call 001
=== tail call into fn unbox/base/1 from unbox ===
0028: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_)
0028: store
0029: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] ([:foo],_,_,_,_,_,_,_)
0029: pop
0030: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0030: return
== returning from fn unbox ==
0097: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_)
0097: store
0098: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0098: leaving scope 0
0100: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0100: releasing binding foos!@2//0
0102: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0102: releasing binding foo!@1//0
0104: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0104: releasing binding foos@0//0
0106: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0106: ***leaving block before pop stack depth: 3
0108: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0108: popping back from 3 to 0
0110: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_)
0110: pop_n 003
0112: [] ([:foo],_,_,_,_,_,_,_)
0112: load
0112: [] (_,_,_,_,_,_,_,_)
[:foo]

249
scratch/first.txt Normal file
View File

@ -0,0 +1,249 @@
=== vm run: test ===
0000: [] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [] (_,_,_,_,_,_,_,_)
0001: constant 00000: 2
0004: [->2<-] (_,_,_,_,_,_,_,_)
0004: match
0005: [->2<-] (_,_,_,_,_,_,_,_)
0005: panic_if_no_match
0006: [->2<-] (_,_,_,_,_,_,_,_)
0006: push_list
0007: [->2<-|[]] (_,_,_,_,_,_,_,_)
0007: constant 00001: 1
0010: [->2<-|[]|1] (_,_,_,_,_,_,_,_)
0010: append_list
0011: [->2<-|[1]] (_,_,_,_,_,_,_,_)
0011: constant 00000: 2
0014: [->2<-|[1]|2] (_,_,_,_,_,_,_,_)
0014: append_list
0015: [->2<-|[1, 2]] (_,_,_,_,_,_,_,_)
0015: constant 00002: 3
0018: [->2<-|[1, 2]|3] (_,_,_,_,_,_,_,_)
0018: append_list
0019: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0019: ***entering loop with stack depth of 2
0021: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0021: store_n 001
0023: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_)
0023: ***after store, stack depth is now 2
0025: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_)
0025: load
0026: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0026: ***after load, stack depth is now 2
0028: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0028: reset_match
0029: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0029: match_depth 000
0031: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0031: match_list 000
0033: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0033: jump_if_no_match 00006
0042: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0042: jump_if_no_match 00010
0055: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0055: reset_match
0056: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0056: match_depth 000
0058: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0058: match_list 001
0060: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0060: jump_if_no_match 00012
0075: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0075: jump_if_no_match 00030
0108: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0108: reset_match
0109: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0109: match_depth 000
0111: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0111: match_splatted_list 002
0113: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0113: jump_if_no_match 00019
0116: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0116: load_splatted_list 002
0118: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0118: match_depth 001
0120: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0120: match
0121: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0121: jump_if_no_match 00010
0124: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0124: match_depth 000
0126: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0126: match
0127: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0127: jump_if_no_match 00004
0130: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0130: jump 00002
0135: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0135: jump_if_no_match 00068
0138: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0138: ***before visiting body, the stack depth is 4
0140: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0140: ***calling function eq? stack depth: 4
0142: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0142: ***calling function first stack depth: 4
0144: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0144: resolving binding `xs` in test
0146: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0146: push_binding 003
0148: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0148: resolving binding `first` in test
0150: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0150: constant 00004: :first
0153: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_)
0153: push_global
0154: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
0154: ***after 1 args stack depth: 6
0156: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
0156: call 001
=== calling into fn first/1 ===
0000: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0001: match_depth 000
0003: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0003: match_list 000
0005: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0005: jump_if_no_match 00006
0014: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0014: jump_if_no_match 00003
0020: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0020: jump_if_no_match 00005
0028: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0028: match_depth 000
0030: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0030: constant 00000: :list
0033: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_)
0033: match_type
0034: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0034: jump_if_no_match 00003
0037: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0037: jump 00000
0040: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0040: jump_if_no_match 00024
0043: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0043: ***accessing keyword: base :first stack depth: 1
0045: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0045: resolving binding `base` in first
0047: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0047: get_upvalue 000
0049: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_)
0049: constant 00001: :first
0052: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...|:first] (_,_,_,_,_,_,_,_)
0052: get_key
0053: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
0053: ***after keyword access stack depth: 2
0055: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
0055: stash
0056: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_)
0056: pop
0057: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
0057: resolving binding `xs` in first
0059: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
0059: push_binding 000
0061: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_)
0061: load
0062: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_)
0062: tail_call 001
=== tail call into fn first/base/1 from first ===
0158: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
0158: resolving binding `test` in test
0160: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
0160: push_binding 000
0162: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_)
0162: resolving binding `eq?` in test
0164: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_)
0164: constant 00003: :eq?
0167: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|:eq?] (_,_,_,_,_,_,_,_)
0167: push_global
0168: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_)
0168: ***after 2 args stack depth: 7
0170: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_)
0170: call 002
=== calling into fn eq?/2 ===
0000: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0001: match_depth 001
0003: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0003: match
0004: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0004: jump_if_no_match 00009
0007: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0007: match_depth 000
0009: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0009: match
0010: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0010: jump_if_no_match 00003
0013: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0013: jump 00000
0016: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0016: jump_if_no_match 00029
0019: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0019: ***accessing keyword: base :eq? stack depth: 2
0021: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0021: resolving binding `base` in eq?
0023: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_)
0023: get_upvalue 000
0025: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_)
0025: constant 00000: :eq?
0028: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...|:eq?] (_,_,_,_,_,_,_,_)
0028: get_key
0029: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_)
0029: ***after keyword access stack depth: 3
0031: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_)
0031: stash
0032: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_)
0032: pop
0033: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_)
0033: resolving binding `x` in eq?
0035: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_)
0035: push_binding 000
0037: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_)
0037: resolving binding `y` in eq?
0039: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_)
0039: push_binding 001
0041: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2] (fn eq?/base,_,_,_,_,_,_,_)
0041: load
0042: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2|fn eq?/base] (_,_,_,_,_,_,_,_)
0042: tail_call 002
=== tail call into fn eq?/base/2 from eq? ===
0172: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
0172: jump_if_false 00004
0175: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0175: true
0176: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
0176: jump 00018
0197: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
0197: ***after visiting loop body, the stack depth is 5
0199: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_)
0199: store
0200: [->2<-|[1, 2, 3]|1|[2, 3]|_] (true,_,_,_,_,_,_,_)
0200: pop
0201: [->2<-|[1, 2, 3]|1|[2, 3]] (true,_,_,_,_,_,_,_)
0201: pop
0202: [->2<-|[1, 2, 3]|1] (true,_,_,_,_,_,_,_)
0202: pop
0203: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_)
0203: jump 00001
0207: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_)
0207: load
0208: [->2<-|[1, 2, 3]|true] (_,_,_,_,_,_,_,_)
0208: store
0209: [->2<-|[1, 2, 3]|_] (true,_,_,_,_,_,_,_)
0209: pop_n 002
0211: [->2<-] (true,_,_,_,_,_,_,_)
0211: load
0212: [->2<-] (_,_,_,_,_,_,_,_)
true
**********
**********

291
scratch/second.txt Normal file
View File

@ -0,0 +1,291 @@
=== vm run: test ===
0000: [] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [] (_,_,_,_,_,_,_,_)
0001: constant 00000: 4
0004: [->4<-] (_,_,_,_,_,_,_,_)
0004: match
0005: [->4<-] (_,_,_,_,_,_,_,_)
0005: panic_if_no_match
0006: [->4<-] (_,_,_,_,_,_,_,_)
0006: push_list
0007: [->4<-|[]] (_,_,_,_,_,_,_,_)
0007: constant 00001: 1
0010: [->4<-|[]|1] (_,_,_,_,_,_,_,_)
0010: append_list
0011: [->4<-|[1]] (_,_,_,_,_,_,_,_)
0011: constant 00002: 2
0014: [->4<-|[1]|2] (_,_,_,_,_,_,_,_)
0014: append_list
0015: [->4<-|[1, 2]] (_,_,_,_,_,_,_,_)
0015: constant 00003: 3
0018: [->4<-|[1, 2]|3] (_,_,_,_,_,_,_,_)
0018: append_list
0019: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0019: ***entering loop with stack depth of 2
0021: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0021: store_n 001
0023: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_)
0023: ***after store, stack depth is now 2
0025: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_)
0025: load
0026: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0026: ***after load, stack depth is now 2
0028: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0028: reset_match
0029: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0029: match_depth 000
0031: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0031: match_list 000
0033: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0033: jump_if_no_match 00006
0042: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0042: jump_if_no_match 00010
0055: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0055: reset_match
0056: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0056: match_depth 000
0058: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0058: match_list 001
0060: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0060: jump_if_no_match 00012
0075: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0075: jump_if_no_match 00030
0108: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0108: reset_match
0109: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0109: match_depth 000
0111: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0111: match_splatted_list 002
0113: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0113: jump_if_no_match 00019
0116: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_)
0116: load_splatted_list 002
0118: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0118: match_depth 001
0120: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0120: match
0121: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0121: jump_if_no_match 00010
0124: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0124: match_depth 000
0126: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0126: match
0127: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0127: jump_if_no_match 00004
0130: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0130: jump 00002
0135: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0135: jump_if_no_match 00068
0138: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0138: ***before visiting body, the stack depth is 4
0140: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0140: ***calling function eq? stack depth: 4
0142: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0142: ***calling function first stack depth: 4
0144: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0144: resolving binding `xs` in test
0146: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0146: push_binding 003
0148: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0148: resolving binding `first` in test
0150: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0150: constant 00005: :first
0153: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_)
0153: push_global
0154: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
0154: ***after 1 args stack depth: 6
0156: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_)
0156: call 001
=== calling into fn first/1 ===
0000: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0001: match_depth 000
0003: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0003: match_list 000
0005: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0005: jump_if_no_match 00006
0014: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0014: jump_if_no_match 00003
0020: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0020: jump_if_no_match 00005
0028: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0028: match_depth 000
0030: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0030: constant 00000: :list
0033: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_)
0033: match_type
0034: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0034: jump_if_no_match 00003
0037: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0037: jump 00000
0040: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0040: jump_if_no_match 00024
0043: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0043: ***accessing keyword: base :first stack depth: 1
0045: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0045: resolving binding `base` in first
0047: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_)
0047: get_upvalue 000
0049: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...] (_,_,_,_,_,_,_,_)
0049: constant 00001: :first
0052: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...|:first] (_,_,_,_,_,_,_,_)
0052: get_key?
0053: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
0053: ***after keyword access stack depth: 2
0055: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_)
0055: stash
0056: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_)
0056: pop
0057: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
0057: resolving binding `xs` in first
0059: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_)
0059: push_binding 000
0061: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_)
0061: load
0062: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_)
0062: tail_call 001
=== tail call into fn first/base/1 from first ===
0158: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
0158: resolving binding `test` in test
0160: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_)
0160: push_binding 000
0162: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_)
0162: resolving binding `eq?` in test
0164: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_)
0164: constant 00004: :eq?
0167: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|:eq?] (_,_,_,_,_,_,_,_)
0167: push_global
0168: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_)
0168: ***after 2 args stack depth: 7
0170: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_)
0170: call 002
=== calling into fn eq?/2 ===
0000: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0000: reset_match
0001: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0001: match_depth 001
0003: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0003: match
0004: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0004: jump_if_no_match 00009
0007: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0007: match_depth 000
0009: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0009: match
0010: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0010: jump_if_no_match 00003
0013: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0013: jump 00000
0016: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0016: jump_if_no_match 00029
0019: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0019: ***accessing keyword: base :eq? stack depth: 2
0021: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0021: resolving binding `base` in eq?
0023: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_)
0023: get_upvalue 000
0025: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...] (_,_,_,_,_,_,_,_)
0025: constant 00000: :eq?
0028: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...|:eq?] (_,_,_,_,_,_,_,_)
0028: get_key
0029: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_)
0029: ***after keyword access stack depth: 3
0031: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_)
0031: stash
0032: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_)
0032: pop
0033: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_)
0033: resolving binding `x` in eq?
0035: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_)
0035: push_binding 000
0037: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_)
0037: resolving binding `y` in eq?
0039: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_)
0039: push_binding 001
0041: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4] (fn eq?/base,_,_,_,_,_,_,_)
0041: load
0042: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4|fn eq?/base] (_,_,_,_,_,_,_,_)
0042: tail_call 002
=== tail call into fn eq?/base/2 from eq? ===
0172: [->4<-|[1, 2, 3]|1|[2, 3]|false] (_,_,_,_,_,_,_,_)
0172: jump_if_false 00004
0179: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0179: before visiting recur args the compiler thinks the stack depth is 5
0181: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0181: recur arg: 0
0183: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0183: resolving binding `xs` in test
0185: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_)
0185: push_binding 003
0187: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0187: after visiting recur args the compiler thinks the stack depth is 6
0189: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_)
0189: store_n 001
0191: [->4<-|[1, 2, 3]|1|[2, 3]] ([2, 3],_,_,_,_,_,_,_)
0191: pop_n 004
0193: [] ([2, 3],_,_,_,_,_,_,_)
0193: load
0194: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0194: jump_back 00168
0026: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0026: ***after load, stack depth is now 2
0028: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0028: reset_match
0029: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0029: match_depth 000
0031: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0031: match_list 000
0033: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0033: jump_if_no_match 00006
0042: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0042: jump_if_no_match 00010
0055: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0055: reset_match
0056: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0056: match_depth 000
0058: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0058: match_list 001
0060: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0060: jump_if_no_match 00012
0075: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0075: jump_if_no_match 00030
0108: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0108: reset_match
0109: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0109: match_depth 000
0111: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0111: match_splatted_list 002
0113: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0113: jump_if_no_match 00019
0116: [->[2, 3]<-] (_,_,_,_,_,_,_,_)
0116: load_splatted_list 002
0118: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0118: match_depth 001
0120: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0120: match
0121: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0121: jump_if_no_match 00010
0124: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0124: match_depth 000
0126: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0126: match
0127: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0127: jump_if_no_match 00004
0130: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0130: jump 00002
0135: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0135: jump_if_no_match 00068
0138: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0138: ***before visiting body, the stack depth is 4
0140: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0140: ***calling function eq? stack depth: 4
0142: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0142: ***calling function first stack depth: 4
0144: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0144: resolving binding `xs` in test
0146: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_)
0146: push_binding 003
thread 'main' panicked at src/vm.rs:313:51:
index out of bounds: the len is 3 but the index is 3

View File

@ -4,40 +4,44 @@ use ran::ran_f64;
use std::rc::Rc;
#[derive(Clone, Debug)]
pub enum BaseFn<'src> {
Nullary(fn() -> Value<'src>),
Unary(fn(&Value<'src>) -> Value<'src>),
Binary(fn(&Value<'src>, &Value<'src>) -> Value<'src>),
Ternary(fn(&Value<'src>, &Value<'src>, &Value<'src>) -> Value<'src>),
pub enum BaseFn {
Nullary(&'static str, fn() -> Value),
Unary(&'static str, fn(&Value) -> Value),
Binary(&'static str, fn(&Value, &Value) -> Value),
Ternary(&'static str, fn(&Value, &Value, &Value) -> Value),
}
pub fn eq<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
Value::Boolean(x == y)
}
pub fn add<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x + y),
_ => unreachable!("internal Ludus error"),
pub fn eq(x: &Value, y: &Value) -> Value {
if x == y {
Value::True
} else {
Value::False
}
}
pub fn sub<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
pub fn add(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x + y),
_ => unreachable!("internal Ludus error: wrong arguments to base add: {x}, {y}"),
}
}
pub fn sub(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x - y),
_ => unreachable!("internal Ludus error"),
}
}
pub fn unbox<'src>(x: &Value<'src>) -> Value<'src> {
pub fn unbox(x: &Value) -> Value {
match x {
Value::Box(_, cell) => cell.borrow().clone(),
Value::Box(cell) => cell.as_ref().borrow().clone(),
_ => unreachable!("internal Ludus error"),
}
}
pub fn store<'src>(b: &Value<'src>, val: &Value<'src>) -> Value<'src> {
if let Value::Box(_, cell) = b {
pub fn store(b: &Value, val: &Value) -> Value {
if let Value::Box(cell) = b {
cell.replace(val.clone());
val.clone()
} else {
@ -47,103 +51,88 @@ pub fn store<'src>(b: &Value<'src>, val: &Value<'src>) -> Value<'src> {
// TODO: do better than returning just the docstr
// name, patterns, AND docstring
pub fn doc<'src>(f: &Value<'src>) -> Value<'src> {
pub fn doc(f: &Value) -> Value {
match f {
Value::Fn(f) => {
let name = &f.name;
let doc = &f.doc;
if let Some(docstr) = doc {
Value::AllocatedString(Rc::new(format!("{name}: {docstr}")))
} else {
Value::AllocatedString(Rc::new(format!("{name}: no documentation found")))
}
}
_ => Value::InternedString("no documentation found"),
Value::Fn(f) => f.as_ref().doc(),
_ => Value::Interned("no documentation found"),
}
}
pub fn and<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
Value::Boolean(x.bool() && y.bool())
}
pub fn assoc<'src>(dict: &Value<'src>, key: &Value<'src>, value: &Value<'src>) -> Value<'src> {
pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value {
match (dict, key) {
(Value::Dict(d), Value::Keyword(k)) => Value::Dict(d.update(k, value.clone())),
_ => unreachable!("internal Ludus error"),
(Value::Dict(d), Value::Keyword(k)) => Value::Dict(Box::new(d.update(k, value.clone()))),
_ => unreachable!("internal Ludus error calling assoc with ({dict}, {key}, {value})"),
}
}
pub fn r#bool<'src>(x: &Value<'src>) -> Value<'src> {
Value::Boolean(x.bool())
pub fn r#bool(x: &Value) -> Value {
match x {
Value::Nil | Value::False => Value::False,
_ => Value::True,
}
}
pub fn chars<'src>(x: &Value<'src>) -> Value<'src> {
pub fn chars(x: &Value) -> Value {
match x {
Value::InternedString(s) => {
Value::Interned(s) => {
let chars = s.chars();
let mut charlist = vector![];
for char in chars {
if char.is_ascii() {
charlist.push_back(Value::AllocatedString(Rc::new(char.to_string())))
charlist.push_back(Value::String(Rc::new(char.to_string())))
} else {
return Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::AllocatedString(Rc::new(format!(
"{char} is not an ascii character"
))),
Value::String(Rc::new(format!("{char} is not an ascii character"))),
]));
}
}
Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)]))
Value::Tuple(Rc::new(vec![
Value::Keyword("ok"),
Value::List(Box::new(charlist)),
]))
}
Value::AllocatedString(s) => {
Value::String(s) => {
let chars = s.chars();
let mut charlist = vector![];
for char in chars {
if char.is_ascii() {
charlist.push_back(Value::AllocatedString(Rc::new(char.to_string())))
charlist.push_back(Value::String(Rc::new(char.to_string())))
} else {
return Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::AllocatedString(Rc::new(format!(
"{char} is not an ascii character"
))),
Value::String(Rc::new(format!("{char} is not an ascii character"))),
]));
}
}
Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)]))
Value::Tuple(Rc::new(vec![
Value::Keyword("ok"),
Value::List(Box::new(charlist)),
]))
}
_ => unreachable!("internal Ludus error"),
}
}
// TODO: figure out how to get to opportunistic mutation here
pub fn concat<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
pub fn concat(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::InternedString(x), Value::InternedString(y)) => {
Value::AllocatedString(Rc::new(format!("{x}{y}")))
}
(Value::AllocatedString(x), Value::AllocatedString(y)) => {
Value::AllocatedString(Rc::new(format!("{x}{y}")))
}
(Value::AllocatedString(x), Value::InternedString(y)) => {
Value::AllocatedString(Rc::new(format!("{x}{y}")))
}
(Value::InternedString(x), Value::AllocatedString(y)) => {
Value::AllocatedString(Rc::new(format!("{x}{y}")))
}
(Value::Interned(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))),
(Value::String(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))),
(Value::String(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))),
(Value::Interned(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))),
(Value::List(x), Value::List(y)) => {
let mut newlist = x.clone();
newlist.append(y.clone());
Value::List(newlist)
let mut newlist = *x.clone();
newlist.append(*y.clone());
Value::List(Box::new(newlist))
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn append<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
pub fn append(x: &Value, y: &Value) -> Value {
match x {
Value::List(list) => {
let mut newlist = list.clone();
@ -154,35 +143,35 @@ pub fn append<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
}
}
pub fn dec<'src>(x: &Value<'src>) -> Value<'src> {
pub fn dec(x: &Value) -> Value {
match x {
Value::Number(n) => Value::Number(n - 1.0),
_ => unreachable!("internal Ludus error"),
}
}
pub fn inc<'src>(x: &Value<'src>) -> Value<'src> {
pub fn inc(x: &Value) -> Value {
match x {
Value::Number(n) => Value::Number(n + 1.0),
_ => unreachable!("internal Ludus error"),
}
}
pub fn div<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
pub fn div(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x / y),
_ => unreachable!("internal Ludus error"),
}
}
pub fn mult<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
pub fn mult(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x * y),
_ => unreachable!("internal Ludus error"),
}
}
pub fn dissoc<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
pub fn dissoc(dict: &Value, key: &Value) -> Value {
match (dict, key) {
(Value::Dict(dict), Value::Keyword(key)) => {
let mut new = dict.clone();
@ -193,7 +182,7 @@ pub fn dissoc<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
}
}
pub fn first<'src>(ordered: &Value<'src>) -> Value<'src> {
pub fn first(ordered: &Value) -> Value {
match ordered {
Value::List(list) => match list.front() {
Some(n) => n.clone(),
@ -209,7 +198,7 @@ pub fn first<'src>(ordered: &Value<'src>) -> Value<'src> {
// TODO: figure out how to handle negative numbers
// the cast from f64 to usize discards sign info
pub fn at<'src>(ordered: &Value<'src>, i: &Value<'src>) -> Value<'src> {
pub fn at(ordered: &Value, i: &Value) -> Value {
match (ordered, i) {
(Value::List(list), Value::Number(n)) => {
let i = *n as usize;
@ -229,7 +218,7 @@ pub fn at<'src>(ordered: &Value<'src>, i: &Value<'src>) -> Value<'src> {
}
}
pub fn get<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
pub fn get(dict: &Value, key: &Value) -> Value {
match (dict, key) {
(Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) {
Some(x) => x.clone(),
@ -239,7 +228,7 @@ pub fn get<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
}
}
pub fn last<'src>(ordered: &Value<'src>) -> Value<'src> {
pub fn last(ordered: &Value) -> Value {
match ordered {
Value::List(list) => match list.last() {
Some(x) => x.clone(),
@ -253,12 +242,8 @@ pub fn last<'src>(ordered: &Value<'src>) -> Value<'src> {
}
}
pub fn or<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
Value::Boolean(x.bool() || y.bool())
}
// TODO: fix this: x is a list of all the args passed to Ludus's print!
pub fn print<'src>(x: &Value<'src>) -> Value<'src> {
pub fn print(x: &Value) -> Value {
let Value::List(args) = x else {
unreachable!("internal Ludus error")
};
@ -266,35 +251,41 @@ pub fn print<'src>(x: &Value<'src>) -> Value<'src> {
.iter()
.map(|val| format!("{val}"))
.collect::<Vec<_>>()
.join("");
.join(" ");
println!("{out}");
Value::Keyword("ok")
}
pub fn show<'src>(x: &Value<'src>) -> Value<'src> {
Value::AllocatedString(Rc::new(format!("{x}")))
pub fn show(x: &Value) -> Value {
Value::String(Rc::new(format!("{x}")))
}
pub fn rest<'src>(ordered: &Value<'src>) -> Value<'src> {
pub fn rest(ordered: &Value) -> Value {
match ordered {
Value::List(list) => Value::List(list.clone().split_at(1).1),
Value::Tuple(tuple) => Value::List(Vector::from_iter(tuple.iter().next().cloned())),
Value::List(list) => Value::List(Box::new(list.clone().split_at(1).1)),
Value::Tuple(tuple) => {
Value::List(Box::new(Vector::from_iter(tuple.iter().next().cloned())))
}
Value::Interned(str) => Value::String(Rc::new(str.get(1..).unwrap_or("").to_string())),
Value::String(str) => Value::String(Rc::new(
str.clone().as_str().get(1..).unwrap_or("").to_string(),
)),
_ => unreachable!("internal Ludus error"),
}
}
pub fn count<'src>(coll: &Value<'src>) -> Value<'src> {
pub fn count(coll: &Value) -> Value {
match coll {
Value::Dict(d) => Value::Number(d.len() as f64),
Value::List(l) => Value::Number(l.len() as f64),
Value::Tuple(t) => Value::Number(t.len() as f64),
Value::AllocatedString(s) => Value::Number(s.len() as f64),
Value::InternedString(s) => Value::Number(s.len() as f64),
Value::String(s) => Value::Number(s.len() as f64),
Value::Interned(s) => Value::Number(s.len() as f64),
_ => unreachable!("internal Ludus error"),
}
}
pub fn range<'src>(start: &Value<'src>, end: &Value<'src>) -> Value<'src> {
pub fn range(start: &Value, end: &Value) -> Value {
match (start, end) {
(Value::Number(start), Value::Number(end)) => {
let start = *start as isize;
@ -303,25 +294,25 @@ pub fn range<'src>(start: &Value<'src>, end: &Value<'src>) -> Value<'src> {
for n in start..end {
range.push_back(Value::Number(n as f64))
}
Value::List(range)
Value::List(Box::new(range))
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn slice<'src>(ordered: &Value<'src>, start: &Value<'src>, end: &Value<'src>) -> Value<'src> {
pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
match (ordered, start, end) {
(Value::List(list), Value::Number(start), Value::Number(end)) => {
let mut newlist = list.clone();
let start = std::cmp::max(*start as usize, 0);
let end = std::cmp::min(*end as usize, list.len());
Value::List(newlist.slice(start..end))
Value::List(Box::new(newlist.slice(start..end)))
}
// TODO: figure out something better to do than return an empty string on a bad slice
(Value::AllocatedString(string), Value::Number(start), Value::Number(end)) => {
(Value::String(string), Value::Number(start), Value::Number(end)) => {
let start = std::cmp::max(*start as usize, 0);
let end = std::cmp::min(*end as usize, string.len());
Value::AllocatedString(Rc::new(
Value::String(Rc::new(
string
.clone()
.as_str()
@ -330,19 +321,19 @@ pub fn slice<'src>(ordered: &Value<'src>, start: &Value<'src>, end: &Value<'src>
.to_string(),
))
}
(Value::InternedString(string), Value::Number(start), Value::Number(end)) => {
(Value::Interned(string), Value::Number(start), Value::Number(end)) => {
let start = std::cmp::max(*start as usize, 0);
let end = std::cmp::min(*end as usize, string.len());
Value::AllocatedString(Rc::new(string.get(start..end).unwrap_or("").to_string()))
Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string()))
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn list<'src>(x: &Value<'src>) -> Value<'src> {
pub fn list(x: &Value) -> Value {
match x {
Value::List(_) => x.clone(),
Value::Tuple(t) => Value::List(Vector::from_iter(t.iter().cloned())),
Value::Tuple(t) => Value::List(Box::new(Vector::from_iter(t.iter().cloned()))),
Value::Dict(d) => {
let kvs = d.iter();
let mut list = vector![];
@ -350,292 +341,292 @@ pub fn list<'src>(x: &Value<'src>) -> Value<'src> {
let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()]));
list.push_back(kv);
}
Value::List(list)
Value::List(Box::new(list))
}
_ => Value::List(vector![x.clone()]),
_ => Value::List(Box::new(vector![x.clone()])),
}
}
pub fn number<'src>(x: &Value<'src>) -> Value<'src> {
pub fn number(x: &Value) -> Value {
match x {
Value::InternedString(string) => match string.parse::<f64>() {
Value::Interned(string) => match string.parse::<f64>() {
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])),
Err(_) => Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))),
Value::String(Rc::new(format!("could not parse `{string}` as a number"))),
])),
},
Value::AllocatedString(string) => match string.parse::<f64>() {
Value::String(string) => match string.parse::<f64>() {
Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])),
Err(_) => Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))),
Value::String(Rc::new(format!("could not parse `{string}` as a number"))),
])),
},
_ => unreachable!("internal Ludus error"),
}
}
pub fn r#type<'src>(x: &Value<'src>) -> Value<'src> {
pub fn r#type(x: &Value) -> Value {
match x {
Value::Nil => Value::Keyword("nil"),
Value::Number(_) => Value::Keyword("number"),
Value::Boolean(_) => Value::Keyword("boolean"),
Value::True | Value::False => Value::Keyword("bool"),
Value::Keyword(_) => Value::Keyword("keyword"),
Value::Tuple(_) => Value::Keyword("tuple"),
Value::InternedString(_) => Value::Keyword("string"),
Value::AllocatedString(_) => Value::Keyword("string"),
Value::Interned(_) => Value::Keyword("string"),
Value::String(_) => Value::Keyword("string"),
Value::List(_) => Value::Keyword("list"),
Value::Dict(_) => Value::Keyword("dict"),
Value::Fn(_) => Value::Keyword("fn"),
Value::Box(_, _) => Value::Keyword("box"),
Value::Placeholder => unreachable!("internal Ludus error"),
Value::Args(_) => unreachable!("internal Ludus error"),
Value::Base(_) => Value::Keyword("fn"),
Value::Recur(..) => unreachable!("internal Ludus error"),
Value::FnDecl(_) => Value::Keyword("fn"),
Value::Box(_) => Value::Keyword("box"),
Value::BaseFn(_) => Value::Keyword("fn"),
Value::Partial(_) => Value::Keyword("fn"),
Value::Nothing => unreachable!(),
}
}
pub fn split<'src>(source: &Value<'src>, splitter: &Value) -> Value<'src> {
pub fn split(source: &Value, splitter: &Value) -> Value {
match (source, splitter) {
(Value::AllocatedString(source), Value::AllocatedString(splitter)) => {
(Value::String(source), Value::String(splitter)) => {
println!("splitting {source} with {splitter}");
let parts = source.split_terminator(splitter.as_str());
let mut list = vector![];
for part in parts {
list.push_back(Value::AllocatedString(Rc::new(part.to_string())));
list.push_back(Value::String(Rc::new(part.to_string())));
}
Value::List(list)
Value::List(Box::new(list))
}
(Value::AllocatedString(source), Value::InternedString(splitter)) => {
let parts = source.split_terminator(splitter);
let mut list = vector![];
for part in parts {
list.push_back(Value::AllocatedString(Rc::new(part.to_string())));
}
Value::List(list)
(Value::String(_), Value::Interned(splitter)) => {
split(source, &Value::String(Rc::new(splitter.to_string())))
}
(Value::InternedString(source), Value::AllocatedString(splitter)) => {
let parts = source.split_terminator(splitter.as_str());
let mut list = vector![];
for part in parts {
list.push_back(Value::AllocatedString(Rc::new(part.to_string())));
}
Value::List(list)
(Value::Interned(source), Value::String(_)) => {
split(&Value::String(Rc::new(source.to_string())), splitter)
}
(Value::InternedString(source), Value::InternedString(splitter)) => {
let parts = source.split_terminator(splitter);
let mut list = vector![];
for part in parts {
list.push_back(Value::AllocatedString(Rc::new(part.to_string())));
}
Value::List(list)
(Value::Interned(source), Value::Interned(splitter)) => {
let source = Value::String(Rc::new(source.to_string()));
let splitter = Value::String(Rc::new(splitter.to_string()));
split(&source, &splitter)
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn upcase<'src>(string: &Value<'src>) -> Value<'src> {
pub fn upcase(string: &Value) -> Value {
match string {
Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())),
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())),
Value::String(string) => Value::String(Rc::new(string.to_uppercase())),
Value::Interned(string) => Value::String(Rc::new(string.to_uppercase())),
_ => unreachable!("internal Ludus error"),
}
}
pub fn downcase<'src>(string: &Value<'src>) -> Value<'src> {
pub fn downcase(string: &Value) -> Value {
match string {
Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())),
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())),
Value::String(string) => Value::String(Rc::new(string.to_lowercase())),
Value::Interned(string) => Value::String(Rc::new(string.to_lowercase())),
_ => unreachable!("internal Ludus error"),
}
}
pub fn trim<'src>(string: &Value<'src>) -> Value<'src> {
pub fn trim(string: &Value) -> Value {
match string {
Value::AllocatedString(string) => {
Value::AllocatedString(Rc::new(string.trim().to_string()))
}
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.trim().to_string())),
Value::String(string) => Value::String(Rc::new(string.trim().to_string())),
Value::Interned(string) => Value::String(Rc::new(string.trim().to_string())),
_ => unreachable!("internal Ludus error"),
}
}
pub fn triml<'src>(string: &Value<'src>) -> Value<'src> {
pub fn triml(string: &Value) -> Value {
match string {
Value::AllocatedString(string) => {
Value::AllocatedString(Rc::new(string.trim_start().to_string()))
}
Value::InternedString(string) => {
Value::AllocatedString(Rc::new(string.trim_start().to_string()))
}
Value::String(string) => Value::String(Rc::new(string.trim_start().to_string())),
Value::Interned(string) => Value::String(Rc::new(string.trim_start().to_string())),
_ => unreachable!("internal Ludus error"),
}
}
pub fn trimr<'src>(string: &Value<'src>) -> Value<'src> {
pub fn trimr(string: &Value) -> Value {
match string {
Value::AllocatedString(string) => {
Value::AllocatedString(Rc::new(string.trim_end().to_string()))
}
Value::InternedString(string) => {
Value::AllocatedString(Rc::new(string.trim_end().to_string()))
}
Value::String(string) => Value::String(Rc::new(string.trim_end().to_string())),
Value::Interned(string) => Value::String(Rc::new(string.trim_end().to_string())),
_ => unreachable!("internal Ludus error"),
}
}
pub fn atan_2<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn atan_2(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)),
_ => unreachable!("internal Ludus error"),
}
}
pub fn ceil<'src>(x: &Value) -> Value<'src> {
pub fn ceil(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.ceil()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn cos<'src>(x: &Value) -> Value<'src> {
pub fn cos(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.cos()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn floor<'src>(x: &Value) -> Value<'src> {
pub fn floor(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.floor()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn random<'src>() -> Value<'src> {
pub fn random() -> Value {
Value::Number(ran_f64())
}
pub fn round<'src>(x: &Value) -> Value<'src> {
pub fn round(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.round()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn sin<'src>(x: &Value) -> Value<'src> {
pub fn sin(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.sin()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn sqrt<'src>(x: &Value) -> Value<'src> {
pub fn sqrt(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.sqrt()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn tan<'src>(x: &Value) -> Value<'src> {
pub fn tan(x: &Value) -> Value {
match x {
Value::Number(x) => Value::Number(x.tan()),
_ => unreachable!("internal Ludus error"),
}
}
pub fn gt<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn gt(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Boolean(x > y),
(Value::Number(x), Value::Number(y)) => {
if x > y {
Value::True
} else {
Value::False
}
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn gte<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn gte(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Boolean(x >= y),
(Value::Number(x), Value::Number(y)) => {
if x >= y {
Value::True
} else {
Value::False
}
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn lt<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn lt(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Boolean(x < y),
(Value::Number(x), Value::Number(y)) => {
if x < y {
Value::True
} else {
Value::False
}
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn lte<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn lte(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Boolean(x <= y),
(Value::Number(x), Value::Number(y)) => {
if x <= y {
Value::True
} else {
Value::False
}
}
_ => unreachable!("internal Ludus error"),
}
}
pub fn r#mod<'src>(x: &Value, y: &Value) -> Value<'src> {
pub fn r#mod(x: &Value, y: &Value) -> Value {
match (x, y) {
(Value::Number(x), Value::Number(y)) => Value::Number(x % y),
_ => unreachable!("internal Ludus error"),
}
}
pub fn base<'src>() -> Vec<(String, Value<'src>)> {
pub fn make_base() -> Value {
let members = vec![
("add", Value::Base(BaseFn::Binary(add))),
("and", Value::Base(BaseFn::Binary(and))),
("append", Value::Base(BaseFn::Binary(append))),
("assoc", Value::Base(BaseFn::Ternary(assoc))),
("at", Value::Base(BaseFn::Binary(at))),
("atan_2", Value::Base(BaseFn::Binary(atan_2))),
("bool", Value::Base(BaseFn::Unary(r#bool))),
("ceil", Value::Base(BaseFn::Unary(ceil))),
("chars", Value::Base(BaseFn::Unary(chars))),
("concat", Value::Base(BaseFn::Binary(concat))),
("cos", Value::Base(BaseFn::Unary(cos))),
("count", Value::Base(BaseFn::Unary(count))),
("dec", Value::Base(BaseFn::Unary(dec))),
("dissoc", Value::Base(BaseFn::Binary(dissoc))),
("div", Value::Base(BaseFn::Binary(div))),
("doc!", Value::Base(BaseFn::Unary(doc))),
("downcase", Value::Base(BaseFn::Unary(downcase))),
("eq?", Value::Base(BaseFn::Binary(eq))),
("first", Value::Base(BaseFn::Unary(first))),
("floor", Value::Base(BaseFn::Unary(floor))),
("get", Value::Base(BaseFn::Binary(get))),
("gt?", Value::Base(BaseFn::Binary(gt))),
("gte?", Value::Base(BaseFn::Binary(gte))),
("inc", Value::Base(BaseFn::Unary(inc))),
("last", Value::Base(BaseFn::Unary(last))),
("list", Value::Base(BaseFn::Unary(list))),
("lt?", Value::Base(BaseFn::Binary(lt))),
("lte?", Value::Base(BaseFn::Binary(lte))),
("mod", Value::Base(BaseFn::Binary(r#mod))),
("mult", Value::Base(BaseFn::Binary(mult))),
("number", Value::Base(BaseFn::Unary(number))),
("or", Value::Base(BaseFn::Binary(or))),
("add", Value::BaseFn(BaseFn::Binary("add", add))),
("append", Value::BaseFn(BaseFn::Binary("append", append))),
("assoc", Value::BaseFn(BaseFn::Ternary("assoc", assoc))),
("at", Value::BaseFn(BaseFn::Binary("at", at))),
("atan_2", Value::BaseFn(BaseFn::Binary("atan_2", atan_2))),
("bool", Value::BaseFn(BaseFn::Unary("bool", r#bool))),
("ceil", Value::BaseFn(BaseFn::Unary("ceil", ceil))),
("chars", Value::BaseFn(BaseFn::Unary("chars", chars))),
("concat", Value::BaseFn(BaseFn::Binary("concat", concat))),
("cos", Value::BaseFn(BaseFn::Unary("cos", cos))),
("count", Value::BaseFn(BaseFn::Unary("count", count))),
("dec", Value::BaseFn(BaseFn::Unary("dec", dec))),
("dissoc", Value::BaseFn(BaseFn::Binary("dissoc", dissoc))),
("div", Value::BaseFn(BaseFn::Binary("div", div))),
("doc!", Value::BaseFn(BaseFn::Unary("doc!", doc))),
(
"downcase",
Value::BaseFn(BaseFn::Unary("downcase", downcase)),
),
("eq?", Value::BaseFn(BaseFn::Binary("eq?", eq))),
("first", Value::BaseFn(BaseFn::Unary("first", first))),
("floor", Value::BaseFn(BaseFn::Unary("floor", floor))),
("get", Value::BaseFn(BaseFn::Binary("get", get))),
("gt?", Value::BaseFn(BaseFn::Binary("gt?", gt))),
("gte?", Value::BaseFn(BaseFn::Binary("gte?", gte))),
("inc", Value::BaseFn(BaseFn::Unary("inc", inc))),
("last", Value::BaseFn(BaseFn::Unary("last", last))),
("list", Value::BaseFn(BaseFn::Unary("list", list))),
("lt?", Value::BaseFn(BaseFn::Binary("lt?", lt))),
("lte?", Value::BaseFn(BaseFn::Binary("lte?", lte))),
("mod", Value::BaseFn(BaseFn::Binary("mod", r#mod))),
("mult", Value::BaseFn(BaseFn::Binary("mult", mult))),
("number", Value::BaseFn(BaseFn::Unary("number", number))),
("pi", Value::Number(std::f64::consts::PI)),
("print!", Value::Base(BaseFn::Unary(print))),
("random", Value::Base(BaseFn::Nullary(random))),
("range", Value::Base(BaseFn::Binary(range))),
("rest", Value::Base(BaseFn::Unary(rest))),
("round", Value::Base(BaseFn::Unary(round))),
("show", Value::Base(BaseFn::Unary(show))),
("sin", Value::Base(BaseFn::Unary(sin))),
("slice", Value::Base(BaseFn::Ternary(slice))),
("split", Value::Base(BaseFn::Binary(split))),
("sqrt", Value::Base(BaseFn::Unary(sqrt))),
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
("random", Value::BaseFn(BaseFn::Nullary("random", random))),
("range", Value::BaseFn(BaseFn::Binary("range", range))),
("rest", Value::BaseFn(BaseFn::Unary("rest", rest))),
("round", Value::BaseFn(BaseFn::Unary("round", round))),
("show", Value::BaseFn(BaseFn::Unary("show", show))),
("sin", Value::BaseFn(BaseFn::Unary("sin", sin))),
("slice", Value::BaseFn(BaseFn::Ternary("slice", slice))),
("split", Value::BaseFn(BaseFn::Binary("split", split))),
("sqrt", Value::BaseFn(BaseFn::Unary("sqrt", sqrt))),
("sqrt_2", Value::Number(std::f64::consts::SQRT_2)),
("store!", Value::Base(BaseFn::Binary(store))),
("sub", Value::Base(BaseFn::Binary(sub))),
("tan", Value::Base(BaseFn::Unary(tan))),
("trim", Value::Base(BaseFn::Unary(trim))),
("triml", Value::Base(BaseFn::Unary(triml))),
("trimr", Value::Base(BaseFn::Unary(trimr))),
("type", Value::Base(BaseFn::Unary(r#type))),
("unbox", Value::Base(BaseFn::Unary(unbox))),
("upcase", Value::Base(BaseFn::Unary(upcase))),
("store!", Value::BaseFn(BaseFn::Binary("store!", store))),
("sub", Value::BaseFn(BaseFn::Binary("sub", sub))),
("tan", Value::BaseFn(BaseFn::Unary("tan", tan))),
("trim", Value::BaseFn(BaseFn::Unary("trim", trim))),
("triml", Value::BaseFn(BaseFn::Unary("triml", triml))),
("trimr", Value::BaseFn(BaseFn::Unary("trimr", trimr))),
("type", Value::BaseFn(BaseFn::Unary("type", r#type))),
("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))),
("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))),
];
let pkg = Value::Dict(HashMap::from(members));
vec![("base".to_string(), pkg)]
Value::Dict(Box::new(HashMap::from(members)))
}

86
src/chunk.rs Normal file
View File

@ -0,0 +1,86 @@
use crate::op::Op;
use crate::value::Value;
use imbl::HashMap;
use num_traits::FromPrimitive;
use regex::Regex;
#[derive(Clone, Debug)]
pub struct StrPattern {
pub words: Vec<String>,
pub re: Regex,
}
#[derive(Clone, Debug)]
pub struct Chunk {
pub constants: Vec<Value>,
pub bytecode: Vec<u8>,
pub keywords: Vec<&'static str>,
pub string_patterns: Vec<StrPattern>,
pub env: HashMap<&'static str, Value>,
pub msgs: Vec<String>,
}
impl Chunk {
pub fn dissasemble_instr(&self, i: &mut usize) {
let op = Op::from_u8(self.bytecode[*i]).unwrap();
use Op::*;
match op {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf
| Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub
| 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 => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
let high = self.bytecode[*i + 1];
let low = self.bytecode[*i + 2];
let idx = ((high as usize) << 8) + low as usize;
let value = &self.constants[idx].show();
println!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
*i += 2;
}
Msg => {
let msg_idx = self.bytecode[*i + 1];
let msg = &self.msgs[msg_idx as usize];
println!("{i:04}: {msg}");
*i += 1;
}
PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
| MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict
| DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
| Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => {
let next = self.bytecode[*i + 1];
println!("{i:04}: {:16} {next:03}", op.to_string());
*i += 1;
}
Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack
| JumpIfZero => {
let high = self.bytecode[*i + 1];
let low = self.bytecode[*i + 2];
let len = ((high as u16) << 8) + low as u16;
println!("{i:04}: {:16} {len:05}", op.to_string());
*i += 2;
}
}
}
pub fn dissasemble(&self) {
println!("IDX | CODE | INFO");
let mut i = 0;
while i < self.bytecode.len() {
self.dissasemble_instr(&mut i);
i += 1;
}
}
// pub fn kw_from(&self, kw: &str) -> Option<Value> {
// self.kw_index_from(kw).map(Value::Keyword)
// }
// pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
// self.keywords.iter().position(|s| *s == kw)
// }
}

1441
src/compiler.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +1,44 @@
use crate::process::{LErr, Trace};
// use crate::process::{LErr, Trace};
use crate::validator::VErr;
use crate::value::Value;
use ariadne::{sources, Color, Label, Report, ReportKind};
use std::collections::HashSet;
pub fn report_panic(err: LErr) {
let mut srcs = HashSet::new();
let mut stack = vec![];
let mut order = 1;
for entry in err.trace.iter().rev() {
let Trace {
callee,
caller,
function,
arguments,
input,
src,
} = entry;
let (_, first_span) = callee;
let (_, second_span) = caller;
let Value::Fn(f) = function else {
unreachable!()
};
let fn_name = f.name.clone();
let i = first_span.start;
let j = second_span.end;
let label = Label::new((entry.input, i..j))
.with_color(Color::Yellow)
.with_message(format!("({order}) calling `{fn_name}` with `{arguments}`"));
order += 1;
stack.push(label);
srcs.insert((*input, *src));
}
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
.with_message(format!("Ludus panicked! {}", err.msg))
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red))
.with_labels(stack)
.with_note(err.extra)
.finish()
.print(sources(srcs.iter().copied()))
.unwrap();
}
// pub fn report_panic(err: LErr) {
// let mut srcs = HashSet::new();
// let mut stack = vec![];
// let mut order = 1;
// for entry in err.trace.iter().rev() {
// let Trace {
// callee,
// caller,
// function,
// arguments,
// input,
// src,
// } = entry;
// let (_, first_span) = callee;
// let (_, second_span) = caller;
// let Value::Fn(f) = function else {
// unreachable!()
// };
// let fn_name = f.borrow().name.clone();
// let i = first_span.start;
// let j = second_span.end;
// let label = Label::new((entry.input, i..j))
// .with_color(Color::Yellow)
// .with_message(format!("({order}) calling `{fn_name}` with `{arguments}`"));
// order += 1;
// stack.push(label);
// srcs.insert((*input, *src));
// }
// Report::build(ReportKind::Error, (err.input, err.span.into_range()))
// .with_message(format!("Ludus panicked! {}", err.msg))
// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red))
// .with_labels(stack)
// .with_note(err.extra)
// .finish()
// .print(sources(srcs.iter().copied()))
// .unwrap();
// }
pub fn report_invalidation(errs: Vec<VErr>) {
for err in errs {

View File

@ -56,7 +56,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" => Token::Reserved(word),
| "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" => Token::Reserved(word),
_ => Token::Word(word),
});

210
src/lib.rs Normal file
View File

@ -0,0 +1,210 @@
use chumsky::{input::Stream, prelude::*};
use imbl::HashMap;
use wasm_bindgen::prelude::*;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
mod base;
mod world;
mod spans;
use crate::spans::Spanned;
mod lexer;
use crate::lexer::lexer;
mod parser;
use crate::parser::{parser, Ast};
mod validator;
use crate::validator::Validator;
mod errors;
use crate::errors::report_invalidation;
mod chunk;
mod op;
mod compiler;
use crate::compiler::Compiler;
mod value;
use value::Value;
mod vm;
use vm::Creature;
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
fn prelude() -> HashMap<&'static str, Value> {
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
let (parsed, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
println!("ERROR PARSING PRELUDE:");
println!("{:?}", parse_errors);
panic!();
}
let parsed = parsed.unwrap();
let (ast, span) = &parsed;
let base = base::make_base();
let mut base_env = imbl::HashMap::new();
base_env.insert("base", base.clone());
let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env);
validator.validate();
if !validator.errors.is_empty() {
println!("VALIDATION ERRORS IN PRLUDE:");
report_invalidation(validator.errors);
panic!();
}
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
let mut compiler = Compiler::new(
parsed,
"prelude",
PRELUDE,
0,
HashMap::new(),
DEBUG_PRELUDE_COMPILE,
);
compiler.emit_constant(base);
compiler.bind("base");
compiler.compile();
let chunk = compiler.chunk;
let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN);
let prelude = vm.run().clone().unwrap();
match prelude {
Value::Dict(hashmap) => *hashmap,
_ => unreachable!(),
}
}
#[wasm_bindgen]
pub fn ludus(src: String) -> String {
let src = src.to_string().leak();
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
return format!("{:?}", lex_errs);
}
let tokens = tokens.unwrap();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
return format!("{:?}", parse_errors);
}
// ::sigh:: The AST should be 'static
// This simplifies lifetimes, and
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
let prelude = prelude();
let postlude = prelude.clone();
// let prelude = imbl::HashMap::new();
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
validator.validate();
// TODO: validator should generate a string, not print to the console
if !validator.errors.is_empty() {
report_invalidation(validator.errors);
return "Ludus found some validation errors.".to_string();
}
let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE);
// let base = base::make_base();
// compiler.emit_constant(base);
// compiler.bind("base");
compiler.compile();
if DEBUG_SCRIPT_COMPILE {
println!("=== source code ===");
println!("{src}");
compiler.disassemble();
println!("\n\n")
}
if DEBUG_SCRIPT_RUN {
println!("=== vm run ===");
}
let vm_chunk = compiler.chunk;
let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN);
let result = vm.run();
let console = postlude.get("console").unwrap();
let Value::Box(console) = console else {
unreachable!()
};
let Value::List(ref lines) = *console.borrow() else {
unreachable!()
};
let mut console = lines
.iter()
.map(|line| line.stringify())
.collect::<Vec<_>>()
.join("\n");
let turtle_commands = postlude.get("turtle_commands").unwrap();
let Value::Box(commands) = turtle_commands else {
unreachable!()
};
let commands = commands.borrow();
dbg!(&commands);
let commands = commands.to_json().unwrap();
let output = match result {
Ok(val) => val.show(),
Err(panic) => {
console = format!("{console}\nLudus panicked! {panic}");
"".to_string()
}
};
if DEBUG_SCRIPT_RUN {
vm.print_stack();
}
// TODO: use serde_json to make this more robust?
format!(
"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}"
)
}
pub fn fmt(src: &'static str) -> Result<String, String> {
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
println!("{:?}", lex_errs);
return Err(format!("{:?}", lex_errs));
}
let tokens = tokens.unwrap();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
return Err(format!("{:?}", parse_errors));
}
// ::sigh:: The AST should be 'static
// This simplifies lifetimes, and
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
Ok(parsed.0.show())
}

View File

@ -1,214 +1,10 @@
// an implementation of Ludus
// curently left undone (and not adding for a while yet):
// * sets
// * interpolated strings & string patterns
// * pkgs, namespaces, imports, `use` forms
// * with forms
// * test forms
// * ignored words
// todo:
// * [x] rewrite fn parser to use chumsky::Recursive::declare/define
// - [x] do this to extract/simplify/DRY things like tuple patterns, fn clauses, etc.
// * [x] Work around chumsky::Stream::from_iter().spanned disappearing in most recent version
// * [x] investigate using labels (which is behind a compiler flag, somehow)
// * [ ] write parsing errors
// * [ ] wire up Ariadne parsing errors
// * [x] add stack traces and code locations to panics
// * [x] validation
// * [x] break this out into multiple files
// * [x] write a tree-walk VM
// - [x] learn how to deal with lifetimes
// - [x] with stack mechanics and refcounting
// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
// - [ ] with all the necessary forms for current Ludus
// * [x] guards in match clauses
// * [x] `as` patterns
// * [x] splat patterns in tuples, lists, dicts
// * [x] splats in list and dict literals
// * [x] `loop` and `recur`
// * [x] string patterns
// * [x] string interpolation
// * [x] docstrings
// * [x] write `base` in Rust
// * [ ] turn this into a library function
// * [ ] compile this into WASM
// * [ ] perf testing
use chumsky::{input::Stream, prelude::*};
use rust_embed::Embed;
mod spans;
mod lexer;
use crate::lexer::*;
mod value;
use crate::value::*;
mod parser;
use crate::parser::*;
mod base;
use crate::base::*;
mod validator;
use crate::validator::*;
mod process;
use crate::process::*;
mod errors;
use crate::errors::*;
#[derive(Embed)]
#[folder = "assets/"]
struct Asset;
pub fn prelude<'src>() -> (
Vec<(String, Value<'src>)>,
std::collections::HashMap<*const Ast, FnInfo>,
) {
let prelude = Asset::get("prelude.ld").unwrap().data.into_owned();
// we know for sure Prelude should live through the whole run of the program
let leaked = Box::leak(Box::new(prelude));
let prelude = std::str::from_utf8(leaked).unwrap();
let (ptoks, perrs) = lexer().parse(prelude).into_output_errors();
if !perrs.is_empty() {
println!("Errors lexing Prelude");
println!("{:?}", perrs);
panic!();
}
let ptoks = ptoks.unwrap();
let (p_ast, perrs) = parser()
.parse(Stream::from_iter(ptoks).map((0..prelude.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !perrs.is_empty() {
println!("Errors parsing Prelude");
println!("{:?}", perrs);
panic!();
}
let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
let base_pkg = Box::leak(Box::new(base()));
let mut v6or = Validator::new(
&prelude_parsed.0,
prelude_parsed.1,
"prelude",
prelude,
base_pkg,
);
v6or.validate();
if !v6or.errors.is_empty() {
report_invalidation(v6or.errors);
panic!("interal Ludus error: invalid prelude")
}
let static_vec = Box::leak(Box::new(vec![]));
let mut base_ctx = Process::<'src> {
input: "prelude",
src: prelude,
locals: base_pkg.clone(),
ast: &prelude_parsed.0,
span: prelude_parsed.1,
prelude: static_vec,
fn_info: v6or.fn_info,
};
let prelude = base_ctx.eval();
let mut p_ctx = vec![];
match prelude {
Ok(Value::Dict(p_dict)) => {
for (key, value) in p_dict.iter() {
p_ctx.push((key.to_string(), value.clone()))
}
}
Ok(_) => {
println!("Bad Prelude export");
panic!();
}
Err(LErr { msg, .. }) => {
println!("Error running Prelude");
println!("{:?}", msg);
panic!();
}
};
(p_ctx, base_ctx.fn_info)
}
pub fn run(src: &'static str) {
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
println!("{:?}", lex_errs);
return;
}
let tokens = tokens.unwrap();
let to_parse = tokens.clone();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(to_parse).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
println!("{:?}", parse_errors);
return;
}
let parsed = parse_result.unwrap();
let (prelude_ctx, mut prelude_fn_info) = prelude();
let prelude_ctx = Box::leak(Box::new(prelude_ctx));
let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, prelude_ctx);
v6or.validate();
if !v6or.errors.is_empty() {
report_invalidation(v6or.errors);
return;
}
prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
let mut proc = Process {
input: "script",
src,
locals: vec![],
prelude: prelude_ctx,
ast: &parsed.0,
span: parsed.1,
fn_info: prelude_fn_info,
};
let result = proc.eval();
match result {
Ok(result) => println!("{}", result),
Err(err) => report_panic(err),
}
}
use rudus::ludus;
use std::env;
use std::fs;
pub fn main() {
let src = "
fn sum_to {
(n) -> sum_to (n, 0)
(1, acc) -> acc
(n, acc) -> sum_to (dec (n), add (n, acc))
}
sum_to (10000)
";
run(src);
// struct_scalpel::print_dissection_info::<value::Value>()
// struct_scalpel::print_dissection_info::<parser::Ast>();
// println!("{}", std::mem::size_of::<parser::Ast>())
env::set_var("RUST_BACKTRACE", "1");
let src = fs::read_to_string("sandbox.ld").unwrap();
let json = ludus(src);
println!("{json}");
}

58
src/memory_sandbox.rs Normal file
View File

@ -0,0 +1,58 @@
use imbl::{HashMap, Vector};
use index_vec::Idx;
use std::cell::RefCell;
use std::ops::Range;
use std::rc::Rc;
struct Word(&'static str);
struct Keyword(&'static str);
struct Interned(&'static str);
enum StringPart {
Word(&'static str),
Data(&'static str),
Inline(&'static str),
}
#[derive(Clone, Debug, PartialEq)]
struct LBox {
name: usize,
cell: RefCell<Value>,
}
#[derive(Clone, Debug, PartialEq)]
struct Fn {
name: &'static str,
body: Vec<String>,
//...etc
}
#[derive(Clone, Debug, PartialEq)]
enum Value {
Nil,
Placeholder,
Boolean(bool),
Keyword(usize),
Interned(usize),
FnDecl(usize),
String(Rc<String>),
Number(f64),
Tuple(Rc<Vec<Value>>),
List(Box<Vector<Value>>),
Dict(Box<HashMap<&'static str, Value>>),
Box(Rc<LBox>),
Fn(Rc<RefCell<Fn>>),
}
fn futz() {
let foo: &'static str = "foo";
let baz: Vec<u8> = vec![];
let bar: Range<usize> = 1..3;
let quux: Vector<u8> = Vector::new();
let fuzz = Rc::new(quux);
let blah = Box::new(foo);
let val = Value::Number(12.09);
let foo: f64 = 12.0;
}

212
src/old_main.rs Normal file
View File

@ -0,0 +1,212 @@
// an implementation of Ludus
// curently left undone (and not adding for a while yet):
// * sets
// * interpolated strings & string patterns
// * pkgs, namespaces, imports, `use` forms
// * with forms
// * test forms
// * ignored words
// todo:
// * [x] rewrite fn parser to use chumsky::Recursive::declare/define
// - [x] do this to extract/simplify/DRY things like tuple patterns, fn clauses, etc.
// * [x] Work around chumsky::Stream::from_iter().spanned disappearing in most recent version
// * [x] investigate using labels (which is behind a compiler flag, somehow)
// * [ ] write parsing errors
// * [ ] wire up Ariadne parsing errors
// * [x] add stack traces and code locations to panics
// * [x] validation
// * [x] break this out into multiple files
// * [x] write a tree-walk VM
// - [x] learn how to deal with lifetimes
// - [x] with stack mechanics and refcounting
// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
// - [ ] with all the necessary forms for current Ludus
// * [x] guards in match clauses
// * [x] `as` patterns
// * [x] splat patterns in tuples, lists, dicts
// * [x] splats in list and dict literals
// * [x] `loop` and `recur`
// * [x] string patterns
// * [x] string interpolation
// * [x] docstrings
// * [x] write `base` in Rust
// * [ ] turn this into a library function
// * [ ] compile this into WASM
// * [ ] perf testing
use chumsky::{input::Stream, prelude::*};
use rust_embed::Embed;
mod spans;
mod lexer;
use crate::lexer::*;
mod value;
use crate::value::*;
mod parser;
use crate::parser::*;
mod base;
use crate::base::*;
mod validator;
use crate::validator::*;
mod process;
use crate::process::*;
mod errors;
use crate::errors::*;
mod byte_values;
mod compiler;
mod memory_sandbox;
#[derive(Embed)]
#[folder = "assets/"]
struct Asset;
pub fn prelude<'src>() -> (
Vec<(String, Value<'src>)>,
std::collections::HashMap<*const Ast, FnInfo>,
) {
let prelude = Asset::get("prelude.ld").unwrap().data.into_owned();
// we know for sure Prelude should live through the whole run of the program
let leaked = Box::leak(Box::new(prelude));
let prelude = std::str::from_utf8(leaked).unwrap();
let (ptoks, perrs) = lexer().parse(prelude).into_output_errors();
if !perrs.is_empty() {
println!("Errors lexing Prelude");
println!("{:?}", perrs);
panic!();
}
let ptoks = ptoks.unwrap();
let (p_ast, perrs) = parser()
.parse(Stream::from_iter(ptoks).map((0..prelude.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !perrs.is_empty() {
println!("Errors parsing Prelude");
println!("{:?}", perrs);
panic!();
}
let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
let base_pkg = base();
let mut v6or = Validator::new(
&prelude_parsed.0,
prelude_parsed.1,
"prelude",
prelude,
&base_pkg,
);
v6or.validate();
if !v6or.errors.is_empty() {
report_invalidation(v6or.errors);
panic!("interal Ludus error: invalid prelude")
}
let mut base_ctx = Process::<'src> {
input: "prelude",
src: prelude,
locals: base_pkg.clone(),
ast: &prelude_parsed.0,
span: prelude_parsed.1,
prelude: vec![],
fn_info: v6or.fn_info,
};
let prelude = base_ctx.eval();
let mut p_ctx = vec![];
match prelude {
Ok(Value::Dict(p_dict)) => {
for (key, value) in p_dict.iter() {
p_ctx.push((key.to_string(), value.clone()))
}
}
Ok(_) => {
println!("Bad Prelude export");
panic!();
}
Err(LErr { msg, .. }) => {
println!("Error running Prelude");
println!("{:?}", msg);
panic!();
}
};
(p_ctx, base_ctx.fn_info)
}
pub fn run(src: &'static str) {
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
println!("{:?}", lex_errs);
return;
}
let tokens = tokens.unwrap();
let to_parse = tokens.clone();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(to_parse).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
println!("{:?}", parse_errors);
return;
}
let parsed = parse_result.unwrap();
let (prelude_ctx, mut prelude_fn_info) = prelude();
let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, &prelude_ctx);
v6or.validate();
if !v6or.errors.is_empty() {
report_invalidation(v6or.errors);
return;
}
prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
let mut proc = Process {
input: "script",
src,
locals: vec![],
prelude: prelude_ctx,
ast: &parsed.0,
span: parsed.1,
fn_info: prelude_fn_info,
};
let result = proc.eval();
match result {
Ok(result) => println!("{}", result),
Err(err) => report_panic(err),
}
}
pub fn main() {
let src = "
loop (100000, 1) with {
(1, acc) -> acc
(n, acc) -> recur (dec (n), add (n, acc))
}
";
run(src);
// struct_scalpel::print_dissection_info::<value::Value>()
// struct_scalpel::print_dissection_info::<parser::Ast>();
// println!("{}", std::mem::size_of::<parser::Ast>())
}

193
src/old_value.rs Normal file
View File

@ -0,0 +1,193 @@
use crate::base::*;
use crate::parser::*;
use crate::spans::*;
use imbl::*;
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use struct_scalpel::Dissectible;
#[derive(Clone, Debug)]
pub struct Fn<'src> {
pub name: String,
pub body: &'src Vec<Spanned<Ast>>,
pub doc: Option<String>,
pub enclosing: Vec<(String, Value<'src>)>,
pub has_run: bool,
pub input: &'static str,
pub src: &'static str,
}
#[derive(Debug, Dissectible)]
pub enum Value<'src> {
Nil,
Placeholder,
Boolean(bool),
Number(f64),
Keyword(&'static str),
InternedString(&'static str),
AllocatedString(Rc<String>),
// on the heap for now
Tuple(Rc<Vec<Self>>),
Args(Rc<Vec<Self>>),
List(Vector<Self>),
Dict(HashMap<&'static str, Self>),
Box(&'static str, Rc<RefCell<Self>>),
Fn(Rc<RefCell<Fn<'src>>>),
FnDecl(&'static str),
Base(BaseFn<'src>),
Recur(Vec<Self>),
// Set(HashSet<Self>),
// Sets are hard
// Sets require Eq
// Eq is not implemented on f64, because NaNs
// We could use ordered_float::NotNan
// Let's defer that
// We're not really using sets in Ludus
// Other things we're not implementing yet:
// pkgs, nses, tests
}
impl<'src> Clone for Value<'src> {
fn clone(&self) -> Value<'src> {
match self {
Value::Nil => Value::Nil,
Value::Boolean(b) => Value::Boolean(*b),
Value::InternedString(s) => Value::InternedString(s),
Value::AllocatedString(s) => Value::AllocatedString(s.clone()),
Value::Keyword(s) => Value::Keyword(s),
Value::Number(n) => Value::Number(*n),
Value::Tuple(t) => Value::Tuple(t.clone()),
Value::Args(a) => Value::Args(a.clone()),
Value::Fn(f) => Value::Fn(f.clone()),
Value::FnDecl(name) => Value::FnDecl(name),
Value::List(l) => Value::List(l.clone()),
Value::Dict(d) => Value::Dict(d.clone()),
Value::Box(name, b) => Value::Box(name, b.clone()),
Value::Placeholder => Value::Placeholder,
Value::Base(b) => Value::Base(b.clone()),
Value::Recur(..) => unreachable!(),
}
}
}
impl fmt::Display for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Nil => write!(f, "nil"),
Value::Boolean(b) => write!(f, "{b}"),
Value::Number(n) => write!(f, "{n}"),
Value::Keyword(k) => write!(f, ":{k}"),
Value::InternedString(s) => write!(f, "\"{s}\""),
Value::AllocatedString(s) => write!(f, "\"{s}\""),
Value::Fn(fun) => write!(f, "fn {}", fun.borrow().name),
Value::FnDecl(name) => write!(f, "fn {name}"),
Value::Tuple(t) | Value::Args(t) => write!(
f,
"({})",
t.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Value::List(l) => write!(
f,
"[{}]",
l.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Value::Dict(d) => write!(
f,
"#{{{}}}",
d.iter()
.map(|(k, v)| format!(":{k} {v}"))
.collect::<Vec<_>>()
.join(", ")
),
Value::Box(name, value) => {
write!(
f,
"box {}: [{}]",
name,
&value.try_borrow().unwrap().to_string()
)
}
Value::Placeholder => write!(f, "_"),
Value::Base(..) => unreachable!(),
Value::Recur(..) => unreachable!(),
}
}
}
impl Value<'_> {
pub fn bool(&self) -> bool {
!matches!(self, Value::Nil | Value::Boolean(false))
}
}
impl<'src> PartialEq for Value<'src> {
fn eq(&self, other: &Value<'src>) -> bool {
match (self, other) {
// value equality types
(Value::Nil, Value::Nil) => true,
(Value::Boolean(x), Value::Boolean(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y,
(Value::InternedString(x), Value::InternedString(y)) => x == y,
(Value::AllocatedString(x), Value::AllocatedString(y)) => x == y,
(Value::InternedString(x), Value::AllocatedString(y)) => *x == **y,
(Value::AllocatedString(x), Value::InternedString(y)) => **x == *y,
(Value::Keyword(x), Value::Keyword(y)) => x == y,
(Value::Tuple(x), Value::Tuple(y)) => x == y,
(Value::List(x), Value::List(y)) => x == y,
(Value::Dict(x), Value::Dict(y)) => x == y,
// reference equality types
(Value::Fn(x), Value::Fn(y)) => {
Rc::<RefCell<Fn<'_>>>::as_ptr(x) == Rc::<RefCell<Fn<'_>>>::as_ptr(y)
}
(Value::Box(_, x), Value::Box(_, y)) => {
Rc::<RefCell<Value<'_>>>::as_ptr(x) == Rc::<RefCell<Value<'_>>>::as_ptr(y)
}
_ => false,
}
}
}
impl Eq for Value<'_> {}
impl Value<'_> {
pub fn interpolate(&self) -> String {
match self {
Value::Nil => String::new(),
Value::Boolean(b) => format!("{b}"),
Value::Number(n) => format!("{n}"),
Value::Keyword(k) => format!(":{k}"),
Value::AllocatedString(s) => format!("{s}"),
Value::InternedString(s) => s.to_string(),
Value::Box(_, x) => x.borrow().interpolate(),
Value::Tuple(xs) => xs
.iter()
.map(|x| x.interpolate())
.collect::<Vec<_>>()
.join(", "),
Value::List(xs) => xs
.iter()
.map(|x| x.interpolate())
.collect::<Vec<_>>()
.join(", "),
Value::Dict(xs) => xs
.iter()
.map(|(k, v)| format!(":{} {}", k, v.interpolate()))
.collect::<Vec<_>>()
.join(", "),
Value::Fn(x) => format!("fn {}", x.borrow().name),
Value::FnDecl(name) => format!("fn {name}"),
Value::Placeholder => unreachable!(),
Value::Args(_) => unreachable!(),
Value::Recur(_) => unreachable!(),
Value::Base(_) => unreachable!(),
}
}
}

533
src/old_vm.rs Normal file
View File

@ -0,0 +1,533 @@
use crate::base::*;
use crate::parser::*;
use crate::value::*;
use imbl::HashMap;
use imbl::Vector;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct LudusError {
pub msg: String,
}
// oy
// lifetimes are a mess
// I need 'src kind of everywhere
// But (maybe) using 'src in eval
// for ctx
// means I can't borrow it mutably
// I guess the question is how to get
// the branches for Ast::Block and Ast::If
// to work with a mutable borrow of ctx
// pub struct Ctx<'src> {
// pub locals: Vec<(&'src str, Value<'src>)>,
// // pub names: Vec<&'src str>,
// // pub values: Vec<Value<'src>>,
// }
// impl<'src> Ctx<'src> {
// pub fn resolve(&self, name: &'src str) -> Value {
// if let Some((_, val)) = self.locals.iter().rev().find(|(bound, _)| *bound == name) {
// val.clone()
// } else {
// unreachable!()
// }
// }
// pub fn store(&mut self, name: &'src str, value: Value<'src>) {
// self.locals.push((name, value));
// }
// }
type Context<'src> = Vec<(String, Value<'src>)>;
pub fn match_eq<T, U>(x: T, y: T, z: U) -> Option<U>
where
T: PartialEq,
{
if x == y {
Some(z)
} else {
None
}
}
pub fn match_pattern<'src, 'a>(
patt: &Pattern,
val: &Value<'src>,
ctx: &'a mut Context<'src>,
) -> Option<&'a mut Context<'src>> {
match (patt, val) {
(Pattern::Nil, Value::Nil) => Some(ctx),
(Pattern::Placeholder, _) => Some(ctx),
(Pattern::Number(x), Value::Number(y)) => match_eq(x, y, ctx),
(Pattern::Boolean(x), Value::Boolean(y)) => match_eq(x, y, ctx),
(Pattern::Keyword(x), Value::Keyword(y)) => match_eq(x, y, ctx),
(Pattern::String(x), Value::InternedString(y)) => match_eq(x, y, ctx),
(Pattern::String(x), Value::AllocatedString(y)) => match_eq(&x.to_string(), y, ctx),
(Pattern::Interpolated(_, StringMatcher(matcher)), Value::InternedString(y)) => {
match matcher(y.to_string()) {
Some(matches) => {
let mut matches = matches
.iter()
.map(|(word, string)| {
(
word.clone(),
Value::AllocatedString(Rc::new(string.clone())),
)
})
.collect::<Vec<_>>();
ctx.append(&mut matches);
Some(ctx)
}
None => None,
}
}
(Pattern::Word(w), val) => {
ctx.push((w.to_string(), val.clone()));
Some(ctx)
}
(Pattern::As(word, type_str), value) => {
let ludus_type = r#type(value);
let type_kw = Value::Keyword(type_str);
if type_kw == ludus_type {
ctx.push((word.to_string(), value.clone()));
Some(ctx)
} else {
None
}
}
// todo: add splats to these match clauses
(Pattern::Tuple(x), Value::Tuple(y)) => {
let has_splat = x
.iter()
.any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = ctx.len();
for i in 0..x.len() {
if let Pattern::Splattern(patt) = &x[i].0 {
let mut list = Vector::new();
for i in i..y.len() {
list.push_back(y[i].clone())
}
let list = Value::List(list);
match_pattern(&patt.0, &list, ctx);
} else if match_pattern(&x[i].0, &y[i], ctx).is_none() {
while ctx.len() > to {
ctx.pop();
}
return None;
}
}
Some(ctx)
}
(Pattern::List(x), Value::List(y)) => {
let has_splat = x
.iter()
.any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = ctx.len();
for (i, (patt, _)) in x.iter().enumerate() {
if let Pattern::Splattern(patt) = &patt {
let list = Value::List(y.skip(i));
match_pattern(&patt.0, &list, ctx);
} else if match_pattern(patt, y.get(i).unwrap(), ctx).is_none() {
while ctx.len() > to {
ctx.pop();
}
return None;
}
}
Some(ctx)
}
// TODO: optimize this on several levels
// - [ ] opportunistic mutation
// - [ ] get rid of all the pointer indirection in word splats
(Pattern::Dict(x), Value::Dict(y)) => {
let has_splat = x
.iter()
.any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = ctx.len();
let mut matched = vec![];
for (pattern, _) in x {
match pattern {
Pattern::Pair(key, patt) => {
if let Some(val) = y.get(key) {
if match_pattern(&patt.0, val, ctx).is_none() {
while ctx.len() > to {
ctx.pop();
}
return None;
} else {
matched.push(key);
}
} else {
return None;
};
}
Pattern::Splattern(pattern) => match pattern.0 {
Pattern::Word(w) => {
// TODO: find a way to take ownership
// this will ALWAYS make structural changes, because of this clone
// we want opportunistic mutation if possible
let mut unmatched = y.clone();
for key in matched.iter() {
unmatched.remove(*key);
}
ctx.push((w.to_string(), Value::Dict(unmatched)));
}
Pattern::Placeholder => (),
_ => unreachable!(),
},
_ => unreachable!(),
}
}
Some(ctx)
}
_ => None,
}
}
pub fn match_clauses<'src>(
value: &Value<'src>,
clauses: &'src [MatchClause],
ctx: &mut Context<'src>,
) -> Result<Value<'src>, LudusError> {
let to = ctx.len();
for MatchClause { patt, body, guard } in clauses.iter() {
if let Some(ctx) = match_pattern(&patt.0, value, ctx) {
let pass_guard = match guard {
None => true,
Some((ast, _)) => {
let guard_res = eval(ast, ctx);
match &guard_res {
Err(_) => return guard_res,
Ok(val) => val.bool(),
}
}
};
if !pass_guard {
while ctx.len() > to {
ctx.pop();
}
continue;
}
let res = eval(&body.0, ctx);
while ctx.len() > to {
ctx.pop();
}
return res;
}
}
Err(LudusError {
msg: "no match".to_string(),
})
}
pub fn apply<'src>(
callee: Value<'src>,
caller: Value<'src>,
ctx: &mut Context,
) -> Result<Value<'src>, LudusError> {
match (callee, caller) {
(Value::Keyword(kw), Value::Dict(dict)) => {
if let Some(val) = dict.get(kw) {
Ok(val.clone())
} else {
Ok(Value::Nil)
}
}
(Value::Dict(dict), Value::Keyword(kw)) => {
if let Some(val) = dict.get(kw) {
Ok(val.clone())
} else {
Ok(Value::Nil)
}
}
(Value::Fn(f), Value::Tuple(args)) => {
let args = Value::Tuple(args);
match_clauses(&args, f.body, ctx)
}
(Value::Fn(_f), Value::Args(_args)) => todo!(),
(_, Value::Keyword(_)) => Ok(Value::Nil),
(_, Value::Args(_)) => Err(LudusError {
msg: "you may only call a function".to_string(),
}),
(Value::Base(f), Value::Tuple(args)) => match f {
Base::Nullary(f) => {
if args.len() != 0 {
Err(LudusError {
msg: "wrong arity: expected 0 arguments".to_string(),
})
} else {
Ok(f())
}
}
Base::Unary(f) => {
if args.len() != 1 {
Err(LudusError {
msg: "wrong arity: expected 1 argument".to_string(),
})
} else {
Ok(f(&args[0]))
}
}
Base::Binary(r#fn) => {
if args.len() != 2 {
Err(LudusError {
msg: "wrong arity: expected 2 arguments".to_string(),
})
} else {
Ok(r#fn(&args[0], &args[1]))
}
}
Base::Ternary(f) => {
if args.len() != 3 {
Err(LudusError {
msg: "wrong arity: expected 3 arguments".to_string(),
})
} else {
Ok(f(&args[0], &args[1], &args[2]))
}
}
},
_ => unreachable!(),
}
}
pub fn eval<'src, 'a>(
ast: &'src Ast,
ctx: &'a mut Vec<(String, Value<'src>)>,
) -> Result<Value<'src>, LudusError> {
match ast {
Ast::Nil => Ok(Value::Nil),
Ast::Boolean(b) => Ok(Value::Boolean(*b)),
Ast::Number(n) => Ok(Value::Number(*n)),
Ast::Keyword(k) => Ok(Value::Keyword(k)),
Ast::String(s) => Ok(Value::InternedString(s)),
Ast::Interpolated(parts) => {
let mut interpolated = String::new();
for part in parts {
match &part.0 {
StringPart::Data(s) => interpolated.push_str(s.as_str()),
StringPart::Word(w) => {
let val = if let Some((_, value)) =
ctx.iter().rev().find(|(name, _)| w == name)
{
value.clone()
} else {
return Err(LudusError {
msg: format!("unbound name {w}"),
});
};
interpolated.push_str(val.interpolate().as_str())
}
StringPart::Inline(_) => unreachable!(),
}
}
Ok(Value::AllocatedString(Rc::new(interpolated)))
}
Ast::Block(exprs) => {
let to = ctx.len();
let mut result = Value::Nil;
for (expr, _) in exprs {
result = eval(expr, ctx)?;
}
while ctx.len() > to {
ctx.pop();
}
Ok(result)
}
Ast::If(cond, if_true, if_false) => {
let truthy = eval(&cond.0, ctx)?.bool();
if truthy {
eval(&if_true.0, ctx)
} else {
eval(&if_false.0, ctx)
}
}
Ast::List(members) => {
let mut vect = Vector::new();
for member in members {
if let Ast::Splat(_) = member.0 {
let to_splat = eval(&member.0, ctx)?;
match to_splat {
Value::List(list) => vect.append(list),
_ => {
return Err(LudusError {
msg: "only lists may be splatted into lists".to_string(),
})
}
}
} else {
vect.push_back(eval(&member.0, ctx)?)
}
}
Ok(Value::List(vect))
}
Ast::Tuple(members) => {
let mut vect = Vec::new();
for member in members {
vect.push(eval(&member.0, ctx)?);
}
Ok(Value::Tuple(Rc::new(vect)))
}
Ast::Word(w) | Ast::Splat(w) => {
let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) {
value.clone()
} else {
return Err(LudusError {
msg: format!("unbound name {w}"),
});
};
Ok(val)
}
Ast::Let(patt, expr) => {
let val = eval(&expr.0, ctx)?;
match match_pattern(&patt.0, &val, ctx) {
Some(_) => Ok(val),
None => Err(LudusError {
msg: "No match".to_string(),
}),
}
}
Ast::Placeholder => Ok(Value::Placeholder),
Ast::Error => unreachable!(),
Ast::Arguments(a) => {
let mut args = vec![];
for (arg, _) in a.iter() {
let arg = eval(arg, ctx)?;
args.push(arg);
}
if args.iter().any(|arg| matches!(arg, Value::Placeholder)) {
Ok(Value::Args(Rc::new(args)))
} else {
Ok(Value::Tuple(Rc::new(args)))
}
}
Ast::Dict(terms) => {
let mut dict = HashMap::new();
for term in terms {
let (term, _) = term;
match term {
Ast::Pair(key, value) => {
let value = eval(&value.0, ctx)?;
dict.insert(*key, value);
}
Ast::Splat(_) => {
let resolved = eval(term, ctx)?;
let Value::Dict(to_splat) = resolved else {
return Err(LudusError {
msg: "cannot splat non-dict into dict".to_string(),
});
};
dict = to_splat.union(dict);
}
_ => unreachable!(),
}
}
Ok(Value::Dict(dict))
}
Ast::Box(name, expr) => {
let val = eval(&expr.0, ctx)?;
let boxed = Value::Box(name, Rc::new(RefCell::new(val)));
ctx.push((name.to_string(), boxed.clone()));
Ok(boxed)
}
Ast::Synthetic(root, first, rest) => {
let root = eval(&root.0, ctx)?;
let first = eval(&first.0, ctx)?;
let mut curr = apply(root, first, ctx)?;
for term in rest.iter() {
let next = eval(&term.0, ctx)?;
curr = apply(curr, next, ctx)?;
}
Ok(curr)
}
Ast::When(clauses) => {
for clause in clauses.iter() {
let WhenClause { cond, body } = &clause.0;
if eval(&cond.0, ctx)?.bool() {
return eval(&body.0, ctx);
};
}
Err(LudusError {
msg: "no match".to_string(),
})
}
Ast::Match(value, clauses) => {
let value = eval(&value.0, ctx)?;
match_clauses(&value, clauses, ctx)
}
Ast::Fn(name, clauses, doc) => {
let doc = doc.map(|s| s.to_string());
let the_fn = Value::Fn::<'src>(Rc::new(Fn::<'src> {
name: name.to_string(),
body: clauses,
doc,
}));
ctx.push((name.to_string(), the_fn.clone()));
Ok(the_fn)
}
Ast::FnDeclaration(_name) => todo!(),
Ast::Panic(msg) => {
let msg = eval(&msg.0, ctx)?;
Err(LudusError {
msg: msg.to_string(),
})
}
Ast::Repeat(times, body) => {
let times_num = match eval(&times.0, ctx) {
Ok(Value::Number(n)) => n as usize,
_ => {
return Err(LudusError {
msg: "repeat may only take numbers".to_string(),
})
}
};
for _ in 0..times_num {
eval(&body.0, ctx)?;
}
Ok(Value::Nil)
}
Ast::Do(terms) => {
let mut result = eval(&terms[0].0, ctx)?;
for (term, _) in terms.iter().skip(1) {
let next = eval(term, ctx)?;
let arg = Value::Tuple(Rc::new(vec![result]));
result = apply(next, arg, ctx)?;
}
Ok(result)
}
Ast::Pair(..) => {
unreachable!()
}
Ast::Loop(init, clauses) => {
let mut args = eval(&init.0, ctx)?;
loop {
let result = match_clauses(&args, clauses, ctx)?;
if let Value::Recur(recur_args) = result {
args = Value::Tuple(Rc::new(recur_args));
} else {
return Ok(result);
}
}
}
Ast::Recur(args) => {
let mut vect = Vec::new();
for arg in args {
vect.push(eval(&arg.0, ctx)?);
}
Ok(Value::Recur(vect))
}
}
}

226
src/op.rs Normal file
View File

@ -0,0 +1,226 @@
use num_derive::{FromPrimitive, ToPrimitive};
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op {
Noop,
Nothing,
Nil,
True,
False,
Constant,
Jump,
JumpIfFalse,
JumpIfTrue,
Pop,
PopN,
PushBinding,
PushGlobal,
Store,
StoreN,
Stash,
Load,
LoadN,
ResetMatch,
UnconditionalMatch,
MatchNil,
MatchTrue,
MatchFalse,
PanicIfNoMatch,
MatchConstant,
MatchString,
PushStringMatches,
MatchType,
MatchTuple,
MatchSplattedTuple,
PushTuple,
LoadTuple,
LoadSplattedTuple,
MatchList,
MatchSplattedList,
LoadList,
LoadSplattedList,
PushList,
AppendList,
ConcatList,
PushDict,
AppendDict,
ConcatDict,
LoadDictValue,
MatchDict,
MatchSplattedDict,
DropDictEntry,
PushBox,
GetKey,
PanicNoWhen,
JumpIfNoMatch,
JumpIfMatch,
PanicNoMatch,
TypeOf,
JumpBack,
JumpIfZero,
Duplicate,
Decrement,
ToInt,
MatchDepth,
Panic,
EmptyString,
ConcatStrings,
Stringify,
Call,
TailCall,
Return,
Partial,
Eq,
Add,
Sub,
Mult,
Div,
Unbox,
BoxStore,
Assert,
Get,
At,
Not,
Print,
SetUpvalue,
GetUpvalue,
Msg,
// Inc,
// Dec,
// Gt,
// Gte,
// Lt,
// Lte,
// Mod,
// Round,
// Ceil,
// Floor,
// Random,
// Sqrt,
// Assoc,
// Concat,
// Conj,
// Count,
// Disj,
// Dissoc,
// Range,
// Rest,
// Slice,
// "atan_2" math/atan2
// "chars" chars
// "cos" math/cos
// "doc" doc
// "downcase" string/ascii-lower
// "pi" math/pi
// "show" show
// "sin" math/sin
// "split" string/split
// "str_slice" string/slice
// "tan" math/tan
// "trim" string/trim
// "triml" string/triml
// "trimr" string/trimr
// "upcase" string/ascii-upper
}
impl std::fmt::Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Op::*;
let rep = match self {
Msg => "msg",
Noop => "noop",
Nothing => "nothing",
Nil => "nil",
True => "true",
False => "false",
Constant => "constant",
Jump => "jump",
JumpIfFalse => "jump_if_false",
JumpIfTrue => "jump_if_true",
Pop => "pop",
PopN => "pop_n",
PushBinding => "push_binding",
PushGlobal => "push_global",
Store => "store",
StoreN => "store_n",
Stash => "stash",
Load => "load",
LoadN => "load_n",
UnconditionalMatch => "match",
MatchNil => "match_nil",
MatchTrue => "match_true",
MatchFalse => "match_false",
ResetMatch => "reset_match",
PanicIfNoMatch => "panic_if_no_match",
MatchConstant => "match_constant",
MatchString => "match_string",
PushStringMatches => "push_string_matches",
MatchType => "match_type",
MatchTuple => "match_tuple",
MatchSplattedTuple => "match_splatted_tuple",
PushTuple => "push_tuple",
LoadTuple => "load_tuple",
LoadSplattedTuple => "load_splatted_tuple",
MatchList => "match_list",
MatchSplattedList => "match_splatted_list",
LoadList => "load_list",
LoadSplattedList => "load_splatted_list",
PushList => "push_list",
AppendList => "append_list",
ConcatList => "concat_list",
PushDict => "push_dict",
AppendDict => "append_dict",
ConcatDict => "concat_dict",
LoadDictValue => "load_dict_value",
MatchDict => "match_dict",
MatchSplattedDict => "match_splatted_dict",
DropDictEntry => "drop_dict_entry",
PushBox => "push_box",
GetKey => "get_key",
PanicNoWhen => "panic_no_when",
JumpIfNoMatch => "jump_if_no_match",
JumpIfMatch => "jump_if_match",
PanicNoMatch => "panic_no_match",
TypeOf => "type_of",
JumpBack => "jump_back",
JumpIfZero => "jump_if_zero",
Decrement => "decrement",
ToInt => "truncate",
Duplicate => "duplicate",
MatchDepth => "match_depth",
Panic => "panic",
EmptyString => "empty_string",
ConcatStrings => "concat_strings",
Stringify => "stringify",
Print => "print",
Eq => "eq",
Add => "add",
Sub => "sub",
Mult => "mult",
Div => "div",
Unbox => "unbox",
BoxStore => "box_store",
Assert => "assert",
Get => "get",
At => "at",
Not => "not",
Call => "call",
Return => "return",
Partial => "partial",
TailCall => "tail_call",
SetUpvalue => "set_upvalue",
GetUpvalue => "get_upvalue",
};
write!(f, "{rep}")
}
}

View File

@ -1,37 +1,11 @@
// TODO: move AST to its own module
// TODO: remove StringMatcher cruft
// TODO: good error messages?
use crate::lexer::*;
use crate::spans::*;
use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
use std::fmt;
use struct_scalpel::Dissectible;
// #[derive(Clone, Debug, PartialEq)]
// pub struct WhenClause {
// pub cond: Spanned<Ast>,
// pub body: Spanned<Ast>,
// }
// impl fmt::Display for WhenClause {
// fn fmt(self: &WhenClause, f: &mut fmt::Formatter) -> fmt::Result {
// write!(f, "cond: {}, body: {}", self.cond.0, self.body.0)
// }
// }
// #[derive(Clone, Debug, PartialEq)]
// pub struct MatchClause {
// pub patt: Spanned<Pattern>,
// pub guard: Option<Spanned<Ast>>,
// pub body: Spanned<Ast>,
// }
// impl fmt::Display for MatchClause {
// fn fmt(self: &MatchClause, f: &mut fmt::Formatter) -> fmt::Result {
// write!(
// f,
// "pattern: {}, guard: {:?} body: {}",
// self.patt.0, self.guard, self.body.0
// )
// }
// }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StringPart {
@ -51,12 +25,15 @@ impl fmt::Display for StringPart {
}
}
#[derive(Clone, Debug, PartialEq, Dissectible)]
#[derive(Clone, Debug, PartialEq)]
pub enum Ast {
// a special Error node
// may come in handy?
Error,
And,
Or,
// expression nodes
Placeholder,
Nil,
@ -83,7 +60,8 @@ pub enum Ast {
Box<Option<Spanned<Self>>>,
Box<Spanned<Self>>,
),
Fn(&'static str, Vec<Spanned<Self>>, Option<&'static str>),
Fn(&'static str, Box<Spanned<Ast>>, Option<&'static str>),
FnBody(Vec<Spanned<Ast>>),
FnDeclaration(&'static str),
Panic(Box<Spanned<Self>>),
Do(Vec<Spanned<Self>>),
@ -110,10 +88,167 @@ pub enum Ast {
DictPattern(Vec<Spanned<Self>>),
}
impl Ast {
pub fn show(&self) -> String {
use Ast::*;
match self {
And => "and".to_string(),
Or => "or".to_string(),
Error => unreachable!(),
Nil | NilPattern => "nil".to_string(),
String(s) | StringPattern(s) => format!("\"{s}\""),
Interpolated(strs) | InterpolatedPattern(strs, _) => {
let mut out = "".to_string();
out = format!("\"{out}");
for (part, _) in strs {
out = format!("{out}{part}");
}
format!("{out}\"")
}
Boolean(b) | BooleanPattern(b) => b.to_string(),
Number(n) | NumberPattern(n) => n.to_string(),
Keyword(k) | KeywordPattern(k) => format!(":{k}"),
Word(w) | WordPattern(w) => w.to_string(),
Block(lines) => {
let mut out = "{\n".to_string();
for (line, _) in lines {
out = format!("{out}\n {}", line.show());
}
format!("{out}\n}}")
}
If(cond, then, r#else) => format!(
"if {}\n then {}\n else {}",
cond.0.show(),
then.0.show(),
r#else.0.show()
),
Let(pattern, expression) => {
format!("let {} = {}", pattern.0.show(), expression.0.show())
}
Dict(entries) | DictPattern(entries) => {
format!(
"#{{{}}}",
entries
.iter()
.map(|(pair, _)| pair.show())
.collect::<Vec<_>>()
.join(", ")
)
}
List(members) | ListPattern(members) => format!(
"[{}]",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Arguments(members) => format!(
"({})",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Tuple(members) | TuplePattern(members) => format!(
"({})",
members
.iter()
.map(|(member, _)| member.show())
.collect::<Vec<_>>()
.join(", ")
),
Synthetic(root, first, rest) => format!(
"{} {} {}",
root.0.show(),
first.0.show(),
rest.iter()
.map(|(term, _)| term.show())
.collect::<Vec<_>>()
.join(" ")
),
When(clauses) => format!(
"when {{\n {}\n}}",
clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n ")
),
Placeholder | PlaceholderPattern => "_".to_string(),
LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()),
Match(scrutinee, clauses) => format!(
"match {} with {{\n {}\n}}",
scrutinee.0.show(),
clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n ")
),
FnBody(clauses) => clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n "),
Fn(name, body, doc) => {
let mut out = format!("fn {name} {{\n");
if let Some(doc) = doc {
out = format!("{out} {doc}\n");
}
format!("{out} {}\n}}", body.0.show())
}
FnDeclaration(name) => format!("fn {name}"),
Panic(expr) => format!("panic! {}", expr.0.show()),
Do(terms) => {
format!(
"do {}",
terms
.iter()
.map(|(term, _)| term.show())
.collect::<Vec<_>>()
.join(" > ")
)
}
Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
Splat(word) => format!("...{}", word),
Splattern(pattern) => format!("...{}", pattern.0.show()),
AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"),
Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()),
Loop(init, body) => format!(
"loop {} with {{\n {}\n}}",
init.0.show(),
body.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n ")
),
Recur(args) => format!(
"recur ({})",
args.iter()
.map(|(arg, _)| arg.show())
.collect::<Vec<_>>()
.join(", ")
),
MatchClause(pattern, guard, body) => {
let mut out = pattern.0.show();
if let Some(guard) = guard.as_ref() {
out = format!("{out} if {}", guard.0.show());
}
format!("{out} -> {}", body.0.show())
}
WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()),
}
}
}
impl fmt::Display for Ast {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Ast::*;
match self {
And => write!(f, "And"),
Or => write!(f, "Or"),
Error => write!(f, "Error"),
Nil => write!(f, "nil"),
String(s) => write!(f, "String: \"{}\"", s),
@ -164,7 +299,15 @@ impl fmt::Display for Ast {
.collect::<Vec<_>>()
.join("\n")
),
Tuple(t) | Ast::Arguments(t) => write!(
Arguments(a) => write!(
f,
"Arguments: ({})",
a.iter()
.map(|(line, _)| line.to_string())
.collect::<Vec<_>>()
.join("\n")
),
Tuple(t) => write!(
f,
"Tuple: ({})",
t.iter()
@ -191,7 +334,7 @@ impl fmt::Display for Ast {
.collect::<Vec<_>>()
.join("\n")
),
Placeholder => todo!(),
Placeholder => write!(f, "Placeholder"),
LBox(_name, _rhs) => todo!(),
Match(value, clauses) => {
write!(
@ -205,11 +348,10 @@ impl fmt::Display for Ast {
.join("\n")
)
}
Fn(name, clauses, _) => {
FnBody(clauses) => {
write!(
f,
"fn: {}\n{}",
name,
"{}",
clauses
.iter()
.map(|clause| clause.0.to_string())
@ -217,6 +359,9 @@ impl fmt::Display for Ast {
.join("\n")
)
}
Fn(name, body, ..) => {
write!(f, "fn: {name}\n{}", body.0)
}
FnDeclaration(_name) => todo!(),
Panic(_expr) => todo!(),
Do(terms) => {
@ -343,75 +488,6 @@ impl fmt::Debug for StringMatcher {
}
}
// #[derive(Clone, Debug, PartialEq)]
// pub enum Pattern {
// Nil,
// Boolean(bool),
// Number(f64),
// String(&'static str),
// Interpolated(Vec<Spanned<StringPart>>, StringMatcher),
// Keyword(&'static str),
// Word(&'static str),
// As(&'static str, &'static str),
// Splattern(Box<Spanned<Self>>),
// Placeholder,
// Tuple(Vec<Spanned<Self>>),
// List(Vec<Spanned<Self>>),
// Pair(&'static str, Box<Spanned<Self>>),
// Dict(Vec<Spanned<Self>>),
// }
// impl fmt::Display for Pattern {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// match self {
// Pattern::Nil => write!(f, "nil"),
// Pattern::Boolean(b) => write!(f, "{}", b),
// Pattern::Number(n) => write!(f, "{}", n),
// Pattern::String(s) => write!(f, "{}", s),
// Pattern::Keyword(k) => write!(f, ":{}", k),
// Pattern::Word(w) => write!(f, "{}", w),
// Pattern::As(w, t) => write!(f, "{} as {}", w, t),
// Pattern::Splattern(p) => write!(f, "...{}", p.0),
// Pattern::Placeholder => write!(f, "_"),
// Pattern::Tuple(t) => write!(
// f,
// "({})",
// t.iter()
// .map(|x| x.0.to_string())
// .collect::<Vec<_>>()
// .join(", ")
// ),
// Pattern::List(l) => write!(
// f,
// "({})",
// l.iter()
// .map(|x| x.0.to_string())
// .collect::<Vec<_>>()
// .join(", ")
// ),
// Pattern::Dict(entries) => write!(
// f,
// "#{{{}}}",
// entries
// .iter()
// .map(|(pair, _)| pair.to_string())
// .collect::<Vec<_>>()
// .join(", ")
// ),
// Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0),
// Pattern::Interpolated(strprts, _) => write!(
// f,
// "interpolated: \"{}\"",
// strprts
// .iter()
// .map(|part| part.0.to_string())
// .collect::<Vec<_>>()
// .join("")
// ),
// }
// }
// }
fn is_word_char(c: char) -> bool {
if c.is_ascii_alphanumeric() {
return true;
@ -420,6 +496,7 @@ fn is_word_char(c: char) -> bool {
}
fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringPart>>, String> {
// println!("parsing string pattern: {s}");
let mut parts = vec![];
let mut current_part = String::new();
let mut start = span.start;
@ -488,6 +565,12 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
StringPart::Inline(current_part),
SimpleSpan::new(start, span.end),
))
} else if !current_part.is_empty() {
let part_len = current_part.len();
parts.push((
StringPart::Data(current_part),
SimpleSpan::new(start, part_len),
))
}
Ok(parts)
@ -718,7 +801,11 @@ where
.delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")")))
.map_with(|args, e| (Arguments(args), e.span()));
let synth_root = word.or(keyword);
let or = just(Token::Reserved("or")).map_with(|_, e| (Or, e.span()));
let and = just(Token::Reserved("and")).map_with(|_, e| (And, e.span()));
let synth_root = or.or(and).or(word).or(keyword);
let synth_term = keyword.or(args);
@ -922,7 +1009,12 @@ where
let lambda = just(Token::Reserved("fn"))
.ignore_then(fn_unguarded.clone())
.map_with(|clause, e| (Fn("anonymous", vec![clause], None), e.span()));
.map_with(|clause, e| {
(
Fn("", Box::new((Ast::FnBody(vec![clause]), e.span())), None),
e.span(),
)
});
let fn_clauses = fn_clause
.clone()
@ -1010,7 +1102,10 @@ where
} else {
unreachable!()
};
(Fn(name, vec![clause], None), e.span())
(
Fn(name, Box::new((Ast::FnBody(vec![clause]), e.span())), None),
e.span(),
)
});
let docstr = select! {Token::String(s) => s};
@ -1032,7 +1127,10 @@ where
} else {
unreachable!()
};
(Fn(name, clauses, docstr), e.span())
(
Fn(name, Box::new((Ast::FnBody(clauses), e.span())), docstr),
e.span(),
)
});
let fn_ = fn_named.or(fn_compound).or(fn_decl);

View File

@ -1,635 +0,0 @@
use crate::base::*;
use crate::parser::*;
use crate::spans::*;
use crate::validator::FnInfo;
use crate::value::Value;
use chumsky::prelude::SimpleSpan;
use imbl::HashMap;
use imbl::Vector;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug, Clone, PartialEq)]
pub struct LErr<'src> {
pub input: &'static str,
pub src: &'static str,
pub msg: String,
pub span: SimpleSpan,
pub trace: Vec<Trace<'src>>,
pub extra: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Trace<'src> {
pub callee: Spanned<Ast>,
pub caller: Spanned<Ast>,
pub function: Value<'src>,
pub arguments: Value<'src>,
pub input: &'static str,
pub src: &'static str,
}
impl<'src> LErr<'src> {
pub fn new(
msg: String,
span: SimpleSpan,
input: &'static str,
src: &'static str,
) -> LErr<'src> {
LErr {
msg,
span,
input,
src,
trace: vec![],
extra: "".to_string(),
}
}
}
type LResult<'src> = Result<Value<'src>, LErr<'src>>;
#[derive(Debug)]
pub struct Process<'src> {
pub input: &'static str,
pub src: &'static str,
pub locals: Vec<(String, Value<'src>)>,
pub prelude: &'src Vec<(String, Value<'src>)>,
pub ast: &'src Ast,
pub span: SimpleSpan,
pub fn_info: std::collections::HashMap<*const Ast, FnInfo>,
}
impl<'src> Process<'src> {
pub fn resolve(&self, word: &String) -> LResult<'src> {
let resolved_local = self.locals.iter().rev().find(|(name, _)| word == name);
match resolved_local {
Some((_, value)) => Ok(value.clone()),
None => {
let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name);
match resolved_prelude {
Some((_, value)) => Ok(value.clone()),
None => Err(LErr::new(
format!("unbound name `{word}`"),
self.span,
self.input,
self.src,
)),
}
}
}
}
pub fn panic(&self, msg: String) -> LResult<'src> {
Err(LErr::new(msg, self.span, self.input, self.src))
}
pub fn bind(&mut self, word: String, value: &Value<'src>) {
self.locals.push((word, value.clone()));
}
pub fn match_eq<T>(&self, x: T, y: T) -> Option<&Process<'src>>
where
T: PartialEq,
{
if x == y {
Some(self)
} else {
None
}
}
pub fn match_pattern(&mut self, patt: &Ast, val: &Value<'src>) -> Option<&Process<'src>> {
use Ast::*;
match (patt, val) {
(NilPattern, Value::Nil) => Some(self),
(PlaceholderPattern, _) => Some(self),
(NumberPattern(x), Value::Number(y)) => self.match_eq(x, y),
(BooleanPattern(x), Value::Boolean(y)) => self.match_eq(x, y),
(KeywordPattern(x), Value::Keyword(y)) => self.match_eq(x, y),
(StringPattern(x), Value::InternedString(y)) => self.match_eq(x, y),
(StringPattern(x), Value::AllocatedString(y)) => self.match_eq(&x.to_string(), y),
(InterpolatedPattern(_, StringMatcher(matcher)), Value::InternedString(y)) => {
match matcher(y.to_string()) {
Some(matches) => {
let mut matches = matches
.iter()
.map(|(word, string)| {
(
word.clone(),
Value::AllocatedString(Rc::new(string.clone())),
)
})
.collect::<Vec<_>>();
self.locals.append(&mut matches);
Some(self)
}
None => None,
}
}
(WordPattern(w), val) => {
self.bind(w.to_string(), val);
Some(self)
}
(AsPattern(word, type_str), value) => {
let ludus_type = r#type(value);
let type_kw = Value::Keyword(type_str);
if type_kw == ludus_type {
self.bind(word.to_string(), value);
Some(self)
} else {
None
}
}
(TuplePattern(x), Value::Tuple(y)) => {
let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = self.locals.len();
for i in 0..x.len() {
if let Splattern(patt) = &x[i].0 {
let mut list = Vector::new();
for i in i..y.len() {
list.push_back(y[i].clone())
}
let list = Value::List(list);
self.match_pattern(&patt.0, &list);
} else if self.match_pattern(&x[i].0, &y[i]).is_none() {
self.locals.truncate(to);
return None;
}
}
Some(self)
}
(ListPattern(x), Value::List(y)) => {
let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = self.locals.len();
for (i, (patt, _)) in x.iter().enumerate() {
if let Splattern(patt) = &patt {
let list = Value::List(y.skip(i));
self.match_pattern(&patt.0, &list);
} else if self.match_pattern(patt, y.get(i).unwrap()).is_none() {
self.locals.truncate(to);
return None;
}
}
Some(self)
}
// TODO: optimize this on several levels
// - [ ] opportunistic mutation
// - [ ] get rid of all the pointer indirection in word splats
(DictPattern(x), Value::Dict(y)) => {
let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _)));
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None;
};
let to = self.locals.len();
let mut matched = vec![];
for (pattern, _) in x {
match pattern {
PairPattern(key, patt) => {
if let Some(val) = y.get(key) {
if self.match_pattern(&patt.0, val).is_none() {
self.locals.truncate(to);
return None;
} else {
matched.push(key);
}
} else {
return None;
};
}
Splattern(pattern) => match pattern.0 {
WordPattern(w) => {
// TODO: find a way to take ownership
// this will ALWAYS make structural changes, because of this clone
// we want opportunistic mutation if possible
let mut unmatched = y.clone();
for key in matched.iter() {
unmatched.remove(*key);
}
self.bind(w.to_string(), &Value::Dict(unmatched));
}
PlaceholderPattern => (),
_ => unreachable!(),
},
_ => unreachable!(),
}
}
Some(self)
}
_ => None,
}
}
pub fn match_clauses(
&mut self,
value: &Value<'src>,
clauses: &'src [Spanned<Ast>],
) -> LResult<'src> {
{
let root = self.ast;
let to = self.locals.len();
let mut clauses_iter = clauses.iter();
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses_iter.next() {
if self.match_pattern(&patt.0, value).is_some() {
let pass_guard = match guard.as_ref() {
None => true,
Some(guard_expr) => self.visit(guard_expr)?.bool(),
};
if !pass_guard {
self.locals.truncate(to);
continue;
}
let result = self.visit(body);
self.locals.truncate(to);
self.ast = root;
return result;
}
}
let patterns = clauses
.iter()
.map(|clause| {
let (Ast::MatchClause(patt, ..), _) = clause else {
unreachable!("internal Ludus error")
};
let patt = &patt.as_ref().0;
patt.to_string()
})
.collect::<Vec<_>>()
.join("\n");
dbg!(&patterns);
Err(LErr {
input: self.input,
src: self.src,
msg: "no match".to_string(),
span: self.span,
trace: vec![],
extra: format!("expected {value} to match one of\n{}", patterns),
})
}
}
pub fn apply(&mut self, callee: Value<'src>, caller: Value<'src>) -> LResult<'src> {
use Value::*;
match (callee, caller) {
(Keyword(kw), Dict(dict)) => {
if let Some(val) = dict.get(kw) {
Ok(val.clone())
} else {
Ok(Nil)
}
}
(Dict(dict), Keyword(kw)) => {
if let Some(val) = dict.get(kw) {
Ok(val.clone())
} else {
Ok(Nil)
}
}
(Fn(f), Tuple(args)) => {
// can't just use the `caller` value b/c borrow checker nonsense
let args = Tuple(args);
let to = self.locals.len();
if !f.has_run {
for i in 0..f.enclosing.len() {
let (name, value) = f.enclosing[i].clone();
if !f.has_run && matches!(value, Value::FnDecl(_)) {
let defined = self.resolve(&name);
match defined {
Ok(Value::Fn(defined)) => {
f.enclosing[i] = (name.clone(), Fn(defined))
}
Ok(Value::FnDecl(_)) => {
return self.panic(format!(
"function `{name}` called before it was defined"
))
}
_ => unreachable!("internal Ludus error"),
}
}
self.locals.push(f.enclosing[i].clone());
}
f.has_run = true;
}
let input = self.input;
let src = self.src;
self.input = f.input;
self.src = f.src;
let result = self.match_clauses(&args, f.body);
self.locals.truncate(to);
self.input = input;
self.src = src;
result
}
// TODO: partially applied functions shnould work! In #15
(Fn(_f), Args(_partial_args)) => todo!(),
(_, Keyword(_)) => Ok(Nil),
(_, Args(_)) => self.panic("only functions and keywords may be called".to_string()),
(Base(f), Tuple(args)) => match f {
BaseFn::Nullary(f) => {
let num_args = args.len();
if num_args != 0 {
self.panic(format!("wrong arity: expected 0 arguments, got {num_args}"))
} else {
Ok(f())
}
}
BaseFn::Unary(f) => {
let num_args = args.len();
if num_args != 1 {
self.panic(format!("wrong arity: expected 1 argument, got {num_args}"))
} else {
Ok(f(&args[0]))
}
}
BaseFn::Binary(r#fn) => {
let num_args = args.len();
if num_args != 2 {
self.panic(format!("wrong arity: expected 2 arguments, got {num_args}"))
} else {
Ok(r#fn(&args[0], &args[1]))
}
}
BaseFn::Ternary(f) => {
let num_args = args.len();
if num_args != 3 {
self.panic(format!("wrong arity: expected 3 arguments, got {num_args}"))
} else {
Ok(f(&args[0], &args[1], &args[2]))
}
}
},
_ => unreachable!(),
}
}
pub fn visit(&mut self, node: &'src Spanned<Ast>) -> LResult<'src> {
let (expr, span) = node;
self.ast = expr;
self.span = *span;
self.eval()
}
pub fn eval(&mut self) -> LResult<'src> {
use Ast::*;
let (root_node, root_span) = (self.ast, self.span);
let result = match root_node {
Nil => Ok(Value::Nil),
Boolean(b) => Ok(Value::Boolean(*b)),
Number(n) => Ok(Value::Number(*n)),
Keyword(k) => Ok(Value::Keyword(k)),
String(s) => Ok(Value::InternedString(s)),
Interpolated(parts) => {
let mut interpolated = std::string::String::new();
for part in parts {
match &part.0 {
StringPart::Data(s) => interpolated.push_str(s.as_str()),
StringPart::Word(w) => {
let val = self.resolve(w)?;
interpolated.push_str(val.interpolate().as_str())
}
StringPart::Inline(_) => unreachable!(),
}
}
Ok(Value::AllocatedString(Rc::new(interpolated)))
}
Block(exprs) => {
let to = self.locals.len();
let mut result = Value::Nil;
for expr in exprs {
result = self.visit(expr)?;
}
self.locals.truncate(to);
Ok(result)
}
If(cond, if_true, if_false) => {
let truthy = self.visit(cond)?;
let to_visit = if truthy.bool() { if_true } else { if_false };
self.visit(to_visit)
}
List(members) => {
let mut vect = Vector::new();
for member in members {
let member_value = self.visit(member)?;
match member.0 {
Ast::Splat(_) => match member_value {
Value::List(list) => vect.append(list),
_ => {
return self
.panic("only lists may be splatted into lists".to_string())
}
},
_ => vect.push_back(member_value),
}
}
Ok(Value::List(vect))
}
Tuple(members) => {
let mut vect = Vec::new();
for member in members {
vect.push(self.visit(member)?);
}
Ok(Value::Tuple(Rc::new(vect)))
}
Word(w) | Ast::Splat(w) => {
let val = self.resolve(&w.to_string())?;
Ok(val)
}
Let(patt, expr) => {
let val = self.visit(expr)?;
let result = match self.match_pattern(&patt.0, &val) {
Some(_) => Ok(val),
None => self.panic("no match".to_string()),
};
result
}
Placeholder => Ok(Value::Placeholder),
Arguments(a) => {
let mut args = vec![];
for arg in a.iter() {
args.push(self.visit(arg)?)
}
let result = if args.iter().any(|arg| matches!(arg, Value::Placeholder)) {
Ok(Value::Args(Rc::new(args)))
} else {
Ok(Value::Tuple(Rc::new(args)))
};
result
}
Dict(terms) => {
let mut dict = HashMap::new();
for term in terms {
match term {
(Ast::Pair(key, value), _) => {
dict.insert(*key, self.visit(value)?);
}
(Ast::Splat(_), _) => {
let resolved = self.visit(term)?;
let Value::Dict(to_splat) = resolved else {
return self.panic("cannot splat non-dict into dict".to_string());
};
dict = to_splat.union(dict);
}
_ => unreachable!(),
}
}
Ok(Value::Dict(dict))
}
LBox(name, expr) => {
let val = self.visit(expr)?;
let boxed = Value::Box(name, Rc::new(RefCell::new(val)));
self.bind(name.to_string(), &boxed);
Ok(boxed)
}
Synthetic(root, first, rest) => {
let root_val = self.visit(root)?;
let first_val = self.visit(first)?;
let mut result = self.apply(root_val.clone(), first_val.clone());
if let Err(mut err) = result {
err.trace.push(Trace {
callee: *root.clone(),
caller: *first.clone(),
function: root_val,
arguments: first_val,
input: self.input,
src: self.src,
});
return Err(err);
};
let mut prev_node;
let mut this_node = first.as_ref();
for term in rest.iter() {
prev_node = this_node;
this_node = term;
let caller = self.visit(term)?;
let callee = result.unwrap();
result = self.apply(callee.clone(), caller.clone());
if let Err(mut err) = result {
err.trace.push(Trace {
callee: prev_node.clone(),
caller: this_node.clone(),
function: caller,
arguments: callee,
input: self.input,
src: self.src,
});
return Err(err);
}
}
result
}
When(clauses) => {
for clause in clauses.iter() {
let WhenClause(cond, body) = &clause.0 else {
unreachable!()
};
if self.visit(cond)?.bool() {
return self.visit(body);
};
}
self.panic("no match".to_string())
}
Match(scrutinee, clauses) => {
let value = self.visit(scrutinee)?;
self.match_clauses(&value, clauses)
}
Fn(name, clauses, doc) => {
let doc = doc.map(|s| s.to_string());
let ptr: *const Ast = root_node;
let info = self.fn_info.get(&ptr).unwrap();
let FnInfo::Defined(_, _, enclosing) = info else {
unreachable!()
};
let enclosing = enclosing
.iter()
.filter(|binding| binding != name)
.map(|binding| (binding.clone(), self.resolve(binding).unwrap().clone()))
.collect();
let the_fn = Value::Fn::<'src>(Rc::new(RefCell::new(crate::value::Fn::<'src> {
name: name.to_string(),
body: clauses,
doc,
enclosing,
has_run: false,
input: self.input,
src: self.src,
})));
let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name);
match maybe_decl_i {
None => self.bind(name.to_string(), &the_fn),
Some(i) => {
let declared = &self.locals[i].1;
match declared {
Value::FnDecl(_) => {
self.locals[i] = (name.to_string(), the_fn.clone());
}
_ => unreachable!("internal Ludus error"),
}
}
}
Ok(the_fn)
}
FnDeclaration(name) => {
let decl = Value::FnDecl(name);
self.bind(name.to_string(), &decl);
Ok(decl)
}
Panic(msg) => {
let msg = self.visit(msg)?;
self.panic(format!("{msg}"))
}
Repeat(times, body) => {
let times_num = match self.visit(times) {
Ok(Value::Number(n)) => n as usize,
_ => return self.panic("`repeat` may only take numbers".to_string()),
};
for _ in 0..times_num {
self.visit(body)?;
}
Ok(Value::Nil)
}
Do(terms) => {
let mut result = self.visit(&terms[0])?;
for term in terms.iter().skip(1) {
let next = self.visit(term)?;
let arg = Value::Tuple(Rc::new(vec![result]));
result = self.apply(next, arg)?;
}
Ok(result)
}
Loop(init, clauses) => {
let mut args = self.visit(init)?;
loop {
let result = self.match_clauses(&args, clauses)?;
if let Value::Recur(recur_args) = result {
args = Value::Tuple(Rc::new(recur_args));
} else {
return Ok(result);
}
}
}
Recur(args) => {
let mut vect = Vec::new();
for arg in args {
vect.push(self.visit(arg)?);
}
Ok(Value::Recur(vect))
}
_ => unreachable!(),
};
self.ast = root_node;
self.span = root_span;
result
}
}

View File

@ -1,18 +1,24 @@
// TODO:
// * [ ] ensure `or` and `and` never get passed by reference
// * [ ] ensure no placeholder in `or` and `and` args
// * [ ] ensure loops have fixed arity (no splats)
// * [ ] ensure fn pattern splats are always highest (and same) arity
use crate::parser::*;
use crate::spans::{Span, Spanned};
use crate::value::Value;
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, PartialEq)]
pub struct VErr {
pub struct VErr<'a> {
pub msg: String,
pub span: Span,
pub span: &'a Span,
pub input: &'static str,
pub src: &'static str,
}
impl VErr {
pub fn new(msg: String, span: Span, input: &'static str, src: &'static str) -> VErr {
impl<'a> VErr<'a> {
pub fn new(msg: String, span: &'a Span, input: &'static str, src: &'static str) -> VErr<'a> {
VErr {
msg,
span,
@ -54,13 +60,13 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
#[derive(Debug, PartialEq)]
pub struct Validator<'a> {
pub locals: Vec<(String, Span, FnInfo)>,
pub prelude: &'a Vec<(String, Value<'a>)>,
pub locals: Vec<(String, &'a Span, FnInfo)>,
pub prelude: imbl::HashMap<&'static str, Value>,
pub input: &'static str,
pub src: &'static str,
pub ast: &'a Ast,
pub span: Span,
pub errors: Vec<VErr>,
pub span: &'a Span,
pub errors: Vec<VErr<'a>>,
pub fn_info: HashMap<*const Ast, FnInfo>,
status: VStatus,
}
@ -68,10 +74,10 @@ pub struct Validator<'a> {
impl<'a> Validator<'a> {
pub fn new(
ast: &'a Ast,
span: Span,
span: &'a Span,
input: &'static str,
src: &'static str,
prelude: &'a Vec<(String, Value<'a>)>,
prelude: imbl::HashMap<&'static str, Value>,
) -> Validator<'a> {
Validator {
input,
@ -112,7 +118,7 @@ impl<'a> Validator<'a> {
|| self.prelude.iter().any(|(bound, _)| name == *bound)
}
fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> {
fn bound(&self, name: &str) -> Option<&(String, &Span, FnInfo)> {
match self.locals.iter().rev().find(|(bound, ..)| name == bound) {
Some(binding) => Some(binding),
None => None,
@ -146,7 +152,7 @@ impl<'a> Validator<'a> {
fn visit(&mut self, node: &'a Spanned<Ast>) {
let (expr, span) = node;
self.ast = expr;
self.span = *span;
self.span = span;
self.validate();
}
@ -165,7 +171,7 @@ impl<'a> Validator<'a> {
Interpolated(parts) => {
for part in parts {
if let (StringPart::Word(name), span) = part {
self.span = *span;
self.span = span;
if !self.resolved(name.as_str()) {
self.err(format!("unbound name `{name}`"));
} else {
@ -278,6 +284,9 @@ impl<'a> Validator<'a> {
// check arity against fn info if first term is word and second term is args
Synthetic(first, second, rest) => {
match (&first.0, &second.0) {
(Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Arguments(_)) => {
self.visit(second.as_ref())
}
(Ast::Word(_), Ast::Keyword(_)) => self.visit(first.as_ref()),
(Ast::Keyword(_), Ast::Arguments(args)) => {
if args.len() != 1 {
@ -298,7 +307,10 @@ impl<'a> Validator<'a> {
}
}
}
_ => unreachable!(),
_ => unreachable!(
"malformed synthetic root with\nfirst: {}\nsecond: {}",
first.0, second.0
),
}
for term in rest {
self.visit(term);
@ -362,7 +374,8 @@ impl<'a> Validator<'a> {
self.declare_fn(name.to_string());
self.status.tail_position = tailpos;
}
Fn(name, clauses, ..) => {
FnBody(..) => unreachable!(),
Fn(name, body, ..) => {
let mut is_declared = false;
match self.bound(name) {
Some((_, _, FnInfo::Declared)) => is_declared = true,
@ -380,22 +393,21 @@ impl<'a> Validator<'a> {
let from = self.status.used_bindings.len();
let mut arities = HashSet::new();
let (Ast::FnBody(clauses), _) = body.as_ref() else {
unreachable!()
};
for clause in clauses {
// TODO: validate all parts of clauses
// we have to do this explicitly here because of arity checking
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
self.span = span;
// add clause arity to arities
arities.insert(self.arity());
self.validate();
}
// this should be right
// we can't bind anything that's already bound,
// even in arg names
// so anything that is already bound and used
// will, of necessity, be closed over
// we don't want to try to close over locals in functions
// collect info about what the function closes over
let mut closed_over = HashSet::new();
for binding in self.status.used_bindings.iter().skip(from) {
if self.bound(binding.as_str()).is_some() {
@ -458,7 +470,7 @@ impl<'a> Validator<'a> {
for clause in body {
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
self.span = span;
let arity = self.arity();
// dbg!(&arity);
match arity {
@ -511,7 +523,7 @@ impl<'a> Validator<'a> {
InterpolatedPattern(parts, _) => {
for (part, span) in parts {
if let StringPart::Word(name) = part {
self.span = *span;
self.span = span;
match self.bound(name) {
Some(_) => self.err(format!("name `{name}` is already bound")),
None => self.bind(name.to_string()),
@ -543,7 +555,7 @@ impl<'a> Validator<'a> {
(PlaceholderPattern, _) => (),
(WordPattern(name), span) => match self.bound(name) {
Some(_) => {
self.span = *span;
self.span = span;
self.err(format!("name `{name}` is already bound"))
}
None => self.bind(name.to_string()),
@ -570,7 +582,7 @@ impl<'a> Validator<'a> {
}
PairPattern(_, patt) => self.visit(patt.as_ref()),
// terminals can never be invalid
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) => (),
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (),
// terminal patterns can never be invalid
NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..)
| KeywordPattern(..) | PlaceholderPattern => (),

View File

@ -1,193 +1,381 @@
use crate::base::*;
use crate::parser::*;
use crate::spans::*;
use imbl::*;
use crate::base::BaseFn;
use crate::chunk::Chunk;
// use crate::parser::Ast;
// use crate::spans::Spanned;
use imbl::{HashMap, Vector};
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use struct_scalpel::Dissectible;
#[derive(Clone, Debug)]
pub struct Fn<'src> {
pub name: String,
pub body: &'src Vec<Spanned<Ast>>,
pub doc: Option<String>,
pub enclosing: Vec<(String, Value<'src>)>,
pub has_run: bool,
pub input: &'static str,
pub src: &'static str,
pub enum LFn {
Declared {
name: &'static str,
},
Defined {
name: &'static str,
doc: Option<&'static str>,
arities: Vec<u8>,
chunks: Vec<Chunk>,
splat: u8,
closed: RefCell<Vec<Value>>,
},
}
#[derive(Debug, Dissectible)]
pub enum Value<'src> {
impl LFn {
pub fn close(&self, value: Value) {
match self {
LFn::Declared { .. } => unreachable!(),
LFn::Defined { closed, .. } => {
let shown = value.show();
closed.borrow_mut().push(value);
let pos = closed.borrow().len();
if crate::DEBUG_SCRIPT_RUN {
println!("closing over in {} at {pos}: {shown}", self.name(),);
}
}
}
}
pub fn doc(&self) -> Value {
match self {
LFn::Declared { name } => {
Value::String(Rc::new(format!("fn {name}: undefined function")))
}
LFn::Defined {
name,
doc: Some(doc),
..
} => Value::String(Rc::new(format!("fn {name}\n{doc}"))),
LFn::Defined { name, .. } => {
Value::String(Rc::new(format!("fn {name}: no documentation")))
}
}
}
pub fn accepts(&self, arity: u8) -> bool {
match self {
LFn::Defined { arities, splat, .. } => {
if arities.contains(&arity) {
return true;
}
if *splat == 0 {
return false;
}
let max_arity = arities.iter().fold(0, |a, b| a.max(*b));
arity > max_arity
}
LFn::Declared { .. } => unreachable!(),
}
}
pub fn splat_arity(&self) -> u8 {
match self {
LFn::Defined { splat, .. } => *splat,
LFn::Declared { .. } => unreachable!(),
}
}
pub fn name(&self) -> &'static str {
match self {
LFn::Declared { name } | LFn::Defined { name, .. } => name,
}
}
pub fn chunk(&self, arity: u8) -> &Chunk {
match self {
LFn::Declared { .. } => unreachable!(),
LFn::Defined {
arities,
splat,
chunks,
..
} => {
let chunk_pos = arities.iter().position(|a| arity == *a);
match chunk_pos {
Some(pos) => &chunks[pos],
None => &chunks[*splat as usize],
}
}
}
}
pub fn upvalue(&self, idx: u8) -> Value {
match self {
LFn::Declared { .. } => unreachable!(),
LFn::Defined { closed, .. } => closed.borrow()[idx as usize].clone(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Partial {
pub args: Vec<Value>,
pub name: &'static str,
pub function: Value,
}
#[derive(Clone, Debug)]
pub enum Value {
Nothing,
Nil,
Placeholder,
Boolean(bool),
Number(f64),
True,
False,
Keyword(&'static str),
InternedString(&'static str),
AllocatedString(Rc<String>),
// on the heap for now
Tuple(Rc<Vec<Self>>),
Args(Rc<Vec<Self>>),
List(Vector<Self>),
Dict(HashMap<&'static str, Self>),
Box(&'static str, Rc<RefCell<Self>>),
Fn(Rc<Fn<'src>>),
FnDecl(&'static str),
Base(BaseFn<'src>),
Recur(Vec<Self>),
// Set(HashSet<Self>),
// Sets are hard
// Sets require Eq
// Eq is not implemented on f64, because NaNs
// We could use ordered_float::NotNan
// Let's defer that
// We're not really using sets in Ludus
// Other things we're not implementing yet:
// pkgs, nses, tests
Interned(&'static str),
String(Rc<String>),
Number(f64),
Tuple(Rc<Vec<Value>>),
List(Box<Vector<Value>>),
Dict(Box<HashMap<&'static str, Value>>),
Box(Rc<RefCell<Value>>),
Fn(Rc<LFn>),
BaseFn(BaseFn),
Partial(Rc<Partial>),
}
impl<'src> Clone for Value<'src> {
fn clone(&self) -> Value<'src> {
match self {
Value::Nil => Value::Nil,
Value::Boolean(b) => Value::Boolean(*b),
Value::InternedString(s) => Value::InternedString(s),
Value::AllocatedString(s) => Value::AllocatedString(s.clone()),
Value::Keyword(s) => Value::Keyword(s),
Value::Number(n) => Value::Number(*n),
Value::Tuple(t) => Value::Tuple(t.clone()),
Value::Args(a) => Value::Args(a.clone()),
Value::Fn(f) => Value::Fn(f.clone()),
Value::FnDecl(name) => Value::FnDecl(name),
Value::List(l) => Value::List(l.clone()),
Value::Dict(d) => Value::Dict(d.clone()),
Value::Box(name, b) => Value::Box(name, b.clone()),
Value::Placeholder => Value::Placeholder,
Value::Base(b) => Value::Base(b.clone()),
Value::Recur(..) => unreachable!(),
}
}
}
impl fmt::Display for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Nil => write!(f, "nil"),
Value::Boolean(b) => write!(f, "{b}"),
Value::Number(n) => write!(f, "{n}"),
Value::Keyword(k) => write!(f, ":{k}"),
Value::InternedString(s) => write!(f, "\"{s}\""),
Value::AllocatedString(s) => write!(f, "\"{s}\""),
Value::Fn(fun) => write!(f, "fn {}", fun.borrow().name),
Value::FnDecl(name) => write!(f, "fn {name}"),
Value::Tuple(t) | Value::Args(t) => write!(
f,
"({})",
t.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Value::List(l) => write!(
f,
"[{}]",
l.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Value::Dict(d) => write!(
f,
"#{{{}}}",
d.iter()
.map(|(k, v)| format!(":{k} {v}"))
.collect::<Vec<_>>()
.join(", ")
),
Value::Box(name, value) => {
write!(
f,
"box {}: [{}]",
name,
&value.try_borrow().unwrap().to_string()
)
}
Value::Placeholder => write!(f, "_"),
Value::Base(..) => unreachable!(),
Value::Recur(..) => unreachable!(),
}
}
}
impl Value<'_> {
pub fn bool(&self) -> bool {
!matches!(self, Value::Nil | Value::Boolean(false))
}
}
impl<'src> PartialEq for Value<'src> {
fn eq(&self, other: &Value<'src>) -> bool {
impl PartialEq for Value {
fn eq(&self, other: &Value) -> bool {
use Value::*;
match (self, other) {
// value equality types
(Value::Nil, Value::Nil) => true,
(Value::Boolean(x), Value::Boolean(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y,
(Value::InternedString(x), Value::InternedString(y)) => x == y,
(Value::AllocatedString(x), Value::AllocatedString(y)) => x == y,
(Value::InternedString(x), Value::AllocatedString(y)) => *x == **y,
(Value::AllocatedString(x), Value::InternedString(y)) => **x == *y,
(Value::Keyword(x), Value::Keyword(y)) => x == y,
(Value::Tuple(x), Value::Tuple(y)) => x == y,
(Value::List(x), Value::List(y)) => x == y,
(Value::Dict(x), Value::Dict(y)) => x == y,
// reference equality types
(Value::Fn(x), Value::Fn(y)) => {
Rc::<RefCell<Fn<'_>>>::as_ptr(x) == Rc::<RefCell<Fn<'_>>>::as_ptr(y)
}
(Value::Box(_, x), Value::Box(_, y)) => {
Rc::<RefCell<Value<'_>>>::as_ptr(x) == Rc::<RefCell<Value<'_>>>::as_ptr(y)
}
(Nothing, Nothing) | (Nil, Nil) | (True, True) | (False, False) => true,
(Keyword(str1), Keyword(str2)) | (Interned(str1), Interned(str2)) => str1 == str2,
(String(x), String(y)) => x == y,
(String(x), Interned(y)) => x.as_ref() == y,
(Interned(x), String(y)) => x == y.as_ref(),
(Number(x), Number(y)) => x == y,
(Tuple(x), Tuple(y)) => x == y,
(List(x), List(y)) => x == y,
(Dict(x), Dict(y)) => x == y,
(Box(x), Box(y)) => std::ptr::eq(x.as_ref().as_ptr(), y.as_ref().as_ptr()),
(Fn(x), Fn(y)) => std::ptr::eq(x, y),
(BaseFn(x), BaseFn(y)) => std::ptr::eq(x, y),
(Partial(x), Partial(y)) => x == y,
_ => false,
}
}
}
impl Eq for Value<'_> {}
impl Value<'_> {
pub fn interpolate(&self) -> String {
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Value::*;
match self {
Value::Nil => String::new(),
Value::Boolean(b) => format!("{b}"),
Value::Number(n) => format!("{n}"),
Value::Keyword(k) => format!(":{k}"),
Value::AllocatedString(s) => format!("{s}"),
Value::InternedString(s) => s.to_string(),
Value::Box(_, x) => x.borrow().interpolate(),
Value::Tuple(xs) => xs
.iter()
.map(|x| x.interpolate())
.collect::<Vec<_>>()
.join(", "),
Value::List(xs) => xs
.iter()
.map(|x| x.interpolate())
.collect::<Vec<_>>()
.join(", "),
Value::Dict(xs) => xs
.iter()
.map(|(k, v)| format!(":{} {}", k, v.interpolate()))
.collect::<Vec<_>>()
.join(", "),
Value::Fn(x) => format!("fn {}", x.borrow().name),
Value::FnDecl(name) => format!("fn {name}"),
Value::Placeholder => unreachable!(),
Value::Args(_) => unreachable!(),
Value::Recur(_) => unreachable!(),
Value::Base(_) => unreachable!(),
Nothing => write!(f, "_"),
Nil => write!(f, "nil"),
True => write!(f, "true"),
False => write!(f, "false"),
Keyword(str) => write!(f, ":{str}"),
Interned(str) => write!(f, "\"{str}\""),
String(str) => write!(f, "\"{str}\""),
Number(n) => write!(f, "{n}"),
Tuple(members) => write!(
f,
"({})",
members
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
List(members) => write!(
f,
"[{}]",
members
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Dict(members) => write!(
f,
"#{{{}}}",
members
.iter()
.map(|(k, v)| format!("{k} {v}"))
.collect::<Vec<_>>()
.join(", ")
),
Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()),
Fn(lfn) => write!(f, "fn {}", lfn.name()),
BaseFn(inner) => {
let name = match inner {
crate::base::BaseFn::Nullary(name, _)
| crate::base::BaseFn::Unary(name, _)
| crate::base::BaseFn::Binary(name, _)
| crate::base::BaseFn::Ternary(name, _) => name,
};
write!(f, "fn {name}/base")
}
Partial(partial) => write!(f, "fn {}/partial", partial.name),
}
}
}
impl Value {
pub fn show(&self) -> String {
use Value::*;
let mut out = match &self {
Nil => "nil".to_string(),
True => "true".to_string(),
False => "false".to_string(),
Number(n) => format!("{n}"),
Interned(str) => format!("\"{str}\""),
String(str) => format!("\"{str}\""),
Keyword(str) => format!(":{str}"),
Tuple(t) => {
let members = t.iter().map(|e| e.show()).collect::<Vec<_>>().join(", ");
format!("({members})")
}
List(l) => {
let members = l.iter().map(|e| e.show()).collect::<Vec<_>>().join(", ");
format!("[{members}]")
}
Dict(d) => {
let members = d
.iter()
.map(|(k, v)| {
let key_show = Value::Keyword(k).show();
let value_show = v.show();
format!("{key_show} {value_show}")
})
.collect::<Vec<_>>()
.join(", ");
format!("#{{{members}}}")
}
Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show()),
Fn(lfn) => format!("fn {}", lfn.name()),
Partial(partial) => format!("fn {}/partial", partial.name),
BaseFn(_) => format!("{self}"),
Nothing => "_".to_string(),
};
if out.len() > 20 {
out.truncate(20);
format!("{out}...")
} else {
out
}
}
pub fn to_json(&self) -> Option<String> {
use Value::*;
match self {
True | False | String(..) | Interned(..) | Number(..) => Some(self.show()),
Keyword(str) => Some(format!("\"{str}\"")),
List(members) => {
let mut joined = "".to_string();
let mut members = members.iter();
if let Some(member) = members.next() {
joined = member.to_json()?;
}
for member in members {
let json = member.to_json()?;
joined = format!("{joined},{json}");
}
Some(format!("[{joined}]"))
}
Tuple(members) => {
let mut joined = "".to_string();
let mut members = members.iter();
if let Some(member) = members.next() {
joined = member.to_json()?;
}
for member in members {
let json = member.to_json()?;
joined = format!("{joined},{json}");
}
Some(format!("[{joined}]"))
}
Dict(members) => {
let mut joined = "".to_string();
let mut members = members.iter();
if let Some((key, value)) = members.next() {
let json = value.to_json()?;
joined = format!("\"{key}\":{json}")
}
for (key, value) in members {
let json = value.to_json()?;
joined = format!("{joined},\"{key}\": {json}");
}
Some(format!("{{{joined}}}"))
}
not_serializable => {
println!("Cannot convert to json:");
dbg!(not_serializable);
None
}
}
}
pub fn stringify(&self) -> String {
use Value::*;
match &self {
Nil => "nil".to_string(),
True => "true".to_string(),
False => "false".to_string(),
Number(n) => format!("{n}"),
Interned(str) => str.to_string(),
Keyword(str) => format!(":{str}"),
Tuple(t) => {
let members = t
.iter()
.map(|e| e.stringify())
.collect::<Vec<_>>()
.join(", ");
format!("({members})")
}
List(l) => {
let members = l
.iter()
.map(|e| e.stringify())
.collect::<Vec<_>>()
.join(", ");
format!("[{members}]")
}
Dict(d) => {
let members = d
.iter()
.map(|(k, v)| {
let key_show = Value::Keyword(k).stringify();
let value_show = v.stringify();
format!("{key_show} {value_show}")
})
.collect::<Vec<_>>()
.join(", ");
format!("#{{{members}}}")
}
String(s) => s.as_ref().clone(),
Box(x) => x.as_ref().borrow().stringify(),
Fn(lfn) => format!("fn {}", lfn.name()),
Partial(partial) => format!("fn {}/partial", partial.name),
BaseFn(_) => format!("{self}"),
Nothing => unreachable!(),
}
}
pub fn type_of(&self) -> &'static str {
use Value::*;
match self {
Nothing => unreachable!(),
Nil => "nil",
True => "bool",
False => "bool",
Keyword(..) => "keyword",
Interned(..) => "string",
String(..) => "string",
Number(..) => "number",
Tuple(..) => "tuple",
List(..) => "list",
Dict(..) => "dict",
Box(..) => "box",
Fn(..) => "fn",
BaseFn(..) => "fn",
Partial(..) => "fn",
}
}
pub fn as_fn(&self) -> &LFn {
match self {
Value::Fn(inner) => inner.as_ref(),
_ => unreachable!(),
}
}
}

1535
src/vm.rs

File diff suppressed because it is too large Load Diff

205
src/world.rs Normal file
View File

@ -0,0 +1,205 @@
use crate::value::Value;
use crate::vm::{Creature, Panic};
use ran::ran_u8;
use std::collections::{HashMap, VecDeque};
const ANIMALS: [&str; 24] = [
"turtle",
"tortoise",
"hare",
"squirrel",
"hawk",
"woodpecker",
"cardinal",
"coyote",
"raccoon",
"rat",
"axolotl",
"cormorant",
"duck",
"orca",
"humbpack",
"tern",
"quokka",
"koala",
"kangaroo",
"zebra",
"hyena",
"giraffe",
"leopard",
"lion",
];
#[derive(Debug, Clone, PartialEq)]
enum Status {
Empty,
Borrowed,
Nested(Creature),
}
#[derive(Debug, Clone, PartialEq)]
struct Zoo {
procs: Vec<Status>,
empty: Vec<usize>,
ids: HashMap<&'static str, usize>,
dead: Vec<&'static str>,
}
impl Zoo {
pub fn new() -> Zoo {
Zoo {
procs: vec![],
empty: vec![],
ids: HashMap::new(),
dead: vec![],
}
}
pub fn put(&mut self, mut proc: Creature) -> &'static str {
if self.empty.is_empty() {
let rand = ran_u8() as usize % 24;
let idx = self.procs.len();
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
proc.id = id;
self.procs.push(Status::Nested(proc));
self.ids.insert(id, idx);
id
} else {
let idx = self.empty.pop().unwrap();
let rand = ran_u8() as usize % 24;
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
proc.id = id;
self.ids.insert(id, idx);
self.procs[idx] = Status::Nested(proc);
id
}
}
pub fn kill(&mut self, id: &'static str) {
if let Some(idx) = self.ids.get(id) {
self.procs[*idx] = Status::Empty;
self.empty.push(*idx);
self.ids.remove(&id);
self.dead.push(id);
}
}
pub fn catch(&mut self, id: &'static str) -> Creature {
if let Some(idx) = self.ids.get(id) {
let mut proc = Status::Borrowed;
std::mem::swap(&mut proc, &mut self.procs[*idx]);
let Status::Nested(proc) = proc else {
unreachable!("tried to borrow an empty or already-borrowed process");
};
proc
} else {
unreachable!("tried to borrow a non-existent process");
}
}
pub fn release(&mut self, proc: Creature) {
let id = proc.id;
if let Some(idx) = self.ids.get(id) {
let mut proc = Status::Nested(proc);
std::mem::swap(&mut proc, &mut self.procs[*idx]);
} else {
unreachable!("tried to return a process the world doesn't know about");
}
}
}
pub struct World {
procs: Zoo,
mbxes: HashMap<String, VecDeque<Value>>,
active: Creature,
// TODO: we need a lifetime here
main: &'static str,
}
impl World {
pub fn new(proc: Creature) -> World {
let mut creatures = Zoo::new();
let id = creatures.put(proc);
let caught = creatures.catch(id);
World {
procs: creatures,
mbxes: HashMap::new(),
active: caught,
main: id,
}
}
pub fn spawn(&mut self, proc: Creature) -> Value {
let id = self.procs.put(proc);
Value::Keyword(id)
}
pub fn send_msg(&mut self, id: &'static str, msg: Value) {
let mbx = self.mbxes.get_mut(id).unwrap();
mbx.push_back(msg);
}
pub fn sleep(&mut self, id: &'static str) {
// check if the id is the actually active process
// return it to the nursery
// get the next process from the nursery
}
pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> {
// check if the id is of the active process
todo!()
}
pub fn match_msg(&mut self, id: &'static str, idx: usize) {
// again, check for activity
// delete the message at idx, which we gave along with the value as the tuple in get_msg
}
pub fn r#yield(&mut self, id: &'static str) {
// check if the id is active
// swap out the currently active process for the next one
}
pub fn panic(&mut self, id: &'static str) {
// TODO: devise some way of linking processes (study the BEAM on this)
// check if the id is active
// check if the process is `main`, and crash the program if it is
// kill the process
// swap out this process for the next one
}
pub fn complete(&mut self, id: &'static str, value: Value) {
// check if the id is active
// check if the process is main
// if it is: stash the value somehow and exit the program cleanly
}
pub fn run(&mut self) -> Result<Value, Panic> {
todo!()
}
// TODO:
// * [ ] Maybe I need to write this from the bottom up?
// What do processes need to do?
// - [ ] send a message to another process
// - [ ] tell the world to spawn a new process, get the pid back
// - [ ] receive its messages (always until something matches, or sleep if nothing matches)
// - [ ] delete a message from the mbx if it's a match (by idx)
// - [ ] yield
// - [ ] panic
// - [ ] complete
// Thus the other side of this looks like:
// * [x] Spawn a process
// * [x]
}
// Okay, some more thinking
// The world and process can't have mutable references to one another
// They will each need an Rc<RefCell<PostOffice>>
// All the message passing and world/proc communication will happen through there
// And ownership goes World -> Process A -> World -> Process B
// Both the world and a process will have an endless `loop`.
// But I already have three terms: Zoo, Creature, and World
// That should be enough indirection?
// To solve tomorrow.