work on things
This commit is contained in:
parent
4f80c500a2
commit
8da5ae6cc4
|
@ -834,7 +834,7 @@ fn slice {
|
||||||
|
|
||||||
fn butlast {
|
fn butlast {
|
||||||
"Returns a list, omitting the last element."
|
"Returns a list, omitting the last element."
|
||||||
(xs as :list) -> slice (xs, 0, dec (count (xs)))
|
(xs as :list) -> base :slice (xs, 0, dec (count (xs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
&&& keywords: funny names
|
&&& keywords: funny names
|
||||||
|
|
|
@ -496,9 +496,9 @@ Here's a list of things that need doing:
|
||||||
- [ ] test this
|
- [ ] test this
|
||||||
- I did not fix it.
|
- I did not fix it.
|
||||||
* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard?
|
* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard?
|
||||||
* [ ] This is in the service of getting turtle graphics working
|
* [x] This is in the service of getting turtle graphics working
|
||||||
* Other forms in the language need help:
|
* Other forms in the language need help:
|
||||||
* [ ] repeat needs its stack discipline updated, it currently crashes the compiler
|
* [x] repeat needs its stack discipline updated, it currently crashes the compiler
|
||||||
|
|
||||||
### More closure problems
|
### More closure problems
|
||||||
#### 2025-06-23
|
#### 2025-06-23
|
||||||
|
@ -522,4 +522,110 @@ SOLUTION: test to see if the function has been forward-declared, and if it has,
|
||||||
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
|
NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls.
|
||||||
|
|
||||||
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
|
NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack.
|
||||||
This is the thing I am about to do
|
This is the thing I am about to do.
|
||||||
|
|
||||||
|
### I think the interpreter, uh, works?
|
||||||
|
#### 2025-06-24
|
||||||
|
I'm sure I'll find some small problems.
|
||||||
|
But right now the thing works.
|
||||||
|
At the moment, I'm thinking about how to get good error messages.
|
||||||
|
Panics are difficult.
|
||||||
|
And I'm worried about ariadne as the error reporting crate.
|
||||||
|
Since it writes to stdout, it has all kinds of escape codes.
|
||||||
|
I need a plain ass string, at least for the web frontend.
|
||||||
|
So.
|
||||||
|
|
||||||
|
Current task, however, is how to get reasonable panic error messages.
|
||||||
|
Let's simplify the problem.
|
||||||
|
|
||||||
|
First, let's get tracebacks and line numbers.
|
||||||
|
We use Chumsky spanned ASTs.
|
||||||
|
The span is just a range into an str.
|
||||||
|
What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback.
|
||||||
|
So instead of using Ariadne's fancy report builder, let's just do something more what we already have here:
|
||||||
|
|
||||||
|
```
|
||||||
|
Ludus panicked! no match
|
||||||
|
on line 1 in input,
|
||||||
|
calling: add
|
||||||
|
with arguments: ("foo")
|
||||||
|
expected match with one of:
|
||||||
|
()
|
||||||
|
(x as :number)
|
||||||
|
(x as :number, y as :number)
|
||||||
|
(x, y, ...zs)
|
||||||
|
((x1, y1), (x2, y2))
|
||||||
|
>>> add ("foo")
|
||||||
|
......^
|
||||||
|
```
|
||||||
|
We need:
|
||||||
|
* the location of the function call in terms of the line number
|
||||||
|
* the arguments used
|
||||||
|
* the patterns expected to match (special cases: `let` vs `match` vs `fn`)
|
||||||
|
|
||||||
|
That means, for bookkeeping, we need:
|
||||||
|
* In the compiler, line number
|
||||||
|
* In the VM, the arguments
|
||||||
|
* In the VM, the pattern AST node.
|
||||||
|
|
||||||
|
In Janet-Ludus, there are only a few types of panic:
|
||||||
|
* `fn-no-match`: no match against a function call
|
||||||
|
* `let-no-match`: no match against a `let` binding
|
||||||
|
* `match-no-match`: no match against a `match` form
|
||||||
|
* `generic-panic`: everything else
|
||||||
|
* `runtime-error`: an internal Ludus error
|
||||||
|
|
||||||
|
The first three are simply formatting differences.
|
||||||
|
There are no tracebacks.
|
||||||
|
|
||||||
|
Tracebacks should be easy enough, although there's some fiddly bits.
|
||||||
|
While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter.
|
||||||
|
And the traceback should look something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
calling foo with (:bar, :baz)
|
||||||
|
at line 12 in input
|
||||||
|
calling bar with ()
|
||||||
|
at line 34 in prelude
|
||||||
|
calling baz with (1, 2, 3)
|
||||||
|
at line 12 in input
|
||||||
|
```
|
||||||
|
|
||||||
|
Which means, again: function names, ip->line conversion, and arguments.
|
||||||
|
|
||||||
|
The runtime needs a representation of the patterns in _any_ matching form.
|
||||||
|
The speed is so much greater now that I'm not so concerned about little optimizations.
|
||||||
|
So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec<String>`.)
|
||||||
|
So does a function, for `doc!`.
|
||||||
|
Same same re: `Vec<String>`.
|
||||||
|
A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured).
|
||||||
|
A VM also needs a register for the pattern.
|
||||||
|
So when there's a no match, we just yank the pattern and the scrutinee out of these registers.
|
||||||
|
|
||||||
|
This all seems very straightforward compared to the compiling & VM stuff.
|
||||||
|
|
||||||
|
Here's some stub code I wrote for dealing with ranges, source, line numbers:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let str = "foo bar baz\nquux frob\nthing thing thing";
|
||||||
|
let range = 0..4;
|
||||||
|
|
||||||
|
println!("{}", str.get(range).unwrap());
|
||||||
|
|
||||||
|
let lines: Vec<&str> = str.split_terminator("\n").collect();
|
||||||
|
|
||||||
|
println!("{:?}", lines);
|
||||||
|
|
||||||
|
let idx = 20;
|
||||||
|
|
||||||
|
let mut line_no = 1;
|
||||||
|
for i in 0..idx {
|
||||||
|
if str.chars().nth(i).unwrap() == '\n' {
|
||||||
|
line_no += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("line {line_no}: {}", lines[line_no - 1]);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
22
sandbox.ld
22
sandbox.ld
|
@ -1,19 +1,5 @@
|
||||||
fn circle! () -> repeat 20 {
|
let foo = :foo
|
||||||
fd! (2)
|
|
||||||
rt! (inv (20))
|
repeat foo {
|
||||||
|
print! ("hi")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flower! () -> repeat 10 {
|
|
||||||
circle! ()
|
|
||||||
rt! (inv (10))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn garland! () -> repeat 10 {
|
|
||||||
flower! ()
|
|
||||||
fd! (30)
|
|
||||||
}
|
|
||||||
|
|
||||||
garland! ()
|
|
||||||
|
|
||||||
do turtle_commands > unbox > print!
|
|
||||||
do turtle_state > unbox > print!
|
|
||||||
|
|
1198
sandbox_run.txt
1198
sandbox_run.txt
File diff suppressed because it is too large
Load Diff
22
src/chunk.rs
22
src/chunk.rs
|
@ -1,5 +1,8 @@
|
||||||
use crate::op::Op;
|
use crate::op::Op;
|
||||||
|
use crate::parser::Ast;
|
||||||
|
use crate::spans::Spanned;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
use chumsky::prelude::SimpleSpan;
|
||||||
use imbl::HashMap;
|
use imbl::HashMap;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -14,13 +17,32 @@ pub struct StrPattern {
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pub constants: Vec<Value>,
|
pub constants: Vec<Value>,
|
||||||
pub bytecode: Vec<u8>,
|
pub bytecode: Vec<u8>,
|
||||||
|
pub nodes: Vec<&'static Ast>,
|
||||||
|
pub spans: Vec<SimpleSpan>,
|
||||||
pub keywords: Vec<&'static str>,
|
pub keywords: Vec<&'static str>,
|
||||||
pub string_patterns: Vec<StrPattern>,
|
pub string_patterns: Vec<StrPattern>,
|
||||||
pub env: HashMap<&'static str, Value>,
|
pub env: HashMap<&'static str, Value>,
|
||||||
pub msgs: Vec<String>,
|
pub msgs: Vec<String>,
|
||||||
|
pub src: &'static str,
|
||||||
|
pub input: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
|
pub fn new(env: HashMap<&'static str, Value>, src: &'static str, input: &'static str) -> Chunk {
|
||||||
|
Chunk {
|
||||||
|
constants: vec![],
|
||||||
|
bytecode: vec![],
|
||||||
|
nodes: vec![],
|
||||||
|
spans: vec![],
|
||||||
|
keywords: vec![],
|
||||||
|
string_patterns: vec![],
|
||||||
|
env,
|
||||||
|
msgs: vec![],
|
||||||
|
src,
|
||||||
|
input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dissasemble_instr(&self, i: &mut usize) {
|
pub fn dissasemble_instr(&self, i: &mut usize) {
|
||||||
let op = Op::from_u8(self.bytecode[*i]).unwrap();
|
let op = Op::from_u8(self.bytecode[*i]).unwrap();
|
||||||
use Op::*;
|
use Op::*;
|
||||||
|
|
|
@ -62,18 +62,17 @@ fn get_builtin(name: &str, arity: usize) -> Option<Op> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Compiler {
|
pub struct Compiler {
|
||||||
pub chunk: Chunk,
|
pub chunk: Chunk,
|
||||||
pub bindings: Vec<Binding>,
|
pub bindings: Vec<Binding>,
|
||||||
pub scope_depth: isize,
|
pub scope_depth: isize,
|
||||||
pub match_depth: usize,
|
pub match_depth: usize,
|
||||||
pub stack_depth: usize,
|
pub stack_depth: usize,
|
||||||
pub spans: Vec<SimpleSpan>,
|
|
||||||
pub nodes: Vec<&'static Ast>,
|
|
||||||
pub ast: &'static Ast,
|
pub ast: &'static Ast,
|
||||||
pub span: SimpleSpan,
|
pub span: SimpleSpan,
|
||||||
pub src: &'static str,
|
pub src: &'static str,
|
||||||
|
pub input: &'static str,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub upvalues: Vec<&'static str>,
|
pub upvalues: Vec<&'static str>,
|
||||||
|
@ -101,18 +100,13 @@ impl Compiler {
|
||||||
ast: &'static Spanned<Ast>,
|
ast: &'static Spanned<Ast>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
src: &'static str,
|
src: &'static str,
|
||||||
|
input: &'static str,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
env: imbl::HashMap<&'static str, Value>,
|
env: imbl::HashMap<&'static str, Value>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Compiler {
|
) -> Compiler {
|
||||||
let chunk = Chunk {
|
let chunk = Chunk::new(env, src, input);
|
||||||
constants: vec![],
|
|
||||||
bytecode: vec![],
|
|
||||||
keywords: vec![],
|
|
||||||
string_patterns: vec![],
|
|
||||||
env,
|
|
||||||
msgs: vec![],
|
|
||||||
};
|
|
||||||
Compiler {
|
Compiler {
|
||||||
chunk,
|
chunk,
|
||||||
bindings: vec![],
|
bindings: vec![],
|
||||||
|
@ -120,13 +114,12 @@ impl Compiler {
|
||||||
scope_depth: -1,
|
scope_depth: -1,
|
||||||
match_depth: 0,
|
match_depth: 0,
|
||||||
stack_depth: 0,
|
stack_depth: 0,
|
||||||
spans: vec![],
|
|
||||||
nodes: vec![],
|
|
||||||
ast: &ast.0,
|
ast: &ast.0,
|
||||||
span: ast.1,
|
span: ast.1,
|
||||||
loop_info: vec![],
|
loop_info: vec![],
|
||||||
upvalues: vec![],
|
upvalues: vec![],
|
||||||
src,
|
src,
|
||||||
|
input,
|
||||||
name,
|
name,
|
||||||
tail_pos: false,
|
tail_pos: false,
|
||||||
debug,
|
debug,
|
||||||
|
@ -222,12 +215,14 @@ impl Compiler {
|
||||||
|
|
||||||
fn emit_op(&mut self, op: Op) {
|
fn emit_op(&mut self, op: Op) {
|
||||||
self.chunk.bytecode.push(op as u8);
|
self.chunk.bytecode.push(op as u8);
|
||||||
self.spans.push(self.span);
|
self.chunk.spans.push(self.span);
|
||||||
|
self.chunk.nodes.push(self.ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_byte(&mut self, byte: usize) {
|
fn emit_byte(&mut self, byte: usize) {
|
||||||
self.chunk.bytecode.push(byte as u8);
|
self.chunk.bytecode.push(byte as u8);
|
||||||
self.spans.push(self.span);
|
self.chunk.spans.push(self.span);
|
||||||
|
self.chunk.nodes.push(self.ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
|
@ -1096,6 +1091,7 @@ impl Compiler {
|
||||||
clause,
|
clause,
|
||||||
name,
|
name,
|
||||||
self.src,
|
self.src,
|
||||||
|
self.input,
|
||||||
self.depth + 1,
|
self.depth + 1,
|
||||||
self.chunk.env.clone(),
|
self.chunk.env.clone(),
|
||||||
self.debug,
|
self.debug,
|
||||||
|
|
|
@ -1,9 +1,90 @@
|
||||||
// use crate::process::{LErr, Trace};
|
// use crate::process::{LErr, Trace};
|
||||||
|
use crate::parser::Ast;
|
||||||
|
use crate::spans::Spanned;
|
||||||
use crate::validator::VErr;
|
use crate::validator::VErr;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
use crate::vm::CallFrame;
|
||||||
use ariadne::{sources, Color, Label, Report, ReportKind};
|
use ariadne::{sources, Color, Label, Report, ReportKind};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum PanicInfo {
|
||||||
|
RepeatNumberType(Value),
|
||||||
|
NoMatch,
|
||||||
|
LetMatchFail,
|
||||||
|
MatchFail,
|
||||||
|
FnMatchFail,
|
||||||
|
Internal(&'static str),
|
||||||
|
ListSplat(Value),
|
||||||
|
DictSplat(Value),
|
||||||
|
DecType(Value),
|
||||||
|
WhenFail,
|
||||||
|
AddType(Value, Value),
|
||||||
|
SubType(Value, Value),
|
||||||
|
MultType(Value, Value),
|
||||||
|
DivType(Value, Value),
|
||||||
|
UnboxType(Value),
|
||||||
|
StoreType(Value),
|
||||||
|
Assert,
|
||||||
|
Explicit(Value),
|
||||||
|
GetType(Value),
|
||||||
|
AtIdxType(Value),
|
||||||
|
AtCollectionType(Value),
|
||||||
|
PartialType(Value),
|
||||||
|
ArityMismatch(Value, Vec<Value>),
|
||||||
|
CallType(Value),
|
||||||
|
DivisionByZero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanicInfo {
|
||||||
|
pub fn msg(&self) -> String {
|
||||||
|
use PanicInfo::*;
|
||||||
|
match self {
|
||||||
|
RepeatNumberType(value) => format!("`repeat` expects a number; it got `{value}`"),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Panic {
|
||||||
|
pub frame: CallFrame,
|
||||||
|
pub call_stack: Vec<CallFrame>,
|
||||||
|
pub info: PanicInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panic {
|
||||||
|
pub fn report(&self) {
|
||||||
|
println!("got frame: {}", self.frame);
|
||||||
|
let mut srcs = HashSet::new();
|
||||||
|
let ast_spans = std::iter::zip(
|
||||||
|
self.frame.chunk().spans.iter(),
|
||||||
|
self.frame.chunk().nodes.iter(),
|
||||||
|
);
|
||||||
|
let ast_spans = ast_spans
|
||||||
|
.map(|(span, ast)| format!("{span} || {}", ast.show()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
println!("{ast_spans}");
|
||||||
|
let msg = self.info.msg();
|
||||||
|
let input = self.frame.chunk().input;
|
||||||
|
let src = self.frame.chunk().src;
|
||||||
|
srcs.insert((input, src));
|
||||||
|
let mut traceback = format!("Ludus panicked! {msg}");
|
||||||
|
for frame in self.call_stack.iter().rev() {
|
||||||
|
let name = frame.function.as_fn().name();
|
||||||
|
let args = frame
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| arg.show())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
traceback = format!("{traceback}\n calling `{name}` with ({args})");
|
||||||
|
}
|
||||||
|
println!("{traceback}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn report_panic(err: LErr) {
|
// pub fn report_panic(err: LErr) {
|
||||||
// let mut srcs = HashSet::new();
|
// let mut srcs = HashSet::new();
|
||||||
// let mut stack = vec![];
|
// let mut stack = vec![];
|
||||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -3,8 +3,8 @@ use imbl::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
const DEBUG_SCRIPT_COMPILE: bool = false;
|
const DEBUG_SCRIPT_COMPILE: bool = true;
|
||||||
const DEBUG_SCRIPT_RUN: bool = false;
|
const DEBUG_SCRIPT_RUN: bool = true;
|
||||||
const DEBUG_PRELUDE_COMPILE: bool = false;
|
const DEBUG_PRELUDE_COMPILE: bool = false;
|
||||||
const DEBUG_PRELUDE_RUN: bool = false;
|
const DEBUG_PRELUDE_RUN: bool = false;
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ pub fn prelude() -> HashMap<&'static str, Value> {
|
||||||
parsed,
|
parsed,
|
||||||
"prelude",
|
"prelude",
|
||||||
PRELUDE,
|
PRELUDE,
|
||||||
|
"prelude",
|
||||||
0,
|
0,
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
DEBUG_PRELUDE_COMPILE,
|
DEBUG_PRELUDE_COMPILE,
|
||||||
|
@ -90,7 +91,7 @@ pub fn prelude() -> HashMap<&'static str, Value> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(src: &'static str) {
|
pub fn run(src: &'static str, input: &'static str) {
|
||||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||||
if !lex_errs.is_empty() {
|
if !lex_errs.is_empty() {
|
||||||
println!("{:?}", lex_errs);
|
println!("{:?}", lex_errs);
|
||||||
|
@ -115,7 +116,7 @@ pub fn run(src: &'static str) {
|
||||||
let prelude = prelude();
|
let prelude = prelude();
|
||||||
// let prelude = imbl::HashMap::new();
|
// let prelude = imbl::HashMap::new();
|
||||||
|
|
||||||
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
|
let mut validator = Validator::new(&parsed.0, &parsed.1, input, src, prelude.clone());
|
||||||
validator.validate();
|
validator.validate();
|
||||||
|
|
||||||
if !validator.errors.is_empty() {
|
if !validator.errors.is_empty() {
|
||||||
|
@ -124,7 +125,7 @@ pub fn run(src: &'static str) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE);
|
let mut compiler = Compiler::new(parsed, input, src, input, 0, prelude, DEBUG_SCRIPT_COMPILE);
|
||||||
// let base = base::make_base();
|
// let base = base::make_base();
|
||||||
// compiler.emit_constant(base);
|
// compiler.emit_constant(base);
|
||||||
// compiler.bind("base");
|
// compiler.bind("base");
|
||||||
|
@ -147,7 +148,10 @@ pub fn run(src: &'static str) {
|
||||||
let result = vm.run();
|
let result = vm.run();
|
||||||
let output = match result {
|
let output = match result {
|
||||||
Ok(val) => val.to_string(),
|
Ok(val) => val.to_string(),
|
||||||
Err(panic) => format!("Ludus panicked! {panic}"),
|
Err(panic) => {
|
||||||
|
panic.report();
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if DEBUG_SCRIPT_RUN {
|
if DEBUG_SCRIPT_RUN {
|
||||||
vm.print_stack();
|
vm.print_stack();
|
||||||
|
@ -180,11 +184,28 @@ pub fn ld_fmt(src: &'static str) -> Result<String, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
env::set_var("RUST_BACKTRACE", "1");
|
// env::set_var("RUST_BACKTRACE", "1");
|
||||||
let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak();
|
// let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak();
|
||||||
match ld_fmt(src) {
|
// run(src, "sandbox.ld");
|
||||||
Ok(src) => println!("{}", src),
|
let str = "foo bar baz\nquux frob\nthing thing thing";
|
||||||
Err(msg) => println!("Could not format source with errors:\n{}", msg),
|
let range = 0..4;
|
||||||
|
|
||||||
|
println!("{}", range.start);
|
||||||
|
|
||||||
|
println!("{}", str.get(range).unwrap());
|
||||||
|
|
||||||
|
let lines: Vec<&str> = str.split_terminator("\n").collect();
|
||||||
|
|
||||||
|
println!("{:?}", lines);
|
||||||
|
|
||||||
|
let idx = 20;
|
||||||
|
|
||||||
|
let mut line_no = 1;
|
||||||
|
for i in 0..idx {
|
||||||
|
if str.chars().nth(i).unwrap() == '\n' {
|
||||||
|
line_no += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
run(src);
|
|
||||||
|
println!("line {line_no}: {}", lines[line_no - 1]);
|
||||||
}
|
}
|
||||||
|
|
114
src/parser.rs
114
src/parser.rs
|
@ -4,35 +4,6 @@ use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use struct_scalpel::Dissectible;
|
use struct_scalpel::Dissectible;
|
||||||
|
|
||||||
// #[derive(Clone, Debug, PartialEq)]
|
|
||||||
// pub struct WhenClause {
|
|
||||||
// pub cond: Spanned<Ast>,
|
|
||||||
// pub body: Spanned<Ast>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl fmt::Display for WhenClause {
|
|
||||||
// fn fmt(self: &WhenClause, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// write!(f, "cond: {}, body: {}", self.cond.0, self.body.0)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(Clone, Debug, PartialEq)]
|
|
||||||
// pub struct MatchClause {
|
|
||||||
// pub patt: Spanned<Pattern>,
|
|
||||||
// pub guard: Option<Spanned<Ast>>,
|
|
||||||
// pub body: Spanned<Ast>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl fmt::Display for MatchClause {
|
|
||||||
// fn fmt(self: &MatchClause, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// write!(
|
|
||||||
// f,
|
|
||||||
// "pattern: {}, guard: {:?} body: {}",
|
|
||||||
// self.patt.0, self.guard, self.body.0
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum StringPart {
|
pub enum StringPart {
|
||||||
Data(String),
|
Data(String),
|
||||||
|
@ -219,14 +190,12 @@ impl Ast {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n ")
|
.join("\n ")
|
||||||
),
|
),
|
||||||
FnBody(clauses) => format!(
|
FnBody(clauses) => clauses
|
||||||
"{}",
|
.iter()
|
||||||
clauses
|
.map(|(clause, _)| clause.show())
|
||||||
.iter()
|
.collect::<Vec<_>>()
|
||||||
.map(|(clause, _)| clause.show())
|
.join("\n ")
|
||||||
.collect::<Vec<_>>()
|
.to_string(),
|
||||||
.join("\n ")
|
|
||||||
),
|
|
||||||
Fn(name, body, doc) => {
|
Fn(name, body, doc) => {
|
||||||
let mut out = format!("fn {name} {{\n");
|
let mut out = format!("fn {name} {{\n");
|
||||||
if let Some(doc) = doc {
|
if let Some(doc) = doc {
|
||||||
|
@ -267,7 +236,7 @@ impl Ast {
|
||||||
.join(", ")
|
.join(", ")
|
||||||
),
|
),
|
||||||
MatchClause(pattern, guard, body) => {
|
MatchClause(pattern, guard, body) => {
|
||||||
let mut out = format!("{}", pattern.0.show());
|
let mut out = pattern.0.show().to_string();
|
||||||
if let Some(guard) = guard.as_ref() {
|
if let Some(guard) = guard.as_ref() {
|
||||||
out = format!("{out} if {}", guard.0.show());
|
out = format!("{out} if {}", guard.0.show());
|
||||||
}
|
}
|
||||||
|
@ -523,75 +492,6 @@ impl fmt::Debug for StringMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Clone, Debug, PartialEq)]
|
|
||||||
// pub enum Pattern {
|
|
||||||
// Nil,
|
|
||||||
// Boolean(bool),
|
|
||||||
// Number(f64),
|
|
||||||
// String(&'static str),
|
|
||||||
// Interpolated(Vec<Spanned<StringPart>>, StringMatcher),
|
|
||||||
// Keyword(&'static str),
|
|
||||||
// Word(&'static str),
|
|
||||||
// As(&'static str, &'static str),
|
|
||||||
// Splattern(Box<Spanned<Self>>),
|
|
||||||
// Placeholder,
|
|
||||||
// Tuple(Vec<Spanned<Self>>),
|
|
||||||
// List(Vec<Spanned<Self>>),
|
|
||||||
// Pair(&'static str, Box<Spanned<Self>>),
|
|
||||||
// Dict(Vec<Spanned<Self>>),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl fmt::Display for Pattern {
|
|
||||||
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// match self {
|
|
||||||
// Pattern::Nil => write!(f, "nil"),
|
|
||||||
// Pattern::Boolean(b) => write!(f, "{}", b),
|
|
||||||
// Pattern::Number(n) => write!(f, "{}", n),
|
|
||||||
// Pattern::String(s) => write!(f, "{}", s),
|
|
||||||
// Pattern::Keyword(k) => write!(f, ":{}", k),
|
|
||||||
// Pattern::Word(w) => write!(f, "{}", w),
|
|
||||||
// Pattern::As(w, t) => write!(f, "{} as {}", w, t),
|
|
||||||
// Pattern::Splattern(p) => write!(f, "...{}", p.0),
|
|
||||||
// Pattern::Placeholder => write!(f, "_"),
|
|
||||||
// Pattern::Tuple(t) => write!(
|
|
||||||
// f,
|
|
||||||
// "({})",
|
|
||||||
// t.iter()
|
|
||||||
// .map(|x| x.0.to_string())
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join(", ")
|
|
||||||
// ),
|
|
||||||
// Pattern::List(l) => write!(
|
|
||||||
// f,
|
|
||||||
// "({})",
|
|
||||||
// l.iter()
|
|
||||||
// .map(|x| x.0.to_string())
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join(", ")
|
|
||||||
// ),
|
|
||||||
// Pattern::Dict(entries) => write!(
|
|
||||||
// f,
|
|
||||||
// "#{{{}}}",
|
|
||||||
// entries
|
|
||||||
// .iter()
|
|
||||||
// .map(|(pair, _)| pair.to_string())
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join(", ")
|
|
||||||
// ),
|
|
||||||
// Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0),
|
|
||||||
// Pattern::Interpolated(strprts, _) => write!(
|
|
||||||
// f,
|
|
||||||
// "interpolated: \"{}\"",
|
|
||||||
// strprts
|
|
||||||
// .iter()
|
|
||||||
// .map(|part| part.0.to_string())
|
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join("")
|
|
||||||
// ),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn is_word_char(c: char) -> bool {
|
fn is_word_char(c: char) -> bool {
|
||||||
if c.is_ascii_alphanumeric() {
|
if c.is_ascii_alphanumeric() {
|
||||||
return true;
|
return true;
|
||||||
|
|
269
src/vm.rs
269
src/vm.rs
|
@ -1,8 +1,7 @@
|
||||||
use crate::base::BaseFn;
|
use crate::base::BaseFn;
|
||||||
use crate::chunk::Chunk;
|
use crate::chunk::Chunk;
|
||||||
|
use crate::errors::{Panic, PanicInfo};
|
||||||
use crate::op::Op;
|
use crate::op::Op;
|
||||||
use crate::parser::Ast;
|
|
||||||
use crate::spans::Spanned;
|
|
||||||
use crate::value::{LFn, Value};
|
use crate::value::{LFn, Value};
|
||||||
use imbl::{HashMap, Vector};
|
use imbl::{HashMap, Vector};
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
@ -12,43 +11,11 @@ use std::mem::swap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
// pub struct Panic {
|
|
||||||
// pub input: &'static str,
|
|
||||||
// pub src: &'static str,
|
|
||||||
// pub msg: String,
|
|
||||||
// pub span: SimpleSpan,
|
|
||||||
// pub trace: Vec<Trace>,
|
|
||||||
// pub extra: String,
|
|
||||||
// }
|
|
||||||
pub enum Panic {
|
|
||||||
Str(&'static str),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Panic {
|
|
||||||
fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Panic::Str(msg) => write!(f, "{msg}"),
|
|
||||||
Panic::String(msg) => write!(f, "{msg}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Trace {
|
|
||||||
pub callee: Spanned<Ast>,
|
|
||||||
pub caller: Spanned<Ast>,
|
|
||||||
pub function: Value,
|
|
||||||
pub arguments: Value,
|
|
||||||
pub input: &'static str,
|
|
||||||
pub src: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CallFrame {
|
pub struct CallFrame {
|
||||||
pub function: Value,
|
pub function: Value,
|
||||||
pub arity: u8,
|
|
||||||
pub stack_base: usize,
|
pub stack_base: usize,
|
||||||
pub ip: usize,
|
pub ip: usize,
|
||||||
|
pub args: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallFrame {
|
impl CallFrame {
|
||||||
|
@ -56,7 +23,7 @@ impl CallFrame {
|
||||||
let Value::Fn(ref function) = self.function else {
|
let Value::Fn(ref function) = self.function else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
function.chunk(self.arity)
|
function.chunk(self.args.len() as u8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +36,7 @@ impl fmt::Display for CallFrame {
|
||||||
f,
|
f,
|
||||||
"CallFrame: {}/{} @ {}",
|
"CallFrame: {}/{} @ {}",
|
||||||
function.name(),
|
function.name(),
|
||||||
self.arity,
|
self.args.len(),
|
||||||
self.ip
|
self.ip
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +75,7 @@ impl Vm {
|
||||||
function: base_fn.clone(),
|
function: base_fn.clone(),
|
||||||
stack_base: 0,
|
stack_base: 0,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
arity: 0,
|
args: vec![],
|
||||||
};
|
};
|
||||||
Vm {
|
Vm {
|
||||||
stack: vec![],
|
stack: vec![],
|
||||||
|
@ -186,15 +153,21 @@ impl Vm {
|
||||||
stack
|
stack
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn panic(&mut self, msg: &'static str) {
|
pub fn panic(&mut self, info: PanicInfo) {
|
||||||
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
|
let mut frame = self.frame.clone();
|
||||||
self.result = Some(Err(Panic::String(msg)));
|
frame.ip = self.ip;
|
||||||
|
let panic = Panic {
|
||||||
|
info,
|
||||||
|
frame,
|
||||||
|
call_stack: self.call_stack.clone(),
|
||||||
|
};
|
||||||
|
self.result = Some(Err(panic));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn panic_with(&mut self, msg: String) {
|
// pub fn panic_with(&mut self, msg: String) {
|
||||||
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
|
// let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
|
||||||
self.result = Some(Err(Panic::String(msg)));
|
// self.result = Some(Err(Panic::String(msg)));
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn get_value_at(&mut self, idx: u8) -> Value {
|
fn get_value_at(&mut self, idx: u8) -> Value {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
|
@ -280,7 +253,7 @@ impl Vm {
|
||||||
match cond {
|
match cond {
|
||||||
Value::Number(x) if x <= 0.0 => self.ip += jump_len,
|
Value::Number(x) if x <= 0.0 => self.ip += jump_len,
|
||||||
Value::Number(..) => (),
|
Value::Number(..) => (),
|
||||||
_ => return self.panic("repeat requires a number"),
|
_ => return self.panic(PanicInfo::RepeatNumberType(cond.clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pop => {
|
Pop => {
|
||||||
|
@ -358,7 +331,7 @@ impl Vm {
|
||||||
}
|
}
|
||||||
PanicIfNoMatch => {
|
PanicIfNoMatch => {
|
||||||
if !self.matches {
|
if !self.matches {
|
||||||
return self.panic("no match");
|
return self.panic(PanicInfo::NoMatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MatchConstant => {
|
MatchConstant => {
|
||||||
|
@ -434,14 +407,14 @@ impl Vm {
|
||||||
self.push(member.clone());
|
self.push(member.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return self.panic("internal error: expected tuple"),
|
_ => return self.panic(PanicInfo::Internal("expected tuple")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
LoadSplattedTuple => {
|
LoadSplattedTuple => {
|
||||||
let load_len = self.read() as usize;
|
let load_len = self.read() as usize;
|
||||||
let tuple = self.get_scrutinee();
|
let tuple = self.get_scrutinee();
|
||||||
let Value::Tuple(members) = tuple else {
|
let Value::Tuple(members) = tuple else {
|
||||||
return self.panic("internal error: expected tuple");
|
return self.panic(PanicInfo::Internal("expected tuple"));
|
||||||
};
|
};
|
||||||
for i in 0..load_len - 1 {
|
for i in 0..load_len - 1 {
|
||||||
self.push(members[i].clone());
|
self.push(members[i].clone());
|
||||||
|
@ -458,8 +431,9 @@ impl Vm {
|
||||||
AppendList => {
|
AppendList => {
|
||||||
let value = self.pop();
|
let value = self.pop();
|
||||||
let list = self.pop();
|
let list = self.pop();
|
||||||
let Value::List(mut list) = list else {
|
let mut list = match list {
|
||||||
return self.panic("only lists may be splatted into lists");
|
Value::List(mut list) => list,
|
||||||
|
not_list => return self.panic(PanicInfo::ListSplat(not_list.clone())),
|
||||||
};
|
};
|
||||||
list.push_back(value);
|
list.push_back(value);
|
||||||
self.push(Value::List(list));
|
self.push(Value::List(list));
|
||||||
|
@ -470,8 +444,9 @@ impl Vm {
|
||||||
let Value::List(mut target) = target else {
|
let Value::List(mut target) = target else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let Value::List(splatted) = splatted else {
|
let splatted = match splatted {
|
||||||
return self.panic("only lists may be splatted into lists");
|
Value::List(list) => list,
|
||||||
|
not_list => return self.panic(PanicInfo::ListSplat(not_list.clone())),
|
||||||
};
|
};
|
||||||
target.append(*splatted);
|
target.append(*splatted);
|
||||||
self.push(Value::List(target));
|
self.push(Value::List(target));
|
||||||
|
@ -503,14 +478,14 @@ impl Vm {
|
||||||
self.push(member.clone());
|
self.push(member.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return self.panic("internal error: expected list"),
|
_ => return self.panic(PanicInfo::Internal("expected list")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
LoadSplattedList => {
|
LoadSplattedList => {
|
||||||
let loaded_len = self.read() as usize;
|
let loaded_len = self.read() as usize;
|
||||||
let list = self.get_scrutinee();
|
let list = self.get_scrutinee();
|
||||||
let Value::List(members) = list else {
|
let Value::List(members) = list else {
|
||||||
return self.panic("internal error: expected list");
|
return self.panic(PanicInfo::Internal("expected list"));
|
||||||
};
|
};
|
||||||
for i in 0..loaded_len - 1 {
|
for i in 0..loaded_len - 1 {
|
||||||
self.push(members[i].clone());
|
self.push(members[i].clone());
|
||||||
|
@ -533,11 +508,13 @@ impl Vm {
|
||||||
self.push(Value::Dict(dict));
|
self.push(Value::Dict(dict));
|
||||||
}
|
}
|
||||||
ConcatDict => {
|
ConcatDict => {
|
||||||
let Value::Dict(splatted) = self.pop() else {
|
let splatted = match self.pop() {
|
||||||
return self.panic("only dicts may be splatted into dicts");
|
Value::Dict(dict) => dict,
|
||||||
|
not_dict => return self.panic(PanicInfo::DictSplat(not_dict.clone())),
|
||||||
};
|
};
|
||||||
let Value::Dict(target) = self.pop() else {
|
let target = match self.pop() {
|
||||||
unreachable!()
|
Value::Dict(dict) => dict,
|
||||||
|
not_dict => return self.panic(PanicInfo::Internal("expected dict")),
|
||||||
};
|
};
|
||||||
let union = splatted.union(*target);
|
let union = splatted.union(*target);
|
||||||
self.push(Value::Dict(Box::new(union)));
|
self.push(Value::Dict(Box::new(union)));
|
||||||
|
@ -547,15 +524,12 @@ impl Vm {
|
||||||
let dict = match self.get_value_at(dict_idx) {
|
let dict = match self.get_value_at(dict_idx) {
|
||||||
Value::Dict(dict) => dict,
|
Value::Dict(dict) => dict,
|
||||||
value => {
|
value => {
|
||||||
println!(
|
|
||||||
"internal Ludus error in function {}",
|
|
||||||
self.frame.function.as_fn().name()
|
|
||||||
);
|
|
||||||
unreachable!("expected dict, got {value}")
|
unreachable!("expected dict, got {value}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let Value::Keyword(key) = self.pop() else {
|
let key = match self.pop() {
|
||||||
unreachable!("expected keyword, got something else")
|
Value::Keyword(key) => key,
|
||||||
|
not_keyword => unreachable!("expected keyword, got {not_keyword}"),
|
||||||
};
|
};
|
||||||
let value = dict.get(&key).unwrap_or(&Value::Nil);
|
let value = dict.get(&key).unwrap_or(&Value::Nil);
|
||||||
self.push(value.clone());
|
self.push(value.clone());
|
||||||
|
@ -622,31 +596,22 @@ impl Vm {
|
||||||
let type_of = Value::Keyword(val.type_of());
|
let type_of = Value::Keyword(val.type_of());
|
||||||
self.push(type_of);
|
self.push(type_of);
|
||||||
}
|
}
|
||||||
ToInt => {
|
ToInt => match self.pop() {
|
||||||
let val = self.pop();
|
Value::Number(x) => self.push(Value::Number(x as usize as f64)),
|
||||||
if let Value::Number(x) = val {
|
not_number => return self.panic(PanicInfo::RepeatNumberType(not_number)),
|
||||||
self.push(Value::Number(x as usize as f64));
|
},
|
||||||
} else {
|
Decrement => match self.pop() {
|
||||||
return self.panic("repeat requires a number");
|
Value::Number(x) => self.push(Value::Number(x - 1.0)),
|
||||||
}
|
not_number => return self.panic(PanicInfo::DecType(not_number)),
|
||||||
}
|
},
|
||||||
Decrement => {
|
|
||||||
let val = self.pop();
|
|
||||||
if let Value::Number(x) = val {
|
|
||||||
self.push(Value::Number(x - 1.0));
|
|
||||||
} else {
|
|
||||||
return self.panic("you may only decrement a number");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Duplicate => {
|
Duplicate => {
|
||||||
self.push(self.peek().clone());
|
self.push(self.peek().clone());
|
||||||
}
|
}
|
||||||
MatchDepth => {
|
MatchDepth => {
|
||||||
self.match_depth = self.read();
|
self.match_depth = self.read();
|
||||||
}
|
}
|
||||||
PanicNoWhen | PanicNoMatch => {
|
PanicNoWhen => return self.panic(PanicInfo::WhenFail),
|
||||||
return self.panic("no match");
|
PanicNoMatch => return self.panic(PanicInfo::NoMatch),
|
||||||
}
|
|
||||||
Eq => {
|
Eq => {
|
||||||
let first = self.pop();
|
let first = self.pop();
|
||||||
let second = self.pop();
|
let second = self.pop();
|
||||||
|
@ -659,65 +624,61 @@ impl Vm {
|
||||||
Add => {
|
Add => {
|
||||||
let first = self.pop();
|
let first = self.pop();
|
||||||
let second = self.pop();
|
let second = self.pop();
|
||||||
if let (Value::Number(x), Value::Number(y)) = (first, second) {
|
match (first, second) {
|
||||||
self.push(Value::Number(x + y))
|
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(x + y)),
|
||||||
} else {
|
(x, y) => return self.panic(PanicInfo::AddType(y, x)),
|
||||||
return self.panic("`add` requires two numbers");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Sub => {
|
Sub => {
|
||||||
let first = self.pop();
|
let first = self.pop();
|
||||||
let second = self.pop();
|
let second = self.pop();
|
||||||
if let (Value::Number(x), Value::Number(y)) = (first, second) {
|
match (first, second) {
|
||||||
self.push(Value::Number(y - x))
|
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(y - x)),
|
||||||
} else {
|
(x, y) => return self.panic(PanicInfo::SubType(y, x)),
|
||||||
return self.panic("`sub` requires two numbers");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mult => {
|
Mult => {
|
||||||
let first = self.pop();
|
let first = self.pop();
|
||||||
let second = self.pop();
|
let second = self.pop();
|
||||||
if let (Value::Number(x), Value::Number(y)) = (first, second) {
|
match (first, second) {
|
||||||
self.push(Value::Number(x * y))
|
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(x * y)),
|
||||||
} else {
|
(x, y) => return self.panic(PanicInfo::MultType(y, x)),
|
||||||
return self.panic("`mult` requires two numbers");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Div => {
|
Div => {
|
||||||
let first = self.pop();
|
let first = self.pop();
|
||||||
let second = self.pop();
|
let second = self.pop();
|
||||||
if let (Value::Number(x), Value::Number(y)) = (first, second) {
|
match (first, second) {
|
||||||
if x == 0.0 {
|
(Value::Number(0.0), Value::Number(_)) => {
|
||||||
return self.panic("division by 0");
|
return self.panic(PanicInfo::DivisionByZero)
|
||||||
}
|
}
|
||||||
self.push(Value::Number(y / x))
|
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(y / x)),
|
||||||
} else {
|
(x, y) => return self.panic(PanicInfo::DivType(y, x)),
|
||||||
return self.panic("`div` requires two numbers");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unbox => {
|
Unbox => {
|
||||||
let the_box = self.pop();
|
let the_box = self.pop();
|
||||||
let inner = if let Value::Box(b) = the_box {
|
let inner = match the_box {
|
||||||
b.borrow().clone()
|
Value::Box(b) => b.borrow().clone(),
|
||||||
} else {
|
not_a_box => return self.panic(PanicInfo::UnboxType(not_a_box)),
|
||||||
return self.panic("`unbox` requires a box");
|
|
||||||
};
|
};
|
||||||
self.push(inner);
|
self.push(inner);
|
||||||
}
|
}
|
||||||
BoxStore => {
|
BoxStore => {
|
||||||
let new_value = self.pop();
|
let new_value = self.pop();
|
||||||
let the_box = self.pop();
|
let the_box = self.pop();
|
||||||
if let Value::Box(b) = the_box {
|
match the_box {
|
||||||
b.replace(new_value.clone());
|
Value::Box(b) => {
|
||||||
} else {
|
b.replace(new_value.clone());
|
||||||
return self.panic("`store` requires a box");
|
}
|
||||||
|
not_a_box => return self.panic(PanicInfo::StoreType(not_a_box)),
|
||||||
}
|
}
|
||||||
self.push(new_value);
|
self.push(new_value);
|
||||||
}
|
}
|
||||||
Assert => {
|
Assert => {
|
||||||
let value = self.stack.last().unwrap();
|
let value = self.stack.last().unwrap();
|
||||||
if let Value::Nil | Value::False = value {
|
if let Value::Nil | Value::False = value {
|
||||||
return self.panic("asserted falsy value");
|
return self.panic(PanicInfo::Assert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Get => {
|
Get => {
|
||||||
|
@ -728,7 +689,7 @@ impl Vm {
|
||||||
d.as_ref().get(&k).unwrap_or(&Value::Nil).clone()
|
d.as_ref().get(&k).unwrap_or(&Value::Nil).clone()
|
||||||
}
|
}
|
||||||
(Value::Keyword(_), _) => Value::Nil,
|
(Value::Keyword(_), _) => Value::Nil,
|
||||||
_ => return self.panic("keys must be keywords"),
|
(not_a_keyword, _) => return self.panic(PanicInfo::GetType(not_a_keyword)),
|
||||||
};
|
};
|
||||||
self.push(value);
|
self.push(value);
|
||||||
}
|
}
|
||||||
|
@ -742,8 +703,10 @@ impl Vm {
|
||||||
(Value::Tuple(t), Value::Number(i)) => {
|
(Value::Tuple(t), Value::Number(i)) => {
|
||||||
t.get(i as usize).unwrap_or(&Value::Nil).clone()
|
t.get(i as usize).unwrap_or(&Value::Nil).clone()
|
||||||
}
|
}
|
||||||
(_, Value::Number(_)) => Value::Nil,
|
(coll, Value::Number(_)) => {
|
||||||
_ => return self.panic("indexes must be numbers"),
|
return self.panic(PanicInfo::AtCollectionType(coll))
|
||||||
|
}
|
||||||
|
(coll, idx) => return self.panic(PanicInfo::AtIdxType(idx)),
|
||||||
};
|
};
|
||||||
self.push(value);
|
self.push(value);
|
||||||
}
|
}
|
||||||
|
@ -756,8 +719,8 @@ impl Vm {
|
||||||
self.push(negated);
|
self.push(negated);
|
||||||
}
|
}
|
||||||
Panic => {
|
Panic => {
|
||||||
let msg = self.pop().show();
|
let msg = self.pop();
|
||||||
return self.panic_with(msg);
|
return self.panic(PanicInfo::Explicit(msg));
|
||||||
}
|
}
|
||||||
EmptyString => {
|
EmptyString => {
|
||||||
self.push(Value::String(Rc::new("".to_string())));
|
self.push(Value::String(Rc::new("".to_string())));
|
||||||
|
@ -786,7 +749,7 @@ impl Vm {
|
||||||
let arity = self.read();
|
let arity = self.read();
|
||||||
let the_fn = self.pop();
|
let the_fn = self.pop();
|
||||||
let Value::Fn(ref inner) = the_fn else {
|
let Value::Fn(ref inner) = the_fn else {
|
||||||
return self.panic("only functions may be partially applied");
|
return self.panic(PanicInfo::PartialType(the_fn));
|
||||||
};
|
};
|
||||||
let args = self.stack.split_off(self.stack.len() - arity as usize);
|
let args = self.stack.split_off(self.stack.len() - arity as usize);
|
||||||
let partial = crate::value::Partial {
|
let partial = crate::value::Partial {
|
||||||
|
@ -810,15 +773,13 @@ impl Vm {
|
||||||
|
|
||||||
match called {
|
match called {
|
||||||
Value::Fn(_) => {
|
Value::Fn(_) => {
|
||||||
|
let args = self.stack.split_off(self.stack.len() - arity as usize);
|
||||||
if !called.as_fn().accepts(arity) {
|
if !called.as_fn().accepts(arity) {
|
||||||
return self.panic_with(format!(
|
return self.panic(PanicInfo::ArityMismatch(called, args));
|
||||||
"wrong number of arguments to {} passing {arity} args",
|
|
||||||
called.show()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
// first put the arguments in the register
|
// first put the arguments in the register
|
||||||
for i in 0..arity as usize {
|
for i in 0..arity as usize {
|
||||||
self.return_register[arity as usize - i - 1] = self.pop();
|
self.return_register[i] = args[i].clone();
|
||||||
}
|
}
|
||||||
// self.print_stack();
|
// self.print_stack();
|
||||||
|
|
||||||
|
@ -849,7 +810,7 @@ impl Vm {
|
||||||
|
|
||||||
let mut frame = CallFrame {
|
let mut frame = CallFrame {
|
||||||
function: called,
|
function: called,
|
||||||
arity,
|
args,
|
||||||
stack_base: self.stack.len() - arity as usize,
|
stack_base: self.stack.len() - arity as usize,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
};
|
};
|
||||||
|
@ -874,7 +835,9 @@ impl Vm {
|
||||||
let x = &self.pop();
|
let x = &self.pop();
|
||||||
f(x, y, z)
|
f(x, y, z)
|
||||||
}
|
}
|
||||||
_ => return self.panic("internal ludus error"),
|
_ => {
|
||||||
|
return self.panic(PanicInfo::Internal("no match calling base"))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// // algo:
|
// // algo:
|
||||||
// // clear the stack
|
// // clear the stack
|
||||||
|
@ -891,19 +854,19 @@ impl Vm {
|
||||||
}
|
}
|
||||||
Value::Partial(partial) => {
|
Value::Partial(partial) => {
|
||||||
let last_arg = self.pop();
|
let last_arg = self.pop();
|
||||||
let args = &partial.args;
|
let mut args = partial.args.clone();
|
||||||
for arg in args {
|
let placeholder_idx =
|
||||||
if *arg == Value::Nothing {
|
args.iter().position(|arg| *arg == Value::Nothing).unwrap();
|
||||||
self.push(last_arg.clone());
|
args[placeholder_idx] = last_arg;
|
||||||
} else {
|
for arg in args.iter() {
|
||||||
self.push(arg.clone());
|
self.push(arg.clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let the_fn = partial.function.clone();
|
let the_fn = partial.function.clone();
|
||||||
|
let arity = args.len();
|
||||||
let mut frame = CallFrame {
|
let mut frame = CallFrame {
|
||||||
function: the_fn,
|
function: the_fn,
|
||||||
arity: args.len() as u8,
|
args,
|
||||||
stack_base: self.stack.len() - args.len(),
|
stack_base: self.stack.len() - arity,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -913,9 +876,7 @@ impl Vm {
|
||||||
self.call_stack.push(frame);
|
self.call_stack.push(frame);
|
||||||
self.ip = 0;
|
self.ip = 0;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => return self.panic(PanicInfo::CallType(called)),
|
||||||
return self.panic_with(format!("{} is not a function", called.show()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Call => {
|
Call => {
|
||||||
|
@ -930,10 +891,8 @@ impl Vm {
|
||||||
match called {
|
match called {
|
||||||
Value::Fn(_) => {
|
Value::Fn(_) => {
|
||||||
if !called.as_fn().accepts(arity) {
|
if !called.as_fn().accepts(arity) {
|
||||||
return self.panic_with(format!(
|
let args = self.stack.split_off(self.stack.len() - arity as usize);
|
||||||
"wrong number of arguments to {} passing {arity} args",
|
return self.panic(PanicInfo::ArityMismatch(called, args));
|
||||||
called.show()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let splat_arity = called.as_fn().splat_arity();
|
let splat_arity = called.as_fn().splat_arity();
|
||||||
if splat_arity > 0 && arity >= splat_arity {
|
if splat_arity > 0 && arity >= splat_arity {
|
||||||
|
@ -948,9 +907,13 @@ impl Vm {
|
||||||
} else {
|
} else {
|
||||||
arity
|
arity
|
||||||
};
|
};
|
||||||
|
let mut args = vec![];
|
||||||
|
for offset in (0..arity as usize).rev() {
|
||||||
|
args.push(self.stack[self.stack.len() - offset - 1].clone());
|
||||||
|
}
|
||||||
let mut frame = CallFrame {
|
let mut frame = CallFrame {
|
||||||
function: called,
|
function: called,
|
||||||
arity,
|
args,
|
||||||
stack_base: self.stack.len() - arity as usize,
|
stack_base: self.stack.len() - arity as usize,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
};
|
};
|
||||||
|
@ -976,25 +939,25 @@ impl Vm {
|
||||||
let x = &self.pop();
|
let x = &self.pop();
|
||||||
f(x, y, z)
|
f(x, y, z)
|
||||||
}
|
}
|
||||||
_ => return self.panic("internal ludus error"),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
self.push(value);
|
self.push(value);
|
||||||
}
|
}
|
||||||
Value::Partial(partial) => {
|
Value::Partial(partial) => {
|
||||||
let last_arg = self.pop();
|
let last_arg = self.pop();
|
||||||
let args = &partial.args;
|
let mut args = partial.args.clone();
|
||||||
for arg in args {
|
let placeholder_idx =
|
||||||
if *arg == Value::Nothing {
|
args.iter().position(|arg| *arg == Value::Nothing).unwrap();
|
||||||
self.push(last_arg.clone());
|
args[placeholder_idx] = last_arg;
|
||||||
} else {
|
for arg in args.iter() {
|
||||||
self.push(arg.clone());
|
self.push(arg.clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let the_fn = partial.function.clone();
|
let the_fn = partial.function.clone();
|
||||||
|
let arity = args.len();
|
||||||
let mut frame = CallFrame {
|
let mut frame = CallFrame {
|
||||||
function: the_fn,
|
function: the_fn,
|
||||||
arity: args.len() as u8,
|
args,
|
||||||
stack_base: self.stack.len() - args.len(),
|
stack_base: self.stack.len() - arity,
|
||||||
ip: 0,
|
ip: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1004,9 +967,7 @@ impl Vm {
|
||||||
self.call_stack.push(frame);
|
self.call_stack.push(frame);
|
||||||
self.ip = 0;
|
self.ip = 0;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => return self.panic(PanicInfo::CallType(called)),
|
||||||
return self.panic_with(format!("{} is not a function", called.show()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Return => {
|
Return => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user