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 {
"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

View File

@ -496,9 +496,9 @@ Here's a list of things that need doing:
- [ ] test this
- I did not fix it.
* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard?
* [ ] This is in the service of getting turtle graphics working
* [x] This is in the service of getting turtle graphics working
* Other forms in the language need help:
* [ ] repeat needs its stack discipline updated, it currently crashes the compiler
* [x] repeat needs its stack discipline updated, it currently crashes the compiler
### More closure problems
#### 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 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 {
fd! (2)
rt! (inv (20))
let foo = :foo
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::parser::Ast;
use crate::spans::Spanned;
use crate::value::Value;
use chumsky::prelude::SimpleSpan;
use imbl::HashMap;
use num_traits::FromPrimitive;
use regex::Regex;
@ -14,13 +17,32 @@ pub struct StrPattern {
pub struct Chunk {
pub constants: Vec<Value>,
pub bytecode: Vec<u8>,
pub nodes: Vec<&'static Ast>,
pub spans: Vec<SimpleSpan>,
pub keywords: Vec<&'static str>,
pub string_patterns: Vec<StrPattern>,
pub env: HashMap<&'static str, Value>,
pub msgs: Vec<String>,
pub src: &'static str,
pub input: &'static str,
}
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) {
let op = Op::from_u8(self.bytecode[*i]).unwrap();
use Op::*;

View File

@ -62,18 +62,17 @@ fn get_builtin(name: &str, arity: usize) -> Option<Op> {
None
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Compiler {
pub chunk: Chunk,
pub bindings: Vec<Binding>,
pub scope_depth: isize,
pub match_depth: usize,
pub stack_depth: usize,
pub spans: Vec<SimpleSpan>,
pub nodes: Vec<&'static Ast>,
pub ast: &'static Ast,
pub span: SimpleSpan,
pub src: &'static str,
pub input: &'static str,
pub name: &'static str,
pub depth: usize,
pub upvalues: Vec<&'static str>,
@ -101,18 +100,13 @@ impl Compiler {
ast: &'static Spanned<Ast>,
name: &'static str,
src: &'static str,
input: &'static str,
depth: usize,
env: imbl::HashMap<&'static str, Value>,
debug: bool,
) -> Compiler {
let chunk = Chunk {
constants: vec![],
bytecode: vec![],
keywords: vec![],
string_patterns: vec![],
env,
msgs: vec![],
};
let chunk = Chunk::new(env, src, input);
Compiler {
chunk,
bindings: vec![],
@ -120,13 +114,12 @@ impl Compiler {
scope_depth: -1,
match_depth: 0,
stack_depth: 0,
spans: vec![],
nodes: vec![],
ast: &ast.0,
span: ast.1,
loop_info: vec![],
upvalues: vec![],
src,
input,
name,
tail_pos: false,
debug,
@ -222,12 +215,14 @@ impl Compiler {
fn emit_op(&mut self, op: Op) {
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) {
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 {
@ -1096,6 +1091,7 @@ impl Compiler {
clause,
name,
self.src,
self.input,
self.depth + 1,
self.chunk.env.clone(),
self.debug,

View File

@ -1,9 +1,90 @@
// use crate::process::{LErr, Trace};
use crate::parser::Ast;
use crate::spans::Spanned;
use crate::validator::VErr;
use crate::value::Value;
use crate::vm::CallFrame;
use ariadne::{sources, Color, Label, Report, ReportKind};
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) {
// let mut srcs = HashSet::new();
// let mut stack = vec![];

View File

@ -3,8 +3,8 @@ use imbl::HashMap;
use std::env;
use std::fs;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_SCRIPT_COMPILE: bool = true;
const DEBUG_SCRIPT_RUN: bool = true;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
@ -73,6 +73,7 @@ pub fn prelude() -> HashMap<&'static str, Value> {
parsed,
"prelude",
PRELUDE,
"prelude",
0,
HashMap::new(),
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();
if !lex_errs.is_empty() {
println!("{:?}", lex_errs);
@ -115,7 +116,7 @@ pub fn run(src: &'static str) {
let prelude = prelude();
// 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();
if !validator.errors.is_empty() {
@ -124,7 +125,7 @@ pub fn run(src: &'static str) {
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();
// compiler.emit_constant(base);
// compiler.bind("base");
@ -147,7 +148,10 @@ pub fn run(src: &'static str) {
let result = vm.run();
let output = match result {
Ok(val) => val.to_string(),
Err(panic) => format!("Ludus panicked! {panic}"),
Err(panic) => {
panic.report();
std::process::exit(1)
}
};
if DEBUG_SCRIPT_RUN {
vm.print_stack();
@ -180,11 +184,28 @@ pub fn ld_fmt(src: &'static str) -> Result<String, String> {
}
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak();
match ld_fmt(src) {
Ok(src) => println!("{}", src),
Err(msg) => println!("Could not format source with errors:\n{}", msg),
// env::set_var("RUST_BACKTRACE", "1");
// let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak();
// run(src, "sandbox.ld");
let str = "foo bar baz\nquux frob\nthing thing thing";
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 struct_scalpel::Dissectible;
// #[derive(Clone, Debug, PartialEq)]
// pub struct WhenClause {
// pub cond: Spanned<Ast>,
// pub body: Spanned<Ast>,
// }
// impl fmt::Display for WhenClause {
// fn fmt(self: &WhenClause, f: &mut fmt::Formatter) -> fmt::Result {
// write!(f, "cond: {}, body: {}", self.cond.0, self.body.0)
// }
// }
// #[derive(Clone, Debug, PartialEq)]
// pub struct MatchClause {
// pub patt: Spanned<Pattern>,
// pub guard: Option<Spanned<Ast>>,
// pub body: Spanned<Ast>,
// }
// impl fmt::Display for MatchClause {
// fn fmt(self: &MatchClause, f: &mut fmt::Formatter) -> fmt::Result {
// write!(
// f,
// "pattern: {}, guard: {:?} body: {}",
// self.patt.0, self.guard, self.body.0
// )
// }
// }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StringPart {
Data(String),
@ -219,14 +190,12 @@ impl Ast {
.collect::<Vec<_>>()
.join("\n ")
),
FnBody(clauses) => format!(
"{}",
clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n ")
),
FnBody(clauses) => clauses
.iter()
.map(|(clause, _)| clause.show())
.collect::<Vec<_>>()
.join("\n ")
.to_string(),
Fn(name, body, doc) => {
let mut out = format!("fn {name} {{\n");
if let Some(doc) = doc {
@ -267,7 +236,7 @@ impl Ast {
.join(", ")
),
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() {
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 {
if c.is_ascii_alphanumeric() {
return true;

269
src/vm.rs
View File

@ -1,8 +1,7 @@
use crate::base::BaseFn;
use crate::chunk::Chunk;
use crate::errors::{Panic, PanicInfo};
use crate::op::Op;
use crate::parser::Ast;
use crate::spans::Spanned;
use crate::value::{LFn, Value};
use imbl::{HashMap, Vector};
use num_traits::FromPrimitive;
@ -12,43 +11,11 @@ use std::mem::swap;
use std::rc::Rc;
#[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 function: Value,
pub arity: u8,
pub stack_base: usize,
pub ip: usize,
pub args: Vec<Value>,
}
impl CallFrame {
@ -56,7 +23,7 @@ impl CallFrame {
let Value::Fn(ref function) = self.function else {
unreachable!()
};
function.chunk(self.arity)
function.chunk(self.args.len() as u8)
}
}
@ -69,7 +36,7 @@ impl fmt::Display for CallFrame {
f,
"CallFrame: {}/{} @ {}",
function.name(),
self.arity,
self.args.len(),
self.ip
)
}
@ -108,7 +75,7 @@ impl Vm {
function: base_fn.clone(),
stack_base: 0,
ip: 0,
arity: 0,
args: vec![],
};
Vm {
stack: vec![],
@ -186,15 +153,21 @@ impl Vm {
stack
}
pub fn panic(&mut self, msg: &'static str) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
self.result = Some(Err(Panic::String(msg)));
pub fn panic(&mut self, info: PanicInfo) {
let mut frame = self.frame.clone();
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) {
let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
self.result = Some(Err(Panic::String(msg)));
}
// pub fn panic_with(&mut self, msg: String) {
// let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack());
// self.result = Some(Err(Panic::String(msg)));
// }
fn get_value_at(&mut self, idx: u8) -> Value {
let idx = idx as usize;
@ -280,7 +253,7 @@ impl Vm {
match cond {
Value::Number(x) if x <= 0.0 => self.ip += jump_len,
Value::Number(..) => (),
_ => return self.panic("repeat requires a number"),
_ => return self.panic(PanicInfo::RepeatNumberType(cond.clone())),
}
}
Pop => {
@ -358,7 +331,7 @@ impl Vm {
}
PanicIfNoMatch => {
if !self.matches {
return self.panic("no match");
return self.panic(PanicInfo::NoMatch);
}
}
MatchConstant => {
@ -434,14 +407,14 @@ impl Vm {
self.push(member.clone());
}
}
_ => return self.panic("internal error: expected tuple"),
_ => return self.panic(PanicInfo::Internal("expected tuple")),
};
}
LoadSplattedTuple => {
let load_len = self.read() as usize;
let tuple = self.get_scrutinee();
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 {
self.push(members[i].clone());
@ -458,8 +431,9 @@ impl Vm {
AppendList => {
let value = self.pop();
let list = self.pop();
let Value::List(mut list) = list else {
return self.panic("only lists may be splatted into lists");
let mut list = match list {
Value::List(mut list) => list,
not_list => return self.panic(PanicInfo::ListSplat(not_list.clone())),
};
list.push_back(value);
self.push(Value::List(list));
@ -470,8 +444,9 @@ impl Vm {
let Value::List(mut target) = target else {
unreachable!()
};
let Value::List(splatted) = splatted else {
return self.panic("only lists may be splatted into lists");
let splatted = match splatted {
Value::List(list) => list,
not_list => return self.panic(PanicInfo::ListSplat(not_list.clone())),
};
target.append(*splatted);
self.push(Value::List(target));
@ -503,14 +478,14 @@ impl Vm {
self.push(member.clone());
}
}
_ => return self.panic("internal error: expected list"),
_ => return self.panic(PanicInfo::Internal("expected list")),
};
}
LoadSplattedList => {
let loaded_len = self.read() as usize;
let list = self.get_scrutinee();
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 {
self.push(members[i].clone());
@ -533,11 +508,13 @@ impl Vm {
self.push(Value::Dict(dict));
}
ConcatDict => {
let Value::Dict(splatted) = self.pop() else {
return self.panic("only dicts may be splatted into dicts");
let splatted = match self.pop() {
Value::Dict(dict) => dict,
not_dict => return self.panic(PanicInfo::DictSplat(not_dict.clone())),
};
let Value::Dict(target) = self.pop() else {
unreachable!()
let target = match self.pop() {
Value::Dict(dict) => dict,
not_dict => return self.panic(PanicInfo::Internal("expected dict")),
};
let union = splatted.union(*target);
self.push(Value::Dict(Box::new(union)));
@ -547,15 +524,12 @@ impl Vm {
let dict = match self.get_value_at(dict_idx) {
Value::Dict(dict) => dict,
value => {
println!(
"internal Ludus error in function {}",
self.frame.function.as_fn().name()
);
unreachable!("expected dict, got {value}")
}
};
let Value::Keyword(key) = self.pop() else {
unreachable!("expected keyword, got something else")
let key = match self.pop() {
Value::Keyword(key) => key,
not_keyword => unreachable!("expected keyword, got {not_keyword}"),
};
let value = dict.get(&key).unwrap_or(&Value::Nil);
self.push(value.clone());
@ -622,31 +596,22 @@ impl Vm {
let type_of = Value::Keyword(val.type_of());
self.push(type_of);
}
ToInt => {
let val = self.pop();
if let Value::Number(x) = val {
self.push(Value::Number(x as usize as f64));
} else {
return self.panic("repeat requires a 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");
}
}
ToInt => match self.pop() {
Value::Number(x) => self.push(Value::Number(x as usize as f64)),
not_number => return self.panic(PanicInfo::RepeatNumberType(not_number)),
},
Decrement => match self.pop() {
Value::Number(x) => self.push(Value::Number(x - 1.0)),
not_number => return self.panic(PanicInfo::DecType(not_number)),
},
Duplicate => {
self.push(self.peek().clone());
}
MatchDepth => {
self.match_depth = self.read();
}
PanicNoWhen | PanicNoMatch => {
return self.panic("no match");
}
PanicNoWhen => return self.panic(PanicInfo::WhenFail),
PanicNoMatch => return self.panic(PanicInfo::NoMatch),
Eq => {
let first = self.pop();
let second = self.pop();
@ -659,65 +624,61 @@ impl Vm {
Add => {
let first = self.pop();
let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.push(Value::Number(x + y))
} else {
return self.panic("`add` requires two numbers");
match (first, second) {
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(x + y)),
(x, y) => return self.panic(PanicInfo::AddType(y, x)),
}
}
Sub => {
let first = self.pop();
let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.push(Value::Number(y - x))
} else {
return self.panic("`sub` requires two numbers");
match (first, second) {
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(y - x)),
(x, y) => return self.panic(PanicInfo::SubType(y, x)),
}
}
Mult => {
let first = self.pop();
let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.push(Value::Number(x * y))
} else {
return self.panic("`mult` requires two numbers");
match (first, second) {
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(x * y)),
(x, y) => return self.panic(PanicInfo::MultType(y, x)),
}
}
Div => {
let first = self.pop();
let second = self.pop();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
if x == 0.0 {
return self.panic("division by 0");
match (first, second) {
(Value::Number(0.0), Value::Number(_)) => {
return self.panic(PanicInfo::DivisionByZero)
}
self.push(Value::Number(y / x))
} else {
return self.panic("`div` requires two numbers");
(Value::Number(x), Value::Number(y)) => self.push(Value::Number(y / x)),
(x, y) => return self.panic(PanicInfo::DivType(y, x)),
}
}
Unbox => {
let the_box = self.pop();
let inner = if let Value::Box(b) = the_box {
b.borrow().clone()
} else {
return self.panic("`unbox` requires a box");
let inner = match the_box {
Value::Box(b) => b.borrow().clone(),
not_a_box => return self.panic(PanicInfo::UnboxType(not_a_box)),
};
self.push(inner);
}
BoxStore => {
let new_value = self.pop();
let the_box = self.pop();
if let Value::Box(b) = the_box {
b.replace(new_value.clone());
} else {
return self.panic("`store` requires a box");
match the_box {
Value::Box(b) => {
b.replace(new_value.clone());
}
not_a_box => return self.panic(PanicInfo::StoreType(not_a_box)),
}
self.push(new_value);
}
Assert => {
let value = self.stack.last().unwrap();
if let Value::Nil | Value::False = value {
return self.panic("asserted falsy value");
return self.panic(PanicInfo::Assert);
}
}
Get => {
@ -728,7 +689,7 @@ impl Vm {
d.as_ref().get(&k).unwrap_or(&Value::Nil).clone()
}
(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);
}
@ -742,8 +703,10 @@ impl Vm {
(Value::Tuple(t), Value::Number(i)) => {
t.get(i as usize).unwrap_or(&Value::Nil).clone()
}
(_, Value::Number(_)) => Value::Nil,
_ => return self.panic("indexes must be numbers"),
(coll, Value::Number(_)) => {
return self.panic(PanicInfo::AtCollectionType(coll))
}
(coll, idx) => return self.panic(PanicInfo::AtIdxType(idx)),
};
self.push(value);
}
@ -756,8 +719,8 @@ impl Vm {
self.push(negated);
}
Panic => {
let msg = self.pop().show();
return self.panic_with(msg);
let msg = self.pop();
return self.panic(PanicInfo::Explicit(msg));
}
EmptyString => {
self.push(Value::String(Rc::new("".to_string())));
@ -786,7 +749,7 @@ impl Vm {
let arity = self.read();
let the_fn = self.pop();
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 partial = crate::value::Partial {
@ -810,15 +773,13 @@ impl Vm {
match called {
Value::Fn(_) => {
let args = self.stack.split_off(self.stack.len() - arity as usize);
if !called.as_fn().accepts(arity) {
return self.panic_with(format!(
"wrong number of arguments to {} passing {arity} args",
called.show()
));
return self.panic(PanicInfo::ArityMismatch(called, args));
}
// first put the arguments in the register
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();
@ -849,7 +810,7 @@ impl Vm {
let mut frame = CallFrame {
function: called,
arity,
args,
stack_base: self.stack.len() - arity as usize,
ip: 0,
};
@ -874,7 +835,9 @@ impl Vm {
let x = &self.pop();
f(x, y, z)
}
_ => return self.panic("internal ludus error"),
_ => {
return self.panic(PanicInfo::Internal("no match calling base"))
}
};
// // algo:
// // clear the stack
@ -891,19 +854,19 @@ impl Vm {
}
Value::Partial(partial) => {
let last_arg = self.pop();
let args = &partial.args;
for arg in args {
if *arg == Value::Nothing {
self.push(last_arg.clone());
} else {
self.push(arg.clone());
}
let mut args = partial.args.clone();
let placeholder_idx =
args.iter().position(|arg| *arg == Value::Nothing).unwrap();
args[placeholder_idx] = last_arg;
for arg in args.iter() {
self.push(arg.clone());
}
let the_fn = partial.function.clone();
let arity = args.len();
let mut frame = CallFrame {
function: the_fn,
arity: args.len() as u8,
stack_base: self.stack.len() - args.len(),
args,
stack_base: self.stack.len() - arity,
ip: 0,
};
@ -913,9 +876,7 @@ impl Vm {
self.call_stack.push(frame);
self.ip = 0;
}
_ => {
return self.panic_with(format!("{} is not a function", called.show()))
}
_ => return self.panic(PanicInfo::CallType(called)),
}
}
Call => {
@ -930,10 +891,8 @@ impl Vm {
match called {
Value::Fn(_) => {
if !called.as_fn().accepts(arity) {
return self.panic_with(format!(
"wrong number of arguments to {} passing {arity} args",
called.show()
));
let args = self.stack.split_off(self.stack.len() - arity as usize);
return self.panic(PanicInfo::ArityMismatch(called, args));
}
let splat_arity = called.as_fn().splat_arity();
if splat_arity > 0 && arity >= splat_arity {
@ -948,9 +907,13 @@ impl Vm {
} else {
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 {
function: called,
arity,
args,
stack_base: self.stack.len() - arity as usize,
ip: 0,
};
@ -976,25 +939,25 @@ impl Vm {
let x = &self.pop();
f(x, y, z)
}
_ => return self.panic("internal ludus error"),
_ => unreachable!(),
};
self.push(value);
}
Value::Partial(partial) => {
let last_arg = self.pop();
let args = &partial.args;
for arg in args {
if *arg == Value::Nothing {
self.push(last_arg.clone());
} else {
self.push(arg.clone());
}
let mut args = partial.args.clone();
let placeholder_idx =
args.iter().position(|arg| *arg == Value::Nothing).unwrap();
args[placeholder_idx] = last_arg;
for arg in args.iter() {
self.push(arg.clone());
}
let the_fn = partial.function.clone();
let arity = args.len();
let mut frame = CallFrame {
function: the_fn,
arity: args.len() as u8,
stack_base: self.stack.len() - args.len(),
args,
stack_base: self.stack.len() - arity,
ip: 0,
};
@ -1004,9 +967,7 @@ impl Vm {
self.call_stack.push(frame);
self.ip = 0;
}
_ => {
return self.panic_with(format!("{} is not a function", called.show()))
}
_ => return self.panic(PanicInfo::CallType(called)),
}
}
Return => {