Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8a9170b002 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
/node_modules
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -3,17 +3,12 @@ name = "rudus"
|
|||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chumsky = "0.10.1"
|
||||
ariadne = { git = "https://github.com/zesterer/ariadne" }
|
||||
chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] }
|
||||
imbl = "3.0.0"
|
||||
num-derive = "0.4.2"
|
||||
num-traits = "0.2.19"
|
||||
regex = "1.11.1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
struct_scalpel = "0.1.1"
|
||||
ran = "2.0.1"
|
||||
rust-embed = "8.5.0"
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
fn agent (val) -> receive {
|
||||
(:set, new) -> agent (new)
|
||||
(:get, pid) -> {
|
||||
send (pid, (:response, val))
|
||||
agent (val)
|
||||
}
|
||||
(:update, f) -> agent (f (val))
|
||||
}
|
||||
|
||||
fn agent/set (pid, val) -> {
|
||||
send (pid, (:set, val))
|
||||
val
|
||||
}
|
||||
|
||||
fn agent/get (pid) -> {
|
||||
send (pid, (:get, self ()))
|
||||
receive {
|
||||
(:response, val) -> val
|
||||
}
|
||||
}
|
||||
|
||||
fn agent/update (pid, f) -> {
|
||||
send (pid, (:update, f))
|
||||
agent/get (pid)
|
||||
}
|
||||
|
||||
let myagent = spawn! (fn () -> agent (42))
|
||||
|
||||
print! ("incrementing agent value to", agent/update (myagent, inc))
|
||||
|
||||
:done!
|
|
@ -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,6 +25,7 @@ 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)
|
||||
|
@ -123,19 +124,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? {
|
||||
|
@ -588,10 +589,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
|
||||
}
|
||||
|
@ -600,10 +601,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
|
||||
}
|
||||
|
@ -612,10 +613,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
|
||||
}
|
||||
|
@ -624,10 +625,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
|
||||
}
|
||||
|
@ -668,7 +669,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 base :lt? (x, y) then x else y
|
||||
(x as :number, y as :number) -> if lt? (x, y) then x else y
|
||||
(x, y, ...zs) -> fold (min, zs, min (x, y))
|
||||
}
|
||||
|
||||
|
@ -697,7 +698,7 @@ fn at {
|
|||
|
||||
fn first {
|
||||
"Returns the first element of a list or tuple."
|
||||
(xs) if ordered? -> at (xs, 0)
|
||||
(xs) if ordered? (xs) -> at (xs, 0)
|
||||
}
|
||||
|
||||
fn second {
|
||||
|
@ -737,22 +738,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."
|
||||
|
@ -1307,7 +1308,7 @@ box state = nil
|
|||
#{
|
||||
abs & math
|
||||
add & math
|
||||
& and & bool
|
||||
and & bool
|
||||
angle & math
|
||||
any? & dicts lists strings sets tuples
|
||||
append & lists sets
|
||||
|
@ -1401,7 +1402,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
|
@ -1,255 +0,0 @@
|
|||
# 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
|
40
justfile
40
justfile
|
@ -1,40 +0,0 @@
|
|||
default:
|
||||
@just --list
|
||||
|
||||
# build optimized wasm
|
||||
build: && clean-wasm-pack
|
||||
# build with wasm-pack
|
||||
wasm-pack build --target web
|
||||
|
||||
# build dev wasm
|
||||
dev: && clean-wasm-pack
|
||||
wasm-pack build --dev --target web
|
||||
|
||||
# clean up after wasm-pack
|
||||
clean-wasm-pack:
|
||||
# delete cruft from wasm-pack
|
||||
rm pkg/.gitignore pkg/package.json pkg/README.md
|
||||
rm -rf pkg/snippets
|
||||
# fix imports of rudus.js
|
||||
cp pkg/rudus.js pkg/rudus.js.backup
|
||||
echo 'import { io } from "./worker.js"' > pkg/rudus.js
|
||||
cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js
|
||||
rm pkg/rudus.js.backup
|
||||
|
||||
from_branch := `git branch --show-current`
|
||||
git_status := `git status -s`
|
||||
|
||||
# publish this branch into release
|
||||
release:
|
||||
echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }}
|
||||
just build
|
||||
-git commit -am "release build"
|
||||
git checkout release
|
||||
git merge {{from_branch}}
|
||||
git push
|
||||
git checkout {{from_branch}}
|
||||
|
||||
# serve the pkg directory
|
||||
serve:
|
||||
live-server pkg
|
||||
|
1210
may_2025_thoughts.md
1210
may_2025_thoughts.md
File diff suppressed because it is too large
Load Diff
1594
package-lock.json
generated
1594
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"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": {
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<title>Testing Ludus/WASM integration</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script src="./ludus.js" type="module"></script>
|
||||
<p>
|
||||
Open the console. All the action's in there.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
500
pkg/ludus.js
500
pkg/ludus.js
|
@ -1,500 +0,0 @@
|
|||
if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running}
|
||||
|
||||
const worker_url = new URL("worker.js", import.meta.url)
|
||||
const worker = new Worker(worker_url, {type: "module"})
|
||||
|
||||
let outbox = []
|
||||
let ludus_console = ""
|
||||
let ludus_commands = []
|
||||
let ludus_result = null
|
||||
let code = null
|
||||
let running = false
|
||||
let ready = false
|
||||
let io_interval_id = null
|
||||
|
||||
worker.onmessage = handle_messages
|
||||
|
||||
async function handle_messages (e) {
|
||||
let msgs
|
||||
try {
|
||||
msgs = JSON.parse(e.data)
|
||||
} catch {
|
||||
console.log(e.data)
|
||||
throw Error("Main: bad json from Ludus")
|
||||
}
|
||||
for (const msg of msgs) {
|
||||
switch (msg.verb) {
|
||||
case "Complete": {
|
||||
console.log("Main: ludus completed with => ", msg.data)
|
||||
ludus_result = msg.data
|
||||
running = false
|
||||
ready = false
|
||||
outbox = []
|
||||
break
|
||||
}
|
||||
// TODO: do more than report these
|
||||
case "Console": {
|
||||
console.log("Main: ludus says => ", msg.data)
|
||||
ludus_console = ludus_console + msg.data
|
||||
break
|
||||
}
|
||||
case "Commands": {
|
||||
console.log("Main: ludus commands => ", msg.data)
|
||||
for (const command of msg.data) {
|
||||
// attempt to solve out-of-order command bug
|
||||
ludus_commands[command[1]] = command
|
||||
}
|
||||
break
|
||||
}
|
||||
case "Fetch": {
|
||||
console.log("Main: ludus requests => ", msg.data)
|
||||
const res = await fetch(msg.data, {mode: "cors"})
|
||||
const text = await res.text()
|
||||
console.log("Main: js responds => ", text)
|
||||
outbox.push({verb: "Fetch", data: [msg.data, res.status, text]})
|
||||
}
|
||||
case "Ready": {
|
||||
console.log("Main: ludus is ready")
|
||||
ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function io_poller () {
|
||||
if (io_interval_id && !running) {
|
||||
// flush the outbox one last time
|
||||
// (presumably, with the kill message)
|
||||
worker.postMessage(outbox)
|
||||
// cancel the poller
|
||||
clearInterval(io_interval_id)
|
||||
outbox = []
|
||||
}
|
||||
if (ready && running) {
|
||||
worker.postMessage(outbox)
|
||||
outbox = []
|
||||
}
|
||||
}
|
||||
|
||||
function start_io_polling () {
|
||||
io_interval_id = setInterval(io_poller, 10)
|
||||
}
|
||||
|
||||
// runs a ludus script; does not return the result
|
||||
// the result must be explicitly polled with `result`
|
||||
export function run (source) {
|
||||
if (running || ready) {
|
||||
return "TODO: handle this? should not be running"
|
||||
}
|
||||
running = true
|
||||
ready = false
|
||||
result = null
|
||||
code = source
|
||||
worker.postMessage([{verb: "Run", data: source}])
|
||||
outbox = []
|
||||
start_io_polling()
|
||||
}
|
||||
|
||||
// tells if the ludus script is still running
|
||||
export function is_running() {
|
||||
return running && ready
|
||||
}
|
||||
|
||||
// kills a ludus script
|
||||
export function kill () {
|
||||
running = false
|
||||
outbox.push({verb: "Kill"})
|
||||
console.log("Main: Killed Ludus")
|
||||
}
|
||||
|
||||
// sends text into ludus (status: not working)
|
||||
export function input (text) {
|
||||
console.log("Main: calling `input` with ", text)
|
||||
outbox.push({verb: "Input", data: text})
|
||||
}
|
||||
|
||||
// returns the contents of the ludus console and resets the console
|
||||
export function flush_stdout () {
|
||||
let out = ludus_console
|
||||
ludus_console = ""
|
||||
out
|
||||
}
|
||||
|
||||
// returns the contents of the ludus console, retaining them
|
||||
export function stdout () {
|
||||
return ludus_console
|
||||
}
|
||||
|
||||
// returns the array of turtle commands
|
||||
export function commands () {
|
||||
return ludus_commands
|
||||
}
|
||||
|
||||
// returns the array of turtle commands and clears it
|
||||
export function flush_commands () {
|
||||
let out = ludus_commands
|
||||
ludus_commands = []
|
||||
out
|
||||
}
|
||||
|
||||
// returns the ludus result
|
||||
// this is effectively Option<String>:
|
||||
// null if no result has been returned, or
|
||||
// a string representation of the result
|
||||
export function result () {
|
||||
return ludus_result
|
||||
}
|
||||
|
||||
//////////// turtle plumbing below
|
||||
// TODO: refactor this out into modules
|
||||
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, command) {
|
||||
const [_target, _id, curr_command] = command
|
||||
const [verb] = curr_command
|
||||
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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
export function extract_ludus (svg) {
|
||||
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
||||
return code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, `"`)
|
||||
.replace(/'/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] = 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
|
||||
}
|
||||
|
||||
|
41
pkg/rudus.d.ts
vendored
41
pkg/rudus.d.ts
vendored
|
@ -1,41 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function ludus(src: string): Promise<string>;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly ludus: (a: number, b: number) => any;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export_6: WebAssembly.Table;
|
||||
readonly closure328_externref_shim: (a: number, b: number, c: any) => void;
|
||||
readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => 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>;
|
441
pkg/rudus.js
441
pkg/rudus.js
|
@ -1,441 +0,0 @@
|
|||
import { io } from "./worker.js"
|
||||
|
||||
let wasm;
|
||||
|
||||
function addToExternrefTable0(obj) {
|
||||
const idx = wasm.__externref_table_alloc();
|
||||
wasm.__wbindgen_export_2.set(idx, obj);
|
||||
return idx;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
const idx = addToExternrefTable0(e);
|
||||
wasm.__wbindgen_exn_store(idx);
|
||||
}
|
||||
}
|
||||
|
||||
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(); };
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => {
|
||||
wasm.__wbindgen_export_6.get(state.dtor)(state.a, state.b)
|
||||
});
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_6.get(state.dtor)(a, state.b);
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
/**
|
||||
* @param {string} src
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function ludus(src) {
|
||||
const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.ludus(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function __wbg_adapter_20(arg0, arg1, arg2) {
|
||||
wasm.closure328_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_50(arg0, arg1, arg2, arg3) {
|
||||
wasm.closure341_externref_shim(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
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.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.call(arg1, arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
const ret = io(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) {
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) {
|
||||
try {
|
||||
var state0 = {a: arg0, b: arg1};
|
||||
var cb0 = (arg0, arg1) => {
|
||||
const a = state0.a;
|
||||
state0.a = 0;
|
||||
try {
|
||||
return __wbg_adapter_50(a, state0.b, arg0, arg1);
|
||||
} finally {
|
||||
state0.a = a;
|
||||
}
|
||||
};
|
||||
const ret = new Promise(cb0);
|
||||
return ret;
|
||||
} finally {
|
||||
state0.a = state0.b = 0;
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_new_8a6f238a6ece86ea = function() {
|
||||
const ret = new Error();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_now_8dddb61fa4928554 = function() {
|
||||
const ret = Date.now();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) {
|
||||
queueMicrotask(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) {
|
||||
const ret = arg0.queueMicrotask;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_random_57c118f142535bb6 = function() {
|
||||
const ret = Math.random();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) {
|
||||
const ret = Promise.resolve(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
|
||||
const ret = arg1.stack;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() {
|
||||
const ret = typeof global === 'undefined' ? null : global;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() {
|
||||
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() {
|
||||
const ret = typeof self === 'undefined' ? null : self;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() {
|
||||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) {
|
||||
const ret = arg0.then(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.then(arg1, arg2);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = arg0.original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper984 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
const table = wasm.__wbindgen_export_2;
|
||||
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);
|
||||
;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof(arg0) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = arg1;
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function __wbg_init_memory(imports, memory) {
|
||||
|
||||
}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
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;
|
Binary file not shown.
14
pkg/rudus_bg.wasm.d.ts
vendored
14
pkg/rudus_bg.wasm.d.ts
vendored
|
@ -1,14 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const ludus: (a: number, b: number) => any;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_export_6: WebAssembly.Table;
|
||||
export const closure328_externref_shim: (a: number, b: number, c: any) => void;
|
||||
export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
export const __wbindgen_start: () => void;
|
|
@ -1,55 +0,0 @@
|
|||
import init, {ludus} from "./rudus.js";
|
||||
|
||||
let initialized_wasm = false
|
||||
onmessage = run
|
||||
|
||||
// exposed in rust as:
|
||||
// async fn io (out: String) -> Result<JsValue, JsValue>
|
||||
// rust calls this to perform io
|
||||
export function io (out) {
|
||||
// only send messages if we have some
|
||||
if (out.length > 0) postMessage(out)
|
||||
// make an event handler that captures and delivers messages from the main thread
|
||||
// because our promise resolution isn't about calculating a value but setting a global variable, we can't asyncify it
|
||||
// explicitly return a promise
|
||||
return new Promise((resolve, reject) => {
|
||||
// deliver the response to ludus when we get a response from the main thread
|
||||
onmessage = (e) => {
|
||||
resolve(JSON.stringify(e.data))
|
||||
}
|
||||
// cancel the response if it takes too long
|
||||
setTimeout(() => reject("io took too long"), 500)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// set as default event handler from main thread
|
||||
async function run(e) {
|
||||
// we must NEVER run `await init()` twice
|
||||
if (!initialized_wasm) {
|
||||
// this must come before the init call
|
||||
initialized_wasm = true
|
||||
await init()
|
||||
console.log("Worker: Ludus has been initialized.")
|
||||
}
|
||||
// the data is always an array; we only really expect one member tho
|
||||
let msgs = e.data
|
||||
for (const msg of msgs) {
|
||||
// evaluate source if we get some
|
||||
if (msg.verb === "Run" && typeof msg.data === 'string') {
|
||||
// temporarily stash an empty function so we don't keep calling this one if we receive additional messages
|
||||
onmessage = () => {}
|
||||
// actually run the ludus--which will call `io`--and replace `run` as the event handler for ipc
|
||||
await ludus(msg.data)
|
||||
// once we've returned from `ludus`, make this the event handler again
|
||||
onmessage = run
|
||||
} else {
|
||||
// report and swallow any malformed startup messages
|
||||
console.log("Worker: Did not get valid startup message. Instead got:")
|
||||
console.log(e.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
34
sandbox.ld
34
sandbox.ld
|
@ -1,34 +0,0 @@
|
|||
fn inputter () -> {
|
||||
if do input > unbox > empty?
|
||||
then {
|
||||
yield! ()
|
||||
inputter ()
|
||||
}
|
||||
else receive {
|
||||
(:get, pid) -> send (pid, (:reply, unbox (input)))
|
||||
(:flush, pid) -> {
|
||||
send (pid, (:reply, unbox (input)))
|
||||
store! (input, "")
|
||||
}
|
||||
(:clear) -> store! (input, "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn clear_input () -> store! (input, "")
|
||||
|
||||
fn read_input () -> {
|
||||
let reader = spawn! (inputter)
|
||||
send (reader, (:get, self ()))
|
||||
receive {
|
||||
(:reply, msg) -> msg
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_input () -> {
|
||||
let reader = spawn! (inputter)
|
||||
send (reader, (:flush, self ()))
|
||||
receive {
|
||||
(:reply, msg) -> msg
|
||||
}
|
||||
}
|
444
sandbox_run.txt
444
sandbox_run.txt
|
@ -1,444 +0,0 @@
|
|||
entering world loop; active process is axolotl_0
|
||||
closing over in type at 1: #{:sin fn sin/base, ...
|
||||
closing over in eq? at 1: #{:sin fn sin/base, ...
|
||||
closing over in eq? at 2: fn eq?
|
||||
closing over in first at 1: #{:sin fn sin/base, ...
|
||||
closing over in rest at 1: #{:sin fn sin/base, ...
|
||||
closing over in inc at 1: #{:sin fn sin/base, ...
|
||||
closing over in dec at 1: #{:sin fn sin/base, ...
|
||||
closing over in count at 1: #{:sin fn sin/base, ...
|
||||
closing over in any? at 1: fn empty?
|
||||
closing over in any? at 2: fn not
|
||||
closing over in list at 1: #{:sin fn sin/base, ...
|
||||
closing over in append at 1: #{:sin fn sin/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: #{:sin fn sin/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 unbox at 1: #{:sin fn sin/base, ...
|
||||
closing over in store! at 1: #{:sin fn sin/base, ...
|
||||
closing over in update! at 1: fn unbox
|
||||
closing over in update! at 2: fn store!
|
||||
closing over in show at 1: #{:sin fn sin/base, ...
|
||||
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: #{:sin fn sin/base, ...
|
||||
closing over in trim at 1: #{:sin fn sin/base, ...
|
||||
closing over in upcase at 1: #{:sin fn sin/base, ...
|
||||
closing over in downcase at 1: #{:sin fn sin/base, ...
|
||||
closing over in chars at 1: #{:sin fn sin/base, ...
|
||||
closing over in chars/safe at 1: #{:sin fn sin/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: #{:sin fn sin/base, ...
|
||||
closing over in print! at 1: fn string
|
||||
closing over in print! at 2: fn map
|
||||
closing over in print! at 3: fn join
|
||||
closing over in print! at 4: #{:sin fn sin/base, ...
|
||||
closing over in print! at 5: box { [] }
|
||||
closing over in print! at 6: fn append
|
||||
closing over in print! at 7: fn update!
|
||||
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: #{:sin fn sin/base, ...
|
||||
closing over in doc! at 2: fn print!
|
||||
closing over in add at 1: #{:sin fn sin/base, ...
|
||||
closing over in add at 2: fn add
|
||||
closing over in add at 3: fn fold
|
||||
closing over in sub at 1: #{:sin fn sin/base, ...
|
||||
closing over in sub at 2: fn sub
|
||||
closing over in sub at 3: fn fold
|
||||
closing over in mult at 1: #{:sin fn sin/base, ...
|
||||
closing over in mult at 2: fn mult
|
||||
closing over in mult at 3: fn fold
|
||||
closing over in div at 1: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/base, ...
|
||||
closing over in gte? at 1: #{:sin fn sin/base, ...
|
||||
closing over in lt? at 1: #{:sin fn sin/base, ...
|
||||
closing over in lte? at 1: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/base, ...
|
||||
closing over in mod/0 at 1: #{:sin fn sin/base, ...
|
||||
closing over in mod/safe at 1: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/base, ...
|
||||
closing over in ceil at 1: #{:sin fn sin/base, ...
|
||||
closing over in round at 1: #{:sin fn sin/base, ...
|
||||
closing over in range at 1: #{:sin fn sin/base, ...
|
||||
closing over in at at 1: #{:sin fn sin/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: #{:sin fn sin/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: #{:sin fn sin/base, ...
|
||||
closing over in dissoc at 1: #{:sin fn sin/base, ...
|
||||
closing over in get at 1: fn get
|
||||
closing over in get at 2: #{:sin fn sin/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: #{:sin fn sin/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 { #{:penwidth 1,...
|
||||
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 { #{:penwidth 1,...
|
||||
closing over in position at 2: fn unbox
|
||||
closing over in heading at 1: box { #{:penwidth 1,...
|
||||
closing over in heading at 2: fn unbox
|
||||
closing over in pendown? at 1: box { #{:penwidth 1,...
|
||||
closing over in pendown? at 2: fn unbox
|
||||
closing over in pencolor at 1: box { #{:penwidth 1,...
|
||||
closing over in pencolor at 2: fn unbox
|
||||
closing over in penwidth at 1: box { #{:penwidth 1,...
|
||||
closing over in penwidth at 2: fn unbox
|
||||
closing over in self at 1: #{:sin fn sin/base, ...
|
||||
closing over in send at 1: #{:sin fn sin/base, ...
|
||||
closing over in spawn! at 1: #{:sin fn sin/base, ...
|
||||
closing over in yield! at 1: #{:sin fn sin/base, ...
|
||||
closing over in alive? at 1: #{:sin fn sin/base, ...
|
||||
closing over in link! at 1: fn link!
|
||||
closing over in link! at 2: #{:sin fn sin/base, ...
|
||||
closing over in msgs at 1: #{:sin fn sin/base, ...
|
||||
closing over in flush! at 1: #{:sin fn sin/base, ...
|
||||
closing over in flush_i! at 1: #{:sin fn sin/base, ...
|
||||
closing over in sleep! at 1: #{:sin fn sin/base, ...
|
||||
yielded from axolotl_0
|
||||
***match clause: : (:set, x)
|
||||
binding `x` in sandbox
|
||||
stack depth: 3; match depth: 0
|
||||
at stack index: 2
|
||||
new locals: x@2//1
|
||||
resolving binding `x` in sandbox
|
||||
locals: x@2//1
|
||||
at locals position 2
|
||||
leaving scope 1
|
||||
releasing binding x@2//1
|
||||
leaving scope 0
|
||||
***leaving block before pop stack depth: 1
|
||||
popping back from 1 to 0
|
||||
=== source code ===
|
||||
& fn receive (receiver) -> {
|
||||
& fn looper {
|
||||
& ([], _) -> yield! ()
|
||||
& (xs, i) -> {
|
||||
& print!("looping through messages:", xs)
|
||||
& match receiver (first (xs), i) with {
|
||||
& :does_not_understand -> looper (rest (xs), inc (i))
|
||||
& x -> x
|
||||
& }}
|
||||
& }
|
||||
& print! ("receiving in", self (), "with messages", msgs())
|
||||
& looper (msgs (), 0)
|
||||
& }
|
||||
|
||||
& fn agent (x) -> receive (fn (msg, i) -> {
|
||||
& print!("received msg in agent: ", msg)
|
||||
& match msg with {
|
||||
& (:get, pid) -> {
|
||||
& flush_i! (i)
|
||||
& print!("getted from {pid}")
|
||||
& send (pid, (:response, x))
|
||||
& agent (x)
|
||||
& }
|
||||
& (:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)}
|
||||
& (:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))}
|
||||
& y -> {print!("no agent reception match!!!! {y}");:does_not_understand}
|
||||
& }
|
||||
& })
|
||||
|
||||
& fn agent/get (pid) -> {
|
||||
& send (pid, (:get, self ()))
|
||||
& yield! ()
|
||||
& receive (fn (msg, i) -> match msg with {
|
||||
& (:response, x) -> {flush_i! (i); x}
|
||||
& })
|
||||
& }
|
||||
|
||||
& fn agent/set (pid, val) -> send (pid, (:set, val))
|
||||
|
||||
& fn agent/update (pid, f) -> send (pid, (:update, f))
|
||||
|
||||
& let counter = spawn! (fn () -> agent (0))
|
||||
& agent/set (counter, 12)
|
||||
|
||||
match (:set, 12) with {
|
||||
(:set, x) -> x
|
||||
}
|
||||
|
||||
=== chunk: sandbox ===
|
||||
IDX | CODE | INFO
|
||||
0000: constant 00000: :set
|
||||
0003: constant 00001: 12
|
||||
0006: push_tuple 002
|
||||
0008: ***match clause: : (:set, x)
|
||||
0010: match_tuple 002
|
||||
0012: jump_if_no_match 00028
|
||||
0015: load_tuple
|
||||
0016: match_depth 001
|
||||
0018: match_constant 00000: :set
|
||||
0021: jump_if_no_match 00017
|
||||
0024: match_depth 000
|
||||
0026: match
|
||||
0027: binding `x` in sandbox
|
||||
0029: stack depth: 3; match depth: 0
|
||||
0031: at stack index: 2
|
||||
0033: new locals: x@2//1
|
||||
0035: jump_if_no_match 00003
|
||||
0038: jump 00002
|
||||
0041: pop_n 002
|
||||
0043: jump_if_no_match 00016
|
||||
0046: resolving binding `x` in sandbox
|
||||
locals: x@2//1
|
||||
0048: at locals position 2
|
||||
0050: push_binding 002
|
||||
0052: store
|
||||
0053: leaving scope 1
|
||||
0055: releasing binding x@2//1
|
||||
0057: pop_n 002
|
||||
0059: jump 00001
|
||||
0062: panic_no_match
|
||||
0063: load
|
||||
0064: store
|
||||
0065: leaving scope 0
|
||||
0067: ***leaving block before pop stack depth: 1
|
||||
0069: popping back from 1 to 0
|
||||
0071: pop
|
||||
0072: load
|
||||
|
||||
|
||||
|
||||
=== vm run ===
|
||||
entering world loop; active process is cormorant_0
|
||||
0000: [] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0000: constant 00000: :set
|
||||
0003: [->:set<-] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0003: constant 00001: 12
|
||||
0006: [->:set<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0006: push_tuple 002
|
||||
0008: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0008: ***match clause: : (:set, x)
|
||||
0010: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0010: match_tuple 002
|
||||
0012: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0012: jump_if_no_match 00028
|
||||
0015: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0015: load_tuple
|
||||
0016: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0016: match_depth 001
|
||||
0018: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0018: match_constant 00000: :set
|
||||
0021: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0021: jump_if_no_match 00017
|
||||
0024: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0024: match_depth 000
|
||||
0026: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0026: match
|
||||
0027: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0027: binding `x` in sandbox
|
||||
0029: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0029: stack depth: 3; match depth: 0
|
||||
0031: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0031: at stack index: 2
|
||||
0033: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0033: new locals: x@2//1
|
||||
0035: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0035: jump_if_no_match 00003
|
||||
0038: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0038: jump 00002
|
||||
0043: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0043: jump_if_no_match 00016
|
||||
0046: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0046: resolving binding `x` in sandbox
|
||||
locals: x@2//1
|
||||
0048: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0048: at locals position 2
|
||||
0050: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0050: push_binding 002
|
||||
0052: [->(:set, 12)<-|:set|12|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0052: store
|
||||
0053: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0053: leaving scope 1
|
||||
0055: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0055: releasing binding x@2//1
|
||||
0057: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0057: pop_n 002
|
||||
0059: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0059: jump 00001
|
||||
0063: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0063: load
|
||||
0064: [->(:set, 12)<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0064: store
|
||||
0065: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0065: leaving scope 0
|
||||
0067: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0067: ***leaving block before pop stack depth: 1
|
||||
0069: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0069: popping back from 1 to 0
|
||||
0071: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0071: pop
|
||||
0072: [] (12,_,_,_,_,_,_,_) cormorant_0 {}
|
||||
0072: load
|
||||
yielded from cormorant_0
|
||||
{"result":"12","io":{"stdout":{"proto":["text-stream","0.1.0"],"data":""},"turtle":{"proto":["turtle-graphics","0.1.0"],"data":[]}}}
|
|
@ -1,249 +0,0 @@
|
|||
=== 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
|
||||
|
||||
**********
|
||||
**********
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
=== 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
|
458
src/ast.rs
458
src/ast.rs
|
@ -1,458 +0,0 @@
|
|||
use crate::spans::*;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StringPart {
|
||||
Data(String),
|
||||
Word(String),
|
||||
Inline(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for StringPart {
|
||||
fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let rep = match self {
|
||||
StringPart::Word(s) => format!("{{{s}}}"),
|
||||
StringPart::Data(s) => s.to_string(),
|
||||
StringPart::Inline(s) => s.to_string(),
|
||||
};
|
||||
write!(f, "{}", rep)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Ast {
|
||||
// a special Error node
|
||||
// may come in handy?
|
||||
Error,
|
||||
|
||||
And,
|
||||
Or,
|
||||
|
||||
// expression nodes
|
||||
Placeholder,
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
Keyword(&'static str),
|
||||
Word(&'static str),
|
||||
String(&'static str),
|
||||
Interpolated(Vec<Spanned<StringPart>>),
|
||||
Block(Vec<Spanned<Self>>),
|
||||
If(Box<Spanned<Self>>, Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Tuple(Vec<Spanned<Self>>),
|
||||
Arguments(Vec<Spanned<Self>>),
|
||||
List(Vec<Spanned<Self>>),
|
||||
Dict(Vec<Spanned<Self>>),
|
||||
Let(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
LBox(&'static str, Box<Spanned<Self>>),
|
||||
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
When(Vec<Spanned<Self>>),
|
||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Receive(Vec<Spanned<Self>>),
|
||||
MatchClause(
|
||||
Box<Spanned<Self>>,
|
||||
Box<Option<Spanned<Self>>>,
|
||||
Box<Spanned<Self>>,
|
||||
),
|
||||
Fn(&'static str, Box<Spanned<Ast>>, Option<&'static str>),
|
||||
FnBody(Vec<Spanned<Ast>>),
|
||||
FnDeclaration(&'static str),
|
||||
Panic(Box<Spanned<Self>>),
|
||||
Do(Vec<Spanned<Self>>),
|
||||
Repeat(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Splat(&'static str),
|
||||
Pair(&'static str, Box<Spanned<Self>>),
|
||||
Loop(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Recur(Vec<Spanned<Self>>),
|
||||
|
||||
// pattern nodes
|
||||
NilPattern,
|
||||
BooleanPattern(bool),
|
||||
NumberPattern(f64),
|
||||
StringPattern(&'static str),
|
||||
InterpolatedPattern(Vec<Spanned<StringPart>>),
|
||||
KeywordPattern(&'static str),
|
||||
WordPattern(&'static str),
|
||||
AsPattern(&'static str, &'static str),
|
||||
Splattern(Box<Spanned<Self>>),
|
||||
PlaceholderPattern,
|
||||
TuplePattern(Vec<Spanned<Self>>),
|
||||
ListPattern(Vec<Spanned<Self>>),
|
||||
PairPattern(&'static str, Box<Spanned<Self>>),
|
||||
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) | Receive(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),
|
||||
Interpolated(strs) => {
|
||||
write!(
|
||||
f,
|
||||
"Interpolated: \"{}\"",
|
||||
strs.iter()
|
||||
.map(|(s, _)| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
Boolean(b) => write!(f, "Boolean: {}", b),
|
||||
Number(n) => write!(f, "Number: {}", n),
|
||||
Keyword(k) => write!(f, "Keyword: :{}", k),
|
||||
Word(w) => write!(f, "Word: {}", w),
|
||||
Block(b) => write!(
|
||||
f,
|
||||
"Block: <{}>",
|
||||
b.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
If(cond, then_branch, else_branch) => write!(
|
||||
f,
|
||||
"If: {} Then: {} Else: {}",
|
||||
cond.0, then_branch.0, else_branch.0
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
write!(f, "Let: {} = {}", pattern.0, expression.0)
|
||||
}
|
||||
Dict(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|pair| pair.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
List(l) => write!(
|
||||
f,
|
||||
"List: [{}]",
|
||||
l.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Arguments(a) => write!(
|
||||
f,
|
||||
"Arguments: ({})",
|
||||
a.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Tuple(t) => write!(
|
||||
f,
|
||||
"Tuple: ({})",
|
||||
t.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Synthetic(root, first, rest) => write!(
|
||||
f,
|
||||
"Synth: [{}, {}, {}]",
|
||||
root.0,
|
||||
first.0,
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
When(clauses) | Receive(clauses) => write!(
|
||||
f,
|
||||
"When: [{}]",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Placeholder => write!(f, "Placeholder"),
|
||||
LBox(_name, _rhs) => todo!(),
|
||||
Match(value, clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"match: {} with {}",
|
||||
&value.0.to_string(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
FnBody(clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Fn(name, body, ..) => {
|
||||
write!(f, "fn: {name}\n{}", body.0)
|
||||
}
|
||||
FnDeclaration(_name) => todo!(),
|
||||
Panic(_expr) => todo!(),
|
||||
Do(terms) => {
|
||||
write!(
|
||||
f,
|
||||
"do: {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Repeat(_times, _body) => todo!(),
|
||||
Splat(word) => {
|
||||
write!(f, "splat: {}", word)
|
||||
}
|
||||
Pair(k, v) => {
|
||||
write!(f, "pair: {} {}", k, v.0)
|
||||
}
|
||||
Loop(init, body) => {
|
||||
write!(
|
||||
f,
|
||||
"loop: {} with {}",
|
||||
init.0,
|
||||
body.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Recur(args) => {
|
||||
write!(
|
||||
f,
|
||||
"recur: {}",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
MatchClause(pattern, guard, body) => {
|
||||
write!(
|
||||
f,
|
||||
"match clause: {} if {:?} -> {}",
|
||||
pattern.0, guard, body.0
|
||||
)
|
||||
}
|
||||
WhenClause(cond, body) => {
|
||||
write!(f, "when clause: {} -> {}", cond.0, body.0)
|
||||
}
|
||||
|
||||
NilPattern => write!(f, "nil"),
|
||||
BooleanPattern(b) => write!(f, "{}", b),
|
||||
NumberPattern(n) => write!(f, "{}", n),
|
||||
StringPattern(s) => write!(f, "{}", s),
|
||||
KeywordPattern(k) => write!(f, ":{}", k),
|
||||
WordPattern(w) => write!(f, "{}", w),
|
||||
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||
Splattern(p) => write!(f, "...{}", p.0),
|
||||
PlaceholderPattern => write!(f, "_"),
|
||||
TuplePattern(t) => write!(
|
||||
f,
|
||||
"({})",
|
||||
t.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
ListPattern(l) => write!(
|
||||
f,
|
||||
"({})",
|
||||
l.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
DictPattern(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
PairPattern(key, value) => write!(f, ":{} {}", key, value.0),
|
||||
InterpolatedPattern(strprts) => write!(
|
||||
f,
|
||||
"interpolated: \"{}\"",
|
||||
strprts
|
||||
.iter()
|
||||
.map(|part| part.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
482
src/base.rs
482
src/base.rs
|
@ -1,47 +1,43 @@
|
|||
use crate::value::*;
|
||||
use imbl::*;
|
||||
use ran::ran_f64;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
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 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 fn eq(x: &Value, y: &Value) -> Value {
|
||||
if x == y {
|
||||
Value::True
|
||||
} else {
|
||||
Value::False
|
||||
}
|
||||
pub fn eq<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
|
||||
Value::Boolean(x == y)
|
||||
}
|
||||
|
||||
pub fn add(x: &Value, y: &Value) -> Value {
|
||||
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: wrong arguments to base add: {x}, {y}"),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(x: &Value, y: &Value) -> Value {
|
||||
pub fn sub<'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 unbox(x: &Value) -> Value {
|
||||
pub fn unbox<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Box(cell) => cell.as_ref().borrow().clone(),
|
||||
Value::Box(_, cell) => cell.borrow().clone(),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(b: &Value, val: &Value) -> Value {
|
||||
if let Value::Box(cell) = b {
|
||||
pub fn store<'src>(b: &Value<'src>, val: &Value<'src>) -> Value<'src> {
|
||||
if let Value::Box(_, cell) = b {
|
||||
cell.replace(val.clone());
|
||||
val.clone()
|
||||
} else {
|
||||
|
@ -51,88 +47,103 @@ pub fn store(b: &Value, val: &Value) -> Value {
|
|||
|
||||
// TODO: do better than returning just the docstr
|
||||
// name, patterns, AND docstring
|
||||
pub fn doc(f: &Value) -> Value {
|
||||
pub fn doc<'src>(f: &Value<'src>) -> Value<'src> {
|
||||
match f {
|
||||
Value::Fn(f) => f.as_ref().doc(),
|
||||
_ => Value::Interned("no documentation found"),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value {
|
||||
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> {
|
||||
match (dict, key) {
|
||||
(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})"),
|
||||
(Value::Dict(d), Value::Keyword(k)) => Value::Dict(d.update(k, value.clone())),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#bool(x: &Value) -> Value {
|
||||
match x {
|
||||
Value::Nil | Value::False => Value::False,
|
||||
_ => Value::True,
|
||||
}
|
||||
pub fn r#bool<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
Value::Boolean(x.bool())
|
||||
}
|
||||
|
||||
pub fn chars(x: &Value) -> Value {
|
||||
pub fn chars<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Interned(s) => {
|
||||
Value::InternedString(s) => {
|
||||
let chars = s.chars();
|
||||
|
||||
let mut charlist = vector![];
|
||||
for char in chars {
|
||||
if char.is_ascii() {
|
||||
charlist.push_back(Value::String(Rc::new(char.to_string())))
|
||||
charlist.push_back(Value::AllocatedString(Rc::new(char.to_string())))
|
||||
} else {
|
||||
return Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("{char} is not an ascii character"))),
|
||||
Value::AllocatedString(Rc::new(format!(
|
||||
"{char} is not an ascii character"
|
||||
))),
|
||||
]));
|
||||
}
|
||||
}
|
||||
Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("ok"),
|
||||
Value::List(Box::new(charlist)),
|
||||
]))
|
||||
Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)]))
|
||||
}
|
||||
Value::String(s) => {
|
||||
Value::AllocatedString(s) => {
|
||||
let chars = s.chars();
|
||||
|
||||
let mut charlist = vector![];
|
||||
for char in chars {
|
||||
if char.is_ascii() {
|
||||
charlist.push_back(Value::String(Rc::new(char.to_string())))
|
||||
charlist.push_back(Value::AllocatedString(Rc::new(char.to_string())))
|
||||
} else {
|
||||
return Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("err"),
|
||||
Value::String(Rc::new(format!("{char} is not an ascii character"))),
|
||||
Value::AllocatedString(Rc::new(format!(
|
||||
"{char} is not an ascii character"
|
||||
))),
|
||||
]));
|
||||
}
|
||||
}
|
||||
Value::Tuple(Rc::new(vec![
|
||||
Value::Keyword("ok"),
|
||||
Value::List(Box::new(charlist)),
|
||||
]))
|
||||
Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)]))
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how to get to opportunistic mutation here
|
||||
pub fn concat(x: &Value, y: &Value) -> Value {
|
||||
pub fn concat<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
|
||||
match (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::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::List(x), Value::List(y)) => {
|
||||
let mut newlist = *x.clone();
|
||||
newlist.append(*y.clone());
|
||||
Value::List(Box::new(newlist))
|
||||
let mut newlist = x.clone();
|
||||
newlist.append(y.clone());
|
||||
Value::List(newlist)
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(x: &Value, y: &Value) -> Value {
|
||||
pub fn append<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::List(list) => {
|
||||
let mut newlist = list.clone();
|
||||
|
@ -143,35 +154,35 @@ pub fn append(x: &Value, y: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn dec(x: &Value) -> Value {
|
||||
pub fn dec<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(n) => Value::Number(n - 1.0),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc(x: &Value) -> Value {
|
||||
pub fn inc<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(n) => Value::Number(n + 1.0),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(x: &Value, y: &Value) -> Value {
|
||||
pub fn div<'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 mult(x: &Value, y: &Value) -> Value {
|
||||
pub fn mult<'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 dissoc(dict: &Value, key: &Value) -> Value {
|
||||
pub fn dissoc<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
|
||||
match (dict, key) {
|
||||
(Value::Dict(dict), Value::Keyword(key)) => {
|
||||
let mut new = dict.clone();
|
||||
|
@ -182,7 +193,7 @@ pub fn dissoc(dict: &Value, key: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn first(ordered: &Value) -> Value {
|
||||
pub fn first<'src>(ordered: &Value<'src>) -> Value<'src> {
|
||||
match ordered {
|
||||
Value::List(list) => match list.front() {
|
||||
Some(n) => n.clone(),
|
||||
|
@ -198,7 +209,7 @@ pub fn first(ordered: &Value) -> Value {
|
|||
|
||||
// TODO: figure out how to handle negative numbers
|
||||
// the cast from f64 to usize discards sign info
|
||||
pub fn at(ordered: &Value, i: &Value) -> Value {
|
||||
pub fn at<'src>(ordered: &Value<'src>, i: &Value<'src>) -> Value<'src> {
|
||||
match (ordered, i) {
|
||||
(Value::List(list), Value::Number(n)) => {
|
||||
let i = *n as usize;
|
||||
|
@ -218,7 +229,7 @@ pub fn at(ordered: &Value, i: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get(dict: &Value, key: &Value) -> Value {
|
||||
pub fn get<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> {
|
||||
match (dict, key) {
|
||||
(Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) {
|
||||
Some(x) => x.clone(),
|
||||
|
@ -228,7 +239,7 @@ pub fn get(dict: &Value, key: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn last(ordered: &Value) -> Value {
|
||||
pub fn last<'src>(ordered: &Value<'src>) -> Value<'src> {
|
||||
match ordered {
|
||||
Value::List(list) => match list.last() {
|
||||
Some(x) => x.clone(),
|
||||
|
@ -242,13 +253,12 @@ pub fn last(ordered: &Value) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(msg: String);
|
||||
pub fn or<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> {
|
||||
Value::Boolean(x.bool() || y.bool())
|
||||
}
|
||||
|
||||
pub fn print(x: &Value) -> Value {
|
||||
// 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> {
|
||||
let Value::List(args) = x else {
|
||||
unreachable!("internal Ludus error")
|
||||
};
|
||||
|
@ -257,41 +267,34 @@ pub fn print(x: &Value) -> Value {
|
|||
.map(|val| format!("{val}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
// println!("{out}");
|
||||
log(out);
|
||||
println!("{out}");
|
||||
Value::Keyword("ok")
|
||||
}
|
||||
|
||||
pub fn show(x: &Value) -> Value {
|
||||
Value::String(Rc::new(format!("{x}")))
|
||||
pub fn show<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
Value::AllocatedString(Rc::new(format!("{x}")))
|
||||
}
|
||||
|
||||
pub fn rest(ordered: &Value) -> Value {
|
||||
pub fn rest<'src>(ordered: &Value<'src>) -> Value<'src> {
|
||||
match ordered {
|
||||
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(),
|
||||
)),
|
||||
Value::List(list) => Value::List(list.clone().split_at(1).1),
|
||||
Value::Tuple(tuple) => Value::List(Vector::from_iter(tuple.iter().next().cloned())),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(coll: &Value) -> Value {
|
||||
pub fn count<'src>(coll: &Value<'src>) -> Value<'src> {
|
||||
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::String(s) => Value::Number(s.len() as f64),
|
||||
Value::Interned(s) => Value::Number(s.len() as f64),
|
||||
Value::AllocatedString(s) => Value::Number(s.len() as f64),
|
||||
Value::InternedString(s) => Value::Number(s.len() as f64),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(start: &Value, end: &Value) -> Value {
|
||||
pub fn range<'src>(start: &Value<'src>, end: &Value<'src>) -> Value<'src> {
|
||||
match (start, end) {
|
||||
(Value::Number(start), Value::Number(end)) => {
|
||||
let start = *start as isize;
|
||||
|
@ -300,25 +303,25 @@ pub fn range(start: &Value, end: &Value) -> Value {
|
|||
for n in start..end {
|
||||
range.push_back(Value::Number(n as f64))
|
||||
}
|
||||
Value::List(Box::new(range))
|
||||
Value::List(range)
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
|
||||
pub fn slice<'src>(ordered: &Value<'src>, start: &Value<'src>, end: &Value<'src>) -> Value<'src> {
|
||||
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(Box::new(newlist.slice(start..end)))
|
||||
Value::List(newlist.slice(start..end))
|
||||
}
|
||||
// TODO: figure out something better to do than return an empty string on a bad slice
|
||||
(Value::String(string), Value::Number(start), Value::Number(end)) => {
|
||||
(Value::AllocatedString(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::String(Rc::new(
|
||||
Value::AllocatedString(Rc::new(
|
||||
string
|
||||
.clone()
|
||||
.as_str()
|
||||
|
@ -327,19 +330,19 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
|
|||
.to_string(),
|
||||
))
|
||||
}
|
||||
(Value::Interned(string), Value::Number(start), Value::Number(end)) => {
|
||||
(Value::InternedString(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::String(Rc::new(string.get(start..end).unwrap_or("").to_string()))
|
||||
Value::AllocatedString(Rc::new(string.get(start..end).unwrap_or("").to_string()))
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list(x: &Value) -> Value {
|
||||
pub fn list<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::List(_) => x.clone(),
|
||||
Value::Tuple(t) => Value::List(Box::new(Vector::from_iter(t.iter().cloned()))),
|
||||
Value::Tuple(t) => Value::List(Vector::from_iter(t.iter().cloned())),
|
||||
Value::Dict(d) => {
|
||||
let kvs = d.iter();
|
||||
let mut list = vector![];
|
||||
|
@ -347,303 +350,292 @@ pub fn list(x: &Value) -> Value {
|
|||
let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()]));
|
||||
list.push_back(kv);
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
Value::List(list)
|
||||
}
|
||||
_ => Value::List(Box::new(vector![x.clone()])),
|
||||
_ => Value::List(vector![x.clone()]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number(x: &Value) -> Value {
|
||||
pub fn number<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Interned(string) => match string.parse::<f64>() {
|
||||
Value::InternedString(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::String(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
])),
|
||||
},
|
||||
Value::String(string) => match string.parse::<f64>() {
|
||||
Value::AllocatedString(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::String(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))),
|
||||
])),
|
||||
},
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(x: &Value) -> Value {
|
||||
pub fn r#type<'src>(x: &Value<'src>) -> Value<'src> {
|
||||
match x {
|
||||
Value::Nil => Value::Keyword("nil"),
|
||||
Value::Number(_) => Value::Keyword("number"),
|
||||
Value::True | Value::False => Value::Keyword("bool"),
|
||||
Value::Boolean(_) => Value::Keyword("boolean"),
|
||||
Value::Keyword(_) => Value::Keyword("keyword"),
|
||||
Value::Tuple(_) => Value::Keyword("tuple"),
|
||||
Value::Interned(_) => Value::Keyword("string"),
|
||||
Value::String(_) => Value::Keyword("string"),
|
||||
Value::InternedString(_) => Value::Keyword("string"),
|
||||
Value::AllocatedString(_) => Value::Keyword("string"),
|
||||
Value::List(_) => Value::Keyword("list"),
|
||||
Value::Dict(_) => Value::Keyword("dict"),
|
||||
Value::Fn(_) => Value::Keyword("fn"),
|
||||
Value::Box(_) => Value::Keyword("box"),
|
||||
Value::BaseFn(_) => Value::Keyword("fn"),
|
||||
Value::Partial(_) => Value::Keyword("fn"),
|
||||
Value::Process => Value::Keyword("process"),
|
||||
Value::Nothing => unreachable!(),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(source: &Value, splitter: &Value) -> Value {
|
||||
pub fn split<'src>(source: &Value<'src>, splitter: &Value) -> Value<'src> {
|
||||
match (source, splitter) {
|
||||
(Value::String(source), Value::String(splitter)) => {
|
||||
println!("splitting {source} with {splitter}");
|
||||
(Value::AllocatedString(source), Value::AllocatedString(splitter)) => {
|
||||
let parts = source.split_terminator(splitter.as_str());
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
||||
list.push_back(Value::AllocatedString(Rc::new(part.to_string())));
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
Value::List(list)
|
||||
}
|
||||
(Value::String(_), Value::Interned(splitter)) => {
|
||||
split(source, &Value::String(Rc::new(splitter.to_string())))
|
||||
(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::Interned(source), Value::String(_)) => {
|
||||
split(&Value::String(Rc::new(source.to_string())), splitter)
|
||||
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)
|
||||
(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::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)
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upcase(string: &Value) -> Value {
|
||||
pub fn upcase<'src>(string: &Value<'src>) -> Value<'src> {
|
||||
match string {
|
||||
Value::String(string) => Value::String(Rc::new(string.to_uppercase())),
|
||||
Value::Interned(string) => Value::String(Rc::new(string.to_uppercase())),
|
||||
Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())),
|
||||
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downcase(string: &Value) -> Value {
|
||||
pub fn downcase<'src>(string: &Value<'src>) -> Value<'src> {
|
||||
match string {
|
||||
Value::String(string) => Value::String(Rc::new(string.to_lowercase())),
|
||||
Value::Interned(string) => Value::String(Rc::new(string.to_lowercase())),
|
||||
Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())),
|
||||
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(string: &Value) -> Value {
|
||||
pub fn trim<'src>(string: &Value<'src>) -> Value<'src> {
|
||||
match string {
|
||||
Value::String(string) => Value::String(Rc::new(string.trim().to_string())),
|
||||
Value::Interned(string) => Value::String(Rc::new(string.trim().to_string())),
|
||||
Value::AllocatedString(string) => {
|
||||
Value::AllocatedString(Rc::new(string.trim().to_string()))
|
||||
}
|
||||
Value::InternedString(string) => Value::AllocatedString(Rc::new(string.trim().to_string())),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn triml(string: &Value) -> Value {
|
||||
pub fn triml<'src>(string: &Value<'src>) -> Value<'src> {
|
||||
match 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())),
|
||||
Value::AllocatedString(string) => {
|
||||
Value::AllocatedString(Rc::new(string.trim_start().to_string()))
|
||||
}
|
||||
Value::InternedString(string) => {
|
||||
Value::AllocatedString(Rc::new(string.trim_start().to_string()))
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trimr(string: &Value) -> Value {
|
||||
pub fn trimr<'src>(string: &Value<'src>) -> Value<'src> {
|
||||
match 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())),
|
||||
Value::AllocatedString(string) => {
|
||||
Value::AllocatedString(Rc::new(string.trim_end().to_string()))
|
||||
}
|
||||
Value::InternedString(string) => {
|
||||
Value::AllocatedString(Rc::new(string.trim_end().to_string()))
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atan_2(x: &Value, y: &Value) -> Value {
|
||||
pub fn atan_2<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ceil(x: &Value) -> Value {
|
||||
pub fn ceil<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.ceil()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cos(x: &Value) -> Value {
|
||||
pub fn cos<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.cos()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn floor(x: &Value) -> Value {
|
||||
pub fn floor<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.floor()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = Math)]
|
||||
fn random() -> f64;
|
||||
pub fn random<'src>() -> Value<'src> {
|
||||
Value::Number(ran_f64())
|
||||
}
|
||||
|
||||
pub fn base_random() -> Value {
|
||||
Value::Number(random())
|
||||
}
|
||||
|
||||
pub fn round(x: &Value) -> Value {
|
||||
pub fn round<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.round()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sin(x: &Value) -> Value {
|
||||
pub fn sin<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.sin()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sqrt(x: &Value) -> Value {
|
||||
pub fn sqrt<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.sqrt()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tan(x: &Value) -> Value {
|
||||
pub fn tan<'src>(x: &Value) -> Value<'src> {
|
||||
match x {
|
||||
Value::Number(x) => Value::Number(x.tan()),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gt(x: &Value, y: &Value) -> Value {
|
||||
pub fn gt<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if x > y {
|
||||
Value::True
|
||||
} else {
|
||||
Value::False
|
||||
}
|
||||
}
|
||||
(Value::Number(x), Value::Number(y)) => Value::Boolean(x > y),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gte(x: &Value, y: &Value) -> Value {
|
||||
pub fn gte<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if x >= y {
|
||||
Value::True
|
||||
} else {
|
||||
Value::False
|
||||
}
|
||||
}
|
||||
(Value::Number(x), Value::Number(y)) => Value::Boolean(x >= y),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lt(x: &Value, y: &Value) -> Value {
|
||||
pub fn lt<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if x < y {
|
||||
Value::True
|
||||
} else {
|
||||
Value::False
|
||||
}
|
||||
}
|
||||
(Value::Number(x), Value::Number(y)) => Value::Boolean(x < y),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lte(x: &Value, y: &Value) -> Value {
|
||||
pub fn lte<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if x <= y {
|
||||
Value::True
|
||||
} else {
|
||||
Value::False
|
||||
}
|
||||
}
|
||||
(Value::Number(x), Value::Number(y)) => Value::Boolean(x <= y),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#mod(x: &Value, y: &Value) -> Value {
|
||||
pub fn r#mod<'src>(x: &Value, y: &Value) -> Value<'src> {
|
||||
match (x, y) {
|
||||
(Value::Number(x), Value::Number(y)) => Value::Number(x % y),
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_base() -> Value {
|
||||
pub fn base<'src>() -> Vec<(String, Value<'src>)> {
|
||||
let members = vec![
|
||||
("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))),
|
||||
("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))),
|
||||
("pi", Value::Number(std::f64::consts::PI)),
|
||||
("print!", Value::BaseFn(BaseFn::Unary("print!", print))),
|
||||
("process", Value::Process),
|
||||
(
|
||||
"random",
|
||||
Value::BaseFn(BaseFn::Nullary("random", base_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))),
|
||||
("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))),
|
||||
("sqrt_2", Value::Number(std::f64::consts::SQRT_2)),
|
||||
("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))),
|
||||
("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))),
|
||||
];
|
||||
Value::Dict(Box::new(HashMap::from(members)))
|
||||
let pkg = Value::Dict(HashMap::from(members));
|
||||
vec![("base".to_string(), pkg)]
|
||||
}
|
||||
|
|
92
src/chunk.rs
92
src/chunk.rs
|
@ -1,92 +0,0 @@
|
|||
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 std::fmt::Display for Chunk {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Chunk.")
|
||||
}
|
||||
}
|
||||
|
||||
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 | LoadMessage | NextMessage | MatchMessage | ClearMessage => {
|
||||
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)
|
||||
// }
|
||||
}
|
1486
src/compiler.rs
1486
src/compiler.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,52 +1,55 @@
|
|||
// 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.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_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_invalidation(errs: Vec<VErr>) {
|
||||
for err in errs {
|
||||
// Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
// .with_message(err.msg.to_string())
|
||||
// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan))
|
||||
// .finish()
|
||||
// .print(sources(vec![(err.input, err.src)]))
|
||||
// .unwrap();
|
||||
println!("{}", err.msg);
|
||||
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
.with_message(err.msg.to_string())
|
||||
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan))
|
||||
.finish()
|
||||
.print(sources(vec![(err.input, err.src)]))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
145
src/io.rs
145
src/io.rs
|
@ -1,145 +0,0 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::value::Value;
|
||||
use crate::vm::Panic;
|
||||
use imbl::Vector;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
#[wasm_bindgen(module = "/pkg/worker.js")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch)]
|
||||
async fn io (output: String) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: String);
|
||||
}
|
||||
|
||||
type Lines = Value; // expect a list of values
|
||||
type Commands = Value; // expect a list of values
|
||||
type Url = Value; // expect a string representing a URL
|
||||
type FinalValue = Result<Value, Panic>;
|
||||
|
||||
fn make_json_payload(verb: &'static str, data: String) -> String {
|
||||
format!("{{\"verb\":\"{verb}\",\"data\":{data}}}")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum MsgOut {
|
||||
Console(Lines),
|
||||
Commands(Commands),
|
||||
Fetch(Url),
|
||||
Complete(FinalValue),
|
||||
Ready
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MsgOut {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_json())
|
||||
}
|
||||
}
|
||||
|
||||
impl MsgOut {
|
||||
pub fn to_json(&self) -> String {
|
||||
match self {
|
||||
MsgOut::Complete(value) => match value {
|
||||
Ok(value) => {
|
||||
make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap())
|
||||
},
|
||||
Err(_) => make_json_payload("Complete", "\"null\"".to_string())
|
||||
},
|
||||
MsgOut::Commands(commands) => {
|
||||
let commands = commands.as_list();
|
||||
let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::<Vec<_>>().join(",");
|
||||
let vals_json = format!("[{vals_json}]");
|
||||
make_json_payload("Commands", vals_json)
|
||||
}
|
||||
MsgOut::Fetch(value) => {
|
||||
// TODO: do parsing here?
|
||||
// Right now, defer to fetch
|
||||
let url = value.to_json().unwrap();
|
||||
make_json_payload("Fetch", url)
|
||||
}
|
||||
MsgOut::Console(lines) => {
|
||||
let lines = lines.as_list();
|
||||
let json_lines = lines.iter().map(|line| line.stringify()).collect::<Vec<_>>().join("\\n");
|
||||
let json_lines = format!("\"{json_lines}\"");
|
||||
make_json_payload("Console", json_lines)
|
||||
}
|
||||
MsgOut::Ready => {
|
||||
make_json_payload("Ready", "\"null\"".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "verb", content = "data")]
|
||||
pub enum MsgIn {
|
||||
Input(String),
|
||||
Fetch(String, f64, String),
|
||||
Kill,
|
||||
Keyboard(Vec<String>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MsgIn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
MsgIn::Input(str) => write!(f, "Input: {str}"),
|
||||
MsgIn::Kill => write!(f, "Kill"),
|
||||
MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MsgIn {
|
||||
pub fn to_value(self) -> Value {
|
||||
match self {
|
||||
MsgIn::Input(str) => Value::string(str),
|
||||
MsgIn::Fetch(url, status_f64, string) => {
|
||||
let url = Value::string(url);
|
||||
let status = Value::Number(status_f64);
|
||||
let text = Value::string(string);
|
||||
let result_tuple = if status_f64 == 200.0 {
|
||||
Value::tuple(vec![Value::keyword("ok".to_string()), text])
|
||||
} else {
|
||||
Value::tuple(vec![Value::keyword("err".to_string()), status])
|
||||
};
|
||||
Value::tuple(vec![url, result_tuple])
|
||||
}
|
||||
MsgIn::Kill => Value::Nothing,
|
||||
MsgIn::Keyboard(downkeys) => {
|
||||
let mut vector = Vector::new();
|
||||
for key in downkeys {
|
||||
vector.push_back(Value::String(Rc::new(key)));
|
||||
}
|
||||
Value::List(Box::new(vector))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
|
||||
let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::<Vec<_>>().join(","));
|
||||
let inbox = io (outbox).await;
|
||||
// if our request dies, make sure we return back to the event loop
|
||||
let inbox = match inbox {
|
||||
Ok(msgs) => msgs,
|
||||
Err(_) => return vec![]
|
||||
};
|
||||
let inbox = inbox.as_string().expect("response should be a string");
|
||||
// log(format!("got a message: {inbox}"));
|
||||
let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
|
||||
if !inbox.is_empty() {
|
||||
log("ludus received messages".to_string());
|
||||
for msg in inbox.iter() {
|
||||
log(format!("{}", msg));
|
||||
}
|
||||
}
|
||||
inbox
|
||||
}
|
||||
|
|
@ -56,9 +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" | "or" | "and" | "receive" => {
|
||||
Token::Reserved(word)
|
||||
}
|
||||
| "recur" | "repeat" | "then" | "when" | "with" => Token::Reserved(word),
|
||||
_ => Token::Word(word),
|
||||
});
|
||||
|
||||
|
|
186
src/lib.rs
186
src/lib.rs
|
@ -1,186 +0,0 @@
|
|||
use chumsky::{input::Stream, prelude::*};
|
||||
use imbl::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
||||
const DEBUG_SCRIPT_COMPILE: bool = false;
|
||||
const DEBUG_SCRIPT_RUN: bool = false;
|
||||
const DEBUG_PRELUDE_COMPILE: bool = false;
|
||||
const DEBUG_PRELUDE_RUN: bool = false;
|
||||
|
||||
// #[cfg(target_family = "wasm")]
|
||||
// #[global_allocator]
|
||||
// static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
|
||||
|
||||
mod io;
|
||||
|
||||
mod ast;
|
||||
use crate::ast::Ast;
|
||||
|
||||
mod base;
|
||||
|
||||
mod world;
|
||||
use crate::world::{World, Zoo};
|
||||
|
||||
mod spans;
|
||||
use crate::spans::Spanned;
|
||||
|
||||
mod lexer;
|
||||
use crate::lexer::lexer;
|
||||
|
||||
mod parser;
|
||||
use crate::parser::parser;
|
||||
|
||||
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() {
|
||||
log("ERROR PARSING PRELUDE:");
|
||||
log(format!("{:?}", parse_errors).as_str());
|
||||
panic!("parsing errors in prelude");
|
||||
}
|
||||
|
||||
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() {
|
||||
log("VALIDATION ERRORS IN PRLUDE:");
|
||||
// report_invalidation(validator.errors);
|
||||
log(format!("{:?}", validator.errors).as_str());
|
||||
panic!("validator errors in prelude");
|
||||
}
|
||||
|
||||
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 stub_zoo = Rc::new(RefCell::new(Zoo::new()));
|
||||
let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN);
|
||||
prld_sync.interpret();
|
||||
let prelude = prld_sync.result.unwrap().unwrap();
|
||||
match prelude {
|
||||
Value::Dict(hashmap) => *hashmap,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn ludus(src: String) -> String {
|
||||
console_error_panic_hook::set_once();
|
||||
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);
|
||||
}
|
||||
|
||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
||||
|
||||
let prelude = prelude();
|
||||
// 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,
|
||||
"ludus script",
|
||||
src,
|
||||
0,
|
||||
prelude.clone(),
|
||||
DEBUG_SCRIPT_COMPILE,
|
||||
);
|
||||
|
||||
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 world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN);
|
||||
world.run().await;
|
||||
let result = world.result.clone();
|
||||
|
||||
let output = match result {
|
||||
Some(Ok(val)) => val.show(),
|
||||
Some(Err(panic)) => format!("Ludus panicked! {panic}"),
|
||||
None => "Ludus run terminated by user".to_string()
|
||||
};
|
||||
if DEBUG_SCRIPT_RUN {
|
||||
// vm.print_stack();
|
||||
}
|
||||
|
||||
output
|
||||
}
|
218
src/main.rs
218
src/main.rs
|
@ -1,10 +1,214 @@
|
|||
use rudus::ludus;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
let src = fs::read_to_string("sandbox.ld").unwrap();
|
||||
let json = ludus(src);
|
||||
// println!("{json}");
|
||||
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>())
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
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
212
src/old_main.rs
|
@ -1,212 +0,0 @@
|
|||
// 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
193
src/old_value.rs
|
@ -1,193 +0,0 @@
|
|||
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
533
src/old_vm.rs
|
@ -1,533 +0,0 @@
|
|||
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(×.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))
|
||||
}
|
||||
}
|
||||
}
|
236
src/op.rs
236
src/op.rs
|
@ -1,236 +0,0 @@
|
|||
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,
|
||||
|
||||
LoadMessage,
|
||||
NextMessage,
|
||||
MatchMessage,
|
||||
ClearMessage,
|
||||
// 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",
|
||||
|
||||
LoadMessage => "load_message",
|
||||
NextMessage => "next_message",
|
||||
MatchMessage => "match_message",
|
||||
ClearMessage => "clear_message",
|
||||
};
|
||||
write!(f, "{rep}")
|
||||
}
|
||||
}
|
498
src/parser.rs
498
src/parser.rs
|
@ -1,14 +1,323 @@
|
|||
// TODO: move AST to its own module
|
||||
// TODO: remove StringMatcher cruft
|
||||
// TODO: good error messages?
|
||||
|
||||
use crate::ast::{Ast, StringPart};
|
||||
use crate::lexer::*;
|
||||
use crate::spans::*;
|
||||
use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
|
||||
use std::fmt;
|
||||
use struct_scalpel::Dissectible;
|
||||
|
||||
pub struct StringMatcher();
|
||||
// #[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 {
|
||||
Data(String),
|
||||
Word(String),
|
||||
Inline(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for StringPart {
|
||||
fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let rep = match self {
|
||||
StringPart::Word(s) => format!("{{{s}}}"),
|
||||
StringPart::Data(s) => s.to_string(),
|
||||
StringPart::Inline(s) => s.to_string(),
|
||||
};
|
||||
write!(f, "{}", rep)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Dissectible)]
|
||||
pub enum Ast {
|
||||
// a special Error node
|
||||
// may come in handy?
|
||||
Error,
|
||||
|
||||
// expression nodes
|
||||
Placeholder,
|
||||
Nil,
|
||||
Boolean(bool),
|
||||
Number(f64),
|
||||
Keyword(&'static str),
|
||||
Word(&'static str),
|
||||
String(&'static str),
|
||||
Interpolated(Vec<Spanned<StringPart>>),
|
||||
Block(Vec<Spanned<Self>>),
|
||||
If(Box<Spanned<Self>>, Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Tuple(Vec<Spanned<Self>>),
|
||||
Arguments(Vec<Spanned<Self>>),
|
||||
List(Vec<Spanned<Self>>),
|
||||
Dict(Vec<Spanned<Self>>),
|
||||
Let(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
LBox(&'static str, Box<Spanned<Self>>),
|
||||
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
When(Vec<Spanned<Self>>),
|
||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
MatchClause(
|
||||
Box<Spanned<Self>>,
|
||||
Box<Option<Spanned<Self>>>,
|
||||
Box<Spanned<Self>>,
|
||||
),
|
||||
Fn(&'static str, Vec<Spanned<Self>>, Option<&'static str>),
|
||||
FnDeclaration(&'static str),
|
||||
Panic(Box<Spanned<Self>>),
|
||||
Do(Vec<Spanned<Self>>),
|
||||
Repeat(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||
Splat(&'static str),
|
||||
Pair(&'static str, Box<Spanned<Self>>),
|
||||
Loop(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||
Recur(Vec<Spanned<Self>>),
|
||||
|
||||
// pattern nodes
|
||||
NilPattern,
|
||||
BooleanPattern(bool),
|
||||
NumberPattern(f64),
|
||||
StringPattern(&'static str),
|
||||
InterpolatedPattern(Vec<Spanned<StringPart>>, StringMatcher),
|
||||
KeywordPattern(&'static str),
|
||||
WordPattern(&'static str),
|
||||
AsPattern(&'static str, &'static str),
|
||||
Splattern(Box<Spanned<Self>>),
|
||||
PlaceholderPattern,
|
||||
TuplePattern(Vec<Spanned<Self>>),
|
||||
ListPattern(Vec<Spanned<Self>>),
|
||||
PairPattern(&'static str, Box<Spanned<Self>>),
|
||||
DictPattern(Vec<Spanned<Self>>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Ast::*;
|
||||
match self {
|
||||
Error => write!(f, "Error"),
|
||||
Nil => write!(f, "nil"),
|
||||
String(s) => write!(f, "String: \"{}\"", s),
|
||||
Interpolated(strs) => {
|
||||
write!(
|
||||
f,
|
||||
"Interpolated: \"{}\"",
|
||||
strs.iter()
|
||||
.map(|(s, _)| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
)
|
||||
}
|
||||
Boolean(b) => write!(f, "Boolean: {}", b),
|
||||
Number(n) => write!(f, "Number: {}", n),
|
||||
Keyword(k) => write!(f, "Keyword: :{}", k),
|
||||
Word(w) => write!(f, "Word: {}", w),
|
||||
Block(b) => write!(
|
||||
f,
|
||||
"Block: <{}>",
|
||||
b.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
If(cond, then_branch, else_branch) => write!(
|
||||
f,
|
||||
"If: {} Then: {} Else: {}",
|
||||
cond.0, then_branch.0, else_branch.0
|
||||
),
|
||||
Let(pattern, expression) => {
|
||||
write!(f, "Let: {} = {}", pattern.0, expression.0)
|
||||
}
|
||||
Dict(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|pair| pair.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
List(l) => write!(
|
||||
f,
|
||||
"List: [{}]",
|
||||
l.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Tuple(t) | Ast::Arguments(t) => write!(
|
||||
f,
|
||||
"Tuple: ({})",
|
||||
t.iter()
|
||||
.map(|(line, _)| line.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Synthetic(root, first, rest) => write!(
|
||||
f,
|
||||
"Synth: [{}, {}, {}]",
|
||||
root.0,
|
||||
first.0,
|
||||
rest.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
When(clauses) => write!(
|
||||
f,
|
||||
"When: [{}]",
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Placeholder => todo!(),
|
||||
LBox(_name, _rhs) => todo!(),
|
||||
Match(value, clauses) => {
|
||||
write!(
|
||||
f,
|
||||
"match: {} with {}",
|
||||
&value.0.to_string(),
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Fn(name, clauses, _) => {
|
||||
write!(
|
||||
f,
|
||||
"fn: {}\n{}",
|
||||
name,
|
||||
clauses
|
||||
.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
FnDeclaration(_name) => todo!(),
|
||||
Panic(_expr) => todo!(),
|
||||
Do(terms) => {
|
||||
write!(
|
||||
f,
|
||||
"do: {}",
|
||||
terms
|
||||
.iter()
|
||||
.map(|(term, _)| term.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > ")
|
||||
)
|
||||
}
|
||||
Repeat(_times, _body) => todo!(),
|
||||
Splat(word) => {
|
||||
write!(f, "splat: {}", word)
|
||||
}
|
||||
Pair(k, v) => {
|
||||
write!(f, "pair: {} {}", k, v.0)
|
||||
}
|
||||
Loop(init, body) => {
|
||||
write!(
|
||||
f,
|
||||
"loop: {} with {}",
|
||||
init.0,
|
||||
body.iter()
|
||||
.map(|clause| clause.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
Recur(args) => {
|
||||
write!(
|
||||
f,
|
||||
"recur: {}",
|
||||
args.iter()
|
||||
.map(|(arg, _)| arg.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
MatchClause(pattern, guard, body) => {
|
||||
write!(
|
||||
f,
|
||||
"match clause: {} if {:?} -> {}",
|
||||
pattern.0, guard, body.0
|
||||
)
|
||||
}
|
||||
WhenClause(cond, body) => {
|
||||
write!(f, "when clause: {} -> {}", cond.0, body.0)
|
||||
}
|
||||
|
||||
NilPattern => write!(f, "nil"),
|
||||
BooleanPattern(b) => write!(f, "{}", b),
|
||||
NumberPattern(n) => write!(f, "{}", n),
|
||||
StringPattern(s) => write!(f, "{}", s),
|
||||
KeywordPattern(k) => write!(f, ":{}", k),
|
||||
WordPattern(w) => write!(f, "{}", w),
|
||||
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||
Splattern(p) => write!(f, "...{}", p.0),
|
||||
PlaceholderPattern => write!(f, "_"),
|
||||
TuplePattern(t) => write!(
|
||||
f,
|
||||
"({})",
|
||||
t.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
ListPattern(l) => write!(
|
||||
f,
|
||||
"({})",
|
||||
l.iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
DictPattern(entries) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
entries
|
||||
.iter()
|
||||
.map(|(pair, _)| pair.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
PairPattern(key, value) => write!(f, ":{} {}", key, value.0),
|
||||
InterpolatedPattern(strprts, _) => write!(
|
||||
f,
|
||||
"interpolated: \"{}\"",
|
||||
strprts
|
||||
.iter()
|
||||
.map(|part| part.0.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringMatcher(pub Box<dyn Fn(String) -> Option<Vec<(String, String)>>>);
|
||||
|
||||
impl PartialEq for StringMatcher {
|
||||
fn eq(&self, _other: &StringMatcher) -> bool {
|
||||
|
@ -34,6 +343,75 @@ 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;
|
||||
|
@ -42,7 +420,6 @@ 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;
|
||||
|
@ -62,7 +439,7 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
|
|||
if !current_part.is_empty() {
|
||||
parts.push((
|
||||
StringPart::Data(current_part),
|
||||
SimpleSpan::new(span.context(), start..start + i),
|
||||
SimpleSpan::new(start, start + i),
|
||||
));
|
||||
};
|
||||
current_part = String::new();
|
||||
|
@ -80,7 +457,7 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
|
|||
if is_word {
|
||||
parts.push((
|
||||
StringPart::Word(current_part.clone()),
|
||||
SimpleSpan::new(span.context(), start..start + i),
|
||||
SimpleSpan::new(start, start + i),
|
||||
));
|
||||
current_part = String::new();
|
||||
start = i;
|
||||
|
@ -109,19 +486,61 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result<Vec<Spanned<StringP
|
|||
if current_part == s {
|
||||
parts.push((
|
||||
StringPart::Inline(current_part),
|
||||
SimpleSpan::new(span.context(), start..span.end),
|
||||
))
|
||||
} else if !current_part.is_empty() {
|
||||
let part_len = current_part.len();
|
||||
parts.push((
|
||||
StringPart::Data(current_part),
|
||||
SimpleSpan::new(span.context(), start..part_len),
|
||||
SimpleSpan::new(start, span.end),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
pub fn compile_string_pattern(parts: Vec<Spanned<StringPart>>) -> StringMatcher {
|
||||
StringMatcher(Box::new(move |scrutinee| {
|
||||
let mut last_match = 0;
|
||||
let mut parts_iter = parts.iter();
|
||||
let mut matches = vec![];
|
||||
while let Some((part, _)) = parts_iter.next() {
|
||||
match part {
|
||||
StringPart::Data(string) => match scrutinee.find(string.as_str()) {
|
||||
Some(i) => {
|
||||
// if i = 0, we're at the beginning
|
||||
if i == 0 && last_match == 0 {
|
||||
last_match = i + string.len();
|
||||
continue;
|
||||
}
|
||||
// in theory, we only hit this branch if the first part is Data
|
||||
unreachable!("internal Ludus error: bad string pattern")
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
StringPart::Word(word) => {
|
||||
let to_test = scrutinee.get(last_match..scrutinee.len()).unwrap();
|
||||
match parts_iter.next() {
|
||||
None => matches.push((word.clone(), to_test.to_string())),
|
||||
Some(part) => {
|
||||
let (StringPart::Data(part), _) = part else {
|
||||
unreachable!("internal Ludus error: bad string pattern")
|
||||
};
|
||||
match to_test.find(part) {
|
||||
None => return None,
|
||||
Some(i) => {
|
||||
matches.push((
|
||||
word.clone(),
|
||||
to_test.get(last_match..i).unwrap().to_string(),
|
||||
));
|
||||
last_match = i + part.len();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
Some(matches)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn parser<I>(
|
||||
) -> impl Parser<'static, I, Spanned<Ast>, extra::Err<Rich<'static, Token, Span>>> + Clone
|
||||
where
|
||||
|
@ -167,7 +586,10 @@ where
|
|||
match parsed {
|
||||
Ok(parts) => match parts[0] {
|
||||
(StringPart::Inline(_), _) => Ok((StringPattern(s), e.span())),
|
||||
_ => Ok((InterpolatedPattern(parts.clone()), e.span())),
|
||||
_ => Ok((
|
||||
InterpolatedPattern(parts.clone(), compile_string_pattern(parts)),
|
||||
e.span(),
|
||||
)),
|
||||
},
|
||||
Err(msg) => Err(Rich::custom(e.span(), msg)),
|
||||
}
|
||||
|
@ -296,11 +718,7 @@ where
|
|||
.delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")")))
|
||||
.map_with(|args, e| (Arguments(args), e.span()));
|
||||
|
||||
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_root = word.or(keyword);
|
||||
|
||||
let synth_term = keyword.or(args);
|
||||
|
||||
|
@ -377,7 +795,7 @@ where
|
|||
|span| (Error, span),
|
||||
)));
|
||||
|
||||
let r#if = just(Token::Reserved("if"))
|
||||
let if_ = just(Token::Reserved("if"))
|
||||
.ignore_then(simple.clone())
|
||||
.then_ignore(terminators.clone().or_not())
|
||||
.then_ignore(just(Token::Reserved("then")))
|
||||
|
@ -443,7 +861,7 @@ where
|
|||
.then(
|
||||
match_clause
|
||||
.clone()
|
||||
.or(guarded_clause.clone())
|
||||
.or(guarded_clause)
|
||||
.separated_by(terminators.clone())
|
||||
.allow_leading()
|
||||
.allow_trailing()
|
||||
|
@ -452,20 +870,7 @@ where
|
|||
)
|
||||
.map_with(|(expr, clauses), e| (Match(Box::new(expr), clauses), e.span()));
|
||||
|
||||
let receive = just(Token::Reserved("receive"))
|
||||
.ignore_then(
|
||||
match_clause
|
||||
.clone()
|
||||
.or(guarded_clause)
|
||||
.separated_by(terminators.clone())
|
||||
.allow_leading()
|
||||
.allow_trailing()
|
||||
.collect()
|
||||
.delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))),
|
||||
)
|
||||
.map_with(|clauses, e| (Receive(clauses), e.span()));
|
||||
|
||||
let conditional = when.or(r#if).or(r#match).or(receive);
|
||||
let conditional = when.or(if_).or(r#match);
|
||||
|
||||
let panic = just(Token::Reserved("panic!"))
|
||||
.ignore_then(nonbinding.clone())
|
||||
|
@ -517,12 +922,7 @@ where
|
|||
|
||||
let lambda = just(Token::Reserved("fn"))
|
||||
.ignore_then(fn_unguarded.clone())
|
||||
.map_with(|clause, e| {
|
||||
(
|
||||
Fn("", Box::new((Ast::FnBody(vec![clause]), e.span())), None),
|
||||
e.span(),
|
||||
)
|
||||
});
|
||||
.map_with(|clause, e| (Fn("anonymous", vec![clause], None), e.span()));
|
||||
|
||||
let fn_clauses = fn_clause
|
||||
.clone()
|
||||
|
@ -610,10 +1010,7 @@ where
|
|||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
(
|
||||
Fn(name, Box::new((Ast::FnBody(vec![clause]), e.span())), None),
|
||||
e.span(),
|
||||
)
|
||||
(Fn(name, vec![clause], None), e.span())
|
||||
});
|
||||
|
||||
let docstr = select! {Token::String(s) => s};
|
||||
|
@ -635,10 +1032,7 @@ where
|
|||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
(
|
||||
Fn(name, Box::new((Ast::FnBody(clauses), e.span())), docstr),
|
||||
e.span(),
|
||||
)
|
||||
(Fn(name, clauses, docstr), e.span())
|
||||
});
|
||||
|
||||
let fn_ = fn_named.or(fn_compound).or(fn_decl);
|
||||
|
|
635
src/process.rs
Normal file
635
src/process.rs
Normal file
|
@ -0,0 +1,635 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -1,24 +1,18 @@
|
|||
// 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::ast::{Ast, StringPart};
|
||||
use crate::parser::*;
|
||||
use crate::spans::{Span, Spanned};
|
||||
use crate::value::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VErr<'a> {
|
||||
pub struct VErr {
|
||||
pub msg: String,
|
||||
pub span: &'a Span,
|
||||
pub span: Span,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
}
|
||||
|
||||
impl<'a> VErr<'a> {
|
||||
pub fn new(msg: String, span: &'a Span, input: &'static str, src: &'static str) -> VErr<'a> {
|
||||
impl VErr {
|
||||
pub fn new(msg: String, span: Span, input: &'static str, src: &'static str) -> VErr {
|
||||
VErr {
|
||||
msg,
|
||||
span,
|
||||
|
@ -60,13 +54,13 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
|
|||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Validator<'a> {
|
||||
pub locals: Vec<(String, &'a Span, FnInfo)>,
|
||||
pub prelude: imbl::HashMap<&'static str, Value>,
|
||||
pub locals: Vec<(String, Span, FnInfo)>,
|
||||
pub prelude: &'a Vec<(String, Value<'a>)>,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
pub ast: &'a Ast,
|
||||
pub span: &'a Span,
|
||||
pub errors: Vec<VErr<'a>>,
|
||||
pub span: Span,
|
||||
pub errors: Vec<VErr>,
|
||||
pub fn_info: HashMap<*const Ast, FnInfo>,
|
||||
status: VStatus,
|
||||
}
|
||||
|
@ -74,10 +68,10 @@ pub struct Validator<'a> {
|
|||
impl<'a> Validator<'a> {
|
||||
pub fn new(
|
||||
ast: &'a Ast,
|
||||
span: &'a Span,
|
||||
span: Span,
|
||||
input: &'static str,
|
||||
src: &'static str,
|
||||
prelude: imbl::HashMap<&'static str, Value>,
|
||||
prelude: &'a Vec<(String, Value<'a>)>,
|
||||
) -> Validator<'a> {
|
||||
Validator {
|
||||
input,
|
||||
|
@ -118,7 +112,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,
|
||||
|
@ -152,7 +146,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();
|
||||
}
|
||||
|
||||
|
@ -171,7 +165,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 {
|
||||
|
@ -284,9 +278,6 @@ 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 {
|
||||
|
@ -307,10 +298,7 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!(
|
||||
"malformed synthetic root with\nfirst: {}\nsecond: {}",
|
||||
first.0, second.0
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
for term in rest {
|
||||
self.visit(term);
|
||||
|
@ -364,11 +352,6 @@ impl<'a> Validator<'a> {
|
|||
self.visit(clause);
|
||||
}
|
||||
}
|
||||
Receive(clauses) => {
|
||||
for clause in clauses {
|
||||
self.visit(clause);
|
||||
}
|
||||
}
|
||||
FnDeclaration(name) => {
|
||||
let tailpos = self.status.tail_position;
|
||||
self.status.tail_position = false;
|
||||
|
@ -379,8 +362,7 @@ impl<'a> Validator<'a> {
|
|||
self.declare_fn(name.to_string());
|
||||
self.status.tail_position = tailpos;
|
||||
}
|
||||
FnBody(..) => unreachable!(),
|
||||
Fn(name, body, ..) => {
|
||||
Fn(name, clauses, ..) => {
|
||||
let mut is_declared = false;
|
||||
match self.bound(name) {
|
||||
Some((_, _, FnInfo::Declared)) => is_declared = true,
|
||||
|
@ -398,21 +380,22 @@ 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 {
|
||||
// we have to do this explicitly here because of arity checking
|
||||
// TODO: validate all parts of clauses
|
||||
let (expr, span) = clause;
|
||||
self.ast = expr;
|
||||
self.span = span;
|
||||
self.span = *span;
|
||||
// add clause arity to arities
|
||||
arities.insert(self.arity());
|
||||
self.validate();
|
||||
}
|
||||
|
||||
// collect info about what the function closes over
|
||||
// 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
|
||||
let mut closed_over = HashSet::new();
|
||||
for binding in self.status.used_bindings.iter().skip(from) {
|
||||
if self.bound(binding.as_str()).is_some() {
|
||||
|
@ -475,7 +458,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 {
|
||||
|
@ -525,10 +508,10 @@ impl<'a> Validator<'a> {
|
|||
self.bind(name.to_string());
|
||||
}
|
||||
},
|
||||
InterpolatedPattern(parts) => {
|
||||
InterpolatedPattern(parts, _) => {
|
||||
for (part, span) in parts {
|
||||
if let StringPart::Word(name) = part {
|
||||
self.span = span;
|
||||
self.span = *span;
|
||||
match self.bound(name) {
|
||||
Some(_) => self.err(format!("name `{name}` is already bound")),
|
||||
None => self.bind(name.to_string()),
|
||||
|
@ -560,7 +543,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()),
|
||||
|
@ -587,7 +570,7 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
PairPattern(_, patt) => self.visit(patt.as_ref()),
|
||||
// terminals can never be invalid
|
||||
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (),
|
||||
Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) => (),
|
||||
// terminal patterns can never be invalid
|
||||
NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..)
|
||||
| KeywordPattern(..) | PlaceholderPattern => (),
|
||||
|
|
590
src/value.rs
590
src/value.rs
|
@ -1,449 +1,193 @@
|
|||
use crate::base::BaseFn;
|
||||
use crate::chunk::Chunk;
|
||||
use imbl::{HashMap, Vector};
|
||||
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 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>>,
|
||||
},
|
||||
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,
|
||||
}
|
||||
|
||||
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,
|
||||
#[derive(Debug, Dissectible)]
|
||||
pub enum Value<'src> {
|
||||
Nil,
|
||||
True,
|
||||
False,
|
||||
Keyword(&'static str),
|
||||
Interned(&'static str),
|
||||
String(Rc<String>),
|
||||
Placeholder,
|
||||
Boolean(bool),
|
||||
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>),
|
||||
Process,
|
||||
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
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Value) -> bool {
|
||||
use Value::*;
|
||||
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) {
|
||||
(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,
|
||||
// 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 std::fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use Value::*;
|
||||
impl Eq for Value<'_> {}
|
||||
|
||||
impl Value<'_> {
|
||||
pub fn interpolate(&self) -> String {
|
||||
match self {
|
||||
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}"),
|
||||
Process => write!(f, "Process"),
|
||||
Tuple(members) => write!(
|
||||
f,
|
||||
"({})",
|
||||
members
|
||||
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.to_string())
|
||||
.map(|x| x.interpolate())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
List(members) => write!(
|
||||
f,
|
||||
"[{}]",
|
||||
members
|
||||
.join(", "),
|
||||
Value::List(xs) => xs
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(|x| x.interpolate())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
Dict(members) => write!(
|
||||
f,
|
||||
"#{{{}}}",
|
||||
members
|
||||
.join(", "),
|
||||
Value::Dict(xs) => xs
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k} {v}"))
|
||||
.map(|(k, v)| format!(":{} {}", k, v.interpolate()))
|
||||
.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 {
|
||||
Process => "Process".to_string(),
|
||||
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() > 80 {
|
||||
out.truncate(77);
|
||||
format!("{out}...")
|
||||
} else {
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Option<String> {
|
||||
use Value::*;
|
||||
match self {
|
||||
True | False | Number(..) => Some(self.show()),
|
||||
String(string) => Some(serde_json::to_string(string.as_ref()).unwrap()),
|
||||
Interned(str) => Some(serde_json::to_string(str).unwrap()),
|
||||
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 {
|
||||
Process => "process".to_string(),
|
||||
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",
|
||||
Process => "process",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_fn(&self) -> &LFn {
|
||||
match self {
|
||||
Value::Fn(ref inner) => inner,
|
||||
_ => unreachable!("expected value to be fn"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_list(&self) -> &Vector<Value> {
|
||||
match self {
|
||||
Value::List(ref inner) => inner,
|
||||
_ => unreachable!("expected value to be list"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_box(&self) -> Rc<RefCell<Value>> {
|
||||
match self {
|
||||
Value::Box(inner) => inner.clone(),
|
||||
_ => unreachable!("expected value to be a box"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Rc<String> {
|
||||
match self {
|
||||
Value::String(str) => str.clone(),
|
||||
Value::Interned(str) => Rc::new(str.to_string()),
|
||||
_ => unreachable!("expected value to be a string"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_tuple(&self) -> Rc<Vec<Value>> {
|
||||
match self {
|
||||
Value::Tuple(members) => members.clone(),
|
||||
_ => unreachable!("expected value to be a tuple"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string(str: String) -> Value {
|
||||
Value::String(Rc::new(str))
|
||||
}
|
||||
|
||||
pub fn keyword(str: String) -> Value {
|
||||
Value::Keyword(str.leak())
|
||||
}
|
||||
|
||||
pub fn list(list: Vector<Value>) -> Value {
|
||||
Value::List(Box::new(list))
|
||||
}
|
||||
|
||||
pub fn new_list() -> Value {
|
||||
Value::list(Vector::new())
|
||||
}
|
||||
|
||||
pub fn r#box(value: Value) -> Value {
|
||||
Value::Box(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
|
||||
pub fn tuple(vec: Vec<Value>) -> Value {
|
||||
Value::Tuple(Rc::new(vec))
|
||||
}
|
||||
|
||||
pub fn get_shared_box(&self, name: &'static str) -> Value {
|
||||
match self {
|
||||
Value::Dict(dict) => dict
|
||||
.get(name)
|
||||
.expect("expected dict to have requested value")
|
||||
.clone(),
|
||||
_ => unreachable!("expected dict"),
|
||||
.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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
492
src/world.rs
492
src/world.rs
|
@ -1,492 +0,0 @@
|
|||
use crate::chunk::Chunk;
|
||||
use crate::value::Value;
|
||||
use crate::vm::{Creature, Panic};
|
||||
use crate::io::{MsgOut, MsgIn, do_io};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::mem::swap;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// Grab some JS stuff
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = Math)]
|
||||
fn random() -> f64;
|
||||
|
||||
#[wasm_bindgen(js_namespace = Date)]
|
||||
fn now() -> f64;
|
||||
}
|
||||
|
||||
const ANIMALS: [&str; 32] = [
|
||||
"tortoise",
|
||||
"hare",
|
||||
"squirrel",
|
||||
"hawk",
|
||||
"woodpecker",
|
||||
"cardinal",
|
||||
"coyote",
|
||||
"raccoon",
|
||||
"rat",
|
||||
"axolotl",
|
||||
"cormorant",
|
||||
"duck",
|
||||
"orca",
|
||||
"humbpack",
|
||||
"tern",
|
||||
"quokka",
|
||||
"koala",
|
||||
"kangaroo",
|
||||
"zebra",
|
||||
"hyena",
|
||||
"giraffe",
|
||||
"hippopotamus",
|
||||
"capybara",
|
||||
"python",
|
||||
"gopher",
|
||||
"crab",
|
||||
"trout",
|
||||
"osprey",
|
||||
"lemur",
|
||||
"wobbegong",
|
||||
"walrus",
|
||||
"opossum",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Status {
|
||||
Empty,
|
||||
Borrowed,
|
||||
Nested(Creature),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Status {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Status::Empty => write!(f, "empty"),
|
||||
Status::Borrowed => write!(f, "borrowed"),
|
||||
Status::Nested(creature) => write!(f, "nested {creature}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn receive(&mut self, msg: Value) {
|
||||
match self {
|
||||
Status::Nested(creature) => creature.receive(msg),
|
||||
Status::Borrowed => println!("sending a message to a borrowed process"),
|
||||
Status::Empty => println!("sending a message to a dead process"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Zoo {
|
||||
procs: Vec<Status>,
|
||||
empty: Vec<usize>,
|
||||
ids: HashMap<&'static str, usize>,
|
||||
dead: HashSet<&'static str>,
|
||||
kill_list: Vec<&'static str>,
|
||||
sleeping: HashMap<&'static str, f64>,
|
||||
active_idx: usize,
|
||||
active_id: &'static str,
|
||||
}
|
||||
|
||||
impl Zoo {
|
||||
pub fn new() -> Zoo {
|
||||
Zoo {
|
||||
procs: vec![],
|
||||
empty: vec![],
|
||||
ids: HashMap::new(),
|
||||
kill_list: vec![],
|
||||
dead: HashSet::new(),
|
||||
sleeping: HashMap::new(),
|
||||
active_idx: 0,
|
||||
active_id: "",
|
||||
}
|
||||
}
|
||||
|
||||
fn random_id(&self) -> String {
|
||||
let rand_idx = (random() * 32.0) as usize;
|
||||
let idx = self.procs.len();
|
||||
format!("{}_{idx}", ANIMALS[rand_idx])
|
||||
}
|
||||
|
||||
fn new_id(&self) -> &'static str {
|
||||
let mut new = self.random_id();
|
||||
while self.dead.iter().any(|old| *old == new) {
|
||||
new = self.random_id();
|
||||
}
|
||||
new.leak()
|
||||
}
|
||||
|
||||
pub fn put(&mut self, mut proc: Creature) -> &'static str {
|
||||
if self.empty.is_empty() {
|
||||
let id = self.new_id();
|
||||
let idx = self.procs.len();
|
||||
proc.pid = id;
|
||||
self.procs.push(Status::Nested(proc));
|
||||
self.ids.insert(id, idx);
|
||||
id
|
||||
} else {
|
||||
let idx = self.empty.pop().unwrap();
|
||||
let rand = (random() * 32.0) as usize;
|
||||
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
|
||||
proc.pid = id;
|
||||
self.ids.insert(id, idx);
|
||||
self.procs[idx] = Status::Nested(proc);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill(&mut self, id: &'static str) {
|
||||
self.kill_list.push(id);
|
||||
}
|
||||
|
||||
pub fn sleep(&mut self, id: &'static str, ms: f64) {
|
||||
self.sleeping
|
||||
.insert(id, now() + ms);
|
||||
}
|
||||
|
||||
pub fn is_alive(&self, id: &'static str) -> bool {
|
||||
if self.kill_list.contains(&id) {
|
||||
return false;
|
||||
}
|
||||
let idx = self.ids.get(id);
|
||||
match idx {
|
||||
Some(idx) => match self.procs.get(*idx) {
|
||||
Some(proc) => match proc {
|
||||
Status::Empty => false,
|
||||
Status::Borrowed => true,
|
||||
Status::Nested(_) => true,
|
||||
},
|
||||
None => false,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_up(&mut self) {
|
||||
while let Some(id) = self.kill_list.pop() {
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
println!("buried process {id}");
|
||||
self.procs[*idx] = Status::Empty;
|
||||
self.empty.push(*idx);
|
||||
self.ids.remove(id);
|
||||
self.dead.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
self.sleeping
|
||||
.retain(|_, wakeup_time| now() < *wakeup_time);
|
||||
|
||||
println!(
|
||||
"currently sleeping processes: {}",
|
||||
self.sleeping
|
||||
.keys()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" | ")
|
||||
);
|
||||
}
|
||||
|
||||
pub fn catch(&mut self, id: &'static str) -> Creature {
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
let mut proc = Status::Borrowed;
|
||||
swap(&mut proc, &mut self.procs[*idx]);
|
||||
let Status::Nested(proc) = proc else {
|
||||
unreachable!("tried to borrow an empty or already-borrowed process {id}");
|
||||
};
|
||||
proc
|
||||
} else {
|
||||
unreachable!("tried to borrow a non-existent process {id}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release(&mut self, proc: Creature) {
|
||||
let id = proc.pid;
|
||||
if let Some(idx) = self.ids.get(id) {
|
||||
let mut proc = Status::Nested(proc);
|
||||
swap(&mut proc, &mut self.procs[*idx]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_available(&self) -> bool {
|
||||
match &self.procs[self.active_idx] {
|
||||
Status::Empty => false,
|
||||
Status::Borrowed => false,
|
||||
Status::Nested(proc) => !self.sleeping.contains_key(proc.pid),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> &'static str {
|
||||
self.clean_up();
|
||||
let starting_idx = self.active_idx;
|
||||
self.active_idx = (self.active_idx + 1) % self.procs.len();
|
||||
while !self.is_available() {
|
||||
// we've gone round the process queue already
|
||||
// that means no process is active
|
||||
// but we may have processes that are alive and asleep
|
||||
// if nothing is active, yield back to the world's event loop
|
||||
if self.active_idx == starting_idx {
|
||||
return ""
|
||||
}
|
||||
self.active_idx = (self.active_idx + 1) % self.procs.len();
|
||||
}
|
||||
match &self.procs[self.active_idx] {
|
||||
Status::Empty | Status::Borrowed => unreachable!(),
|
||||
Status::Nested(proc) => proc.pid,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_msg(&mut self, id: &'static str, msg: Value) {
|
||||
let Some(idx) = self.ids.get(id) else {
|
||||
return;
|
||||
};
|
||||
self.procs[*idx].receive(msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Buffers {
|
||||
console: Value,
|
||||
commands: Value,
|
||||
fetch_out: Value,
|
||||
fetch_in: Value,
|
||||
input: Value,
|
||||
}
|
||||
|
||||
impl Buffers {
|
||||
pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers {
|
||||
Buffers {
|
||||
console: prelude.get("console").unwrap().clone(),
|
||||
commands: prelude.get("turtle_commands").unwrap().clone(),
|
||||
fetch_out: prelude.get("fetch_outbox").unwrap().clone(),
|
||||
fetch_in: prelude.get("fetch_inbox").unwrap().clone(),
|
||||
input: prelude.get("input").unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn console (&self) -> Rc<RefCell<Value>> {
|
||||
self.console.as_box()
|
||||
}
|
||||
|
||||
pub fn input (&self) -> Rc<RefCell<Value>> {
|
||||
self.input.as_box()
|
||||
}
|
||||
|
||||
pub fn commands (&self) -> Rc<RefCell<Value>> {
|
||||
self.commands.as_box()
|
||||
}
|
||||
|
||||
pub fn fetch_out (&self) -> Rc<RefCell<Value>> {
|
||||
self.fetch_out.as_box()
|
||||
}
|
||||
|
||||
pub fn fetch_in (&self) -> Rc<RefCell<Value>> {
|
||||
self.fetch_in.as_box()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct World {
|
||||
zoo: Rc<RefCell<Zoo>>,
|
||||
active: Option<Creature>,
|
||||
main: &'static str,
|
||||
pub result: Option<Result<Value, Panic>>,
|
||||
buffers: Buffers,
|
||||
last_io: f64,
|
||||
kill_signal: bool,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(chunk: Chunk, prelude: imbl::HashMap<&'static str, Value>, debug: bool) -> World {
|
||||
let zoo = Rc::new(RefCell::new(Zoo::new()));
|
||||
let main = Creature::new(chunk, zoo.clone(), debug);
|
||||
let id = zoo.borrow_mut().put(main);
|
||||
let buffers = Buffers::new(prelude);
|
||||
|
||||
World {
|
||||
zoo,
|
||||
active: None,
|
||||
main: id,
|
||||
result: None,
|
||||
buffers,
|
||||
last_io: 0.0,
|
||||
kill_signal: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
let mut active = None;
|
||||
swap(&mut active, &mut self.active);
|
||||
let mut zoo = self.zoo.borrow_mut();
|
||||
if let Some(active) = active {
|
||||
zoo.release(active);
|
||||
}
|
||||
let new_active_id = zoo.next();
|
||||
if new_active_id.is_empty() {
|
||||
self.active = None;
|
||||
return;
|
||||
}
|
||||
let mut new_active_proc = zoo.catch(new_active_id);
|
||||
new_active_proc.reset_reductions();
|
||||
let mut new_active_opt = Some(new_active_proc);
|
||||
swap(&mut new_active_opt, &mut self.active);
|
||||
}
|
||||
|
||||
fn activate_main(&mut self) {
|
||||
let main = self.zoo.borrow_mut().catch(self.main);
|
||||
self.active = Some(main);
|
||||
}
|
||||
|
||||
fn active_id(&mut self) -> Option<&'static str> {
|
||||
match &self.active {
|
||||
Some(creature) => Some(creature.pid),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn kill_active(&mut self) {
|
||||
if let Some(pid) = self.active_id() {
|
||||
self.zoo.borrow_mut().kill(pid);
|
||||
}
|
||||
}
|
||||
|
||||
fn active_result(&mut self) -> &Option<Result<Value, Panic>> {
|
||||
if self.active.is_none() { return &None; }
|
||||
&self.active.as_ref().unwrap().result
|
||||
}
|
||||
|
||||
fn flush_buffers(&mut self) -> Vec<MsgOut> {
|
||||
let mut outbox = vec![];
|
||||
if let Some(console) = self.flush_console() {
|
||||
outbox.push(console);
|
||||
}
|
||||
if let Some(commands) = self.flush_commands() {
|
||||
outbox.push(commands);
|
||||
}
|
||||
if let Some(fetch) = self.make_fetch_happen() {
|
||||
outbox.push(fetch);
|
||||
}
|
||||
outbox
|
||||
}
|
||||
|
||||
fn make_fetch_happen(&self) -> Option<MsgOut> {
|
||||
let out = self.buffers.fetch_out();
|
||||
let working = RefCell::new(Value::Interned(""));
|
||||
out.swap(&working);
|
||||
let working = working.borrow();
|
||||
if working.as_string().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Fetch(working.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_console(&self) -> Option<MsgOut> {
|
||||
let console = self.buffers.console();
|
||||
let working_copy = RefCell::new(Value::new_list());
|
||||
console.swap(&working_copy);
|
||||
let working_value = working_copy.borrow();
|
||||
if working_value.as_list().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Console(working_value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_commands(&self) -> Option<MsgOut> {
|
||||
let commands = self.buffers.commands();
|
||||
let working_copy = RefCell::new(Value::new_list());
|
||||
commands.swap(&working_copy);
|
||||
let commands = working_copy.borrow();
|
||||
if commands.as_list().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MsgOut::Commands(commands.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_main(&mut self) -> Vec<MsgOut> {
|
||||
let mut outbox = self.flush_buffers();
|
||||
// TODO: if we have a panic, actually add the panic message to the console
|
||||
let result = self.active_result().clone().unwrap();
|
||||
self.result = Some(result.clone());
|
||||
outbox.push(MsgOut::Complete(result));
|
||||
outbox
|
||||
}
|
||||
|
||||
fn interpret_active(&mut self) {
|
||||
self.active.as_mut().unwrap().interpret();
|
||||
}
|
||||
|
||||
async fn maybe_do_io(&mut self) {
|
||||
if self.last_io + 10.0 < now() {
|
||||
let outbox = self.flush_buffers();
|
||||
let inbox = do_io(outbox).await;
|
||||
self.fill_buffers(inbox);
|
||||
self.last_io = now();
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_input(&mut self, str: String) {
|
||||
let value = Value::string(str);
|
||||
let working = RefCell::new(value);
|
||||
let input = self.buffers.input();
|
||||
input.swap(&working);
|
||||
}
|
||||
|
||||
fn fetch_reply(&mut self, reply: Value) {
|
||||
let inbox_rc = self.buffers.fetch_in();
|
||||
inbox_rc.replace(reply);
|
||||
}
|
||||
|
||||
fn fill_buffers(&mut self, inbox: Vec<MsgIn>) {
|
||||
for msg in inbox {
|
||||
match msg {
|
||||
MsgIn::Input(str) => self.fill_input(str),
|
||||
MsgIn::Kill => self.kill_signal = true,
|
||||
MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ready_io(&mut self) {
|
||||
let inbox = do_io(vec![MsgOut::Ready]).await;
|
||||
self.fill_buffers(inbox);
|
||||
self.last_io = now();
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
self.activate_main();
|
||||
self.ready_io().await;
|
||||
loop {
|
||||
self.maybe_do_io().await;
|
||||
if self.kill_signal {
|
||||
let mut outbox = self.flush_buffers();
|
||||
outbox.push(MsgOut::Complete(Err(Panic::Str("ludus killed by user"))));
|
||||
do_io(outbox).await;
|
||||
return;
|
||||
}
|
||||
if self.active.is_some() {
|
||||
self.interpret_active();
|
||||
}
|
||||
if self.active_result().is_some() {
|
||||
if self.active_id().unwrap() == self.main {
|
||||
let outbox = self.complete_main();
|
||||
do_io(outbox).await;
|
||||
return;
|
||||
}
|
||||
self.kill_active();
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user