do lots of work

This commit is contained in:
Scott Richmond 2024-12-22 19:07:42 -05:00
parent d4342b0623
commit d943185db8
5 changed files with 382 additions and 61 deletions

View File

@ -131,3 +131,4 @@ That's probably the thing to do. Jesus, Scott.
And **another** thing worth internalizing: every single instruction that's not an explicit push or pop should leave the stack length unchanged. And **another** thing worth internalizing: every single instruction that's not an explicit push or pop should leave the stack length unchanged.
So store and load need always to swap in a `nil` So store and load need always to swap in a `nil`

View File

@ -7,6 +7,9 @@ use num_traits::FromPrimitive;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op { pub enum Op {
Nil,
True,
False,
Constant, Constant,
Jump, Jump,
JumpIfFalse, JumpIfFalse,
@ -14,20 +17,52 @@ pub enum Op {
PushBinding, PushBinding,
Store, Store,
Load, Load,
ResetMatch,
MatchNil,
MatchTrue,
MatchFalse,
MatchWord,
PanicIfNoMatch,
MatchConstant,
MatchTuple,
PushTuple,
PushList,
PushDict,
PushBox,
GetKey,
PanicNoWhen,
} }
impl std::fmt::Display for Op { impl std::fmt::Display for Op {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Op::*; use Op::*;
match self { let rep = match self {
Constant => write!(f, "constant"), Nil => "nil",
Jump => write!(f, "jump"), True => "true",
JumpIfFalse => write!(f, "jump_if_false"), False => "false",
Pop => write!(f, "pop"), Constant => "constant",
PushBinding => write!(f, "push_binding"), Jump => "jump",
Store => write!(f, "store"), JumpIfFalse => "jump_if_false",
Load => write!(f, "load"), Pop => "pop",
} PushBinding => "push_binding",
Store => "store",
Load => "load",
MatchNil => "match_nil",
MatchTrue => "match_true",
MatchFalse => "match_false",
MatchWord => "match_word",
ResetMatch => "reset_match",
PanicIfNoMatch => "panic_if_no_match",
MatchConstant => "match_constant",
MatchTuple => "match_tuple",
PushTuple => "push_tuple",
PushList => "push_list",
PushDict => "push_dict",
PushBox => "push_box",
GetKey => "get_key",
PanicNoWhen => "panic_no_when",
};
write!(f, "{rep}")
} }
} }
@ -86,10 +121,11 @@ impl<'a> Chunk<'a> {
} }
pub fn kw_from(&self, kw: &str) -> Option<Value> { pub fn kw_from(&self, kw: &str) -> Option<Value> {
self.keywords self.kw_index_from(kw).map(Value::Keyword)
.iter() }
.position(|s| *s == kw)
.map(Value::Keyword) pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
self.keywords.iter().position(|s| *s == kw)
} }
pub fn visit(&mut self, node: &'a Spanned<Ast>) { pub fn visit(&mut self, node: &'a Spanned<Ast>) {
@ -118,6 +154,27 @@ impl<'a> Chunk<'a> {
self.spans.push(self.span); self.spans.push(self.span);
} }
fn match_constant(&mut self, val: Value) {
let constant_index = match self.constants.iter().position(|v| *v == val) {
Some(idx) => idx,
None => self.constants.len(),
};
if constant_index > u8::MAX as usize {
panic!(
"internal Ludus compiler error: too many constants in chunk:{}:: {}",
self.span, self.ast
)
}
if constant_index == self.constants.len() {
self.constants.push(val);
}
self.bytecode.push(Op::MatchConstant as u8);
self.spans.push(self.span);
self.bytecode.push(constant_index as u8);
self.spans.push(self.span);
self.bind("*constant");
}
fn emit_op(&mut self, op: Op) { fn emit_op(&mut self, op: Op) {
self.bytecode.push(op as u8); self.bytecode.push(op as u8);
self.spans.push(self.span); self.spans.push(self.span);
@ -133,11 +190,15 @@ impl<'a> Chunk<'a> {
pub fn compile(&mut self) { pub fn compile(&mut self) {
use Ast::*; use Ast::*;
match self.ast { match self.ast {
Nil => self.emit_constant(Value::Nil), Nil => self.emit_op(Op::Nil),
Number(n) => self.emit_constant(Value::Number(*n)), Number(n) => self.emit_constant(Value::Number(*n)),
Boolean(b) => self.emit_constant(if *b { Value::True } else { Value::False }), Boolean(b) => self.emit_op(if *b { Op::True } else { Op::False }),
String(s) => { String(s) => {
let str_index = self.strings.len(); let existing_str = self.strings.iter().position(|e| e == s);
let str_index = match existing_str {
Some(idx) => idx,
None => self.strings.len(),
};
self.strings.push(s); self.strings.push(s);
self.emit_constant(Value::Interned(str_index)); self.emit_constant(Value::Interned(str_index));
} }
@ -147,7 +208,9 @@ impl<'a> Chunk<'a> {
Some(index) => index, Some(index) => index,
None => self.keywords.len(), None => self.keywords.len(),
}; };
self.keywords.push(s); if kw_index == self.keywords.len() {
self.keywords.push(s);
}
self.emit_constant(Value::Keyword(kw_index)); self.emit_constant(Value::Keyword(kw_index));
} }
Block(lines) => { Block(lines) => {
@ -190,10 +253,13 @@ impl<'a> Chunk<'a> {
self.bytecode[jump_idx + 1] = jump_offset as u8; self.bytecode[jump_idx + 1] = jump_offset as u8;
} }
Let(patt, expr) => { Let(patt, expr) => {
self.emit_op(Op::ResetMatch);
self.visit(expr); self.visit(expr);
self.visit(patt); self.visit(patt);
self.emit_op(Op::PanicIfNoMatch);
} }
WordPattern(name) => { WordPattern(name) => {
self.emit_op(Op::MatchWord);
self.bind(name); self.bind(name);
} }
Word(name) => { Word(name) => {
@ -207,8 +273,126 @@ impl<'a> Chunk<'a> {
} }
} }
PlaceholderPattern => { PlaceholderPattern => {
self.emit_op(Op::MatchWord);
self.bind("_"); self.bind("_");
} }
NilPattern => {
self.emit_op(Op::MatchNil);
self.bind("nil");
}
BooleanPattern(b) => {
if *b {
self.emit_op(Op::MatchTrue);
self.bind("true");
} else {
self.emit_op(Op::MatchFalse);
self.bind("false");
}
}
NumberPattern(n) => {
self.match_constant(Value::Number(*n));
}
KeywordPattern(s) => {
let existing_kw = self.keywords.iter().position(|kw| kw == s);
let kw_index = match existing_kw {
Some(index) => index,
None => self.keywords.len(),
};
if kw_index == self.keywords.len() {
self.keywords.push(s);
}
self.match_constant(Value::Keyword(kw_index));
}
StringPattern(s) => {
let existing_str = self.strings.iter().position(|e| e == s);
let str_index = match existing_str {
Some(idx) => idx,
None => self.strings.len(),
};
if str_index == self.strings.len() {
self.strings.push(s)
}
self.match_constant(Value::Interned(str_index));
}
Tuple(members) => {
for member in members {
self.visit(member);
}
self.emit_op(Op::PushTuple);
self.bytecode.push(members.len() as u8);
}
List(members) => {
for member in members {
self.visit(member);
}
self.emit_op(Op::PushList);
self.bytecode.push(members.len() as u8);
}
LBox(name, expr) => {
self.visit(expr);
self.emit_op(Op::PushBox);
self.bind(name);
}
Dict(pairs) => {
for pair in pairs {
self.visit(pair);
}
self.emit_op(Op::PushDict);
self.bytecode.push(pairs.len() as u8);
}
Pair(key, value) => {
let existing_kw = self.keywords.iter().position(|kw| kw == key);
let kw_index = match existing_kw {
Some(index) => index,
None => self.keywords.len(),
};
if kw_index == self.keywords.len() {
self.keywords.push(key);
}
self.emit_constant(Value::Keyword(kw_index));
self.visit(value);
}
Synthetic(first, second, rest) => {
match (&first.0, &second.0) {
(Word(_), Keyword(_)) => {
self.visit(first);
self.visit(second);
self.emit_op(Op::GetKey);
}
(Keyword(_), Arguments(args)) => {
self.visit(&args[0]);
self.visit(first);
self.emit_op(Op::GetKey);
}
(Word(_), Arguments(_)) => {
todo!()
}
_ => unreachable!(),
}
for term in rest {
todo!()
}
}
When(clauses) => {
let mut jump_idxes = vec![];
let mut clauses = clauses.iter();
while let Some((WhenClause(cond, body), _)) = clauses.next() {
self.visit(cond.as_ref());
self.emit_op(Op::JumpIfFalse);
let jif_jump_idx = self.bytecode.len();
self.bytecode.push(0xff);
self.visit(body);
self.emit_op(Op::Jump);
jump_idxes.push(self.bytecode.len());
self.bytecode.push(0xff);
self.bytecode[jif_jump_idx] =
self.bytecode.len() as u8 - jif_jump_idx as u8 - 1;
}
self.emit_op(Op::PanicNoWhen);
for idx in jump_idxes {
self.bytecode[idx] = self.bytecode.len() as u8 - idx as u8 + 1;
}
}
_ => todo!(), _ => todo!(),
} }
} }
@ -221,13 +405,16 @@ impl<'a> Chunk<'a> {
let op = Op::from_u8(*byte).unwrap(); let op = Op::from_u8(*byte).unwrap();
use Op::*; use Op::*;
match op { match op {
Pop | Store | Load => println!("{i:04}: {op}"), Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
Constant => { | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
let (_, next) = codes.next().unwrap(); let (_, next) = codes.next().unwrap();
let value = &self.constants[*next as usize].show(self); let value = &self.constants[*next as usize].show(self);
println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); println!("{i:04}: {:16} {next:04}: {value}", op.to_string());
} }
PushBinding => { PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox => {
let (_, next) = codes.next().unwrap(); let (_, next) = codes.next().unwrap();
println!("{i:04}: {:16} {next:04}", op.to_string()); println!("{i:04}: {:16} {next:04}", op.to_string());
} }
@ -243,13 +430,16 @@ impl<'a> Chunk<'a> {
let op = Op::from_u8(self.bytecode[i]).unwrap(); let op = Op::from_u8(self.bytecode[i]).unwrap();
use Op::*; use Op::*;
match op { match op {
Pop | Store | Load => println!("{i:04}: {op}"), Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
Constant => { | PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
let next = self.bytecode[i + 1]; let next = self.bytecode[i + 1];
let value = &self.constants[next as usize].show(self); let value = &self.constants[next as usize].show(self);
println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); println!("{i:04}: {:16} {next:04}: {value}", op.to_string());
} }
PushBinding => { PushBinding | MatchTuple | PushTuple | PushDict | PushList | PushBox => {
let next = self.bytecode[i + 1]; let next = self.bytecode[i + 1];
println!("{i:04}: {:16} {next:04}", op.to_string()); println!("{i:04}: {:16} {next:04}", op.to_string());
} }

View File

@ -64,17 +64,12 @@ pub fn run(src: &'static str) {
pub fn main() { pub fn main() {
let src = " let src = "
let foo = :let_foo let foo = 42
when {
let bar = if true foo -> :two
then { false -> :four
let baz = :baz :else -> :thing
} }
else :whatever
foo
bar
"; ";
run(src); run(src);
} }

View File

@ -5,12 +5,6 @@ use imbl::{HashMap, Vector};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub struct LBox {
pub name: usize,
pub cell: RefCell<Value>,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct LFn { pub struct LFn {
pub name: &'static str, pub name: &'static str,
@ -27,17 +21,15 @@ pub enum Value {
Nil, Nil,
True, True,
False, False,
Keyword(usize), // use an idx, rather than a raw index Keyword(usize),
Interned(usize), Interned(usize),
FnDecl(usize), FnDecl(usize),
String(Rc<String>), String(Rc<String>),
Number(f64), Number(f64),
Tuple(Rc<Vec<Value>>), Tuple(Rc<Vec<Value>>),
TupleStart { len: u8, size: u16 },
TupleEnd { len: u8, size: u16 },
List(Box<Vector<Value>>), List(Box<Vector<Value>>),
Dict(Box<HashMap<usize, Value>>), Dict(Box<HashMap<usize, Value>>),
Box(Rc<LBox>), Box(Rc<RefCell<Value>>),
Fn(Rc<RefCell<LFn>>), Fn(Rc<RefCell<LFn>>),
} }
@ -51,6 +43,34 @@ impl std::fmt::Display for Value {
Keyword(idx) => write!(f, ":{idx}"), Keyword(idx) => write!(f, ":{idx}"),
Interned(idx) => write!(f, "\"@{idx}\""), Interned(idx) => write!(f, "\"@{idx}\""),
Number(n) => write!(f, "{n}"), Number(n) => write!(f, "{n}"),
Tuple(members) => write!(
f,
"({})",
members
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
List(members) => write!(
f,
"[{}]",
members
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Dict(members) => write!(
f,
"#{{{}}}",
members
.iter()
.map(|(k, v)| format!("{k} {v}"))
.collect::<Vec<_>>()
.join(", ")
),
Box(value) => write!(f, "box {}", value.as_ref().borrow()),
_ => todo!(), _ => todo!(),
} }
} }
@ -93,6 +113,7 @@ impl Value {
format!("#{{{members}}}") format!("#{{{members}}}")
} }
String(s) => s.as_ref().clone(), String(s) => s.as_ref().clone(),
Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show(ctx)),
_ => todo!(), _ => todo!(),
} }
} }

