From 182b14e5f422ff8276b1ed6de6891aabcd994d13 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 27 May 2025 14:15:12 -0400 Subject: [PATCH] add builtins, fix basic synthetic expressions --- src/compiler.rs | 61 +++++++++++++++++++++++--- src/main.rs | 12 +----- src/vm.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 16 deletions(-) diff --git a/src/compiler.rs b/src/compiler.rs index 533af5f..73b2d5c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -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 { + 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 => { diff --git a/src/main.rs b/src/main.rs index 81ce8c8..f9de812 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); } diff --git a/src/vm.rs b/src/vm.rs index a24b5e1..419df12 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -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; + } } } }