work on things
This commit is contained in:
parent
4f80c500a2
commit
8da5ae6cc4
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
```
|
||||
|
||||
|
||||
|
|
22
sandbox.ld
22
sandbox.ld
|
@ -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!
|
||||
|
|
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::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::*;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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![];
|
||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -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]);
|
||||
}
|
||||
|
|
106
src/parser.rs
106
src/parser.rs
|
@ -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
|
||||
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;
|
||||
|
|
263
src/vm.rs
263
src/vm.rs
|
@ -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 {
|
||||
match the_box {
|
||||
Value::Box(b) => {
|
||||
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);
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user