work on things

This commit is contained in:
Scott Richmond 2025-06-24 21:15:46 -04:00
parent 4f80c500a2
commit 8da5ae6cc4
10 changed files with 878 additions and 1013 deletions

View File

@ -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

View File

@ -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]);
```

View File

@ -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!

File diff suppressed because it is too large Load Diff

View File

@ -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::*;

View File

@ -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,

View File

@ -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![];

View File

@ -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]);
} }

View File

@ -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
"{}",
clauses
.iter() .iter()
.map(|(clause, _)| clause.show()) .map(|(clause, _)| clause.show())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n ") .join("\n ")
), .to_string(),
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;

263
src/vm.rs
View File

@ -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 {
Value::Box(b) => {
b.replace(new_value.clone()); b.replace(new_value.clone());
} else { }
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 => {