add builtins, fix basic synthetic expressions

This commit is contained in:
Scott Richmond 2025-05-27 14:15:12 -04:00
parent 74cc0025d6
commit 182b14e5f4
3 changed files with 168 additions and 16 deletions

View File

@ -5,6 +5,7 @@ use chumsky::prelude::SimpleSpan;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::cell::OnceCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
@ -51,6 +52,17 @@ pub enum Op {
Decrement,
Truncate,
MatchDepth,
Eq,
Add,
Sub,
Mult,
Div,
Unbox,
BoxStore,
Assert,
Get,
At,
}
impl std::fmt::Display for Op {
@ -99,6 +111,17 @@ impl std::fmt::Display for Op {
Truncate => "truncate",
Duplicate => "duplicate",
MatchDepth => "match_depth",
Eq => "eq",
Add => "add",
Sub => "sub",
Mult => "mult",
Div => "div",
Unbox => "unbox",
BoxStore => "box_store",
Assert => "assert",
Get => "get",
At => "at",
};
write!(f, "{rep}")
}
@ -126,7 +149,8 @@ impl Chunk {
match op {
Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch
| TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList => {
| TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq
| Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -164,6 +188,23 @@ impl LoopInfo {
}
}
fn get_builtin(name: &str, arity: usize) -> Option<Op> {
match (name, arity) {
("type", 1) => Some(Op::TypeOf),
("eq?", 2) => Some(Op::Eq),
("add", 2) => Some(Op::Add),
("sub", 2) => Some(Op::Sub),
("mult", 2) => Some(Op::Mult),
("div", 2) => Some(Op::Div),
("unbox", 1) => Some(Op::Unbox),
("store!", 2) => Some(Op::BoxStore),
("assert!", 1) => Some(Op::Assert),
("get", 2) => Some(Op::Get),
("at", 2) => Some(Op::At),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Compiler {
pub chunk: Chunk,
@ -612,7 +653,6 @@ impl Compiler {
LBox(name, expr) => {
self.visit(expr);
self.emit_op(Op::PushBox);
self.stack_depth += 1;
self.bind(name);
}
Dict(pairs) => {
@ -649,15 +689,24 @@ impl Compiler {
self.visit(first);
self.visit(second);
self.emit_op(Op::GetKey);
self.stack_depth -= 1;
}
(Keyword(_), Arguments(args)) => {
self.visit(&args[0]);
self.visit(first);
self.emit_op(Op::GetKey);
self.stack_depth -= 1;
}
(Word(_), Arguments(_)) => {
todo!()
}
(Word(fn_name), Arguments(args)) => match get_builtin(fn_name, args.len()) {
Some(code) => {
for arg in args {
self.visit(arg);
}
self.emit_op(code);
self.stack_depth -= args.len() - 1;
}
None => todo!(),
},
_ => unreachable!(),
}
// TODO: implement longer synthetic expressions
@ -941,7 +990,7 @@ impl Compiler {
Noop | Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue
| MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen
| PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple
| LoadList => {
| LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {

View File

@ -74,16 +74,8 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
loop (true) with {
(false) -> :done
(true) -> recur (42)
(42) -> recur (:thingie)
(:thingie) -> {
let x = false
12
recur (x)
}
}
let foo = #{:a 1, :b 2}
:a (foo)
";
run(src);
}

111
src/vm.rs
View File

@ -419,6 +419,117 @@ impl<'a> Vm<'a> {
self.interpret()
}
PanicNoWhen | PanicNoMatch => self.panic("no match"),
Eq => {
let first = self.stack.pop().unwrap();
let second = self.stack.pop().unwrap();
if first == second {
self.stack.push(Value::True)
} else {
self.stack.push(Value::False)
}
self.ip += 1;
}
Add => {
let first = self.stack.pop().unwrap();
let second = self.stack.pop().unwrap();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.stack.push(Value::Number(x + y))
} else {
self.panic("`add` requires two numbers")
}
self.ip += 1;
}
Sub => {
let first = self.stack.pop().unwrap();
let second = self.stack.pop().unwrap();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.stack.push(Value::Number(y - x))
} else {
self.panic("`sub` requires two numbers")
}
self.ip += 1;
}
Mult => {
let first = self.stack.pop().unwrap();
let second = self.stack.pop().unwrap();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
self.stack.push(Value::Number(x * y))
} else {
self.panic("`mult` requires two numbers")
}
self.ip += 1;
}
Div => {
let first = self.stack.pop().unwrap();
let second = self.stack.pop().unwrap();
if let (Value::Number(x), Value::Number(y)) = (first, second) {
if x == 0.0 {
self.panic("division by 0")
}
self.stack.push(Value::Number(y / x))
} else {
self.panic("`div` requires two numbers")
}
self.ip += 1;
}
Unbox => {
let the_box = self.stack.pop().unwrap();
let inner = if let Value::Box(b) = the_box {
b.borrow().clone()
} else {
return self.panic("`unbox` requires a box");
};
self.stack.push(inner);
self.ip += 1;
}
BoxStore => {
let new_value = self.stack.pop().unwrap();
let the_box = self.stack.pop().unwrap();
if let Value::Box(b) = the_box {
b.replace(new_value.clone());
} else {
return self.panic("`store` requires a box");
}
self.stack.push(new_value);
self.ip += 1;
}
Assert => {
let value = self.stack.last().unwrap();
if let Value::Nil | Value::False = value {
return self.panic("asserted falsy value");
}
self.ip += 1;
}
Get => {
let key = self.stack.pop().unwrap();
let dict = self.stack.pop().unwrap();
let value = match (key, dict) {
(Value::Keyword(k), Value::Dict(d)) => {
d.as_ref().get(&k).unwrap_or(&Value::Nil).clone()
}
(Value::Keyword(_), _) => Value::Nil,
_ => return self.panic("keys must be keywords"),
};
self.stack.push(value);
self.ip += 1;
}
At => {
let idx = self.stack.pop().unwrap();
let ordered = self.stack.pop().unwrap();
let value = match (ordered, idx) {
(Value::List(l), Value::Number(i)) => {
l.get(i as usize).unwrap_or(&Value::Nil).clone()
}
(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"),
};
self.stack.push(value);
self.ip += 1;
}
}
}
}