148
src/vm.rs
View File

@ -3,18 +3,22 @@ use crate::parser::Ast;
use crate::spans::Spanned; use crate::spans::Spanned;
use crate::value::Value; use crate::value::Value;
use chumsky::prelude::SimpleSpan; use chumsky::prelude::SimpleSpan;
use imbl::{HashMap, Vector};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use std::cell::RefCell;
use std::mem::swap; use std::mem::swap;
use std::rc::Rc;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Panic { // pub struct Panic {
pub input: &'static str, // pub input: &'static str,
pub src: &'static str, // pub src: &'static str,
pub msg: String, // pub msg: String,
pub span: SimpleSpan, // pub span: SimpleSpan,
pub trace: Vec<Trace>, // pub trace: Vec<Trace>,
pub extra: String, // pub extra: String,
} // }
pub struct Panic(&'static str);
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Trace { pub struct Trace {
@ -31,6 +35,7 @@ pub struct Vm<'a> {
pub chunk: &'a Chunk<'a>, pub chunk: &'a Chunk<'a>,
pub ip: usize, pub ip: usize,
pub return_register: Value, pub return_register: Value,
pub matches: bool,
} }
impl<'a> Vm<'a> { impl<'a> Vm<'a> {
@ -40,6 +45,7 @@ impl<'a> Vm<'a> {
stack: vec![], stack: vec![],
ip: 0, ip: 0,
return_register: Value::Nil, return_register: Value::Nil,
matches: false,
} }
} }
@ -76,6 +82,21 @@ impl<'a> Vm<'a> {
let op = Op::from_u8(*byte).unwrap(); let op = Op::from_u8(*byte).unwrap();
use Op::*; use Op::*;
match op { match op {
Nil => {
self.push(Value::Nil);
self.ip += 1;
self.interpret()
}
True => {
self.push(Value::True);
self.ip += 1;
self.interpret()
}
False => {
self.push(Value::False);
self.ip += 1;
self.interpret()
}
Constant => { Constant => {
let const_idx = self.chunk.bytecode[self.ip + 1]; let const_idx = self.chunk.bytecode[self.ip + 1];
let value = self.chunk.constants[const_idx as usize].clone(); let value = self.chunk.constants[const_idx as usize].clone();
@ -122,20 +143,113 @@ impl<'a> Vm<'a> {
} }
Load => { Load => {
let mut value = Value::Nil; let mut value = Value::Nil;
// println!(
// "before swap, return register holds: {}",
// self.return_register
// );
swap(&mut self.return_register, &mut value); swap(&mut self.return_register, &mut value);
// println!(
// "before swap, return register holds: {}",
// self.return_register
// );
// println!("now local value holds {value}");
self.push(value); self.push(value);
self.ip += 1; self.ip += 1;
self.interpret() self.interpret()
} }
ResetMatch => {
self.matches = false;
self.ip += 1;
self.interpret()
}
MatchWord => {
self.matches = true;
self.ip += 1;
self.interpret()
}
MatchNil => {
if *self.stack.last().unwrap() == Value::Nil {
self.matches = true;
};
self.ip += 1;
self.interpret()
}
MatchTrue => {
if *self.stack.last().unwrap() == Value::True {
self.matches = true;
};
self.ip += 1;
self.interpret()
}
MatchFalse => {
if *self.stack.last().unwrap() == Value::False {
self.matches = true;
}
self.ip += 1;
self.interpret()
}
PanicIfNoMatch => {
if !self.matches {
Err(Panic("no match"))
} else {
self.ip += 1;
self.interpret()
}
}
MatchConstant => {
let const_idx = self.chunk.bytecode[self.ip + 1];
let value = self.stack.last().unwrap();
self.matches = *value == self.chunk.constants[const_idx as usize];
self.ip += 2;
self.interpret()
}
PushTuple => {
let tuple_len = self.chunk.bytecode[self.ip + 1];
let tuple_members = self.stack.split_off(self.stack.len() - tuple_len as usize);
let tuple = Value::Tuple(Rc::new(tuple_members));
self.stack.push(tuple);
self.ip += 2;
self.interpret()
}
PushList => {
let list_len = self.chunk.bytecode[self.ip + 1];
let list_members = self.stack.split_off(self.stack.len() - list_len as usize);
let list = Value::List(Box::new(Vector::from(list_members)));
self.stack.push(list);
self.ip += 2;
self.interpret()
}
PushDict => {
let dict_len = self.chunk.bytecode[self.ip + 1] as usize * 2;
let dict_members = self.stack.split_off(self.stack.len() - dict_len);
let mut dict = HashMap::new();
let mut dict_iter = dict_members.iter();
while let Some(kw) = dict_iter.next() {
let Value::Keyword(key) = kw else {
unreachable!()
};
let value = dict_iter.next().unwrap();
dict.insert(*key, value.clone());
}
self.stack.push(Value::Dict(Box::new(dict)));
self.ip += 2;
self.interpret()
}
PushBox => {
let val = self.pop();
self.stack.push(Value::Box(Rc::new(RefCell::new(val))));
self.ip += 1;
self.interpret()
}
GetKey => {
let key = self.pop();
let Value::Keyword(idx) = key else {
unreachable!()
};
let dict = self.pop();
let value = match dict {
Value::Dict(d) => d.as_ref().get(&idx).unwrap_or(&Value::Nil).clone(),
_ => Value::Nil,
};
self.push(value);
self.ip += 1;
self.interpret()
}
MatchTuple => {
todo!()
}
PanicNoWhen => Err(Panic("no match")),
} }
} }
} }