From f66a6ea0e1dcae9f48a1471e4fb34ca780b88ca9 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 10 Dec 2023 02:41:30 +0100 Subject: [PATCH 001/142] Add first version of vm --- src/bin/tulisp_vm.rs | 6 ++ src/lib.rs | 1 + src/vm.rs | 232 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 src/bin/tulisp_vm.rs create mode 100644 src/vm.rs diff --git a/src/bin/tulisp_vm.rs b/src/bin/tulisp_vm.rs new file mode 100644 index 00000000..0a0ca55f --- /dev/null +++ b/src/bin/tulisp_vm.rs @@ -0,0 +1,6 @@ +use tulisp::vm; + +fn main() { + let mut machine = vm::Machine::new(); + machine.run().unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs index dffaf078..120cba4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ A list of currently available builtin functions can be found [here](builtin). mod eval; mod macros; mod parse; +pub mod vm; pub use tulisp_proc_macros::{tulisp_add_func, tulisp_add_macro, tulisp_fn, tulisp_fn_no_eval}; diff --git a/src/vm.rs b/src/vm.rs new file mode 100644 index 00000000..6988fab9 --- /dev/null +++ b/src/vm.rs @@ -0,0 +1,232 @@ +use std::fmt::Display; + +use crate::{Error, TulispObject}; + +macro_rules! compare_ops { + ($oper:expr) => {{ + |selfobj: &TulispObject, other: &TulispObject| -> Result { + if selfobj.floatp() { + let s: f64 = selfobj.as_float().unwrap(); + let o: f64 = other.try_into()?; + Ok($oper(&s, &o)) + } else if other.floatp() { + let o: f64 = other.as_float().unwrap(); + let s: f64 = selfobj.try_into()?; + Ok($oper(&s, &o)) + } else { + let s: i64 = selfobj.try_into()?; + let o: i64 = other.try_into()?; + Ok($oper(&s, &o)) + } + } + }}; +} + +macro_rules! binary_ops { + ($oper:expr) => {{ + |selfobj: &TulispObject, other: &TulispObject| -> Result { + if selfobj.floatp() { + let s: f64 = selfobj.as_float().unwrap(); + let o: f64 = other.try_into()?; + Ok($oper(&s, &o).into()) + } else if other.floatp() { + let o: f64 = other.as_float().unwrap(); + let s: f64 = selfobj.try_into()?; + Ok($oper(&s, &o).into()) + } else { + let s: i64 = selfobj.try_into()?; + let o: i64 = other.try_into()?; + Ok($oper(&s, &o).into()) + } + } + }}; +} + +#[derive(Debug)] +pub enum Pos { + Absolute(usize), + Relative(isize), +} + +/// A single instruction in the VM. +#[derive(Debug)] +pub enum Instruction { + Push(TulispObject), + // variables + Store(TulispObject), + Load(TulispObject), + // arithmetic + Plus, + Minus, + // io + Print, + // comparison + Eq, + Lt, + LtEq, + Gt, + GtEq, + // control flow + JumpIfNil(Pos), + Jump(Pos), +} + +impl Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Instruction::Push(obj) => write!(f, "Push({})", obj), + Instruction::Store(obj) => write!(f, "Store({})", obj), + Instruction::Load(obj) => write!(f, "Load({})", obj), + Instruction::Plus => write!(f, "Plus"), + Instruction::Minus => write!(f, "Minus"), + Instruction::Print => write!(f, "Print"), + Instruction::JumpIfNil(pos) => write!(f, "JumpIfNil({:?})", pos), + Instruction::Eq => write!(f, "Eq"), + Instruction::Lt => write!(f, "Lt"), + Instruction::LtEq => write!(f, "LtEq"), + Instruction::Gt => write!(f, "Gt"), + Instruction::GtEq => write!(f, "GtEq"), + Instruction::Jump(pos) => write!(f, "Jump({:?})", pos), + } + } +} + +pub struct Machine { + stack: Vec, + program: Vec, + pc: usize, +} + +impl Machine { + pub fn new() -> Self { + Machine { + stack: Vec::new(), + program: programs::print_range(92, 100), + pc: 0, + } + } + + #[allow(dead_code)] + fn print_stack(&self) { + println!("Stack:"); + for obj in self.stack.iter() { + println!(" {}", obj); + } + } + + pub fn run(&mut self) -> Result<(), Error> { + let mut ctr: u32 = 0; // safety counter + while self.pc < self.program.len() && ctr < 100000 { + let instr = &self.program[self.pc]; + ctr += 1; + self.pc += 1; + // self.print_stack(); + // println!("\nPC: {}; Executing: {}", self.pc, instr); + match instr { + Instruction::Push(obj) => self.stack.push(obj.clone()), + Instruction::Plus => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(binary_ops!(std::ops::Add::add)(&a, &b)?); + } + Instruction::Minus => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(binary_ops!(std::ops::Sub::sub)(&a, &b)?); + } + Instruction::Print => { + let a = self.stack.pop().unwrap(); + println!("{}", a); + } + Instruction::JumpIfNil(pos) => { + let a = self.stack.pop().unwrap(); + if a.null() { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } + Instruction::Jump(pos) => match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + }, + Instruction::Eq => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(a.eq(&b).into()); + } + Instruction::Lt => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack + .push(compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)?.into()); + } + Instruction::LtEq => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack + .push(compare_ops!(std::cmp::PartialOrd::le)(&a, &b)?.into()); + } + Instruction::Gt => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack + .push(compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)?.into()); + } + Instruction::GtEq => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack + .push(compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)?.into()); + } + Instruction::Store(obj) => { + let a = self.stack.pop().unwrap(); + obj.set(a)?; + } + Instruction::Load(obj) => { + let a = obj.get()?; + self.stack.push(a); + } + } + } + Ok(()) + } +} + +mod programs { + use crate::TulispObject; + + use super::{Instruction, Pos}; + + pub(super) fn print_range(from: i64, to: i64) -> Vec { + let i = TulispObject::symbol("i".to_string(), false); + let n = TulispObject::symbol("n".to_string(), false); + vec![ + // print numbers 1 to 10 + Instruction::Push(from.into()), // 0 + Instruction::Store(i.clone()), // 1 + Instruction::Push(to.into()), // 2 + Instruction::Store(n.clone()), // 3 + // loop: + Instruction::Load(i.clone()), // 4 + Instruction::Print, // 5 + Instruction::Push(1.into()), // 6 + Instruction::Load(i.clone()), // 7 + if from < to { + Instruction::Plus + } else { + Instruction::Minus + }, // 8 + Instruction::Store(i.clone()), // 9 + Instruction::Load(i.clone()), // 10 + Instruction::Load(n.clone()), // 11 + if from < to { + Instruction::Lt + } else { + Instruction::Gt + }, // 12 + Instruction::JumpIfNil(Pos::Absolute(4)), // 13 + ] + } +} From d3a258340d90da7ff5078a3a8cd514d2a83e4a40 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 15 Dec 2023 21:44:01 +0100 Subject: [PATCH 002/142] Add numeric conditional jump instructions --- src/vm.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 6988fab9..e7eb7000 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -68,6 +68,11 @@ pub enum Instruction { GtEq, // control flow JumpIfNil(Pos), + JumpIfEq(Pos), + JumpIfLt(Pos), + JumpIfLtEq(Pos), + JumpIfGt(Pos), + JumpIfGtEq(Pos), Jump(Pos), } @@ -81,6 +86,11 @@ impl Display for Instruction { Instruction::Minus => write!(f, "Minus"), Instruction::Print => write!(f, "Print"), Instruction::JumpIfNil(pos) => write!(f, "JumpIfNil({:?})", pos), + Instruction::JumpIfEq(pos) => write!(f, "JumpIfEq({:?})", pos), + Instruction::JumpIfLt(pos) => write!(f, "JumpIfLt({:?})", pos), + Instruction::JumpIfLtEq(pos) => write!(f, "JumpIfLtEq({:?})", pos), + Instruction::JumpIfGt(pos) => write!(f, "JumpIfGt({:?})", pos), + Instruction::JumpIfGtEq(pos) => write!(f, "JumpIfGtEq({:?})", pos), Instruction::Eq => write!(f, "Eq"), Instruction::Lt => write!(f, "Lt"), Instruction::LtEq => write!(f, "LtEq"), @@ -118,10 +128,10 @@ impl Machine { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { let instr = &self.program[self.pc]; - ctr += 1; - self.pc += 1; // self.print_stack(); // println!("\nPC: {}; Executing: {}", self.pc, instr); + ctr += 1; + self.pc += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Plus => { @@ -147,6 +157,56 @@ impl Machine { } } } + Instruction::JumpIfEq(pos) => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + if a.eq(&b) { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } + Instruction::JumpIfLt(pos) => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } + Instruction::JumpIfLtEq(pos) => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } + Instruction::JumpIfGt(pos) => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } + Instruction::JumpIfGtEq(pos) => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { + match pos { + Pos::Absolute(p) => self.pc = *p, + Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + } + } Instruction::Jump(pos) => match pos { Pos::Absolute(p) => self.pc = *p, Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, @@ -222,11 +282,10 @@ mod programs { Instruction::Load(i.clone()), // 10 Instruction::Load(n.clone()), // 11 if from < to { - Instruction::Lt + Instruction::JumpIfGtEq(Pos::Absolute(4)) } else { - Instruction::Gt + Instruction::JumpIfLtEq(Pos::Absolute(4)) }, // 12 - Instruction::JumpIfNil(Pos::Absolute(4)), // 13 ] } } From 626742b08869f295818774d189baa064a0bdab40 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 15 Dec 2023 21:51:28 +0100 Subject: [PATCH 003/142] =?UTF-8?q?Rename=20`Pos::Absolute/Relative`=20?= =?UTF-8?q?=E2=86=92=20`Abs/Rel`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vm.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index e7eb7000..247ba99b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -44,8 +44,8 @@ macro_rules! binary_ops { #[derive(Debug)] pub enum Pos { - Absolute(usize), - Relative(isize), + Abs(usize), + Rel(isize), } /// A single instruction in the VM. @@ -152,8 +152,8 @@ impl Machine { let a = self.stack.pop().unwrap(); if a.null() { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } @@ -162,8 +162,8 @@ impl Machine { let b = self.stack.pop().unwrap(); if a.eq(&b) { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } @@ -172,8 +172,8 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } @@ -182,8 +182,8 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } @@ -192,8 +192,8 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } @@ -202,14 +202,14 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, } } } Instruction::Jump(pos) => match pos { - Pos::Absolute(p) => self.pc = *p, - Pos::Relative(p) => self.pc = (self.pc as isize + p - 1) as usize, + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, }, Instruction::Eq => { let a = self.stack.pop().unwrap(); @@ -282,9 +282,9 @@ mod programs { Instruction::Load(i.clone()), // 10 Instruction::Load(n.clone()), // 11 if from < to { - Instruction::JumpIfGtEq(Pos::Absolute(4)) + Instruction::JumpIfGtEq(Pos::Abs(4)) } else { - Instruction::JumpIfLtEq(Pos::Absolute(4)) + Instruction::JumpIfLtEq(Pos::Abs(4)) }, // 12 ] } From 4531c2ecff1028291101b3d7c94c25900a7eb01e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 15 Dec 2023 23:01:30 +0100 Subject: [PATCH 004/142] Add support for calling functions --- src/bin/tulisp_vm.rs | 2 +- src/vm.rs | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/bin/tulisp_vm.rs b/src/bin/tulisp_vm.rs index 0a0ca55f..425b391f 100644 --- a/src/bin/tulisp_vm.rs +++ b/src/bin/tulisp_vm.rs @@ -2,5 +2,5 @@ use tulisp::vm; fn main() { let mut machine = vm::Machine::new(); - machine.run().unwrap(); + machine.run(0).unwrap(); } diff --git a/src/vm.rs b/src/vm.rs index 247ba99b..024e6ad9 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::{Error, TulispObject}; +use crate::{context::Scope, Error, TulispObject}; macro_rules! compare_ops { ($oper:expr) => {{ @@ -74,6 +74,9 @@ pub enum Instruction { JumpIfGt(Pos), JumpIfGtEq(Pos), Jump(Pos), + // functions + Call { pos: Pos, params: Vec }, + Ret, } impl Display for Instruction { @@ -97,6 +100,8 @@ impl Display for Instruction { Instruction::Gt => write!(f, "Gt"), Instruction::GtEq => write!(f, "GtEq"), Instruction::Jump(pos) => write!(f, "Jump({:?})", pos), + Instruction::Call { pos, .. } => write!(f, "Call({:?})", pos), + Instruction::Ret => write!(f, "Ret"), } } } @@ -124,12 +129,15 @@ impl Machine { } } - pub fn run(&mut self) -> Result<(), Error> { + pub fn run(&mut self, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { let instr = &self.program[self.pc]; // self.print_stack(); - // println!("\nPC: {}; Executing: {}", self.pc, instr); + // println!( + // "\nDepth: {}: PC: {}; Executing: {}", + // recursion_depth, self.pc, instr + // ); ctr += 1; self.pc += 1; match instr { @@ -248,6 +256,25 @@ impl Machine { let a = obj.get()?; self.stack.push(a); } + Instruction::Call { pos, params } => { + let pc = self.pc; + let mut scope = Scope::default(); + + match pos { + Pos::Abs(p) => self.pc = *p, + Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, + } + + for param in params { + let value = self.stack.pop().unwrap(); + scope.set(param.clone(), value)?; + } + self.run(recursion_depth + 1)?; + scope.remove_all()?; + + self.pc = pc; + } + Instruction::Ret => return Ok(()), } } Ok(()) From a1c242effbb527ef54e18a9e963bbc7c54b14b86 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 15 Dec 2023 23:04:49 +0100 Subject: [PATCH 005/142] Add bytecode for a recursive fib calculator --- src/vm.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/vm.rs b/src/vm.rs index 024e6ad9..1d76e3fa 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -116,7 +116,8 @@ impl Machine { pub fn new() -> Self { Machine { stack: Vec::new(), - program: programs::print_range(92, 100), + // program: programs::print_range(92, 100), + program: programs::fib(30), pc: 0, } } @@ -286,6 +287,7 @@ mod programs { use super::{Instruction, Pos}; + #[allow(dead_code)] pub(super) fn print_range(from: i64, to: i64) -> Vec { let i = TulispObject::symbol("i".to_string(), false); let n = TulispObject::symbol("n".to_string(), false); @@ -315,4 +317,41 @@ mod programs { }, // 12 ] } + + #[allow(dead_code)] + pub(super) fn fib(num: i64) -> Vec { + let n = TulispObject::symbol("n".to_string(), false); + vec![ + Instruction::Jump(Pos::Abs(16)), // 0 + // fib: + Instruction::Push(2.into()), // 1 + Instruction::Load(n.clone()), // 2 + Instruction::JumpIfGt(Pos::Abs(6)), // 3 + Instruction::Push(1.into()), // 4 + Instruction::Ret, // 5 + Instruction::Push(1.into()), // 6 + Instruction::Load(n.clone()), // 7 + Instruction::Minus, // 8 + Instruction::Call { + pos: Pos::Abs(1), + params: vec![n.clone()], + }, // 9 + Instruction::Push(2.into()), // 10 + Instruction::Load(n.clone()), // 11 + Instruction::Minus, // 12 + Instruction::Call { + pos: Pos::Abs(1), + params: vec![n.clone()], + }, // 13 + Instruction::Plus, // 14 + Instruction::Ret, // 15 + // main: + Instruction::Push(num.into()), // 16 + Instruction::Call { + pos: Pos::Abs(1), + params: vec![n.clone()], + }, // 17 + Instruction::Print, // 18 + ] + } } From 6adaba28709a2626dcaa0b86af4f179959ed99ab Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 16 Dec 2023 00:49:03 +0100 Subject: [PATCH 006/142] =?UTF-8?q?Rename=20instructions=20`Plus/Minus`=20?= =?UTF-8?q?=E2=86=92=20`Add/Sub`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vm.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 1d76e3fa..8f64bcbb 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -56,8 +56,8 @@ pub enum Instruction { Store(TulispObject), Load(TulispObject), // arithmetic - Plus, - Minus, + Add, + Sub, // io Print, // comparison @@ -85,8 +85,8 @@ impl Display for Instruction { Instruction::Push(obj) => write!(f, "Push({})", obj), Instruction::Store(obj) => write!(f, "Store({})", obj), Instruction::Load(obj) => write!(f, "Load({})", obj), - Instruction::Plus => write!(f, "Plus"), - Instruction::Minus => write!(f, "Minus"), + Instruction::Add => write!(f, "Plus"), + Instruction::Sub => write!(f, "Minus"), Instruction::Print => write!(f, "Print"), Instruction::JumpIfNil(pos) => write!(f, "JumpIfNil({:?})", pos), Instruction::JumpIfEq(pos) => write!(f, "JumpIfEq({:?})", pos), @@ -143,12 +143,12 @@ impl Machine { self.pc += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), - Instruction::Plus => { + Instruction::Add => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); self.stack.push(binary_ops!(std::ops::Add::add)(&a, &b)?); } - Instruction::Minus => { + Instruction::Sub => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); self.stack.push(binary_ops!(std::ops::Sub::sub)(&a, &b)?); @@ -303,9 +303,9 @@ mod programs { Instruction::Push(1.into()), // 6 Instruction::Load(i.clone()), // 7 if from < to { - Instruction::Plus + Instruction::Add } else { - Instruction::Minus + Instruction::Sub }, // 8 Instruction::Store(i.clone()), // 9 Instruction::Load(i.clone()), // 10 @@ -331,19 +331,19 @@ mod programs { Instruction::Ret, // 5 Instruction::Push(1.into()), // 6 Instruction::Load(n.clone()), // 7 - Instruction::Minus, // 8 + Instruction::Sub, // 8 Instruction::Call { pos: Pos::Abs(1), params: vec![n.clone()], }, // 9 Instruction::Push(2.into()), // 10 Instruction::Load(n.clone()), // 11 - Instruction::Minus, // 12 + Instruction::Sub, // 12 Instruction::Call { pos: Pos::Abs(1), params: vec![n.clone()], }, // 13 - Instruction::Plus, // 14 + Instruction::Add, // 14 Instruction::Ret, // 15 // main: Instruction::Push(num.into()), // 16 From e95c98cbcb64769818f442f0e58d43ff203f3193 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 16:26:00 +0100 Subject: [PATCH 007/142] Print mnemonics for instructions --- src/vm.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 8f64bcbb..808d141c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -82,26 +82,26 @@ pub enum Instruction { impl Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Instruction::Push(obj) => write!(f, "Push({})", obj), - Instruction::Store(obj) => write!(f, "Store({})", obj), - Instruction::Load(obj) => write!(f, "Load({})", obj), - Instruction::Add => write!(f, "Plus"), - Instruction::Sub => write!(f, "Minus"), - Instruction::Print => write!(f, "Print"), - Instruction::JumpIfNil(pos) => write!(f, "JumpIfNil({:?})", pos), - Instruction::JumpIfEq(pos) => write!(f, "JumpIfEq({:?})", pos), - Instruction::JumpIfLt(pos) => write!(f, "JumpIfLt({:?})", pos), - Instruction::JumpIfLtEq(pos) => write!(f, "JumpIfLtEq({:?})", pos), - Instruction::JumpIfGt(pos) => write!(f, "JumpIfGt({:?})", pos), - Instruction::JumpIfGtEq(pos) => write!(f, "JumpIfGtEq({:?})", pos), - Instruction::Eq => write!(f, "Eq"), - Instruction::Lt => write!(f, "Lt"), - Instruction::LtEq => write!(f, "LtEq"), - Instruction::Gt => write!(f, "Gt"), - Instruction::GtEq => write!(f, "GtEq"), - Instruction::Jump(pos) => write!(f, "Jump({:?})", pos), - Instruction::Call { pos, .. } => write!(f, "Call({:?})", pos), - Instruction::Ret => write!(f, "Ret"), + Instruction::Push(obj) => write!(f, "push {}", obj), + Instruction::Store(obj) => write!(f, "store {}", obj), + Instruction::Load(obj) => write!(f, "load {}", obj), + Instruction::Add => write!(f, "add"), + Instruction::Sub => write!(f, "sub"), + Instruction::Print => write!(f, "print"), + Instruction::JumpIfNil(pos) => write!(f, "jnil {:?}", pos), + Instruction::JumpIfEq(pos) => write!(f, "jeq {:?}", pos), + Instruction::JumpIfLt(pos) => write!(f, "jlt {:?}", pos), + Instruction::JumpIfLtEq(pos) => write!(f, "jle {:?}", pos), + Instruction::JumpIfGt(pos) => write!(f, "jgt {:?}", pos), + Instruction::JumpIfGtEq(pos) => write!(f, "jge {:?}", pos), + Instruction::Eq => write!(f, "ceq"), + Instruction::Lt => write!(f, "clt"), + Instruction::LtEq => write!(f, "cle"), + Instruction::Gt => write!(f, "cgt"), + Instruction::GtEq => write!(f, "cge"), + Instruction::Jump(pos) => write!(f, "jmp {:?}", pos), + Instruction::Call { pos, .. } => write!(f, "call {:?}", pos), + Instruction::Ret => write!(f, "ret"), } } } From 4452280f6a9574e1df467343317694ed6d940bf4 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 16:53:25 +0100 Subject: [PATCH 008/142] Pass context to vm --- src/bin/tulisp_vm.rs | 5 +++-- src/vm.rs | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/bin/tulisp_vm.rs b/src/bin/tulisp_vm.rs index 425b391f..2682ad17 100644 --- a/src/bin/tulisp_vm.rs +++ b/src/bin/tulisp_vm.rs @@ -1,6 +1,7 @@ -use tulisp::vm; +use tulisp::{vm, TulispContext}; fn main() { let mut machine = vm::Machine::new(); - machine.run(0).unwrap(); + let mut ctx = TulispContext::new(); + machine.run(&mut ctx, 0).unwrap(); } diff --git a/src/vm.rs b/src/vm.rs index 808d141c..6c31834a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::{context::Scope, Error, TulispObject}; +use crate::{context::Scope, Error, TulispContext, TulispObject}; macro_rules! compare_ops { ($oper:expr) => {{ @@ -130,7 +130,7 @@ impl Machine { } } - pub fn run(&mut self, recursion_depth: u32) -> Result<(), Error> { + pub fn run(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { let instr = &self.program[self.pc]; @@ -270,7 +270,7 @@ impl Machine { let value = self.stack.pop().unwrap(); scope.set(param.clone(), value)?; } - self.run(recursion_depth + 1)?; + self.run(ctx, recursion_depth + 1)?; scope.remove_all()?; self.pc = pc; From ec0556f7ec51a10215c40ad4eda86f1a3d7eff4e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 17:41:49 +0100 Subject: [PATCH 009/142] Add a `RustCall` instruction --- src/bin/tulisp_vm.rs | 7 +++++-- src/value.rs | 2 +- src/vm.rs | 43 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/bin/tulisp_vm.rs b/src/bin/tulisp_vm.rs index 2682ad17..5a9a336e 100644 --- a/src/bin/tulisp_vm.rs +++ b/src/bin/tulisp_vm.rs @@ -1,7 +1,10 @@ use tulisp::{vm, TulispContext}; fn main() { - let mut machine = vm::Machine::new(); let mut ctx = TulispContext::new(); - machine.run(&mut ctx, 0).unwrap(); + let mut machine = vm::Machine::new(&mut ctx); + if let Err(e) = machine.run(&mut ctx, 0) { + println!("{}", e.format(&ctx)); + std::process::exit(-1); + } } diff --git a/src/value.rs b/src/value.rs index 79d2ceaf..6bff5eb6 100644 --- a/src/value.rs +++ b/src/value.rs @@ -98,7 +98,7 @@ impl DefunParams { } } -type TulispFn = dyn Fn(&mut TulispContext, &TulispObject) -> Result; +pub(crate) type TulispFn = dyn Fn(&mut TulispContext, &TulispObject) -> Result; #[derive(Default, Clone, Debug)] pub struct SymbolBindings { diff --git a/src/vm.rs b/src/vm.rs index 6c31834a..fc9546fe 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,6 @@ -use std::fmt::Display; +use std::{fmt::Display, rc::Rc}; -use crate::{context::Scope, Error, TulispContext, TulispObject}; +use crate::{context::Scope, value::TulispFn, Error, TulispContext, TulispObject}; macro_rules! compare_ops { ($oper:expr) => {{ @@ -49,7 +49,6 @@ pub enum Pos { } /// A single instruction in the VM. -#[derive(Debug)] pub enum Instruction { Push(TulispObject), // variables @@ -75,6 +74,7 @@ pub enum Instruction { JumpIfGtEq(Pos), Jump(Pos), // functions + RustCall { func: Rc }, Call { pos: Pos, params: Vec }, Ret, } @@ -102,6 +102,7 @@ impl Display for Instruction { Instruction::Jump(pos) => write!(f, "jmp {:?}", pos), Instruction::Call { pos, .. } => write!(f, "call {:?}", pos), Instruction::Ret => write!(f, "ret"), + Instruction::RustCall { .. } => write!(f, "rustcall"), } } } @@ -113,11 +114,12 @@ pub struct Machine { } impl Machine { - pub fn new() -> Self { + pub fn new(ctx: &mut TulispContext) -> Self { Machine { stack: Vec::new(), // program: programs::print_range(92, 100), - program: programs::fib(30), + // program: programs::fib(30), + program: programs::rustcall_dotimes(ctx, 10), pc: 0, } } @@ -276,6 +278,10 @@ impl Machine { self.pc = pc; } Instruction::Ret => return Ok(()), + Instruction::RustCall { func } => { + let args = self.stack.pop().unwrap(); + self.stack.push(func(ctx, &args)?); + } } } Ok(()) @@ -283,9 +289,9 @@ impl Machine { } mod programs { - use crate::TulispObject; + use super::*; - use super::{Instruction, Pos}; + use crate::{list, TulispContext, TulispObject, TulispValue}; #[allow(dead_code)] pub(super) fn print_range(from: i64, to: i64) -> Vec { @@ -354,4 +360,27 @@ mod programs { Instruction::Print, // 18 ] } + + #[allow(dead_code)] + pub(super) fn rustcall_dotimes(ctx: &mut TulispContext, num: i64) -> Vec { + let var = TulispObject::symbol("var".to_string(), false); + let args = list!( + list!(var.clone(), num.into()).unwrap(), + list!(ctx.intern("print"), var).unwrap() + ) + .unwrap(); + vec![ + Instruction::Push(args), // 0 + Instruction::RustCall { + func: { + let obj = ctx.intern("dotimes").get().unwrap(); + if let TulispValue::Func(ref func) = obj.clone_inner() { + func.clone() + } else { + panic!("Expected function") + } + }, + }, + ] + } } From 7b0cc0ee27dae5f13b8383794a38c9dc75765eaa Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 22:28:01 +0100 Subject: [PATCH 010/142] Add frame for byte-compiler --- src/bin/tulisp_vm.rs | 15 +++++--- src/byte_compile.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ src/context.rs | 23 ++++++++++- src/error.rs | 2 + src/lib.rs | 1 + src/vm.rs | 4 +- 6 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 src/byte_compile.rs diff --git a/src/bin/tulisp_vm.rs b/src/bin/tulisp_vm.rs index 5a9a336e..33be7d24 100644 --- a/src/bin/tulisp_vm.rs +++ b/src/bin/tulisp_vm.rs @@ -1,10 +1,15 @@ -use tulisp::{vm, TulispContext}; +use std::env; + +use tulisp::TulispContext; fn main() { let mut ctx = TulispContext::new(); - let mut machine = vm::Machine::new(&mut ctx); - if let Err(e) = machine.run(&mut ctx, 0) { - println!("{}", e.format(&ctx)); - std::process::exit(-1); + let args: Vec = env::args().skip(1).collect(); + + for arg in args { + if let Err(e) = ctx.vm_eval_file(&arg) { + println!("{}", e.format(&ctx)); + std::process::exit(-1); + } } } diff --git a/src/byte_compile.rs b/src/byte_compile.rs new file mode 100644 index 00000000..adc8fa81 --- /dev/null +++ b/src/byte_compile.rs @@ -0,0 +1,90 @@ +use std::collections::HashMap; + +use crate::{ + lists, + vm::{Instruction, Pos}, + Error, ErrorKind, TulispContext, TulispObject, TulispValue, +}; + +#[allow(dead_code)] +struct VMFunctions { + defun: TulispObject, + le: TulispObject, + plus: TulispObject, + if_: TulispObject, + print: TulispObject, + other: HashMap, +} + +impl VMFunctions { + fn from(value: &mut TulispContext) -> Self { + VMFunctions { + defun: value.intern("defun"), + le: value.intern("<="), + plus: value.intern("+"), + if_: value.intern("if"), + print: value.intern("print"), + other: HashMap::new(), + } + } +} + +pub fn byte_compile( + ctx: &mut TulispContext, + value: &TulispObject, +) -> Result, Error> { + let mut functions = VMFunctions::from(ctx); + let mut result = Vec::new(); + for expr in value.base_iter() { + result.append(&mut byte_compile_expr(ctx, &mut functions, &expr)?); + } + Ok(result) +} + +fn byte_compile_expr( + ctx: &mut TulispContext, + functions: &mut VMFunctions, + expr: &TulispObject, +) -> Result, Error> { + match &*expr.inner_ref() { + TulispValue::Int { .. } + | TulispValue::Float { .. } + | TulispValue::String { .. } + | TulispValue::Lambda { .. } + | TulispValue::Func(_) + | TulispValue::Macro(_) + | TulispValue::Defmacro { .. } + | TulispValue::Any(_) + | TulispValue::Symbol { .. } + | TulispValue::Bounce + | TulispValue::Nil + | TulispValue::Quote { .. } + | TulispValue::Sharpquote { .. } + | TulispValue::Backquote { .. } + | TulispValue::Unquote { .. } + | TulispValue::Splice { .. } + | TulispValue::T => Ok(vec![Instruction::Push(expr.clone())]), + TulispValue::List { cons, .. } => byte_compile_form(ctx, functions, cons), + } +} + +fn byte_compile_form( + ctx: &mut TulispContext, + functions: &mut VMFunctions, + cons: &crate::cons::Cons, +) -> Result, Error> { + let mut result = Vec::new(); + let first = cons.car(); + let rest = cons.cdr(); + if first.eq(&functions.print) { + if lists::length(rest)? != 1 { + return Err(Error::new( + ErrorKind::ArityMismatch, + "print takes exactly one argument".to_string(), + )); + } + result.append(&mut byte_compile_expr(ctx, functions, &rest.car()?)?); + result.push(Instruction::Print); + } + Ok(result) +} diff --git a/src/context.rs b/src/context.rs index c384a3b2..4cca4214 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,12 +1,12 @@ use std::{collections::HashMap, fs}; use crate::{ - builtin, + builtin, byte_compile, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, parse::parse, - TulispObject, + vm, TulispObject, }; #[derive(Debug, Default, Clone)] @@ -193,6 +193,25 @@ impl TulispContext { self.eval_progn(&vv) } + pub fn vm_eval_file(&mut self, filename: &str) -> Result<(), Error> { + let contents = fs::read_to_string(filename).map_err(|e| { + Error::new( + crate::ErrorKind::Undefined, + format!("Unable to read file: {filename}. Error: {e}"), + ) + })?; + self.filenames.push(filename.to_owned()); + + let string: &str = &contents; + let vv = parse(self, self.filenames.len() - 1, string)?; + let bytecode = byte_compile::byte_compile(self, &vv)?; + for instr in &bytecode { + println!("{}", instr); + } + println!(); + vm::Machine::new(bytecode).run(self, 0) + } + pub(crate) fn get_filename(&self, file_id: usize) -> String { self.filenames[file_id].clone() } diff --git a/src/error.rs b/src/error.rs index 75434fce..cf3e6468 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ pub enum ErrorKind { NotImplemented, ParsingError, TypeMismatch, + ArityMismatch, Undefined, Uninitialized, SyntaxError, @@ -18,6 +19,7 @@ impl std::fmt::Display for ErrorKind { ErrorKind::NotImplemented => f.write_str("NotImplemented"), ErrorKind::ParsingError => f.write_str("ParsingError"), ErrorKind::TypeMismatch => f.write_str("TypeMismatch"), + ErrorKind::ArityMismatch => f.write_str("ArityMismatch"), ErrorKind::Undefined => f.write_str("Undefined"), ErrorKind::Uninitialized => f.write_str("Uninitialized"), ErrorKind::SyntaxError => f.write_str("SyntaxError"), diff --git a/src/lib.rs b/src/lib.rs index 120cba4f..b61c43b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ A list of currently available builtin functions can be found [here](builtin). a `TulispContext` object, so that they can be called from lisp code. */ +mod byte_compile; mod eval; mod macros; mod parse; diff --git a/src/vm.rs b/src/vm.rs index fc9546fe..7cddb18c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -114,12 +114,12 @@ pub struct Machine { } impl Machine { - pub fn new(ctx: &mut TulispContext) -> Self { + pub fn new(program: Vec) -> Self { Machine { stack: Vec::new(), // program: programs::print_range(92, 100), // program: programs::fib(30), - program: programs::rustcall_dotimes(ctx, 10), + program, pc: 0, } } From c2b19a0c5f613ce0a364964b1ade0c41fb7bdf42 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 23:12:51 +0100 Subject: [PATCH 011/142] Add a macro for compiling single-argument functions --- src/byte_compile.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index adc8fa81..bddfe454 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -68,23 +68,36 @@ fn byte_compile_expr( } } +macro_rules! function_1_arg { + ($name: ident, $args: ident, $($lambda: tt)+) => {{ + match $args.cdr_and_then(|x| Ok(x.null())) { + Err(e) => return Err(e), + Ok(false) => { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes exactly 1 argument", stringify!($name)), + )) + } + Ok(true) => {} + } + $args.car_and_then($($lambda)+) + }}; +} + fn byte_compile_form( ctx: &mut TulispContext, functions: &mut VMFunctions, cons: &crate::cons::Cons, ) -> Result, Error> { let mut result = Vec::new(); - let first = cons.car(); - let rest = cons.cdr(); - if first.eq(&functions.print) { - if lists::length(rest)? != 1 { - return Err(Error::new( - ErrorKind::ArityMismatch, - "print takes exactly one argument".to_string(), - )); - } - result.append(&mut byte_compile_expr(ctx, functions, &rest.car()?)?); - result.push(Instruction::Print); + let name = cons.car(); + let args = cons.cdr(); + if name.eq(&functions.print) { + function_1_arg!(name, args, |arg| { + result.append(&mut byte_compile_expr(ctx, functions, &arg)?); + result.push(Instruction::Print); + Ok(()) + })?; } Ok(result) } From 9ebeec3b922fcc47c4f4cd3fbb0ed0c0825c0c93 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 23:32:47 +0100 Subject: [PATCH 012/142] Add function for compiling 2-arg functions, and `setq` --- src/byte_compile.rs | 47 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index bddfe454..8baa44ed 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use crate::{ - lists, vm::{Instruction, Pos}, Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; @@ -13,6 +12,7 @@ struct VMFunctions { plus: TulispObject, if_: TulispObject, print: TulispObject, + setq: TulispObject, other: HashMap, } @@ -24,6 +24,7 @@ impl VMFunctions { plus: value.intern("+"), if_: value.intern("if"), print: value.intern("print"), + setq: value.intern("setq"), other: HashMap::new(), } } @@ -55,7 +56,6 @@ fn byte_compile_expr( | TulispValue::Macro(_) | TulispValue::Defmacro { .. } | TulispValue::Any(_) - | TulispValue::Symbol { .. } | TulispValue::Bounce | TulispValue::Nil | TulispValue::Quote { .. } @@ -64,7 +64,10 @@ fn byte_compile_expr( | TulispValue::Unquote { .. } | TulispValue::Splice { .. } | TulispValue::T => Ok(vec![Instruction::Push(expr.clone())]), - TulispValue::List { cons, .. } => byte_compile_form(ctx, functions, cons), + TulispValue::List { cons, .. } => { + byte_compile_form(ctx, functions, cons).map_err(|e| e.with_trace(expr.clone())) + } + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), } } @@ -75,7 +78,7 @@ macro_rules! function_1_arg { Ok(false) => { return Err(Error::new( ErrorKind::ArityMismatch, - format!("{} takes exactly 1 argument", stringify!($name)), + format!("{} takes 1 argument.", stringify!($name)), )) } Ok(true) => {} @@ -84,6 +87,36 @@ macro_rules! function_1_arg { }}; } +fn byte_compile_2_arg_fn( + name: &TulispObject, + args: &TulispObject, + lambda: impl FnOnce(&TulispObject, &TulispObject) -> Result<(), Error>, +) -> Result<(), Error> { + let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + }; + if args.cdr().null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + } + args.cdr().cdr_and_then(|x| { + if !x.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + } + Ok(()) + })?; + let arg1 = args.car(); + args.cdr().car_and_then(|arg2| lambda(arg1, arg2)) +} + fn byte_compile_form( ctx: &mut TulispContext, functions: &mut VMFunctions, @@ -98,6 +131,12 @@ fn byte_compile_form( result.push(Instruction::Print); Ok(()) })?; + } else if name.eq(&functions.setq) { + byte_compile_2_arg_fn(name, args, |arg1, arg2| { + result.append(&mut byte_compile_expr(ctx, functions, arg2)?); + result.push(Instruction::Store(arg1.clone())); + Ok(()) + })?; } Ok(result) } From 4e08ba7afa4a25a4770e0fe6a673dbd9129c6aa3 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 25 Dec 2023 23:58:25 +0100 Subject: [PATCH 013/142] Replace macro `function_1_arg` with `fn byte_compile_1_arg_fn` --- src/byte_compile.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index 8baa44ed..fa244937 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -71,20 +71,22 @@ fn byte_compile_expr( } } -macro_rules! function_1_arg { - ($name: ident, $args: ident, $($lambda: tt)+) => {{ - match $args.cdr_and_then(|x| Ok(x.null())) { - Err(e) => return Err(e), - Ok(false) => { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 1 argument.", stringify!($name)), - )) - } - Ok(true) => {} +fn byte_compile_1_arg_fn( + name: &TulispObject, + args: &TulispObject, + lambda: impl FnOnce(&TulispObject) -> Result<(), Error>, +) -> Result<(), Error> { + match args.cdr_and_then(|x| Ok(x.null())) { + Err(e) => return Err(e), + Ok(false) => { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 1 argument.", name), + )) } - $args.car_and_then($($lambda)+) - }}; + Ok(true) => {} + } + args.car_and_then(lambda) } fn byte_compile_2_arg_fn( @@ -126,7 +128,7 @@ fn byte_compile_form( let name = cons.car(); let args = cons.cdr(); if name.eq(&functions.print) { - function_1_arg!(name, args, |arg| { + byte_compile_1_arg_fn(name, args, |arg| { result.append(&mut byte_compile_expr(ctx, functions, &arg)?); result.push(Instruction::Print); Ok(()) From e58b4a4e115d337cd525cdea4686b93ad287d49a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 00:30:14 +0100 Subject: [PATCH 014/142] Create `Compiler` struct and move all compile methods inside it --- src/byte_compile.rs | 194 +++++++++++++++++++++++--------------------- src/context.rs | 6 +- 2 files changed, 104 insertions(+), 96 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index fa244937..ec99b35f 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -30,115 +30,121 @@ impl VMFunctions { } } -pub fn byte_compile( - ctx: &mut TulispContext, - value: &TulispObject, -) -> Result, Error> { - let mut functions = VMFunctions::from(ctx); - let mut result = Vec::new(); - for expr in value.base_iter() { - result.append(&mut byte_compile_expr(ctx, &mut functions, &expr)?); - } - Ok(result) +#[allow(dead_code)] +pub(crate) struct Compiler<'a> { + ctx: &'a mut TulispContext, + functions: VMFunctions, } -fn byte_compile_expr( - ctx: &mut TulispContext, - functions: &mut VMFunctions, - expr: &TulispObject, -) -> Result, Error> { - match &*expr.inner_ref() { - TulispValue::Int { .. } - | TulispValue::Float { .. } - | TulispValue::String { .. } - | TulispValue::Lambda { .. } - | TulispValue::Func(_) - | TulispValue::Macro(_) - | TulispValue::Defmacro { .. } - | TulispValue::Any(_) - | TulispValue::Bounce - | TulispValue::Nil - | TulispValue::Quote { .. } - | TulispValue::Sharpquote { .. } - | TulispValue::Backquote { .. } - | TulispValue::Unquote { .. } - | TulispValue::Splice { .. } - | TulispValue::T => Ok(vec![Instruction::Push(expr.clone())]), - TulispValue::List { cons, .. } => { - byte_compile_form(ctx, functions, cons).map_err(|e| e.with_trace(expr.clone())) +impl<'a> Compiler<'a> { + pub fn new(ctx: &'a mut TulispContext) -> Self { + let functions = VMFunctions::from(ctx); + Compiler { ctx, functions } + } + + pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { + let mut result = vec![]; + for expr in value.base_iter() { + result.append(&mut self.compile_expr(&expr)?); } - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), + Ok(result) } -} -fn byte_compile_1_arg_fn( - name: &TulispObject, - args: &TulispObject, - lambda: impl FnOnce(&TulispObject) -> Result<(), Error>, -) -> Result<(), Error> { - match args.cdr_and_then(|x| Ok(x.null())) { - Err(e) => return Err(e), - Ok(false) => { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 1 argument.", name), - )) + fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { + match &*expr.inner_ref() { + TulispValue::Int { .. } + | TulispValue::Float { .. } + | TulispValue::String { .. } + | TulispValue::Lambda { .. } + | TulispValue::Func(_) + | TulispValue::Macro(_) + | TulispValue::Defmacro { .. } + | TulispValue::Any(_) + | TulispValue::Bounce + | TulispValue::Nil + | TulispValue::Quote { .. } + | TulispValue::Sharpquote { .. } + | TulispValue::Backquote { .. } + | TulispValue::Unquote { .. } + | TulispValue::Splice { .. } + | TulispValue::T => return Ok(vec![Instruction::Push(expr.clone())]), + TulispValue::List { cons, .. } => self + .compile_form(cons) + .map_err(|e| e.with_trace(expr.clone())), + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), } - Ok(true) => {} } - args.car_and_then(lambda) -} -fn byte_compile_2_arg_fn( - name: &TulispObject, - args: &TulispObject, - lambda: impl FnOnce(&TulispObject, &TulispObject) -> Result<(), Error>, -) -> Result<(), Error> { - let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), - )); - }; - if args.cdr().null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), - )); + fn compile_1_arg_call( + &mut self, + name: &TulispObject, + args: &TulispObject, + lambda: fn(&mut Compiler, &TulispObject) -> Result, Error>, + ) -> Result, Error> { + match args.cdr_and_then(|x| Ok(x.null())) { + Err(e) => return Err(e), + Ok(false) => { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 1 argument.", name), + )) + } + Ok(true) => {} + } + args.car_and_then(|x| lambda(self, x)) } - args.cdr().cdr_and_then(|x| { - if !x.null() { + + fn compile_2_arg_call( + &mut self, + name: &TulispObject, + args: &TulispObject, + lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, + ) -> Result, Error> { + let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + }; + if args.cdr().null() { return Err(Error::new( ErrorKind::ArityMismatch, format!("{} takes 2 arguments.", name), )); } - Ok(()) - })?; - let arg1 = args.car(); - args.cdr().car_and_then(|arg2| lambda(arg1, arg2)) -} - -fn byte_compile_form( - ctx: &mut TulispContext, - functions: &mut VMFunctions, - cons: &crate::cons::Cons, -) -> Result, Error> { - let mut result = Vec::new(); - let name = cons.car(); - let args = cons.cdr(); - if name.eq(&functions.print) { - byte_compile_1_arg_fn(name, args, |arg| { - result.append(&mut byte_compile_expr(ctx, functions, &arg)?); - result.push(Instruction::Print); - Ok(()) - })?; - } else if name.eq(&functions.setq) { - byte_compile_2_arg_fn(name, args, |arg1, arg2| { - result.append(&mut byte_compile_expr(ctx, functions, arg2)?); - result.push(Instruction::Store(arg1.clone())); + args.cdr().cdr_and_then(|x| { + if !x.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + } Ok(()) })?; + let arg1 = args.car(); + args.cdr().car_and_then(|arg2| lambda(self, arg1, arg2)) + } + + fn compile_form(&mut self, cons: &crate::cons::Cons) -> Result, Error> { + let name = cons.car(); + let args = cons.cdr(); + if name.eq(&self.functions.print) { + self.compile_1_arg_call(name, args, |ctx, arg| { + let mut result = ctx.compile_expr(arg)?; + result.push(Instruction::Print); + Ok(result) + }) + } else if name.eq(&self.functions.setq) { + self.compile_2_arg_call(name, args, |ctx, arg1, arg2| { + let mut result = ctx.compile_expr(arg2)?; + result.push(Instruction::Store(arg1.clone())); + Ok(result) + }) + } else { + Err(Error::new( + ErrorKind::Undefined, + format!("undefined function: {}", name), + )) + } } - Ok(result) } diff --git a/src/context.rs b/src/context.rs index 4cca4214..36dc217f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, fs}; use crate::{ - builtin, byte_compile, + builtin, + byte_compile::Compiler, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, @@ -204,7 +205,8 @@ impl TulispContext { let string: &str = &contents; let vv = parse(self, self.filenames.len() - 1, string)?; - let bytecode = byte_compile::byte_compile(self, &vv)?; + let mut compiler = Compiler::new(self); + let bytecode = compiler.compile(&vv)?; for instr in &bytecode { println!("{}", instr); } From 53c069d48a4b44bd7b04be42e20c611cf75a206d Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 00:56:25 +0100 Subject: [PATCH 015/142] Add a `Label` instruction type --- src/vm.rs | 111 +++++++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 7cddb18c..60facee9 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, rc::Rc}; +use std::{collections::HashMap, fmt::Display, rc::Rc}; use crate::{context::Scope, value::TulispFn, Error, TulispContext, TulispObject}; @@ -46,6 +46,7 @@ macro_rules! binary_ops { pub enum Pos { Abs(usize), Rel(isize), + Label(TulispObject), } /// A single instruction in the VM. @@ -74,6 +75,7 @@ pub enum Instruction { JumpIfGtEq(Pos), Jump(Pos), // functions + Label(TulispObject), RustCall { func: Rc }, Call { pos: Pos, params: Vec }, Ret, @@ -82,27 +84,28 @@ pub enum Instruction { impl Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Instruction::Push(obj) => write!(f, "push {}", obj), - Instruction::Store(obj) => write!(f, "store {}", obj), - Instruction::Load(obj) => write!(f, "load {}", obj), - Instruction::Add => write!(f, "add"), - Instruction::Sub => write!(f, "sub"), - Instruction::Print => write!(f, "print"), - Instruction::JumpIfNil(pos) => write!(f, "jnil {:?}", pos), - Instruction::JumpIfEq(pos) => write!(f, "jeq {:?}", pos), - Instruction::JumpIfLt(pos) => write!(f, "jlt {:?}", pos), - Instruction::JumpIfLtEq(pos) => write!(f, "jle {:?}", pos), - Instruction::JumpIfGt(pos) => write!(f, "jgt {:?}", pos), - Instruction::JumpIfGtEq(pos) => write!(f, "jge {:?}", pos), - Instruction::Eq => write!(f, "ceq"), - Instruction::Lt => write!(f, "clt"), - Instruction::LtEq => write!(f, "cle"), - Instruction::Gt => write!(f, "cgt"), - Instruction::GtEq => write!(f, "cge"), - Instruction::Jump(pos) => write!(f, "jmp {:?}", pos), - Instruction::Call { pos, .. } => write!(f, "call {:?}", pos), - Instruction::Ret => write!(f, "ret"), - Instruction::RustCall { .. } => write!(f, "rustcall"), + Instruction::Push(obj) => write!(f, " push {}", obj), + Instruction::Store(obj) => write!(f, " store {}", obj), + Instruction::Load(obj) => write!(f, " load {}", obj), + Instruction::Add => write!(f, " add"), + Instruction::Sub => write!(f, " sub"), + Instruction::Print => write!(f, " print"), + Instruction::JumpIfNil(pos) => write!(f, " jnil {:?}", pos), + Instruction::JumpIfEq(pos) => write!(f, " jeq {:?}", pos), + Instruction::JumpIfLt(pos) => write!(f, " jlt {:?}", pos), + Instruction::JumpIfLtEq(pos) => write!(f, " jle {:?}", pos), + Instruction::JumpIfGt(pos) => write!(f, " jgt {:?}", pos), + Instruction::JumpIfGtEq(pos) => write!(f, " jge {:?}", pos), + Instruction::Eq => write!(f, " ceq"), + Instruction::Lt => write!(f, " clt"), + Instruction::LtEq => write!(f, " cle"), + Instruction::Gt => write!(f, " cgt"), + Instruction::GtEq => write!(f, " cge"), + Instruction::Jump(pos) => write!(f, " jmp {:?}", pos), + Instruction::Call { pos, .. } => write!(f, " call {:?}", pos), + Instruction::Ret => write!(f, " ret"), + Instruction::RustCall { .. } => write!(f, " rustcall"), + Instruction::Label(name) => write!(f, "{}:", name), } } } @@ -110,6 +113,7 @@ impl Display for Instruction { pub struct Machine { stack: Vec, program: Vec, + labels: HashMap, // TulispObject.addr -> instruction index pc: usize, } @@ -117,6 +121,7 @@ impl Machine { pub fn new(program: Vec) -> Self { Machine { stack: Vec::new(), + labels: Self::locate_labels(&program), // program: programs::print_range(92, 100), // program: programs::fib(30), program, @@ -124,6 +129,24 @@ impl Machine { } } + fn locate_labels(program: &Vec) -> HashMap { + let mut labels = HashMap::new(); + for (i, instr) in program.iter().enumerate() { + if let Instruction::Label(name) = instr { + labels.insert(name.addr_as_usize(), i); + } + } + labels + } + + fn goto_pos(&self, pos: &Pos) -> usize { + match pos { + Pos::Abs(p) => *p, + Pos::Rel(p) => (self.pc as isize + p - 1) as usize, + Pos::Label(p) => *self.labels.get(&p.addr_as_usize()).unwrap(), + } + } + #[allow(dead_code)] fn print_stack(&self) { println!("Stack:"); @@ -162,66 +185,45 @@ impl Machine { Instruction::JumpIfNil(pos) => { let a = self.stack.pop().unwrap(); if a.null() { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } Instruction::JumpIfEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if a.eq(&b) { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } Instruction::JumpIfLt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } Instruction::JumpIfLtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } Instruction::JumpIfGt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } Instruction::JumpIfGtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); } } - Instruction::Jump(pos) => match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - }, + Instruction::Jump(pos) => self.pc = self.goto_pos(pos), Instruction::Eq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); @@ -261,13 +263,9 @@ impl Machine { } Instruction::Call { pos, params } => { let pc = self.pc; - let mut scope = Scope::default(); - - match pos { - Pos::Abs(p) => self.pc = *p, - Pos::Rel(p) => self.pc = (self.pc as isize + p - 1) as usize, - } + self.pc = self.goto_pos(pos); + let mut scope = Scope::default(); for param in params { let value = self.stack.pop().unwrap(); scope.set(param.clone(), value)?; @@ -282,6 +280,7 @@ impl Machine { let args = self.stack.pop().unwrap(); self.stack.push(func(ctx, &args)?); } + Instruction::Label(_) => {} } } Ok(()) From 32b61cbb5db6eb21a2b228436584cd1d9563bb05 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 01:59:43 +0100 Subject: [PATCH 016/142] Add `if`, `le`, `plus` and `progn` --- src/byte_compile.rs | 60 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index ec99b35f..09632c24 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -8,6 +8,7 @@ use crate::{ #[allow(dead_code)] struct VMFunctions { defun: TulispObject, + progn: TulispObject, le: TulispObject, plus: TulispObject, if_: TulispObject, @@ -20,6 +21,7 @@ impl VMFunctions { fn from(value: &mut TulispContext) -> Self { VMFunctions { defun: value.intern("defun"), + progn: value.intern("progn"), le: value.intern("<="), plus: value.intern("+"), if_: value.intern("if"), @@ -98,7 +100,13 @@ impl<'a> Compiler<'a> { &mut self, name: &TulispObject, args: &TulispObject, - lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, + has_rest: bool, + lambda: fn( + &mut Compiler, + &TulispObject, + &TulispObject, + &TulispObject, + ) -> Result, Error>, ) -> Result, Error> { let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { return Err(Error::new( @@ -112,17 +120,18 @@ impl<'a> Compiler<'a> { format!("{} takes 2 arguments.", name), )); } - args.cdr().cdr_and_then(|x| { - if !x.null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), - )); - } - Ok(()) - })?; let arg1 = args.car(); - args.cdr().car_and_then(|arg2| lambda(self, arg1, arg2)) + args.cdr().car_and_then(|arg2| { + args.cdr().cdr_and_then(|rest| { + if !has_rest && !rest.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} takes 2 arguments.", name), + )); + } + lambda(self, arg1, arg2, rest) + }) + }) } fn compile_form(&mut self, cons: &crate::cons::Cons) -> Result, Error> { @@ -135,11 +144,38 @@ impl<'a> Compiler<'a> { Ok(result) }) } else if name.eq(&self.functions.setq) { - self.compile_2_arg_call(name, args, |ctx, arg1, arg2| { + self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { let mut result = ctx.compile_expr(arg2)?; result.push(Instruction::Store(arg1.clone())); Ok(result) }) + } else if name.eq(&self.functions.le) { + self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = ctx.compile_expr(arg2)?; + result.append(&mut ctx.compile_expr(arg1)?); + result.push(Instruction::LtEq); + Ok(result) + }) + } else if name.eq(&self.functions.plus) { + self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = ctx.compile_expr(arg2)?; + result.append(&mut ctx.compile_expr(arg1)?); + result.push(Instruction::Add); + Ok(result) + }) + } else if name.eq(&self.functions.if_) { + self.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + let mut result = ctx.compile_expr(cond)?; + let mut then = ctx.compile_expr(then)?; + let mut else_ = ctx.compile(else_)?; + result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); + result.append(&mut then); + result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); + result.append(&mut else_); + Ok(result) + }) + } else if name.eq(&self.functions.progn) { + Ok(self.compile(args)?) } else { Err(Error::new( ErrorKind::Undefined, From 034fb100f1c6aec5ce517daf19855669c1c33b0f Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 02:47:37 +0100 Subject: [PATCH 017/142] Keep result in stack only when necessary --- src/byte_compile.rs | 87 ++++++++++++++++++++++++++++++++++----------- src/vm.rs | 30 +++++++++++----- 2 files changed, 87 insertions(+), 30 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index 09632c24..b5c0ea68 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -14,7 +14,7 @@ struct VMFunctions { if_: TulispObject, print: TulispObject, setq: TulispObject, - other: HashMap, + other: HashMap, // TulispObject.addr() -> Pos } impl VMFunctions { @@ -36,18 +36,33 @@ impl VMFunctions { pub(crate) struct Compiler<'a> { ctx: &'a mut TulispContext, functions: VMFunctions, + keep_result: bool, } impl<'a> Compiler<'a> { pub fn new(ctx: &'a mut TulispContext) -> Self { let functions = VMFunctions::from(ctx); - Compiler { ctx, functions } + Compiler { + ctx, + functions, + keep_result: true, + } } pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { let mut result = vec![]; + let mut prev = None; + let keep_result = self.keep_result; for expr in value.base_iter() { - result.append(&mut self.compile_expr(&expr)?); + if let Some(prev) = prev { + self.keep_result = false; + result.append(&mut self.compile_expr(&prev)?); + } + prev = Some(expr); + } + self.keep_result = keep_result; + if let Some(prev) = prev { + result.append(&mut self.compile_expr(&prev)?); } Ok(result) } @@ -69,7 +84,13 @@ impl<'a> Compiler<'a> { | TulispValue::Backquote { .. } | TulispValue::Unquote { .. } | TulispValue::Splice { .. } - | TulispValue::T => return Ok(vec![Instruction::Push(expr.clone())]), + | TulispValue::T => { + if self.keep_result { + return Ok(vec![Instruction::Push(expr.clone())]); + } else { + return Ok(vec![]); + } + } TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), @@ -77,6 +98,14 @@ impl<'a> Compiler<'a> { } } + fn compile_expr_keep_result(&mut self, expr: &TulispObject) -> Result, Error> { + let keep_result = self.keep_result; + self.keep_result = true; + let ret = self.compile_expr(expr); + self.keep_result = keep_result; + ret + } + fn compile_1_arg_call( &mut self, name: &TulispObject, @@ -138,34 +167,50 @@ impl<'a> Compiler<'a> { let name = cons.car(); let args = cons.cdr(); if name.eq(&self.functions.print) { - self.compile_1_arg_call(name, args, |ctx, arg| { - let mut result = ctx.compile_expr(arg)?; - result.push(Instruction::Print); + self.compile_1_arg_call(name, args, |compiler, arg| { + let mut result = compiler.compile_expr_keep_result(arg)?; + if compiler.keep_result { + result.push(Instruction::Print); + } else { + result.push(Instruction::PrintPop); + } Ok(result) }) } else if name.eq(&self.functions.setq) { - self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { - let mut result = ctx.compile_expr(arg2)?; - result.push(Instruction::Store(arg1.clone())); + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + let mut result = compiler.compile_expr_keep_result(arg2)?; + if compiler.keep_result { + result.push(Instruction::Store(arg1.clone())); + } else { + result.push(Instruction::StorePop(arg1.clone())); + } Ok(result) }) } else if name.eq(&self.functions.le) { - self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { - let mut result = ctx.compile_expr(arg2)?; - result.append(&mut ctx.compile_expr(arg1)?); - result.push(Instruction::LtEq); - Ok(result) + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::LtEq); + Ok(result) + } }) } else if name.eq(&self.functions.plus) { - self.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { - let mut result = ctx.compile_expr(arg2)?; - result.append(&mut ctx.compile_expr(arg1)?); - result.push(Instruction::Add); - Ok(result) + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Add); + Ok(result) + } }) } else if name.eq(&self.functions.if_) { self.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { - let mut result = ctx.compile_expr(cond)?; + let mut result = ctx.compile_expr_keep_result(cond)?; let mut then = ctx.compile_expr(then)?; let mut else_ = ctx.compile(else_)?; result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); diff --git a/src/vm.rs b/src/vm.rs index 60facee9..1f499594 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -53,12 +53,14 @@ pub enum Pos { pub enum Instruction { Push(TulispObject), // variables + StorePop(TulispObject), Store(TulispObject), Load(TulispObject), // arithmetic Add, Sub, // io + PrintPop, Print, // comparison Eq, @@ -85,10 +87,12 @@ impl Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Instruction::Push(obj) => write!(f, " push {}", obj), + Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), Instruction::Store(obj) => write!(f, " store {}", obj), Instruction::Load(obj) => write!(f, " load {}", obj), Instruction::Add => write!(f, " add"), Instruction::Sub => write!(f, " sub"), + Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), Instruction::JumpIfNil(pos) => write!(f, " jnil {:?}", pos), Instruction::JumpIfEq(pos) => write!(f, " jeq {:?}", pos), @@ -178,10 +182,14 @@ impl Machine { let b = self.stack.pop().unwrap(); self.stack.push(binary_ops!(std::ops::Sub::sub)(&a, &b)?); } - Instruction::Print => { + Instruction::PrintPop => { let a = self.stack.pop().unwrap(); println!("{}", a); } + Instruction::Print => { + let a = self.stack.last().unwrap(); + println!("{}", a); + } Instruction::JumpIfNil(pos) => { let a = self.stack.pop().unwrap(); if a.null() { @@ -253,10 +261,14 @@ impl Machine { self.stack .push(compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)?.into()); } - Instruction::Store(obj) => { + Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); obj.set(a)?; } + Instruction::Store(obj) => { + let a = self.stack.last().unwrap(); + obj.set(a.clone())?; + } Instruction::Load(obj) => { let a = obj.get()?; self.stack.push(a); @@ -298,13 +310,13 @@ mod programs { let n = TulispObject::symbol("n".to_string(), false); vec![ // print numbers 1 to 10 - Instruction::Push(from.into()), // 0 - Instruction::Store(i.clone()), // 1 - Instruction::Push(to.into()), // 2 - Instruction::Store(n.clone()), // 3 + Instruction::Push(from.into()), // 0 + Instruction::StorePop(i.clone()), // 1 + Instruction::Push(to.into()), // 2 + Instruction::StorePop(n.clone()), // 3 // loop: Instruction::Load(i.clone()), // 4 - Instruction::Print, // 5 + Instruction::PrintPop, // 5 Instruction::Push(1.into()), // 6 Instruction::Load(i.clone()), // 7 if from < to { @@ -312,7 +324,7 @@ mod programs { } else { Instruction::Sub }, // 8 - Instruction::Store(i.clone()), // 9 + Instruction::StorePop(i.clone()), // 9 Instruction::Load(i.clone()), // 10 Instruction::Load(n.clone()), // 11 if from < to { @@ -356,7 +368,7 @@ mod programs { pos: Pos::Abs(1), params: vec![n.clone()], }, // 17 - Instruction::Print, // 18 + Instruction::PrintPop, // 18 ] } From 7fca3ed1a7a5b252be968befefb4c2d551a096ac Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 13:09:19 +0100 Subject: [PATCH 018/142] Support `rest` arguments in `compile_1_arg_call` Also improve error messages in `compile_2_arg_call`. --- src/byte_compile.rs | 49 +++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index b5c0ea68..97a56890 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -110,19 +110,30 @@ impl<'a> Compiler<'a> { &mut self, name: &TulispObject, args: &TulispObject, - lambda: fn(&mut Compiler, &TulispObject) -> Result, Error>, + has_rest: bool, + lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, ) -> Result, Error> { - match args.cdr_and_then(|x| Ok(x.null())) { - Err(e) => return Err(e), - Ok(false) => { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} takes 1 argument.", name), - )) - } - Ok(true) => {} + if args.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + if has_rest { + format!("{} requires at least 1 argument.", name) + } else { + format!("{} requires 1 argument.", name) + }, + )); } - args.car_and_then(|x| lambda(self, x)) + args.car_and_then(|arg1| { + args.cdr_and_then(|rest| { + if !has_rest && !rest.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} accepts only 1 argument.", name), + )); + } + lambda(self, arg1, rest) + }) + }) } fn compile_2_arg_call( @@ -140,13 +151,21 @@ impl<'a> Compiler<'a> { let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { return Err(Error::new( ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), + if has_rest { + format!("{} requires at least 2 arguments.", name) + } else { + format!("{} requires 2 arguments.", name) + }, )); }; if args.cdr().null() { return Err(Error::new( ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), + if has_rest { + format!("{} requires at least 2 arguments.", name) + } else { + format!("{} requires 2 arguments.", name) + }, )); } let arg1 = args.car(); @@ -155,7 +174,7 @@ impl<'a> Compiler<'a> { if !has_rest && !rest.null() { return Err(Error::new( ErrorKind::ArityMismatch, - format!("{} takes 2 arguments.", name), + format!("{} accepts only 2 arguments.", name), )); } lambda(self, arg1, arg2, rest) @@ -167,7 +186,7 @@ impl<'a> Compiler<'a> { let name = cons.car(); let args = cons.cdr(); if name.eq(&self.functions.print) { - self.compile_1_arg_call(name, args, |compiler, arg| { + self.compile_1_arg_call(name, args, false, |compiler, arg, _| { let mut result = compiler.compile_expr_keep_result(arg)?; if compiler.keep_result { result.push(Instruction::Print); From e271c5299128623b5efa1ceb7e9ce06ed1656835 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 13:23:05 +0100 Subject: [PATCH 019/142] Refactor lisp function-call compilers --- src/byte_compile.rs | 149 +++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index 97a56890..6d3a5399 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -186,60 +186,17 @@ impl<'a> Compiler<'a> { let name = cons.car(); let args = cons.cdr(); if name.eq(&self.functions.print) { - self.compile_1_arg_call(name, args, false, |compiler, arg, _| { - let mut result = compiler.compile_expr_keep_result(arg)?; - if compiler.keep_result { - result.push(Instruction::Print); - } else { - result.push(Instruction::PrintPop); - } - Ok(result) - }) + self.compile_fn_print(name, args) } else if name.eq(&self.functions.setq) { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr_keep_result(arg2)?; - if compiler.keep_result { - result.push(Instruction::Store(arg1.clone())); - } else { - result.push(Instruction::StorePop(arg1.clone())); - } - Ok(result) - }) + self.compile_fn_setq(name, args) } else if name.eq(&self.functions.le) { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::LtEq); - Ok(result) - } - }) + self.compile_fn_le(name, args) } else if name.eq(&self.functions.plus) { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Add); - Ok(result) - } - }) + self.compile_fn_plus(name, args) } else if name.eq(&self.functions.if_) { - self.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr(then)?; - let mut else_ = ctx.compile(else_)?; - result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); - result.append(&mut then); - result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); - result.append(&mut else_); - Ok(result) - }) + self.compile_fn_if(name, args) } else if name.eq(&self.functions.progn) { - Ok(self.compile(args)?) + self.compile_fn_progn(name, args) } else { Err(Error::new( ErrorKind::Undefined, @@ -248,3 +205,97 @@ impl<'a> Compiler<'a> { } } } + +/// Compilers for specific lisp functions. +impl Compiler<'_> { + fn compile_fn_print( + &mut self, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + self.compile_1_arg_call(name, args, false, |compiler, arg, _| { + let mut result = compiler.compile_expr_keep_result(arg)?; + if compiler.keep_result { + result.push(Instruction::Print); + } else { + result.push(Instruction::PrintPop); + } + Ok(result) + }) + } + + fn compile_fn_setq( + &mut self, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + let mut result = compiler.compile_expr_keep_result(arg2)?; + if compiler.keep_result { + result.push(Instruction::Store(arg1.clone())); + } else { + result.push(Instruction::StorePop(arg1.clone())); + } + Ok(result) + }) + } + + fn compile_fn_le( + &mut self, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::LtEq); + Ok(result) + } + }) + } + + fn compile_fn_plus( + &mut self, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Add); + Ok(result) + } + }) + } + + fn compile_fn_if( + &mut self, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + self.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + let mut result = ctx.compile_expr_keep_result(cond)?; + let mut then = ctx.compile_expr(then)?; + let mut else_ = ctx.compile(else_)?; + result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); + result.append(&mut then); + result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); + result.append(&mut else_); + Ok(result) + }) + } + + fn compile_fn_progn( + &mut self, + _name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + Ok(self.compile(args)?) + } +} From d5ca3539a9737db5965efa947d14e16baa6c98d3 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 14:04:10 +0100 Subject: [PATCH 020/142] Use a map-lookup to identify function call compilers Also add compilers for some arithmetic and comparison operators. --- src/byte_compile.rs | 175 +++++++++++++++++++++++++++++++++----------- 1 file changed, 132 insertions(+), 43 deletions(-) diff --git a/src/byte_compile.rs b/src/byte_compile.rs index 6d3a5399..1ef4ff56 100644 --- a/src/byte_compile.rs +++ b/src/byte_compile.rs @@ -5,30 +5,44 @@ use crate::{ Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; +type CompileResult = Result, Error>; +type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; + #[allow(dead_code)] struct VMFunctions { - defun: TulispObject, - progn: TulispObject, - le: TulispObject, - plus: TulispObject, - if_: TulispObject, - print: TulispObject, - setq: TulispObject, - other: HashMap, // TulispObject.addr() -> Pos + // TulispObject.addr() -> implementation + functions: HashMap, +} + +macro_rules! map_fn_call_compilers { + ($ctx:ident, $functions: ident, $(($name:literal, $compiler:ident),)+) => { + $( + $functions.insert( + $ctx.intern($name).addr_as_usize(), + Compiler::$compiler as FnCallCompiler, + ); + )+ + }; } impl VMFunctions { - fn from(value: &mut TulispContext) -> Self { - VMFunctions { - defun: value.intern("defun"), - progn: value.intern("progn"), - le: value.intern("<="), - plus: value.intern("+"), - if_: value.intern("if"), - print: value.intern("print"), - setq: value.intern("setq"), - other: HashMap::new(), + fn from(ctx: &mut TulispContext) -> Self { + let mut functions = HashMap::new(); + map_fn_call_compilers! { + ctx, functions, + ("<=", compile_fn_le), + ("<", compile_fn_lt), + (">=", compile_fn_ge), + (">", compile_fn_gt), + ("eq", compile_fn_eq), + ("+", compile_fn_plus), + ("-", compile_fn_minus), + ("print", compile_fn_print), + ("setq", compile_fn_setq), + ("if", compile_fn_if), + ("progn", compile_fn_progn), } + VMFunctions { functions } } } @@ -185,18 +199,8 @@ impl<'a> Compiler<'a> { fn compile_form(&mut self, cons: &crate::cons::Cons) -> Result, Error> { let name = cons.car(); let args = cons.cdr(); - if name.eq(&self.functions.print) { - self.compile_fn_print(name, args) - } else if name.eq(&self.functions.setq) { - self.compile_fn_setq(name, args) - } else if name.eq(&self.functions.le) { - self.compile_fn_le(name, args) - } else if name.eq(&self.functions.plus) { - self.compile_fn_plus(name, args) - } else if name.eq(&self.functions.if_) { - self.compile_fn_if(name, args) - } else if name.eq(&self.functions.progn) { - self.compile_fn_progn(name, args) + if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { + compiler(self, &name, &args) } else { Err(Error::new( ErrorKind::Undefined, @@ -207,13 +211,13 @@ impl<'a> Compiler<'a> { } /// Compilers for specific lisp functions. -impl Compiler<'_> { +impl<'a> Compiler<'a> { fn compile_fn_print( - &mut self, + compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - self.compile_1_arg_call(name, args, false, |compiler, arg, _| { + compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { let mut result = compiler.compile_expr_keep_result(arg)?; if compiler.keep_result { result.push(Instruction::Print); @@ -225,11 +229,11 @@ impl Compiler<'_> { } fn compile_fn_setq( - &mut self, + compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { let mut result = compiler.compile_expr_keep_result(arg2)?; if compiler.keep_result { result.push(Instruction::Store(arg1.clone())); @@ -240,12 +244,29 @@ impl Compiler<'_> { }) } + fn compile_fn_lt( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Lt); + Ok(result) + } + }) + } + fn compile_fn_le( - &mut self, + compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { if !compiler.keep_result { Ok(vec![]) } else { @@ -257,12 +278,63 @@ impl Compiler<'_> { }) } + fn compile_fn_gt( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Gt); + Ok(result) + } + }) + } + + fn compile_fn_ge( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::GtEq); + Ok(result) + } + }) + } + + fn compile_fn_eq( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Eq); + Ok(result) + } + }) + } + fn compile_fn_plus( - &mut self, + compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - self.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { if !compiler.keep_result { Ok(vec![]) } else { @@ -274,12 +346,29 @@ impl Compiler<'_> { }) } + fn compile_fn_minus( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + ) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Sub); + Ok(result) + } + }) + } + fn compile_fn_if( - &mut self, + compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - self.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { let mut result = ctx.compile_expr_keep_result(cond)?; let mut then = ctx.compile_expr(then)?; let mut else_ = ctx.compile(else_)?; @@ -292,10 +381,10 @@ impl Compiler<'_> { } fn compile_fn_progn( - &mut self, + compiler: &mut Compiler<'_>, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - Ok(self.compile(args)?) + Ok(compiler.compile(args)?) } } From f7975c2b1dde42dae2f023be086e12235eeb6a6a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 26 Dec 2023 15:12:38 +0100 Subject: [PATCH 021/142] Refactor byte_compile into a submodule --- src/byte_compile.rs | 390 ------------------ src/byte_compile/byte_compile.rs | 86 ++++ .../forms/arithmetic_operations.rs | 35 ++ src/byte_compile/forms/common.rs | 79 ++++ .../forms/comparison_of_numbers.rs | 86 ++++ src/byte_compile/forms/mod.rs | 67 +++ src/byte_compile/forms/other_functions.rs | 62 +++ src/byte_compile/mod.rs | 3 + 8 files changed, 418 insertions(+), 390 deletions(-) delete mode 100644 src/byte_compile.rs create mode 100644 src/byte_compile/byte_compile.rs create mode 100644 src/byte_compile/forms/arithmetic_operations.rs create mode 100644 src/byte_compile/forms/common.rs create mode 100644 src/byte_compile/forms/comparison_of_numbers.rs create mode 100644 src/byte_compile/forms/mod.rs create mode 100644 src/byte_compile/forms/other_functions.rs create mode 100644 src/byte_compile/mod.rs diff --git a/src/byte_compile.rs b/src/byte_compile.rs deleted file mode 100644 index 1ef4ff56..00000000 --- a/src/byte_compile.rs +++ /dev/null @@ -1,390 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - vm::{Instruction, Pos}, - Error, ErrorKind, TulispContext, TulispObject, TulispValue, -}; - -type CompileResult = Result, Error>; -type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; - -#[allow(dead_code)] -struct VMFunctions { - // TulispObject.addr() -> implementation - functions: HashMap, -} - -macro_rules! map_fn_call_compilers { - ($ctx:ident, $functions: ident, $(($name:literal, $compiler:ident),)+) => { - $( - $functions.insert( - $ctx.intern($name).addr_as_usize(), - Compiler::$compiler as FnCallCompiler, - ); - )+ - }; -} - -impl VMFunctions { - fn from(ctx: &mut TulispContext) -> Self { - let mut functions = HashMap::new(); - map_fn_call_compilers! { - ctx, functions, - ("<=", compile_fn_le), - ("<", compile_fn_lt), - (">=", compile_fn_ge), - (">", compile_fn_gt), - ("eq", compile_fn_eq), - ("+", compile_fn_plus), - ("-", compile_fn_minus), - ("print", compile_fn_print), - ("setq", compile_fn_setq), - ("if", compile_fn_if), - ("progn", compile_fn_progn), - } - VMFunctions { functions } - } -} - -#[allow(dead_code)] -pub(crate) struct Compiler<'a> { - ctx: &'a mut TulispContext, - functions: VMFunctions, - keep_result: bool, -} - -impl<'a> Compiler<'a> { - pub fn new(ctx: &'a mut TulispContext) -> Self { - let functions = VMFunctions::from(ctx); - Compiler { - ctx, - functions, - keep_result: true, - } - } - - pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { - let mut result = vec![]; - let mut prev = None; - let keep_result = self.keep_result; - for expr in value.base_iter() { - if let Some(prev) = prev { - self.keep_result = false; - result.append(&mut self.compile_expr(&prev)?); - } - prev = Some(expr); - } - self.keep_result = keep_result; - if let Some(prev) = prev { - result.append(&mut self.compile_expr(&prev)?); - } - Ok(result) - } - - fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { - match &*expr.inner_ref() { - TulispValue::Int { .. } - | TulispValue::Float { .. } - | TulispValue::String { .. } - | TulispValue::Lambda { .. } - | TulispValue::Func(_) - | TulispValue::Macro(_) - | TulispValue::Defmacro { .. } - | TulispValue::Any(_) - | TulispValue::Bounce - | TulispValue::Nil - | TulispValue::Quote { .. } - | TulispValue::Sharpquote { .. } - | TulispValue::Backquote { .. } - | TulispValue::Unquote { .. } - | TulispValue::Splice { .. } - | TulispValue::T => { - if self.keep_result { - return Ok(vec![Instruction::Push(expr.clone())]); - } else { - return Ok(vec![]); - } - } - TulispValue::List { cons, .. } => self - .compile_form(cons) - .map_err(|e| e.with_trace(expr.clone())), - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), - } - } - - fn compile_expr_keep_result(&mut self, expr: &TulispObject) -> Result, Error> { - let keep_result = self.keep_result; - self.keep_result = true; - let ret = self.compile_expr(expr); - self.keep_result = keep_result; - ret - } - - fn compile_1_arg_call( - &mut self, - name: &TulispObject, - args: &TulispObject, - has_rest: bool, - lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, - ) -> Result, Error> { - if args.null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - if has_rest { - format!("{} requires at least 1 argument.", name) - } else { - format!("{} requires 1 argument.", name) - }, - )); - } - args.car_and_then(|arg1| { - args.cdr_and_then(|rest| { - if !has_rest && !rest.null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} accepts only 1 argument.", name), - )); - } - lambda(self, arg1, rest) - }) - }) - } - - fn compile_2_arg_call( - &mut self, - name: &TulispObject, - args: &TulispObject, - has_rest: bool, - lambda: fn( - &mut Compiler, - &TulispObject, - &TulispObject, - &TulispObject, - ) -> Result, Error>, - ) -> Result, Error> { - let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { - return Err(Error::new( - ErrorKind::ArityMismatch, - if has_rest { - format!("{} requires at least 2 arguments.", name) - } else { - format!("{} requires 2 arguments.", name) - }, - )); - }; - if args.cdr().null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - if has_rest { - format!("{} requires at least 2 arguments.", name) - } else { - format!("{} requires 2 arguments.", name) - }, - )); - } - let arg1 = args.car(); - args.cdr().car_and_then(|arg2| { - args.cdr().cdr_and_then(|rest| { - if !has_rest && !rest.null() { - return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} accepts only 2 arguments.", name), - )); - } - lambda(self, arg1, arg2, rest) - }) - }) - } - - fn compile_form(&mut self, cons: &crate::cons::Cons) -> Result, Error> { - let name = cons.car(); - let args = cons.cdr(); - if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { - compiler(self, &name, &args) - } else { - Err(Error::new( - ErrorKind::Undefined, - format!("undefined function: {}", name), - )) - } - } -} - -/// Compilers for specific lisp functions. -impl<'a> Compiler<'a> { - fn compile_fn_print( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { - let mut result = compiler.compile_expr_keep_result(arg)?; - if compiler.keep_result { - result.push(Instruction::Print); - } else { - result.push(Instruction::PrintPop); - } - Ok(result) - }) - } - - fn compile_fn_setq( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr_keep_result(arg2)?; - if compiler.keep_result { - result.push(Instruction::Store(arg1.clone())); - } else { - result.push(Instruction::StorePop(arg1.clone())); - } - Ok(result) - }) - } - - fn compile_fn_lt( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Lt); - Ok(result) - } - }) - } - - fn compile_fn_le( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::LtEq); - Ok(result) - } - }) - } - - fn compile_fn_gt( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Gt); - Ok(result) - } - }) - } - - fn compile_fn_ge( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::GtEq); - Ok(result) - } - }) - } - - fn compile_fn_eq( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Eq); - Ok(result) - } - }) - } - - fn compile_fn_plus( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Add); - Ok(result) - } - }) - } - - fn compile_fn_minus( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Sub); - Ok(result) - } - }) - } - - fn compile_fn_if( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr(then)?; - let mut else_ = ctx.compile(else_)?; - result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); - result.append(&mut then); - result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); - result.append(&mut else_); - Ok(result) - }) - } - - fn compile_fn_progn( - compiler: &mut Compiler<'_>, - _name: &TulispObject, - args: &TulispObject, - ) -> Result, Error> { - Ok(compiler.compile(args)?) - } -} diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs new file mode 100644 index 00000000..1728b158 --- /dev/null +++ b/src/byte_compile/byte_compile.rs @@ -0,0 +1,86 @@ +use crate::{vm::Instruction, Error, TulispContext, TulispObject, TulispValue}; + +use super::forms::VMFunctions; + +pub(crate) type CompileResult = Result, Error>; + +#[allow(dead_code)] +pub(crate) struct Compiler<'a> { + pub ctx: &'a mut TulispContext, + pub functions: VMFunctions, + pub keep_result: bool, +} + +impl<'a> Compiler<'a> { + pub fn new(ctx: &'a mut TulispContext) -> Self { + let functions = VMFunctions::new(ctx); + Compiler { + ctx, + functions, + keep_result: true, + } + } + + pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { + let mut result = vec![]; + let mut prev = None; + let keep_result = self.keep_result; + for expr in value.base_iter() { + if let Some(prev) = prev { + self.keep_result = false; + result.append(&mut self.compile_expr(&prev)?); + } + prev = Some(expr); + } + self.keep_result = keep_result; + if let Some(prev) = prev { + result.append(&mut self.compile_expr(&prev)?); + } + Ok(result) + } + + pub(crate) fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { + match &*expr.inner_ref() { + TulispValue::Int { .. } + | TulispValue::Float { .. } + | TulispValue::String { .. } + | TulispValue::Lambda { .. } + | TulispValue::Func(_) + | TulispValue::Macro(_) + | TulispValue::Defmacro { .. } + | TulispValue::Any(_) + | TulispValue::Bounce + | TulispValue::Nil + | TulispValue::Quote { .. } + | TulispValue::Sharpquote { .. } + | TulispValue::Backquote { .. } + | TulispValue::Unquote { .. } + | TulispValue::Splice { .. } + | TulispValue::T => { + if self.keep_result { + return Ok(vec![Instruction::Push(expr.clone())]); + } else { + return Ok(vec![]); + } + } + TulispValue::List { cons, .. } => self + .compile_form(cons) + .map_err(|e| e.with_trace(expr.clone())), + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), + } + } + + pub(crate) fn compile_expr_keep_result( + &mut self, + expr: &TulispObject, + ) -> Result, Error> { + let keep_result = self.keep_result; + self.keep_result = true; + let ret = self.compile_expr(expr); + self.keep_result = keep_result; + ret + } +} + +/// Compilers for specific lisp functions. +impl<'a> Compiler<'a> {} diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/byte_compile/forms/arithmetic_operations.rs new file mode 100644 index 00000000..81445e6e --- /dev/null +++ b/src/byte_compile/forms/arithmetic_operations.rs @@ -0,0 +1,35 @@ +use crate::{byte_compile::Compiler, vm::Instruction, Error, TulispObject}; + +pub(super) fn compile_fn_plus( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Add); + Ok(result) + } + }) +} + +pub(super) fn compile_fn_minus( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Sub); + Ok(result) + } + }) +} diff --git a/src/byte_compile/forms/common.rs b/src/byte_compile/forms/common.rs new file mode 100644 index 00000000..e6cf6aba --- /dev/null +++ b/src/byte_compile/forms/common.rs @@ -0,0 +1,79 @@ +use crate::{byte_compile::Compiler, vm::Instruction, Error, ErrorKind, TulispObject, TulispValue}; + +impl Compiler<'_> { + pub(crate) fn compile_1_arg_call( + &mut self, + name: &TulispObject, + args: &TulispObject, + has_rest: bool, + lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, + ) -> Result, Error> { + if args.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + if has_rest { + format!("{} requires at least 1 argument.", name) + } else { + format!("{} requires 1 argument.", name) + }, + )); + } + args.car_and_then(|arg1| { + args.cdr_and_then(|rest| { + if !has_rest && !rest.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} accepts only 1 argument.", name), + )); + } + lambda(self, arg1, rest) + }) + }) + } + + pub(crate) fn compile_2_arg_call( + &mut self, + name: &TulispObject, + args: &TulispObject, + has_rest: bool, + lambda: fn( + &mut Compiler, + &TulispObject, + &TulispObject, + &TulispObject, + ) -> Result, Error>, + ) -> Result, Error> { + let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { + return Err(Error::new( + ErrorKind::ArityMismatch, + if has_rest { + format!("{} requires at least 2 arguments.", name) + } else { + format!("{} requires 2 arguments.", name) + }, + )); + }; + if args.cdr().null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + if has_rest { + format!("{} requires at least 2 arguments.", name) + } else { + format!("{} requires 2 arguments.", name) + }, + )); + } + let arg1 = args.car(); + args.cdr().car_and_then(|arg2| { + args.cdr().cdr_and_then(|rest| { + if !has_rest && !rest.null() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!("{} accepts only 2 arguments.", name), + )); + } + lambda(self, arg1, arg2, rest) + }) + }) + } +} diff --git a/src/byte_compile/forms/comparison_of_numbers.rs b/src/byte_compile/forms/comparison_of_numbers.rs new file mode 100644 index 00000000..270b98f8 --- /dev/null +++ b/src/byte_compile/forms/comparison_of_numbers.rs @@ -0,0 +1,86 @@ +use crate::{byte_compile::Compiler, vm::Instruction, Error, TulispObject}; + +pub(super) fn compile_fn_lt( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Lt); + Ok(result) + } + }) +} + +pub(super) fn compile_fn_le( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::LtEq); + Ok(result) + } + }) +} + +pub(super) fn compile_fn_gt( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Gt); + Ok(result) + } + }) +} + +pub(super) fn compile_fn_ge( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::GtEq); + Ok(result) + } + }) +} + +pub(super) fn compile_fn_eq( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Eq); + Ok(result) + } + }) +} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs new file mode 100644 index 00000000..be85c2a9 --- /dev/null +++ b/src/byte_compile/forms/mod.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; + +use crate::{vm::Instruction, Error, ErrorKind, TulispContext, TulispObject}; + +use super::{byte_compile::CompileResult, Compiler}; + +mod arithmetic_operations; +mod common; +mod comparison_of_numbers; +mod other_functions; + +type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; + +pub(crate) struct VMFunctions { + // TulispObject.addr() -> implementation + pub functions: HashMap, +} + +macro_rules! map_fn_call_compilers { + ($ctx:ident, $functions: ident, $(($name:literal, $compiler:path),)+) => { + $( + $functions.insert( + $ctx.intern($name).addr_as_usize(), + $compiler as FnCallCompiler, + ); + )+ + }; +} + +impl VMFunctions { + pub fn new(ctx: &mut TulispContext) -> Self { + let mut functions = HashMap::new(); + map_fn_call_compilers! { + ctx, functions, + ("<=", comparison_of_numbers::compile_fn_le), + ("<", comparison_of_numbers::compile_fn_lt), + (">=", comparison_of_numbers::compile_fn_ge), + (">", comparison_of_numbers::compile_fn_gt), + ("eq", comparison_of_numbers::compile_fn_eq), + ("+", arithmetic_operations::compile_fn_plus), + ("-", arithmetic_operations::compile_fn_minus), + ("print", other_functions::compile_fn_print), + ("setq", other_functions::compile_fn_setq), + ("if", other_functions::compile_fn_if), + ("progn", other_functions::compile_fn_progn), + } + VMFunctions { functions } + } +} + +impl Compiler<'_> { + pub(super) fn compile_form( + &mut self, + cons: &crate::cons::Cons, + ) -> Result, Error> { + let name = cons.car(); + let args = cons.cdr(); + if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { + compiler(self, &name, &args) + } else { + Err(Error::new( + ErrorKind::Undefined, + format!("undefined function: {}", name), + )) + } + } +} diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs new file mode 100644 index 00000000..7620f083 --- /dev/null +++ b/src/byte_compile/forms/other_functions.rs @@ -0,0 +1,62 @@ +use crate::{ + byte_compile::Compiler, + vm::{Instruction, Pos}, + Error, TulispObject, +}; + +pub(super) fn compile_fn_print( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { + let mut result = compiler.compile_expr_keep_result(arg)?; + if compiler.keep_result { + result.push(Instruction::Print); + } else { + result.push(Instruction::PrintPop); + } + Ok(result) + }) +} + +pub(super) fn compile_fn_setq( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + let mut result = compiler.compile_expr_keep_result(arg2)?; + if compiler.keep_result { + result.push(Instruction::Store(arg1.clone())); + } else { + result.push(Instruction::StorePop(arg1.clone())); + } + Ok(result) + }) +} + +pub(super) fn compile_fn_if( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + let mut result = ctx.compile_expr_keep_result(cond)?; + let mut then = ctx.compile_expr(then)?; + let mut else_ = ctx.compile(else_)?; + result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); + result.append(&mut then); + result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); + result.append(&mut else_); + Ok(result) + }) +} + +pub(super) fn compile_fn_progn( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + Ok(compiler.compile(args)?) +} diff --git a/src/byte_compile/mod.rs b/src/byte_compile/mod.rs new file mode 100644 index 00000000..e08c1d9e --- /dev/null +++ b/src/byte_compile/mod.rs @@ -0,0 +1,3 @@ +mod byte_compile; +mod forms; +pub(crate) use byte_compile::Compiler; From a4f41b114bd1652ec6044523dd6f6bd0819bdd4c Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 20:08:35 +0100 Subject: [PATCH 022/142] Increment program counter after an instruction has been executed --- src/byte_compile/forms/other_functions.rs | 4 ++-- src/vm.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 7620f083..b487e3d0 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -45,9 +45,9 @@ pub(super) fn compile_fn_if( let mut result = ctx.compile_expr_keep_result(cond)?; let mut then = ctx.compile_expr(then)?; let mut else_ = ctx.compile(else_)?; - result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 2))); + result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 1))); result.append(&mut then); - result.push(Instruction::Jump(Pos::Rel(else_.len() as isize + 1))); + result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); result.append(&mut else_); Ok(result) }) diff --git a/src/vm.rs b/src/vm.rs index 1f499594..24f8d349 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -146,7 +146,7 @@ impl Machine { fn goto_pos(&self, pos: &Pos) -> usize { match pos { Pos::Abs(p) => *p, - Pos::Rel(p) => (self.pc as isize + p - 1) as usize, + Pos::Rel(p) => (self.pc as isize + p) as usize, Pos::Label(p) => *self.labels.get(&p.addr_as_usize()).unwrap(), } } @@ -169,7 +169,6 @@ impl Machine { // recursion_depth, self.pc, instr // ); ctr += 1; - self.pc += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Add => { @@ -294,6 +293,7 @@ impl Machine { } Instruction::Label(_) => {} } + self.pc += 1; } Ok(()) } From 93d9a13feed90eff394df46112f2f4dfdb2690db Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 20:09:16 +0100 Subject: [PATCH 023/142] Update recursive fib instructions to use labels --- src/vm.rs | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 24f8d349..67e7014c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -338,37 +338,39 @@ mod programs { #[allow(dead_code)] pub(super) fn fib(num: i64) -> Vec { let n = TulispObject::symbol("n".to_string(), false); + let fib = TulispObject::symbol("fib".to_string(), false); + let main = TulispObject::symbol("main".to_string(), false); vec![ - Instruction::Jump(Pos::Abs(16)), // 0 - // fib: - Instruction::Push(2.into()), // 1 - Instruction::Load(n.clone()), // 2 - Instruction::JumpIfGt(Pos::Abs(6)), // 3 - Instruction::Push(1.into()), // 4 - Instruction::Ret, // 5 - Instruction::Push(1.into()), // 6 - Instruction::Load(n.clone()), // 7 - Instruction::Sub, // 8 + Instruction::Jump(Pos::Label(main.clone())), // 0 + Instruction::Label(fib.clone()), // 1 + Instruction::Push(2.into()), // 2 + Instruction::Load(n.clone()), // 3 + Instruction::JumpIfGt(Pos::Rel(2)), // 4 + Instruction::Push(1.into()), // 5 + Instruction::Ret, // 6 + Instruction::Push(1.into()), // 7 + Instruction::Load(n.clone()), // 8 + Instruction::Sub, // 9 Instruction::Call { - pos: Pos::Abs(1), + pos: Pos::Label(fib.clone()), params: vec![n.clone()], - }, // 9 - Instruction::Push(2.into()), // 10 - Instruction::Load(n.clone()), // 11 - Instruction::Sub, // 12 + }, // 10 + Instruction::Push(2.into()), // 11 + Instruction::Load(n.clone()), // 12 + Instruction::Sub, // 13 Instruction::Call { - pos: Pos::Abs(1), + pos: Pos::Label(fib.clone()), params: vec![n.clone()], - }, // 13 - Instruction::Add, // 14 - Instruction::Ret, // 15 - // main: - Instruction::Push(num.into()), // 16 + }, // 14 + Instruction::Add, // 15 + Instruction::Ret, // 16 + Instruction::Label(main.clone()), // 17 + Instruction::Push(num.into()), // 18 Instruction::Call { - pos: Pos::Abs(1), + pos: Pos::Label(fib.clone()), params: vec![n.clone()], - }, // 17 - Instruction::PrintPop, // 18 + }, // 19 + Instruction::PrintPop, // 20 ] } From dd775aa3206ed33bf7b92aecd4f9d11e7b476b89 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 20:30:17 +0100 Subject: [PATCH 024/142] Rewrite relative positions to absolute on first encounter --- src/vm.rs | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 67e7014c..dad34f79 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -121,6 +121,26 @@ pub struct Machine { pc: usize, } +macro_rules! jump_to_pos { + ($self:ident, $pos:ident) => { + $self.pc = { + match $pos { + Pos::Abs(p) => *p, + Pos::Rel(p) => { + let abs_pos = ($self.pc as isize + *p) as usize; + *$pos = Pos::Abs(abs_pos); + abs_pos + } + Pos::Label(p) => { + let abs_pos = *$self.labels.get(&p.addr_as_usize()).unwrap(); + *$pos = Pos::Abs(abs_pos); + abs_pos + } + } + } + }; +} + impl Machine { pub fn new(program: Vec) -> Self { Machine { @@ -143,14 +163,6 @@ impl Machine { labels } - fn goto_pos(&self, pos: &Pos) -> usize { - match pos { - Pos::Abs(p) => *p, - Pos::Rel(p) => (self.pc as isize + p) as usize, - Pos::Label(p) => *self.labels.get(&p.addr_as_usize()).unwrap(), - } - } - #[allow(dead_code)] fn print_stack(&self) { println!("Stack:"); @@ -162,7 +174,7 @@ impl Machine { pub fn run(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { - let instr = &self.program[self.pc]; + let instr = &mut self.program[self.pc]; // self.print_stack(); // println!( // "\nDepth: {}: PC: {}; Executing: {}", @@ -192,45 +204,45 @@ impl Machine { Instruction::JumpIfNil(pos) => { let a = self.stack.pop().unwrap(); if a.null() { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } Instruction::JumpIfEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if a.eq(&b) { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } Instruction::JumpIfLt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } Instruction::JumpIfLtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } Instruction::JumpIfGt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } Instruction::JumpIfGtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); } } - Instruction::Jump(pos) => self.pc = self.goto_pos(pos), + Instruction::Jump(pos) => jump_to_pos!(self, pos), Instruction::Eq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); @@ -274,7 +286,7 @@ impl Machine { } Instruction::Call { pos, params } => { let pc = self.pc; - self.pc = self.goto_pos(pos); + jump_to_pos!(self, pos); let mut scope = Scope::default(); for param in params { From 36a8da2257bc413e8bf4242afa3f98897f96d985 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 21:40:30 +0100 Subject: [PATCH 025/142] Support defun with required arguments --- src/byte_compile/byte_compile.rs | 10 +++--- src/byte_compile/forms/mod.rs | 1 + src/byte_compile/forms/other_functions.rs | 44 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 1728b158..246401b1 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::{vm::Instruction, Error, TulispContext, TulispObject, TulispValue}; use super::forms::VMFunctions; @@ -8,6 +10,7 @@ pub(crate) type CompileResult = Result, Error>; pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, pub functions: VMFunctions, + pub defun_args: HashMap>, // fn_name.addr_as_usize() -> args pub keep_result: bool, } @@ -17,6 +20,7 @@ impl<'a> Compiler<'a> { Compiler { ctx, functions, + defun_args: HashMap::new(), keep_result: true, } } @@ -32,10 +36,11 @@ impl<'a> Compiler<'a> { } prev = Some(expr); } - self.keep_result = keep_result; if let Some(prev) = prev { + self.keep_result = true; result.append(&mut self.compile_expr(&prev)?); } + self.keep_result = keep_result; Ok(result) } @@ -81,6 +86,3 @@ impl<'a> Compiler<'a> { ret } } - -/// Compilers for specific lisp functions. -impl<'a> Compiler<'a> {} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index be85c2a9..36bdcdcc 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -42,6 +42,7 @@ impl VMFunctions { ("print", other_functions::compile_fn_print), ("setq", other_functions::compile_fn_setq), ("if", other_functions::compile_fn_if), + ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), } VMFunctions { functions } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index b487e3d0..538ae7a9 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -53,6 +53,50 @@ pub(super) fn compile_fn_if( }) } +pub(super) fn compile_fn_defun_call( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + for arg in args.base_iter() { + result.append(&mut compiler.compile_expr_keep_result(&arg)?); + } + result.push(Instruction::Call { + pos: Pos::Label(name.clone()), + params: compiler.defun_args[&name.addr_as_usize()].clone(), + }); + Ok(result) +} + +pub(super) fn compile_fn_defun( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, true, |ctx, name, args, body| { + ctx.functions + .functions + .insert(name.addr_as_usize(), compile_fn_defun_call); + ctx.defun_args.insert( + name.addr_as_usize(), + args.base_iter() + .collect::>() + .into_iter() + .rev() + .collect(), + ); + let mut body = ctx.compile(body)?; + let mut result = vec![ + Instruction::Jump(Pos::Rel(body.len() as isize + 2)), + Instruction::Label(name.clone()), + ]; + result.append(&mut body); + result.push(Instruction::Ret); + Ok(result) + }) +} + pub(super) fn compile_fn_progn( compiler: &mut Compiler<'_>, _name: &TulispObject, From 6efb874d990c00b28e41531638dec7c7e11c0c0e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 22:13:05 +0100 Subject: [PATCH 026/142] Improve printing of instructions --- src/vm.rs | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index dad34f79..67c3766c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -49,6 +49,16 @@ pub enum Pos { Label(TulispObject), } +impl Display for Pos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Pos::Abs(p) => write!(f, "{}", p), + Pos::Rel(p) => write!(f, ". {}", p), + Pos::Label(p) => write!(f, "{}", p), + } + } +} + /// A single instruction in the VM. pub enum Instruction { Push(TulispObject), @@ -94,19 +104,25 @@ impl Display for Instruction { Instruction::Sub => write!(f, " sub"), Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), - Instruction::JumpIfNil(pos) => write!(f, " jnil {:?}", pos), - Instruction::JumpIfEq(pos) => write!(f, " jeq {:?}", pos), - Instruction::JumpIfLt(pos) => write!(f, " jlt {:?}", pos), - Instruction::JumpIfLtEq(pos) => write!(f, " jle {:?}", pos), - Instruction::JumpIfGt(pos) => write!(f, " jgt {:?}", pos), - Instruction::JumpIfGtEq(pos) => write!(f, " jge {:?}", pos), + Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), + Instruction::JumpIfEq(pos) => write!(f, " jeq {}", pos), + Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), + Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), + Instruction::JumpIfGt(pos) => write!(f, " jgt {}", pos), + Instruction::JumpIfGtEq(pos) => write!(f, " jge {}", pos), Instruction::Eq => write!(f, " ceq"), Instruction::Lt => write!(f, " clt"), Instruction::LtEq => write!(f, " cle"), Instruction::Gt => write!(f, " cgt"), Instruction::GtEq => write!(f, " cge"), - Instruction::Jump(pos) => write!(f, " jmp {:?}", pos), - Instruction::Call { pos, .. } => write!(f, " call {:?}", pos), + Instruction::Jump(pos) => write!(f, " jmp {}", pos), + Instruction::Call { pos, params } => { + write!(f, " call {}", pos)?; + for param in params.iter().rev() { + write!(f, " {}", param)?; + } + Ok(()) + } Instruction::Ret => write!(f, " ret"), Instruction::RustCall { .. } => write!(f, " rustcall"), Instruction::Label(name) => write!(f, "{}:", name), @@ -164,22 +180,22 @@ impl Machine { } #[allow(dead_code)] - fn print_stack(&self) { + fn print_stack(&self, recursion_depth: u32) { println!("Stack:"); for obj in self.stack.iter() { println!(" {}", obj); } + println!( + "\nDepth: {}: PC: {}; Executing: {}", + recursion_depth, self.pc, self.program[self.pc] + ); } pub fn run(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { + // self.print_stack(recursion_depth); let instr = &mut self.program[self.pc]; - // self.print_stack(); - // println!( - // "\nDepth: {}: PC: {}; Executing: {}", - // recursion_depth, self.pc, instr - // ); ctr += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), From 4c348b83bdc94d54886a2b3ea0d63c4d5699bc3e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 22:16:18 +0100 Subject: [PATCH 027/142] Skip going to label instructions --- examples/fib.lisp | 6 ++++-- src/vm.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/fib.lisp b/examples/fib.lisp index 8d0b4485..add265ae 100644 --- a/examples/fib.lisp +++ b/examples/fib.lisp @@ -4,5 +4,7 @@ (+ (fib (- n 1)) (fib (- n 2))))) -(let ((tgt 30)) - (print (format "\n (fib %d)\n %d\n" tgt (fib tgt)))) +;; (let ((tgt 30)) +;; (print (format "\n (fib %d)\n %d\n" tgt (fib tgt)))) + +(print (fib 30)) diff --git a/src/vm.rs b/src/vm.rs index 67c3766c..b33957d1 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -173,7 +173,7 @@ impl Machine { let mut labels = HashMap::new(); for (i, instr) in program.iter().enumerate() { if let Instruction::Label(name) = instr { - labels.insert(name.addr_as_usize(), i); + labels.insert(name.addr_as_usize(), i + 1); } } labels From e4a9d3514bc2acfac86ee3985085693507021117 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 22:39:05 +0100 Subject: [PATCH 028/142] Optimize if to compare and jump in same instruction --- src/byte_compile/forms/other_functions.rs | 34 ++++++++++++++++++++--- src/context.rs | 2 +- src/vm.rs | 8 ++++-- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 538ae7a9..f6692780 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -43,12 +43,38 @@ pub(super) fn compile_fn_if( ) -> Result, Error> { compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr(then)?; + let mut then = ctx.compile_expr_keep_result(then)?; let mut else_ = ctx.compile(else_)?; - result.push(Instruction::JumpIfNil(Pos::Rel(then.len() as isize + 1))); - result.append(&mut then); - result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); + + let tgt_pos = Pos::Rel(else_.len() as isize + 1); + match result.last() { + Some(Instruction::Gt) => { + result.pop(); + result.push(Instruction::JumpIfGt(tgt_pos)); + } + Some(Instruction::Lt) => { + result.pop(); + result.push(Instruction::JumpIfLt(tgt_pos)); + } + Some(Instruction::GtEq) => { + result.pop(); + result.push(Instruction::JumpIfGtEq(tgt_pos)); + } + Some(Instruction::LtEq) => { + result.pop(); + result.push(Instruction::JumpIfLtEq(tgt_pos)); + } + Some(Instruction::Eq) => { + result.pop(); + result.push(Instruction::JumpIfEq(tgt_pos)); + } + _ => { + result.push(Instruction::JumpIfNil(tgt_pos)); + } + } result.append(&mut else_); + result.push(Instruction::Jump(Pos::Rel(then.len() as isize))); + result.append(&mut then); Ok(result) }) } diff --git a/src/context.rs b/src/context.rs index 36dc217f..0412f092 100644 --- a/src/context.rs +++ b/src/context.rs @@ -211,7 +211,7 @@ impl TulispContext { println!("{}", instr); } println!(); - vm::Machine::new(bytecode).run(self, 0) + vm::Machine::new(bytecode).run(self) } pub(crate) fn get_filename(&self, file_id: usize) -> String { diff --git a/src/vm.rs b/src/vm.rs index b33957d1..39a54990 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -191,7 +191,11 @@ impl Machine { ); } - pub fn run(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { + pub fn run(&mut self, ctx: &mut TulispContext) -> Result<(), Error> { + self.run_impl(ctx, 0) + } + + fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter while self.pc < self.program.len() && ctr < 100000 { // self.print_stack(recursion_depth); @@ -309,7 +313,7 @@ impl Machine { let value = self.stack.pop().unwrap(); scope.set(param.clone(), value)?; } - self.run(ctx, recursion_depth + 1)?; + self.run_impl(ctx, recursion_depth + 1)?; scope.remove_all()?; self.pc = pc; From 30efacad139f5ed7f0a93fe92c819bd85ca2fc41 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 23:06:47 +0100 Subject: [PATCH 029/142] Fix bug in `if` compilation for non-comparison operations --- src/byte_compile/forms/other_functions.rs | 2 +- src/vm.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index f6692780..6ab781dc 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -69,7 +69,7 @@ pub(super) fn compile_fn_if( result.push(Instruction::JumpIfEq(tgt_pos)); } _ => { - result.push(Instruction::JumpIfNil(tgt_pos)); + result.push(Instruction::JumpIf(tgt_pos)); } } result.append(&mut else_); diff --git a/src/vm.rs b/src/vm.rs index 39a54990..d645dff7 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -79,6 +79,7 @@ pub enum Instruction { Gt, GtEq, // control flow + JumpIf(Pos), JumpIfNil(Pos), JumpIfEq(Pos), JumpIfLt(Pos), @@ -104,6 +105,7 @@ impl Display for Instruction { Instruction::Sub => write!(f, " sub"), Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), + Instruction::JumpIf(pos) => write!(f, " jif {}", pos), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), Instruction::JumpIfEq(pos) => write!(f, " jeq {}", pos), Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), @@ -221,6 +223,12 @@ impl Machine { let a = self.stack.last().unwrap(); println!("{}", a); } + Instruction::JumpIf(pos) => { + let a = self.stack.pop().unwrap(); + if a.is_truthy() { + jump_to_pos!(self, pos); + } + } Instruction::JumpIfNil(pos) => { let a = self.stack.pop().unwrap(); if a.null() { From 140fdbe9d87cfe6f015921910b0cbb52bd222c4b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 29 Dec 2023 23:07:40 +0100 Subject: [PATCH 030/142] Add a `TulispContext::vm_eval_string` method --- src/context.rs | 9 ++++++++- src/vm.rs | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/context.rs b/src/context.rs index 0412f092..78602e58 100644 --- a/src/context.rs +++ b/src/context.rs @@ -194,7 +194,14 @@ impl TulispContext { self.eval_progn(&vv) } - pub fn vm_eval_file(&mut self, filename: &str) -> Result<(), Error> { + pub fn vm_eval_string(&mut self, string: &str) -> Result { + let vv = parse(self, 0, string)?; + let mut compiler = Compiler::new(self); + let bytecode = compiler.compile(&vv)?; + vm::Machine::new(bytecode).run(self) + } + + pub fn vm_eval_file(&mut self, filename: &str) -> Result { let contents = fs::read_to_string(filename).map_err(|e| { Error::new( crate::ErrorKind::Undefined, diff --git a/src/vm.rs b/src/vm.rs index d645dff7..572b8910 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -193,8 +193,9 @@ impl Machine { ); } - pub fn run(&mut self, ctx: &mut TulispContext) -> Result<(), Error> { - self.run_impl(ctx, 0) + pub fn run(&mut self, ctx: &mut TulispContext) -> Result { + self.run_impl(ctx, 0)?; + Ok(self.stack.pop().unwrap()) } fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { From 26f0cbc0941a2ed655463895efd2e28cd615d9c5 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 01:54:38 +0100 Subject: [PATCH 031/142] =?UTF-8?q?Rename=20`compile`=20=E2=86=92=20`compi?= =?UTF-8?q?le=5Fprogn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/byte_compile/byte_compile.rs | 4 ++++ src/byte_compile/forms/other_functions.rs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 246401b1..6ff1bee2 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -26,6 +26,10 @@ impl<'a> Compiler<'a> { } pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { + self.compile_progn(value) + } + + pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { let mut result = vec![]; let mut prev = None; let keep_result = self.keep_result; diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 6ab781dc..5aca5a8f 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -44,7 +44,7 @@ pub(super) fn compile_fn_if( compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { let mut result = ctx.compile_expr_keep_result(cond)?; let mut then = ctx.compile_expr_keep_result(then)?; - let mut else_ = ctx.compile(else_)?; + let mut else_ = ctx.compile_progn(else_)?; let tgt_pos = Pos::Rel(else_.len() as isize + 1); match result.last() { @@ -112,7 +112,7 @@ pub(super) fn compile_fn_defun( .rev() .collect(), ); - let mut body = ctx.compile(body)?; + let mut body = ctx.compile_progn(body)?; let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), Instruction::Label(name.clone()), @@ -128,5 +128,5 @@ pub(super) fn compile_fn_progn( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - Ok(compiler.compile(args)?) + Ok(compiler.compile_progn(args)?) } From c579bc96f3ab8af9f5e2986e4f9adb876e4dfa14 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 03:11:37 +0100 Subject: [PATCH 032/142] Use a vector to store symbol bindings --- src/byte_compile/byte_compile.rs | 32 ++- src/byte_compile/forms/other_functions.rs | 18 +- src/context.rs | 10 +- src/vm.rs | 259 +++++++++++++--------- 4 files changed, 195 insertions(+), 124 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 6ff1bee2..61c787ba 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::{vm::Instruction, Error, TulispContext, TulispObject, TulispValue}; +use crate::{ + vm::{Instruction, VMBindings}, + Error, TulispContext, TulispObject, TulispValue, +}; use super::forms::VMFunctions; @@ -10,7 +13,9 @@ pub(crate) type CompileResult = Result, Error>; pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, pub functions: VMFunctions, - pub defun_args: HashMap>, // fn_name.addr_as_usize() -> args + pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx + pub bindings: Vec, + pub symbol_to_var_idx: HashMap, pub keep_result: bool, } @@ -21,12 +26,29 @@ impl<'a> Compiler<'a> { ctx, functions, defun_args: HashMap::new(), + bindings: Vec::new(), + symbol_to_var_idx: HashMap::new(), keep_result: true, } } - pub fn compile(&mut self, value: &TulispObject) -> Result, Error> { - self.compile_progn(value) + pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> usize { + let addr = &symbol.addr_as_usize(); + if let Some(idx) = self.symbol_to_var_idx.get(addr) { + *idx + } else { + self.bindings.push(VMBindings::new(symbol.to_string())); + let idx = self.bindings.len() - 1; + self.symbol_to_var_idx.insert(*addr, idx); + idx + } + } + + pub fn compile( + &mut self, + value: &TulispObject, + ) -> Result<(Vec, Vec), Error> { + Ok((self.compile_progn(value)?, self.bindings.clone())) } pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { @@ -75,7 +97,7 @@ impl<'a> Compiler<'a> { TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(self.get_symbol_idx(expr))]), } } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 5aca5a8f..0ce7acc2 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -28,9 +28,9 @@ pub(super) fn compile_fn_setq( compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { let mut result = compiler.compile_expr_keep_result(arg2)?; if compiler.keep_result { - result.push(Instruction::Store(arg1.clone())); + result.push(Instruction::Store(compiler.get_symbol_idx(arg1))); } else { - result.push(Instruction::StorePop(arg1.clone())); + result.push(Instruction::StorePop(compiler.get_symbol_idx(arg1))); } Ok(result) }) @@ -104,14 +104,12 @@ pub(super) fn compile_fn_defun( ctx.functions .functions .insert(name.addr_as_usize(), compile_fn_defun_call); - ctx.defun_args.insert( - name.addr_as_usize(), - args.base_iter() - .collect::>() - .into_iter() - .rev() - .collect(), - ); + let mut params = vec![]; + for arg in args.base_iter().collect::>().into_iter().rev() { + params.push(ctx.get_symbol_idx(&arg)); + } + + ctx.defun_args.insert(name.addr_as_usize(), params); let mut body = ctx.compile_progn(body)?; let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), diff --git a/src/context.rs b/src/context.rs index 78602e58..6746a249 100644 --- a/src/context.rs +++ b/src/context.rs @@ -197,8 +197,8 @@ impl TulispContext { pub fn vm_eval_string(&mut self, string: &str) -> Result { let vv = parse(self, 0, string)?; let mut compiler = Compiler::new(self); - let bytecode = compiler.compile(&vv)?; - vm::Machine::new(bytecode).run(self) + let (bytecode, bindings) = compiler.compile(&vv)?; + vm::Machine::new(bytecode, bindings).run(self) } pub fn vm_eval_file(&mut self, filename: &str) -> Result { @@ -213,12 +213,12 @@ impl TulispContext { let string: &str = &contents; let vv = parse(self, self.filenames.len() - 1, string)?; let mut compiler = Compiler::new(self); - let bytecode = compiler.compile(&vv)?; + let (bytecode, bindings) = compiler.compile(&vv)?; for instr in &bytecode { println!("{}", instr); } - println!(); - vm::Machine::new(bytecode).run(self) + println!("Number of variables: {}", bindings.len()); + vm::Machine::new(bytecode, bindings).run(self) } pub(crate) fn get_filename(&self, file_id: usize) -> String { diff --git a/src/vm.rs b/src/vm.rs index 572b8910..28108d18 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; -use crate::{context::Scope, value::TulispFn, Error, TulispContext, TulispObject}; +use crate::{value::TulispFn, Error, ErrorKind, TulispContext, TulispObject}; macro_rules! compare_ops { ($oper:expr) => {{ @@ -63,9 +63,9 @@ impl Display for Pos { pub enum Instruction { Push(TulispObject), // variables - StorePop(TulispObject), - Store(TulispObject), - Load(TulispObject), + StorePop(usize), + Store(usize), + Load(usize), // arithmetic Add, Sub, @@ -90,7 +90,7 @@ pub enum Instruction { // functions Label(TulispObject), RustCall { func: Rc }, - Call { pos: Pos, params: Vec }, + Call { pos: Pos, params: Vec }, Ret, } @@ -132,10 +132,57 @@ impl Display for Instruction { } } +#[derive(Default, Clone, Debug)] +pub(crate) struct VMBindings { + name: String, + items: Vec, +} + +impl VMBindings { + pub(crate) fn new(name: String) -> Self { + Self { + name, + items: Vec::new(), + } + } + + pub fn set(&mut self, to_set: TulispObject) { + if self.items.is_empty() { + self.items.push(to_set); + } else { + *self.items.last_mut().unwrap() = to_set; + } + } + + pub fn set_scope(&mut self, to_set: TulispObject) { + self.items.push(to_set); + } + + pub fn unset(&mut self) { + self.items.pop(); + } + + #[allow(dead_code)] + pub fn boundp(&self) -> bool { + !self.items.is_empty() + } + + pub fn get(&self) -> Result { + if self.items.is_empty() { + return Err(Error::new( + ErrorKind::TypeMismatch, + format!("Variable definition is void: {}", self.name), + )); + } + return Ok(self.items.last().unwrap().clone()); + } +} + pub struct Machine { stack: Vec, program: Vec, labels: HashMap, // TulispObject.addr -> instruction index + bindings: Vec, pc: usize, } @@ -160,13 +207,14 @@ macro_rules! jump_to_pos { } impl Machine { - pub fn new(program: Vec) -> Self { + pub(crate) fn new(program: Vec, variables: Vec) -> Self { Machine { stack: Vec::new(), labels: Self::locate_labels(&program), // program: programs::print_range(92, 100), // program: programs::fib(30), program, + bindings: variables, pc: 0, } } @@ -303,27 +351,30 @@ impl Machine { } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); - obj.set(a)?; + let _ = self.bindings[*obj].set(a); } Instruction::Store(obj) => { let a = self.stack.last().unwrap(); - obj.set(a.clone())?; + let _ = self.bindings[*obj].set(a.clone()); } Instruction::Load(obj) => { - let a = obj.get()?; + let a = self.bindings[*obj].get().unwrap(); self.stack.push(a); } Instruction::Call { pos, params } => { let pc = self.pc; jump_to_pos!(self, pos); - let mut scope = Scope::default(); + let mut scope: Vec = vec![]; for param in params { let value = self.stack.pop().unwrap(); - scope.set(param.clone(), value)?; + let _ = self.bindings[*param].set_scope(value); + scope.push(*param); } self.run_impl(ctx, recursion_depth + 1)?; - scope.remove_all()?; + for param in scope { + let _ = self.bindings[param].unset(); + } self.pc = pc; } @@ -341,100 +392,100 @@ impl Machine { } mod programs { - use super::*; + // use super::*; - use crate::{list, TulispContext, TulispObject, TulispValue}; + // use crate::{list, TulispContext, TulispObject, TulispValue}; - #[allow(dead_code)] - pub(super) fn print_range(from: i64, to: i64) -> Vec { - let i = TulispObject::symbol("i".to_string(), false); - let n = TulispObject::symbol("n".to_string(), false); - vec![ - // print numbers 1 to 10 - Instruction::Push(from.into()), // 0 - Instruction::StorePop(i.clone()), // 1 - Instruction::Push(to.into()), // 2 - Instruction::StorePop(n.clone()), // 3 - // loop: - Instruction::Load(i.clone()), // 4 - Instruction::PrintPop, // 5 - Instruction::Push(1.into()), // 6 - Instruction::Load(i.clone()), // 7 - if from < to { - Instruction::Add - } else { - Instruction::Sub - }, // 8 - Instruction::StorePop(i.clone()), // 9 - Instruction::Load(i.clone()), // 10 - Instruction::Load(n.clone()), // 11 - if from < to { - Instruction::JumpIfGtEq(Pos::Abs(4)) - } else { - Instruction::JumpIfLtEq(Pos::Abs(4)) - }, // 12 - ] - } + // #[allow(dead_code)] + // pub(super) fn print_range(from: i64, to: i64) -> Vec { + // let i = TulispObject::symbol("i".to_string(), false); + // let n = TulispObject::symbol("n".to_string(), false); + // vec![ + // // print numbers 1 to 10 + // Instruction::Push(from.into()), // 0 + // Instruction::StorePop(i.clone()), // 1 + // Instruction::Push(to.into()), // 2 + // Instruction::StorePop(n.clone()), // 3 + // // loop: + // Instruction::Load(i.clone()), // 4 + // Instruction::PrintPop, // 5 + // Instruction::Push(1.into()), // 6 + // Instruction::Load(i.clone()), // 7 + // if from < to { + // Instruction::Add + // } else { + // Instruction::Sub + // }, // 8 + // Instruction::StorePop(i.clone()), // 9 + // Instruction::Load(i.clone()), // 10 + // Instruction::Load(n.clone()), // 11 + // if from < to { + // Instruction::JumpIfGtEq(Pos::Abs(4)) + // } else { + // Instruction::JumpIfLtEq(Pos::Abs(4)) + // }, // 12 + // ] + // } - #[allow(dead_code)] - pub(super) fn fib(num: i64) -> Vec { - let n = TulispObject::symbol("n".to_string(), false); - let fib = TulispObject::symbol("fib".to_string(), false); - let main = TulispObject::symbol("main".to_string(), false); - vec![ - Instruction::Jump(Pos::Label(main.clone())), // 0 - Instruction::Label(fib.clone()), // 1 - Instruction::Push(2.into()), // 2 - Instruction::Load(n.clone()), // 3 - Instruction::JumpIfGt(Pos::Rel(2)), // 4 - Instruction::Push(1.into()), // 5 - Instruction::Ret, // 6 - Instruction::Push(1.into()), // 7 - Instruction::Load(n.clone()), // 8 - Instruction::Sub, // 9 - Instruction::Call { - pos: Pos::Label(fib.clone()), - params: vec![n.clone()], - }, // 10 - Instruction::Push(2.into()), // 11 - Instruction::Load(n.clone()), // 12 - Instruction::Sub, // 13 - Instruction::Call { - pos: Pos::Label(fib.clone()), - params: vec![n.clone()], - }, // 14 - Instruction::Add, // 15 - Instruction::Ret, // 16 - Instruction::Label(main.clone()), // 17 - Instruction::Push(num.into()), // 18 - Instruction::Call { - pos: Pos::Label(fib.clone()), - params: vec![n.clone()], - }, // 19 - Instruction::PrintPop, // 20 - ] - } + // #[allow(dead_code)] + // pub(super) fn fib(num: i64) -> Vec { + // let n = TulispObject::symbol("n".to_string(), false); + // let fib = TulispObject::symbol("fib".to_string(), false); + // let main = TulispObject::symbol("main".to_string(), false); + // vec![ + // Instruction::Jump(Pos::Label(main.clone())), // 0 + // Instruction::Label(fib.clone()), // 1 + // Instruction::Push(2.into()), // 2 + // Instruction::Load(n.clone()), // 3 + // Instruction::JumpIfGt(Pos::Rel(2)), // 4 + // Instruction::Push(1.into()), // 5 + // Instruction::Ret, // 6 + // Instruction::Push(1.into()), // 7 + // Instruction::Load(n.clone()), // 8 + // Instruction::Sub, // 9 + // Instruction::Call { + // pos: Pos::Label(fib.clone()), + // params: vec![n.clone()], + // }, // 10 + // Instruction::Push(2.into()), // 11 + // Instruction::Load(n.clone()), // 12 + // Instruction::Sub, // 13 + // Instruction::Call { + // pos: Pos::Label(fib.clone()), + // params: vec![n.clone()], + // }, // 14 + // Instruction::Add, // 15 + // Instruction::Ret, // 16 + // Instruction::Label(main.clone()), // 17 + // Instruction::Push(num.into()), // 18 + // Instruction::Call { + // pos: Pos::Label(fib.clone()), + // params: vec![n.clone()], + // }, // 19 + // Instruction::PrintPop, // 20 + // ] + // } - #[allow(dead_code)] - pub(super) fn rustcall_dotimes(ctx: &mut TulispContext, num: i64) -> Vec { - let var = TulispObject::symbol("var".to_string(), false); - let args = list!( - list!(var.clone(), num.into()).unwrap(), - list!(ctx.intern("print"), var).unwrap() - ) - .unwrap(); - vec![ - Instruction::Push(args), // 0 - Instruction::RustCall { - func: { - let obj = ctx.intern("dotimes").get().unwrap(); - if let TulispValue::Func(ref func) = obj.clone_inner() { - func.clone() - } else { - panic!("Expected function") - } - }, - }, - ] - } + // #[allow(dead_code)] + // pub(super) fn rustcall_dotimes(ctx: &mut TulispContext, num: i64) -> Vec { + // let var = TulispObject::symbol("var".to_string(), false); + // let args = list!( + // list!(var.clone(), num.into()).unwrap(), + // list!(ctx.intern("print"), var).unwrap() + // ) + // .unwrap(); + // vec![ + // Instruction::Push(args), // 0 + // Instruction::RustCall { + // func: { + // let obj = ctx.intern("dotimes").get().unwrap(); + // if let TulispValue::Func(ref func) = obj.clone_inner() { + // func.clone() + // } else { + // panic!("Expected function") + // } + // }, + // }, + // ] + // } } From 527b0516d2d82236ac11a581477cbb551bd27d11 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 03:43:30 +0100 Subject: [PATCH 033/142] Wrap bytecode into a type --- src/byte_compile/byte_compile.rs | 21 ++++++------ src/context.rs | 15 +++------ src/vm.rs | 55 ++++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 61c787ba..7501ea2f 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::{ - vm::{Instruction, VMBindings}, + vm::{Bytecode, Instruction, VMBindings}, Error, TulispContext, TulispObject, TulispValue, }; @@ -15,7 +15,7 @@ pub(crate) struct Compiler<'a> { pub functions: VMFunctions, pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx pub bindings: Vec, - pub symbol_to_var_idx: HashMap, + pub symbol_to_binding_idx: HashMap, pub keep_result: bool, } @@ -27,28 +27,29 @@ impl<'a> Compiler<'a> { functions, defun_args: HashMap::new(), bindings: Vec::new(), - symbol_to_var_idx: HashMap::new(), + symbol_to_binding_idx: HashMap::new(), keep_result: true, } } pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> usize { let addr = &symbol.addr_as_usize(); - if let Some(idx) = self.symbol_to_var_idx.get(addr) { + if let Some(idx) = self.symbol_to_binding_idx.get(addr) { *idx } else { self.bindings.push(VMBindings::new(symbol.to_string())); let idx = self.bindings.len() - 1; - self.symbol_to_var_idx.insert(*addr, idx); + self.symbol_to_binding_idx.insert(*addr, idx); idx } } - pub fn compile( - &mut self, - value: &TulispObject, - ) -> Result<(Vec, Vec), Error> { - Ok((self.compile_progn(value)?, self.bindings.clone())) + pub fn compile(mut self, value: &TulispObject) -> Result { + Ok(Bytecode::new( + self.compile_progn(value)?, + self.bindings, + self.symbol_to_binding_idx, + )) } pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { diff --git a/src/context.rs b/src/context.rs index 6746a249..ef0cb697 100644 --- a/src/context.rs +++ b/src/context.rs @@ -196,9 +196,8 @@ impl TulispContext { pub fn vm_eval_string(&mut self, string: &str) -> Result { let vv = parse(self, 0, string)?; - let mut compiler = Compiler::new(self); - let (bytecode, bindings) = compiler.compile(&vv)?; - vm::Machine::new(bytecode, bindings).run(self) + let bytecode = Compiler::new(self).compile(&vv)?; + vm::Machine::new(bytecode).run(self) } pub fn vm_eval_file(&mut self, filename: &str) -> Result { @@ -212,13 +211,9 @@ impl TulispContext { let string: &str = &contents; let vv = parse(self, self.filenames.len() - 1, string)?; - let mut compiler = Compiler::new(self); - let (bytecode, bindings) = compiler.compile(&vv)?; - for instr in &bytecode { - println!("{}", instr); - } - println!("Number of variables: {}", bindings.len()); - vm::Machine::new(bytecode, bindings).run(self) + let bytecode = Compiler::new(self).compile(&vv)?; + bytecode.print(); + vm::Machine::new(bytecode).run(self) } pub(crate) fn get_filename(&self, file_id: usize) -> String { diff --git a/src/vm.rs b/src/vm.rs index 28108d18..b4087f5a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -178,11 +178,39 @@ impl VMBindings { } } +pub(crate) struct Bytecode { + instructions: Vec, + bindings: Vec, + #[allow(dead_code)] + symbol_to_binding_idx: HashMap, +} + +impl Bytecode { + pub(crate) fn new( + instructions: Vec, + bindings: Vec, + symbol_to_binding_idx: HashMap, + ) -> Self { + Self { + instructions, + bindings, + symbol_to_binding_idx, + } + } + + pub(crate) fn print(&self) { + println!("start:"); + for (i, instr) in self.instructions.iter().enumerate() { + println!("{:<40} # {}", instr.to_string(), i); + } + println!("Number of bindings: {}", self.bindings.len()); + } +} + pub struct Machine { stack: Vec, - program: Vec, + bytecode: Bytecode, labels: HashMap, // TulispObject.addr -> instruction index - bindings: Vec, pc: usize, } @@ -207,14 +235,13 @@ macro_rules! jump_to_pos { } impl Machine { - pub(crate) fn new(program: Vec, variables: Vec) -> Self { + pub(crate) fn new(bytecode: Bytecode) -> Self { Machine { stack: Vec::new(), - labels: Self::locate_labels(&program), + labels: Self::locate_labels(&bytecode.instructions), + bytecode, // program: programs::print_range(92, 100), // program: programs::fib(30), - program, - bindings: variables, pc: 0, } } @@ -237,7 +264,7 @@ impl Machine { } println!( "\nDepth: {}: PC: {}; Executing: {}", - recursion_depth, self.pc, self.program[self.pc] + recursion_depth, self.pc, self.bytecode.instructions[self.pc] ); } @@ -248,9 +275,9 @@ impl Machine { fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { let mut ctr: u32 = 0; // safety counter - while self.pc < self.program.len() && ctr < 100000 { + while self.pc < self.bytecode.instructions.len() && ctr < 100000 { // self.print_stack(recursion_depth); - let instr = &mut self.program[self.pc]; + let instr = &mut self.bytecode.instructions[self.pc]; ctr += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), @@ -351,14 +378,14 @@ impl Machine { } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); - let _ = self.bindings[*obj].set(a); + let _ = self.bytecode.bindings[*obj].set(a); } Instruction::Store(obj) => { let a = self.stack.last().unwrap(); - let _ = self.bindings[*obj].set(a.clone()); + let _ = self.bytecode.bindings[*obj].set(a.clone()); } Instruction::Load(obj) => { - let a = self.bindings[*obj].get().unwrap(); + let a = self.bytecode.bindings[*obj].get().unwrap(); self.stack.push(a); } Instruction::Call { pos, params } => { @@ -368,12 +395,12 @@ impl Machine { let mut scope: Vec = vec![]; for param in params { let value = self.stack.pop().unwrap(); - let _ = self.bindings[*param].set_scope(value); + let _ = self.bytecode.bindings[*param].set_scope(value); scope.push(*param); } self.run_impl(ctx, recursion_depth + 1)?; for param in scope { - let _ = self.bindings[param].unset(); + let _ = self.bytecode.bindings[param].unset(); } self.pc = pc; From 94cc9440c3b70d06049c01e46b5c21287462ce15 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 04:11:50 +0100 Subject: [PATCH 034/142] Add `Equal` instruction --- src/byte_compile/forms/comparison_of_numbers.rs | 17 +++++++++++++++++ src/byte_compile/forms/mod.rs | 1 + src/vm.rs | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/src/byte_compile/forms/comparison_of_numbers.rs b/src/byte_compile/forms/comparison_of_numbers.rs index 270b98f8..0d08d7fc 100644 --- a/src/byte_compile/forms/comparison_of_numbers.rs +++ b/src/byte_compile/forms/comparison_of_numbers.rs @@ -84,3 +84,20 @@ pub(super) fn compile_fn_eq( } }) } + +pub(super) fn compile_fn_equal( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + Ok(vec![]) + } else { + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Equal); + Ok(result) + } + }) +} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 36bdcdcc..04b66de8 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -37,6 +37,7 @@ impl VMFunctions { (">=", comparison_of_numbers::compile_fn_ge), (">", comparison_of_numbers::compile_fn_gt), ("eq", comparison_of_numbers::compile_fn_eq), + ("equal", comparison_of_numbers::compile_fn_equal), ("+", arithmetic_operations::compile_fn_plus), ("-", arithmetic_operations::compile_fn_minus), ("print", other_functions::compile_fn_print), diff --git a/src/vm.rs b/src/vm.rs index b4087f5a..9b8b661d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -73,6 +73,7 @@ pub enum Instruction { PrintPop, Print, // comparison + Equal, Eq, Lt, LtEq, @@ -112,6 +113,7 @@ impl Display for Instruction { Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), Instruction::JumpIfGt(pos) => write!(f, " jgt {}", pos), Instruction::JumpIfGtEq(pos) => write!(f, " jge {}", pos), + Instruction::Equal => write!(f, " equal"), Instruction::Eq => write!(f, " ceq"), Instruction::Lt => write!(f, " clt"), Instruction::LtEq => write!(f, " cle"), @@ -347,6 +349,11 @@ impl Machine { } } Instruction::Jump(pos) => jump_to_pos!(self, pos), + Instruction::Equal => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(a.equal(&b).into()); + } Instruction::Eq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); From 1fcacab2c296f18a568581fec2c667834aeec3dc Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 04:25:42 +0100 Subject: [PATCH 035/142] Specialize compilation of quoted values --- src/byte_compile/byte_compile.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 7501ea2f..4f79b856 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -83,7 +83,6 @@ impl<'a> Compiler<'a> { | TulispValue::Any(_) | TulispValue::Bounce | TulispValue::Nil - | TulispValue::Quote { .. } | TulispValue::Sharpquote { .. } | TulispValue::Backquote { .. } | TulispValue::Unquote { .. } @@ -95,6 +94,13 @@ impl<'a> Compiler<'a> { return Ok(vec![]); } } + TulispValue::Quote { value } => { + if self.keep_result { + return Ok(vec![Instruction::Push(value.clone())]); + } else { + return Ok(vec![]); + } + } TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), From 351493fe974bb7a5ada56eb84a0faaebdc2975bc Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 04:26:17 +0100 Subject: [PATCH 036/142] Add a `Cons` instruction and a compilation function --- src/byte_compile/forms/mod.rs | 1 + src/byte_compile/forms/other_functions.rs | 16 ++++++++++++++++ src/vm.rs | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 04b66de8..0c887cde 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -45,6 +45,7 @@ impl VMFunctions { ("if", other_functions::compile_fn_if), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), + ("cons", other_functions::compile_fn_cons), } VMFunctions { functions } } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 0ce7acc2..f2d87ef1 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -36,6 +36,22 @@ pub(super) fn compile_fn_setq( }) } +pub(super) fn compile_fn_cons( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, true, |compiler, arg1, arg2, _| { + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + result.push(Instruction::Cons); + Ok(result) + }) +} + pub(super) fn compile_fn_if( compiler: &mut Compiler<'_>, name: &TulispObject, diff --git a/src/vm.rs b/src/vm.rs index 9b8b661d..ffbaa69a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -93,6 +93,8 @@ pub enum Instruction { RustCall { func: Rc }, Call { pos: Pos, params: Vec }, Ret, + // lists + Cons, } impl Display for Instruction { @@ -130,6 +132,7 @@ impl Display for Instruction { Instruction::Ret => write!(f, " ret"), Instruction::RustCall { .. } => write!(f, " rustcall"), Instruction::Label(name) => write!(f, "{}:", name), + Instruction::Cons => write!(f, " cons"), } } } @@ -418,6 +421,11 @@ impl Machine { self.stack.push(func(ctx, &args)?); } Instruction::Label(_) => {} + Instruction::Cons => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(TulispObject::cons(a, b)); + } } self.pc += 1; } From 1c95126a7ec333ec04d503585e6dec67bb2c1fb3 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 19:16:48 +0100 Subject: [PATCH 037/142] Extract `if` compiler into a `conditionals` module --- src/byte_compile/forms/conditionals.rs | 51 +++++++++++++++++++++++ src/byte_compile/forms/mod.rs | 3 +- src/byte_compile/forms/other_functions.rs | 43 ------------------- 3 files changed, 53 insertions(+), 44 deletions(-) create mode 100644 src/byte_compile/forms/conditionals.rs diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs new file mode 100644 index 00000000..d56c8224 --- /dev/null +++ b/src/byte_compile/forms/conditionals.rs @@ -0,0 +1,51 @@ +use crate::{ + byte_compile::Compiler, + vm::{Instruction, Pos}, + Error, TulispObject, +}; + +fn optimize_jump_if(result: &mut Vec, tgt_pos: Pos) { + match result.last() { + Some(Instruction::Gt) => { + result.pop(); + result.push(Instruction::JumpIfGt(tgt_pos)); + } + Some(Instruction::Lt) => { + result.pop(); + result.push(Instruction::JumpIfLt(tgt_pos)); + } + Some(Instruction::GtEq) => { + result.pop(); + result.push(Instruction::JumpIfGtEq(tgt_pos)); + } + Some(Instruction::LtEq) => { + result.pop(); + result.push(Instruction::JumpIfLtEq(tgt_pos)); + } + Some(Instruction::Eq) => { + result.pop(); + result.push(Instruction::JumpIfEq(tgt_pos)); + } + _ => { + result.push(Instruction::JumpIf(tgt_pos)); + } + } +} + +pub(super) fn compile_fn_if( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + let mut result = ctx.compile_expr_keep_result(cond)?; + let mut then = ctx.compile_expr_keep_result(then)?; + let mut else_ = ctx.compile_progn(else_)?; + + optimize_jump_if(&mut result, Pos::Rel(else_.len() as isize + 1)); + result.append(&mut else_); + result.push(Instruction::Jump(Pos::Rel(then.len() as isize))); + result.append(&mut then); + Ok(result) + }) +} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 0c887cde..6ee88c2d 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -7,6 +7,7 @@ use super::{byte_compile::CompileResult, Compiler}; mod arithmetic_operations; mod common; mod comparison_of_numbers; +mod conditionals; mod other_functions; type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; @@ -42,7 +43,7 @@ impl VMFunctions { ("-", arithmetic_operations::compile_fn_minus), ("print", other_functions::compile_fn_print), ("setq", other_functions::compile_fn_setq), - ("if", other_functions::compile_fn_if), + ("if", conditionals::compile_fn_if), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), ("cons", other_functions::compile_fn_cons), diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index f2d87ef1..eccf84cc 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -52,49 +52,6 @@ pub(super) fn compile_fn_cons( }) } -pub(super) fn compile_fn_if( - compiler: &mut Compiler<'_>, - name: &TulispObject, - args: &TulispObject, -) -> Result, Error> { - compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr_keep_result(then)?; - let mut else_ = ctx.compile_progn(else_)?; - - let tgt_pos = Pos::Rel(else_.len() as isize + 1); - match result.last() { - Some(Instruction::Gt) => { - result.pop(); - result.push(Instruction::JumpIfGt(tgt_pos)); - } - Some(Instruction::Lt) => { - result.pop(); - result.push(Instruction::JumpIfLt(tgt_pos)); - } - Some(Instruction::GtEq) => { - result.pop(); - result.push(Instruction::JumpIfGtEq(tgt_pos)); - } - Some(Instruction::LtEq) => { - result.pop(); - result.push(Instruction::JumpIfLtEq(tgt_pos)); - } - Some(Instruction::Eq) => { - result.pop(); - result.push(Instruction::JumpIfEq(tgt_pos)); - } - _ => { - result.push(Instruction::JumpIf(tgt_pos)); - } - } - result.append(&mut else_); - result.push(Instruction::Jump(Pos::Rel(then.len() as isize))); - result.append(&mut then); - Ok(result) - }) -} - pub(super) fn compile_fn_defun_call( compiler: &mut Compiler<'_>, name: &TulispObject, From b4bd194af70bea8987d4484c75de7a922f909bb5 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 19:25:56 +0100 Subject: [PATCH 038/142] Switch to the more generic `jumpIfNil` idiom --- src/byte_compile/forms/conditionals.rs | 20 ++++++++++---------- src/vm.rs | 16 ++++------------ 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index d56c8224..80f2d2e7 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -4,30 +4,30 @@ use crate::{ Error, TulispObject, }; -fn optimize_jump_if(result: &mut Vec, tgt_pos: Pos) { +fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) { match result.last() { Some(Instruction::Gt) => { result.pop(); - result.push(Instruction::JumpIfGt(tgt_pos)); + result.push(Instruction::JumpIfLtEq(tgt_pos)); } Some(Instruction::Lt) => { result.pop(); - result.push(Instruction::JumpIfLt(tgt_pos)); + result.push(Instruction::JumpIfGtEq(tgt_pos)); } Some(Instruction::GtEq) => { result.pop(); - result.push(Instruction::JumpIfGtEq(tgt_pos)); + result.push(Instruction::JumpIfLt(tgt_pos)); } Some(Instruction::LtEq) => { result.pop(); - result.push(Instruction::JumpIfLtEq(tgt_pos)); + result.push(Instruction::JumpIfGt(tgt_pos)); } Some(Instruction::Eq) => { result.pop(); - result.push(Instruction::JumpIfEq(tgt_pos)); + result.push(Instruction::JumpIfNeq(tgt_pos)); } _ => { - result.push(Instruction::JumpIf(tgt_pos)); + result.push(Instruction::JumpIfNil(tgt_pos)); } } } @@ -42,10 +42,10 @@ pub(super) fn compile_fn_if( let mut then = ctx.compile_expr_keep_result(then)?; let mut else_ = ctx.compile_progn(else_)?; - optimize_jump_if(&mut result, Pos::Rel(else_.len() as isize + 1)); - result.append(&mut else_); - result.push(Instruction::Jump(Pos::Rel(then.len() as isize))); + optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); result.append(&mut then); + result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); + result.append(&mut else_); Ok(result) }) } diff --git a/src/vm.rs b/src/vm.rs index ffbaa69a..6f70fa23 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -80,9 +80,8 @@ pub enum Instruction { Gt, GtEq, // control flow - JumpIf(Pos), JumpIfNil(Pos), - JumpIfEq(Pos), + JumpIfNeq(Pos), JumpIfLt(Pos), JumpIfLtEq(Pos), JumpIfGt(Pos), @@ -108,9 +107,8 @@ impl Display for Instruction { Instruction::Sub => write!(f, " sub"), Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), - Instruction::JumpIf(pos) => write!(f, " jif {}", pos), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), - Instruction::JumpIfEq(pos) => write!(f, " jeq {}", pos), + Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), Instruction::JumpIfGt(pos) => write!(f, " jgt {}", pos), @@ -304,22 +302,16 @@ impl Machine { let a = self.stack.last().unwrap(); println!("{}", a); } - Instruction::JumpIf(pos) => { - let a = self.stack.pop().unwrap(); - if a.is_truthy() { - jump_to_pos!(self, pos); - } - } Instruction::JumpIfNil(pos) => { let a = self.stack.pop().unwrap(); if a.null() { jump_to_pos!(self, pos); } } - Instruction::JumpIfEq(pos) => { + Instruction::JumpIfNeq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - if a.eq(&b) { + if !a.eq(&b) { jump_to_pos!(self, pos); } } From 6e3458f02107479dd5cd6573e6df5944a4f0617a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 19:40:51 +0100 Subject: [PATCH 039/142] Add a `while` compiler --- src/byte_compile/forms/conditionals.rs | 16 ++++++++++++++++ src/byte_compile/forms/mod.rs | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index 80f2d2e7..81f98866 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -49,3 +49,19 @@ pub(super) fn compile_fn_if( Ok(result) }) } + +pub(super) fn compile_fn_while( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(name, args, true, |ctx, cond, body| { + let mut result = ctx.compile_expr_keep_result(cond)?; + let mut body = ctx.compile_progn(body)?; + + optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + result.append(&mut body); + result.push(Instruction::Jump(Pos::Rel(-(result.len() as isize + 1)))); + Ok(result) + }) +} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 6ee88c2d..02583c48 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -43,10 +43,12 @@ impl VMFunctions { ("-", arithmetic_operations::compile_fn_minus), ("print", other_functions::compile_fn_print), ("setq", other_functions::compile_fn_setq), - ("if", conditionals::compile_fn_if), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), ("cons", other_functions::compile_fn_cons), + // conditionals + ("if", conditionals::compile_fn_if), + ("while", conditionals::compile_fn_while), } VMFunctions { functions } } From af1744eeb281cbe1def0c4edb3c8297aa801cce1 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 19:49:01 +0100 Subject: [PATCH 040/142] Ensure stack gets cleaned up And add a `compile_progn_keep_result` to ensure `defun`s always return a value. --- src/byte_compile/byte_compile.rs | 14 ++++++++++++-- src/byte_compile/forms/other_functions.rs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 4f79b856..ba58f665 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -63,11 +63,10 @@ impl<'a> Compiler<'a> { } prev = Some(expr); } + self.keep_result = keep_result; if let Some(prev) = prev { - self.keep_result = true; result.append(&mut self.compile_expr(&prev)?); } - self.keep_result = keep_result; Ok(result) } @@ -118,4 +117,15 @@ impl<'a> Compiler<'a> { self.keep_result = keep_result; ret } + + pub(crate) fn compile_progn_keep_result( + &mut self, + expr: &TulispObject, + ) -> Result, Error> { + let keep_result = self.keep_result; + self.keep_result = true; + let ret = self.compile_progn(expr); + self.keep_result = keep_result; + ret + } } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index eccf84cc..1b742cc6 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -83,7 +83,7 @@ pub(super) fn compile_fn_defun( } ctx.defun_args.insert(name.addr_as_usize(), params); - let mut body = ctx.compile_progn(body)?; + let mut body = ctx.compile_progn_keep_result(body)?; let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), Instruction::Label(name.clone()), From 18b8e7cfe8436b17041639777e2769fb7e89f30a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 20:08:18 +0100 Subject: [PATCH 041/142] Move fn `mark_tail_calls` to the parse module --- src/builtin/functions/functions.rs | 60 +----------------------------- src/parse.rs | 59 +++++++++++++++++++++++++++++ src/vm.rs | 1 + 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/builtin/functions/functions.rs b/src/builtin/functions/functions.rs index 306ee8e1..342aa07e 100644 --- a/src/builtin/functions/functions.rs +++ b/src/builtin/functions/functions.rs @@ -1,6 +1,7 @@ use crate::cons::Cons; use crate::context::Scope; use crate::context::TulispContext; +use crate::destruct_bind; use crate::error::Error; use crate::error::ErrorKind; use crate::eval::eval; @@ -9,9 +10,9 @@ use crate::eval::eval_check_null; use crate::eval::DummyEval; use crate::eval::Eval; use crate::lists; +use crate::parse::mark_tail_calls; use crate::TulispObject; use crate::TulispValue; -use crate::{destruct_bind, list}; use std::convert::TryInto; use std::rc::Rc; @@ -34,63 +35,6 @@ pub(super) fn reduce_with( Ok(first) } -fn mark_tail_calls( - ctx: &mut TulispContext, - name: TulispObject, - body: TulispObject, -) -> Result { - if !body.consp() { - return Ok(body); - } - let ret = TulispObject::nil(); - let mut body_iter = body.base_iter(); - let mut tail = body_iter.next().unwrap(); - for next in body_iter { - ret.push(tail)?; - tail = next; - } - if !tail.consp() { - return Ok(body); - } - let span = tail.span(); - let ctxobj = tail.ctxobj(); - let tail_ident = tail.car()?; - let tail_name_str = tail_ident.as_symbol()?; - let new_tail = if tail_ident.eq(&name) { - let ret_tail = TulispObject::nil().append(tail.cdr()?)?.to_owned(); - list!(,ctx.intern("list") - ,TulispValue::Bounce.into_ref(None) - ,@ret_tail)? - } else if tail_name_str == "progn" || tail_name_str == "let" || tail_name_str == "let*" { - list!(,tail_ident ,@mark_tail_calls(ctx, name, tail.cdr()?)?)? - } else if tail_name_str == "if" { - destruct_bind!((_if condition then_body &rest else_body) = tail); - list!(,tail_ident - ,condition.clone() - ,mark_tail_calls( - ctx, - name.clone(), - list!(,then_body)? - )?.car()? - ,@mark_tail_calls(ctx, name, else_body)? - )? - } else if tail_name_str == "cond" { - destruct_bind!((_cond &rest conds) = tail); - let mut ret = list!(,tail_ident)?; - for cond in conds.base_iter() { - destruct_bind!((condition &rest body) = cond); - ret = list!(,@ret - ,list!(,condition.clone() - ,@mark_tail_calls(ctx, name.clone(), body)?)?)?; - } - ret - } else { - tail - }; - ret.push(new_tail.with_ctxobj(ctxobj).with_span(span))?; - Ok(ret) -} - pub(crate) fn add(ctx: &mut TulispContext) { #[crate_fn(add_func = "ctx")] fn load(ctx: &mut TulispContext, filename: String) -> Result { diff --git a/src/parse.rs b/src/parse.rs index bd8b3990..09c1523d 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,7 +1,9 @@ use std::{collections::HashMap, fmt::Write, iter::Peekable, str::Chars}; use crate::{ + destruct_bind, eval::{eval, macroexpand}, + list, object::Span, Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; @@ -534,6 +536,63 @@ impl Parser<'_, '_> { } } +pub(crate) fn mark_tail_calls( + ctx: &mut TulispContext, + name: TulispObject, + body: TulispObject, +) -> Result { + if !body.consp() { + return Ok(body); + } + let ret = TulispObject::nil(); + let mut body_iter = body.base_iter(); + let mut tail = body_iter.next().unwrap(); + for next in body_iter { + ret.push(tail)?; + tail = next; + } + if !tail.consp() { + return Ok(body); + } + let span = tail.span(); + let ctxobj = tail.ctxobj(); + let tail_ident = tail.car()?; + let tail_name_str = tail_ident.as_symbol()?; + let new_tail = if tail_ident.eq(&name) { + let ret_tail = TulispObject::nil().append(tail.cdr()?)?.to_owned(); + list!(,ctx.intern("list") + ,TulispValue::Bounce.into_ref(None) + ,@ret_tail)? + } else if tail_name_str == "progn" || tail_name_str == "let" || tail_name_str == "let*" { + list!(,tail_ident ,@mark_tail_calls(ctx, name, tail.cdr()?)?)? + } else if tail_name_str == "if" { + destruct_bind!((_if condition then_body &rest else_body) = tail); + list!(,tail_ident + ,condition.clone() + ,mark_tail_calls( + ctx, + name.clone(), + list!(,then_body)? + )?.car()? + ,@mark_tail_calls(ctx, name, else_body)? + )? + } else if tail_name_str == "cond" { + destruct_bind!((_cond &rest conds) = tail); + let mut ret = list!(,tail_ident)?; + for cond in conds.base_iter() { + destruct_bind!((condition &rest body) = cond); + ret = list!(,@ret + ,list!(,condition.clone() + ,@mark_tail_calls(ctx, name.clone(), body)?)?)?; + } + ret + } else { + tail + }; + ret.push(new_tail.with_ctxobj(ctxobj).with_span(span))?; + Ok(ret) +} + pub fn parse( ctx: &mut TulispContext, file_id: usize, diff --git a/src/vm.rs b/src/vm.rs index 6f70fa23..c2a751f4 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -201,6 +201,7 @@ impl Bytecode { } } + #[allow(dead_code)] pub(crate) fn print(&self) { println!("start:"); for (i, instr) in self.instructions.iter().enumerate() { From 0d017d52821b25506a72625c7cc9dc8d66c654d4 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 20:38:18 +0100 Subject: [PATCH 042/142] Add a compiler for the `list` function --- src/byte_compile/forms/mod.rs | 2 ++ src/byte_compile/forms/other_functions.rs | 18 ++++++++++++++++++ src/vm.rs | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 02583c48..cfed9714 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -45,7 +45,9 @@ impl VMFunctions { ("setq", other_functions::compile_fn_setq), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), + // lists ("cons", other_functions::compile_fn_cons), + ("list", other_functions::compile_fn_list), // conditionals ("if", conditionals::compile_fn_if), ("while", conditionals::compile_fn_while), diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 1b742cc6..48860647 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -52,6 +52,24 @@ pub(super) fn compile_fn_cons( }) } +pub(super) fn compile_fn_list( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = vec![]; + let mut len = 0; + for arg in args.base_iter() { + result.append(&mut compiler.compile_expr(&arg)?); + len += 1; + } + result.push(Instruction::List(len)); + Ok(result) +} + pub(super) fn compile_fn_defun_call( compiler: &mut Compiler<'_>, name: &TulispObject, diff --git a/src/vm.rs b/src/vm.rs index c2a751f4..75266338 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -94,6 +94,7 @@ pub enum Instruction { Ret, // lists Cons, + List(usize), } impl Display for Instruction { @@ -131,6 +132,7 @@ impl Display for Instruction { Instruction::RustCall { .. } => write!(f, " rustcall"), Instruction::Label(name) => write!(f, "{}:", name), Instruction::Cons => write!(f, " cons"), + Instruction::List(len) => write!(f, " list {}", len), } } } @@ -419,6 +421,14 @@ impl Machine { let b = self.stack.pop().unwrap(); self.stack.push(TulispObject::cons(a, b)); } + Instruction::List(len) => { + let mut list = TulispObject::nil(); + for _ in 0..*len { + let a = self.stack.pop().unwrap(); + list = TulispObject::cons(a, list); + } + self.stack.push(list); + } } self.pc += 1; } From d80ad846bf463114d6f4eb8968d3fbf0902494f8 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 20:41:36 +0100 Subject: [PATCH 043/142] Add a `Pop` instruction, to pop unused result after function call --- src/byte_compile/forms/other_functions.rs | 3 +++ src/vm.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 48860647..ead070f3 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -83,6 +83,9 @@ pub(super) fn compile_fn_defun_call( pos: Pos::Label(name.clone()), params: compiler.defun_args[&name.addr_as_usize()].clone(), }); + if !compiler.keep_result { + result.push(Instruction::Pop); + } Ok(result) } diff --git a/src/vm.rs b/src/vm.rs index 75266338..c3a821c2 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -61,7 +61,9 @@ impl Display for Pos { /// A single instruction in the VM. pub enum Instruction { + // stack Push(TulispObject), + Pop, // variables StorePop(usize), Store(usize), @@ -101,6 +103,7 @@ impl Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Instruction::Push(obj) => write!(f, " push {}", obj), + Instruction::Pop => write!(f, " pop"), Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), Instruction::Store(obj) => write!(f, " store {}", obj), Instruction::Load(obj) => write!(f, " load {}", obj), @@ -287,6 +290,9 @@ impl Machine { ctr += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), + Instruction::Pop => { + self.stack.pop().unwrap(); + } Instruction::Add => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); From 6af658ce34f1af27417667bf2cf510abcf434e27 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 30 Dec 2023 21:17:25 +0100 Subject: [PATCH 044/142] Update `Bounce` value to hold the name of the function --- src/byte_compile/byte_compile.rs | 2 +- src/eval.rs | 4 ++-- src/object.rs | 12 +++++++++--- src/parse.rs | 2 +- src/value.rs | 19 ++++++++++++------- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index ba58f665..b4aac699 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -80,7 +80,7 @@ impl<'a> Compiler<'a> { | TulispValue::Macro(_) | TulispValue::Defmacro { .. } | TulispValue::Any(_) - | TulispValue::Bounce + | TulispValue::Bounce { .. } | TulispValue::Nil | TulispValue::Sharpquote { .. } | TulispValue::Backquote { .. } diff --git a/src/eval.rs b/src/eval.rs index df5e0105..2e6c3c2c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -98,7 +98,7 @@ fn eval_lambda( args: &TulispObject, ) -> Result { let mut result = eval_function::(ctx, params, body, args)?; - while result.is_bounced() { + while result.is_bounced().is_some() { result = eval_function::(ctx, params, body, &result.cdr()?)?; } Ok(result) @@ -257,7 +257,7 @@ pub(crate) fn eval_basic<'a>( | TulispValue::Macro(_) | TulispValue::Defmacro { .. } | TulispValue::Any(_) - | TulispValue::Bounce + | TulispValue::Bounce { .. } | TulispValue::Nil | TulispValue::T => {} TulispValue::Quote { value, .. } => { diff --git a/src/object.rs b/src/object.rs index 707d1c2f..b1d0a08d 100644 --- a/src/object.rs +++ b/src/object.rs @@ -329,9 +329,6 @@ assert_eq!(ts.value, 25); predicate_fn!(pub, null, "Returns True if `self` is `nil`."); predicate_fn!(pub, is_truthy, "Returns True if `self` is not `nil`."); - - predicate_fn!(pub(crate), is_bounce, "Returns True if `self` is a tail-call trampoline bounce object."); - predicate_fn!(pub(crate), is_bounced, "Returns True if `self` is a tail-call trampoline bounced function call."); // predicates end } @@ -399,10 +396,19 @@ impl TulispObject { self.span.set(in_span); self.clone() } + pub(crate) fn take(&self) -> TulispValue { self.rc.borrow_mut().take() } + pub(crate) fn is_bounce(&self) -> Option { + self.rc.borrow().is_bounce() + } + + pub(crate) fn is_bounced(&self) -> Option { + self.rc.borrow().is_bounced() + } + #[doc(hidden)] pub fn span(&self) -> Option { self.span.get() diff --git a/src/parse.rs b/src/parse.rs index 09c1523d..f51954e5 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -561,7 +561,7 @@ pub(crate) fn mark_tail_calls( let new_tail = if tail_ident.eq(&name) { let ret_tail = TulispObject::nil().append(tail.cdr()?)?.to_owned(); list!(,ctx.intern("list") - ,TulispValue::Bounce.into_ref(None) + ,TulispValue::Bounce{ value: name }.into_ref(None) ,@ret_tail)? } else if tail_name_str == "progn" || tail_name_str == "let" || tail_name_str == "let*" { list!(,tail_ident ,@mark_tail_calls(ctx, name, tail.cdr()?)?)? diff --git a/src/value.rs b/src/value.rs index 6bff5eb6..425da091 100644 --- a/src/value.rs +++ b/src/value.rs @@ -212,7 +212,9 @@ pub enum TulispValue { params: DefunParams, body: TulispObject, }, - Bounce, + Bounce { + value: TulispObject, + }, } impl std::fmt::Debug for TulispValue { @@ -253,7 +255,7 @@ impl std::fmt::Debug for TulispValue { .field("params", params) .field("body", body) .finish(), - Self::Bounce => write!(f, "Bounce"), + Self::Bounce { value } => f.debug_struct("Bounce").field("value", value).finish(), } } } @@ -326,7 +328,7 @@ fn fmt_list(mut vv: TulispObject, f: &mut std::fmt::Formatter<'_>) -> Result<(), impl std::fmt::Display for TulispValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TulispValue::Bounce => f.write_str("Bounce"), + TulispValue::Bounce { value } => f.write_fmt(format_args!("{}::bounce", value)), TulispValue::Nil { .. } => f.write_str("nil"), TulispValue::Symbol { value } => f.write_str(&value.name), TulispValue::Int { value, .. } => f.write_fmt(format_args!("{}", value)), @@ -553,15 +555,18 @@ impl TulispValue { matches!(self, TulispValue::Nil) } - pub fn is_bounced(&self) -> bool { + pub fn is_bounced(&self) -> Option { match self { TulispValue::List { cons, .. } => cons.car().is_bounce(), - _ => false, + _ => None, } } - pub fn is_bounce(&self) -> bool { - matches!(self, TulispValue::Bounce) + pub fn is_bounce(&self) -> Option { + match self { + TulispValue::Bounce { value } => Some(value.clone()), + _ => None, + } } pub fn consp(&self) -> bool { From c1e06ecd2f7fb095fce9facda20c6a0f73cf98cb Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 04:22:46 +0100 Subject: [PATCH 045/142] Add instructions for beginning and ending a scope for a binding And use those for setting function call arguments. --- src/byte_compile/forms/other_functions.rs | 12 +++++--- src/vm.rs | 34 ++++++++++------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index ead070f3..bed1047a 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -79,10 +79,14 @@ pub(super) fn compile_fn_defun_call( for arg in args.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); } - result.push(Instruction::Call { - pos: Pos::Label(name.clone()), - params: compiler.defun_args[&name.addr_as_usize()].clone(), - }); + let params = &compiler.defun_args[&name.addr_as_usize()]; + for param in params { + result.push(Instruction::BeginScope(*param)) + } + result.push(Instruction::Call(Pos::Label(name.clone()))); + for param in params { + result.push(Instruction::EndScope(*param)); + } if !compiler.keep_result { result.push(Instruction::Pop); } diff --git a/src/vm.rs b/src/vm.rs index c3a821c2..79d2536a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -68,6 +68,8 @@ pub enum Instruction { StorePop(usize), Store(usize), Load(usize), + BeginScope(usize), + EndScope(usize), // arithmetic Add, Sub, @@ -92,7 +94,7 @@ pub enum Instruction { // functions Label(TulispObject), RustCall { func: Rc }, - Call { pos: Pos, params: Vec }, + Call(Pos), Ret, // lists Cons, @@ -107,6 +109,8 @@ impl Display for Instruction { Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), Instruction::Store(obj) => write!(f, " store {}", obj), Instruction::Load(obj) => write!(f, " load {}", obj), + Instruction::BeginScope(obj) => write!(f, " begin_scope {}", obj), + Instruction::EndScope(obj) => write!(f, " end_scope {}", obj), Instruction::Add => write!(f, " add"), Instruction::Sub => write!(f, " sub"), Instruction::PrintPop => write!(f, " print_pop"), @@ -124,13 +128,7 @@ impl Display for Instruction { Instruction::Gt => write!(f, " cgt"), Instruction::GtEq => write!(f, " cge"), Instruction::Jump(pos) => write!(f, " jmp {}", pos), - Instruction::Call { pos, params } => { - write!(f, " call {}", pos)?; - for param in params.iter().rev() { - write!(f, " {}", param)?; - } - Ok(()) - } + Instruction::Call(pos) => write!(f, " call {}", pos), Instruction::Ret => write!(f, " ret"), Instruction::RustCall { .. } => write!(f, " rustcall"), Instruction::Label(name) => write!(f, "{}:", name), @@ -399,21 +397,17 @@ impl Machine { let a = self.bytecode.bindings[*obj].get().unwrap(); self.stack.push(a); } - Instruction::Call { pos, params } => { + Instruction::BeginScope(obj) => { + let a = self.stack.pop().unwrap(); + let _ = self.bytecode.bindings[*obj].set_scope(a); + } + Instruction::EndScope(obj) => { + let _ = self.bytecode.bindings[*obj].unset(); + } + Instruction::Call(pos) => { let pc = self.pc; jump_to_pos!(self, pos); - - let mut scope: Vec = vec![]; - for param in params { - let value = self.stack.pop().unwrap(); - let _ = self.bytecode.bindings[*param].set_scope(value); - scope.push(*param); - } self.run_impl(ctx, recursion_depth + 1)?; - for param in scope { - let _ = self.bytecode.bindings[param].unset(); - } - self.pc = pc; } Instruction::Ret => return Ok(()), From 87b22cb6c5d6d1294de7bb8e7d08518c62e3c4af Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 04:45:09 +0100 Subject: [PATCH 046/142] Bypass program counter increment when jumping --- src/vm.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 79d2536a..2e7d87d3 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -227,7 +227,7 @@ macro_rules! jump_to_pos { match $pos { Pos::Abs(p) => *p, Pos::Rel(p) => { - let abs_pos = ($self.pc as isize + *p) as usize; + let abs_pos = ($self.pc as isize + *p + 1) as usize; *$pos = Pos::Abs(abs_pos); abs_pos } @@ -313,6 +313,7 @@ impl Machine { let a = self.stack.pop().unwrap(); if a.null() { jump_to_pos!(self, pos); + continue; } } Instruction::JumpIfNeq(pos) => { @@ -320,6 +321,7 @@ impl Machine { let b = self.stack.pop().unwrap(); if !a.eq(&b) { jump_to_pos!(self, pos); + continue; } } Instruction::JumpIfLt(pos) => { @@ -327,6 +329,7 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { jump_to_pos!(self, pos); + continue; } } Instruction::JumpIfLtEq(pos) => { @@ -334,6 +337,7 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { jump_to_pos!(self, pos); + continue; } } Instruction::JumpIfGt(pos) => { @@ -341,6 +345,7 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { jump_to_pos!(self, pos); + continue; } } Instruction::JumpIfGtEq(pos) => { @@ -348,9 +353,13 @@ impl Machine { let b = self.stack.pop().unwrap(); if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { jump_to_pos!(self, pos); + continue; } } - Instruction::Jump(pos) => jump_to_pos!(self, pos), + Instruction::Jump(pos) => { + jump_to_pos!(self, pos); + continue; + } Instruction::Equal => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); From 5711be85b1012c411d1008f4fb97ba63e4eb8724 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 04:53:06 +0100 Subject: [PATCH 047/142] Support tail-call optimization --- src/byte_compile/forms/other_functions.rs | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index bed1047a..cd56a79c 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -1,5 +1,6 @@ use crate::{ byte_compile::Compiler, + parse::mark_tail_calls, vm::{Instruction, Pos}, Error, TulispObject, }; @@ -57,6 +58,10 @@ pub(super) fn compile_fn_list( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { + if let Some(name) = args.is_bounced() { + return compile_fn_defun_bounce_call(compiler, &name, args); + } + if !compiler.keep_result { return Ok(vec![]); } @@ -70,6 +75,23 @@ pub(super) fn compile_fn_list( Ok(result) } +fn compile_fn_defun_bounce_call( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + for arg in args.cdr()?.base_iter() { + result.append(&mut compiler.compile_expr_keep_result(&arg)?); + } + let params = &compiler.defun_args[&name.addr_as_usize()]; + for param in params { + result.push(Instruction::StorePop(*param)) + } + result.push(Instruction::Jump(Pos::Label(name.clone()))); + return Ok(result); +} + pub(super) fn compile_fn_defun_call( compiler: &mut Compiler<'_>, name: &TulispObject, @@ -108,7 +130,17 @@ pub(super) fn compile_fn_defun( } ctx.defun_args.insert(name.addr_as_usize(), params); - let mut body = ctx.compile_progn_keep_result(body)?; + // TODO: replace with `is_string` + let body = if body.car()?.as_string().is_ok() { + body.cdr()? + } else { + body.clone() + }; + let body = mark_tail_calls(ctx.ctx, name.clone(), body).map_err(|e| { + println!("mark_tail_calls error: {:?}", e); + e + })?; + let mut body = ctx.compile_progn_keep_result(&body)?; let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), Instruction::Label(name.clone()), From 4d5fe7a300be714541471b53b2fb0fe248cee76a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 05:02:46 +0100 Subject: [PATCH 048/142] Remove instruction limit --- src/vm.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 2e7d87d3..ac600cdb 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -281,11 +281,9 @@ impl Machine { } fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { - let mut ctr: u32 = 0; // safety counter - while self.pc < self.bytecode.instructions.len() && ctr < 100000 { + while self.pc < self.bytecode.instructions.len() { // self.print_stack(recursion_depth); let instr = &mut self.bytecode.instructions[self.pc]; - ctr += 1; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Pop => { From 3f4f2b5fbefd9f59a8376879630306fad5da3d1a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 21:06:51 +0100 Subject: [PATCH 049/142] Create a new (lexical) binding for each scope --- src/byte_compile/byte_compile.rs | 34 +++++++++++++++++------ src/byte_compile/forms/other_functions.rs | 9 ++++-- src/vm.rs | 13 +++------ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index b4aac699..defc3e16 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -15,7 +15,7 @@ pub(crate) struct Compiler<'a> { pub functions: VMFunctions, pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx pub bindings: Vec, - pub symbol_to_binding_idx: HashMap, + pub symbol_to_binding_idx: HashMap>, pub keep_result: bool, } @@ -34,22 +34,38 @@ impl<'a> Compiler<'a> { pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> usize { let addr = &symbol.addr_as_usize(); - if let Some(idx) = self.symbol_to_binding_idx.get(addr) { - *idx + if let Some(scopes) = self.symbol_to_binding_idx.get(addr) { + *scopes.last().unwrap() } else { self.bindings.push(VMBindings::new(symbol.to_string())); let idx = self.bindings.len() - 1; - self.symbol_to_binding_idx.insert(*addr, idx); + self.symbol_to_binding_idx.insert(*addr, vec![idx]); idx } } + pub fn begin_scope(&mut self, symbol: &TulispObject) -> usize { + let addr = &symbol.addr_as_usize(); + self.bindings.push(VMBindings::new(symbol.to_string())); + let idx = self.bindings.len() - 1; + if let Some(scopes) = self.symbol_to_binding_idx.get_mut(addr) { + scopes.push(idx); + } else { + self.symbol_to_binding_idx + .insert(*addr, vec![self.bindings.len() - 1]); + } + idx + } + + pub fn end_scope(&mut self, symbol: &TulispObject) { + let addr = &symbol.addr_as_usize(); + if let Some(scopes) = self.symbol_to_binding_idx.get_mut(addr) { + scopes.pop(); + } + } + pub fn compile(mut self, value: &TulispObject) -> Result { - Ok(Bytecode::new( - self.compile_progn(value)?, - self.bindings, - self.symbol_to_binding_idx, - )) + Ok(Bytecode::new(self.compile_progn(value)?, self.bindings)) } pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index cd56a79c..ce4ab5aa 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -125,8 +125,9 @@ pub(super) fn compile_fn_defun( .functions .insert(name.addr_as_usize(), compile_fn_defun_call); let mut params = vec![]; - for arg in args.base_iter().collect::>().into_iter().rev() { - params.push(ctx.get_symbol_idx(&arg)); + let args = args.base_iter().collect::>(); + for arg in args.iter().rev() { + params.push(ctx.begin_scope(&arg)); } ctx.defun_args.insert(name.addr_as_usize(), params); @@ -141,6 +142,10 @@ pub(super) fn compile_fn_defun( e })?; let mut body = ctx.compile_progn_keep_result(&body)?; + for arg in args.iter().rev() { + ctx.end_scope(&arg); + } + let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), Instruction::Label(name.clone()), diff --git a/src/vm.rs b/src/vm.rs index ac600cdb..2f22ab14 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -187,20 +187,13 @@ impl VMBindings { pub(crate) struct Bytecode { instructions: Vec, bindings: Vec, - #[allow(dead_code)] - symbol_to_binding_idx: HashMap, } impl Bytecode { - pub(crate) fn new( - instructions: Vec, - bindings: Vec, - symbol_to_binding_idx: HashMap, - ) -> Self { + pub(crate) fn new(instructions: Vec, bindings: Vec) -> Self { Self { instructions, bindings, - symbol_to_binding_idx, } } @@ -409,7 +402,9 @@ impl Machine { let _ = self.bytecode.bindings[*obj].set_scope(a); } Instruction::EndScope(obj) => { - let _ = self.bytecode.bindings[*obj].unset(); + if self.bytecode.bindings[*obj].items.len() > 1 { + let _ = self.bytecode.bindings[*obj].unset(); + } } Instruction::Call(pos) => { let pc = self.pc; From a7dfb21203cf7d4b6b69476bcb4c38470fef49c8 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 21:38:11 +0100 Subject: [PATCH 050/142] Add a `cond` compiler --- src/byte_compile/forms/conditionals.rs | 32 +++++++++++++++++++++++++- src/byte_compile/forms/mod.rs | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index 81f98866..670f461e 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -39,7 +39,7 @@ pub(super) fn compile_fn_if( ) -> Result, Error> { compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr_keep_result(then)?; + let mut then = ctx.compile_expr(then)?; let mut else_ = ctx.compile_progn(else_)?; optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); @@ -50,6 +50,36 @@ pub(super) fn compile_fn_if( }) } +pub(super) fn compile_fn_cond( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + let cond_end = TulispObject::symbol("cond-end".to_string(), true); + + for branch in args.base_iter() { + result.append( + &mut compiler + .compile_1_arg_call(&"cond-branch".into(), &branch, true, |ctx, cond, body| { + let mut result = ctx.compile_expr_keep_result(cond)?; + let mut body = ctx.compile_progn(body)?; + + optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + result.append(&mut body); + Ok(result) + }) + .map_err(|err| err.with_trace(branch))?, + ); + result.push(Instruction::Jump(Pos::Label(cond_end.clone()))); + } + if compiler.keep_result { + result.push(Instruction::Push(TulispObject::nil())); + } + result.push(Instruction::Label(cond_end)); + Ok(result) +} + pub(super) fn compile_fn_while( compiler: &mut Compiler<'_>, name: &TulispObject, diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index cfed9714..65e29ca4 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -50,6 +50,7 @@ impl VMFunctions { ("list", other_functions::compile_fn_list), // conditionals ("if", conditionals::compile_fn_if), + ("cond", conditionals::compile_fn_cond), ("while", conditionals::compile_fn_while), } VMFunctions { functions } From 024010d4d56481b4b03441df8beb0a305ab544f5 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 21:57:38 +0100 Subject: [PATCH 051/142] Improve error handling of cond branches in `mark_tail_calls` --- src/parse.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index f51954e5..092f3cb9 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -580,10 +580,14 @@ pub(crate) fn mark_tail_calls( destruct_bind!((_cond &rest conds) = tail); let mut ret = list!(,tail_ident)?; for cond in conds.base_iter() { - destruct_bind!((condition &rest body) = cond); - ret = list!(,@ret + if cond.consp() { + destruct_bind!((condition &rest body) = cond); + ret = list!(,@ret ,list!(,condition.clone() ,@mark_tail_calls(ctx, name.clone(), body)?)?)?; + } else { + ret = list!(,@ret ,cond)?; + } } ret } else { From b59eb2ac5d36b822136b0f6dfe23235590bee159 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 22:22:37 +0100 Subject: [PATCH 052/142] Return `nil` from `if` when there are no else-clauses --- src/byte_compile/forms/conditionals.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index 670f461e..e02a6022 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -44,6 +44,9 @@ pub(super) fn compile_fn_if( optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); result.append(&mut then); + if else_.is_empty() && ctx.keep_result { + else_.push(Instruction::Push(TulispObject::nil())); + } result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); result.append(&mut else_); Ok(result) From b6012d5f38d992ec8ed3a0321a81f89ca2b0ae46 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 22:56:32 +0100 Subject: [PATCH 053/142] Update arithmetic functions to accept arbitrary number of arguments Also add implementations for `*` and `/` --- .../forms/arithmetic_operations.rs | 122 ++++++++++++++---- src/byte_compile/forms/mod.rs | 4 + src/vm.rs | 14 ++ 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/byte_compile/forms/arithmetic_operations.rs index 81445e6e..d6bfa1e7 100644 --- a/src/byte_compile/forms/arithmetic_operations.rs +++ b/src/byte_compile/forms/arithmetic_operations.rs @@ -1,35 +1,111 @@ -use crate::{byte_compile::Compiler, vm::Instruction, Error, TulispObject}; +use crate::{byte_compile::Compiler, vm::Instruction, Error, ErrorKind, TulispObject}; pub(super) fn compile_fn_plus( compiler: &mut Compiler<'_>, - name: &TulispObject, + _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Add); - Ok(result) - } - }) + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = vec![]; + let args = args.base_iter().collect::>(); + if args.is_empty() { + return Err(Error::new( + ErrorKind::ArityMismatch, + "+ requires at least 1 argument.".to_string(), + )); + } + for arg in args.iter().rev() { + result.append(&mut compiler.compile_expr(arg)?); + } + for _ in 0..args.len() - 1 { + result.push(Instruction::Add); + } + Ok(result) } pub(super) fn compile_fn_minus( compiler: &mut Compiler<'_>, - name: &TulispObject, + _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Sub); - Ok(result) - } - }) + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = vec![]; + let args = args.base_iter().collect::>(); + if args.is_empty() { + return Err(Error::new( + ErrorKind::ArityMismatch, + "- requires at least 1 argument.".to_string(), + )); + } + for arg in args.iter().rev() { + result.append(&mut compiler.compile_expr(arg)?); + } + if args.len() == 1 { + result.push(Instruction::Push((-1).into())); + result.push(Instruction::Mul); + return Ok(result); + } + for _ in 0..args.len() - 1 { + result.push(Instruction::Sub); + } + Ok(result) +} + +pub(super) fn compile_fn_mul( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = vec![]; + let args = args.base_iter().collect::>(); + if args.is_empty() { + return Err(Error::new( + ErrorKind::ArityMismatch, + "* requires at least 1 argument.".to_string(), + )); + } + for arg in args.iter().rev() { + result.append(&mut compiler.compile_expr(arg)?); + } + for _ in 0..args.len() - 1 { + result.push(Instruction::Mul); + } + Ok(result) +} + +pub(super) fn compile_fn_div( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + if !compiler.keep_result { + return Ok(vec![]); + } + let mut result = vec![]; + let args = args.base_iter().collect::>(); + if args.is_empty() { + return Err(Error::new( + ErrorKind::ArityMismatch, + "/ requires at least 1 argument.".to_string(), + )); + } + for arg in args.iter().rev() { + result.append(&mut compiler.compile_expr(arg)?); + } + if args.len() == 1 { + result.push(Instruction::Push(1.into())); + result.push(Instruction::Div); + return Ok(result); + } + for _ in 0..args.len() - 1 { + result.push(Instruction::Div); + } + Ok(result) } diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 65e29ca4..cbc2db0a 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -39,8 +39,12 @@ impl VMFunctions { (">", comparison_of_numbers::compile_fn_gt), ("eq", comparison_of_numbers::compile_fn_eq), ("equal", comparison_of_numbers::compile_fn_equal), + // arithmetic ("+", arithmetic_operations::compile_fn_plus), ("-", arithmetic_operations::compile_fn_minus), + ("*", arithmetic_operations::compile_fn_mul), + ("/", arithmetic_operations::compile_fn_div), + // other functions ("print", other_functions::compile_fn_print), ("setq", other_functions::compile_fn_setq), ("defun", other_functions::compile_fn_defun), diff --git a/src/vm.rs b/src/vm.rs index 2f22ab14..0ee6ce3a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -73,6 +73,8 @@ pub enum Instruction { // arithmetic Add, Sub, + Mul, + Div, // io PrintPop, Print, @@ -113,6 +115,8 @@ impl Display for Instruction { Instruction::EndScope(obj) => write!(f, " end_scope {}", obj), Instruction::Add => write!(f, " add"), Instruction::Sub => write!(f, " sub"), + Instruction::Mul => write!(f, " mul"), + Instruction::Div => write!(f, " div"), Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), @@ -292,6 +296,16 @@ impl Machine { let b = self.stack.pop().unwrap(); self.stack.push(binary_ops!(std::ops::Sub::sub)(&a, &b)?); } + Instruction::Mul => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(binary_ops!(std::ops::Mul::mul)(&a, &b)?); + } + Instruction::Div => { + let a = self.stack.pop().unwrap(); + let b = self.stack.pop().unwrap(); + self.stack.push(binary_ops!(std::ops::Div::div)(&a, &b)?); + } Instruction::PrintPop => { let a = self.stack.pop().unwrap(); println!("{}", a); From 14dc02f42bcd59f2d18d9bf91d66220d9faaf132 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 23:47:11 +0100 Subject: [PATCH 054/142] Recursively optimize jump_if_nil to skip labels --- src/byte_compile/forms/conditionals.rs | 30 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index e02a6022..f2762013 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -4,31 +4,36 @@ use crate::{ Error, TulispObject, }; -fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) { +fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) -> Instruction { match result.last() { Some(Instruction::Gt) => { result.pop(); - result.push(Instruction::JumpIfLtEq(tgt_pos)); + Instruction::JumpIfLtEq(tgt_pos) } Some(Instruction::Lt) => { result.pop(); - result.push(Instruction::JumpIfGtEq(tgt_pos)); + Instruction::JumpIfGtEq(tgt_pos) } Some(Instruction::GtEq) => { result.pop(); - result.push(Instruction::JumpIfLt(tgt_pos)); + Instruction::JumpIfLt(tgt_pos) } Some(Instruction::LtEq) => { result.pop(); - result.push(Instruction::JumpIfGt(tgt_pos)); + Instruction::JumpIfGt(tgt_pos) } Some(Instruction::Eq) => { result.pop(); - result.push(Instruction::JumpIfNeq(tgt_pos)); + Instruction::JumpIfNeq(tgt_pos) } - _ => { - result.push(Instruction::JumpIfNil(tgt_pos)); + Some(Instruction::Label(_)) => { + let label = result.pop().unwrap(); + let ret = optimize_jump_if_nil(result, tgt_pos); + result.push(label); + + ret } + _ => Instruction::JumpIfNil(tgt_pos), } } @@ -42,7 +47,8 @@ pub(super) fn compile_fn_if( let mut then = ctx.compile_expr(then)?; let mut else_ = ctx.compile_progn(else_)?; - optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); + let res = optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); + result.push(res); result.append(&mut then); if else_.is_empty() && ctx.keep_result { else_.push(Instruction::Push(TulispObject::nil())); @@ -68,7 +74,8 @@ pub(super) fn compile_fn_cond( let mut result = ctx.compile_expr_keep_result(cond)?; let mut body = ctx.compile_progn(body)?; - optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + let res = optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + result.push(res); result.append(&mut body); Ok(result) }) @@ -92,7 +99,8 @@ pub(super) fn compile_fn_while( let mut result = ctx.compile_expr_keep_result(cond)?; let mut body = ctx.compile_progn(body)?; - optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + let res = optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); + result.push(res); result.append(&mut body); result.push(Instruction::Jump(Pos::Rel(-(result.len() as isize + 1)))); Ok(result) From 8a14d363cce3dd946c763a7204cfbd5f716afc17 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 23:48:18 +0100 Subject: [PATCH 055/142] Add a JumpIfNilElsePop instruction --- src/vm.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/vm.rs b/src/vm.rs index 0ee6ce3a..649ee4e7 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -42,7 +42,7 @@ macro_rules! binary_ops { }}; } -#[derive(Debug)] +#[derive(Clone)] pub enum Pos { Abs(usize), Rel(isize), @@ -60,6 +60,7 @@ impl Display for Pos { } /// A single instruction in the VM. +#[derive(Clone)] pub enum Instruction { // stack Push(TulispObject), @@ -87,6 +88,7 @@ pub enum Instruction { GtEq, // control flow JumpIfNil(Pos), + JumpIfNilElsePop(Pos), JumpIfNeq(Pos), JumpIfLt(Pos), JumpIfLtEq(Pos), @@ -120,6 +122,7 @@ impl Display for Instruction { Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), + Instruction::JumpIfNilElsePop(pos) => write!(f, " jnil_else_pop {}", pos), Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), @@ -321,6 +324,15 @@ impl Machine { continue; } } + Instruction::JumpIfNilElsePop(pos) => { + let a = self.stack.last().unwrap(); + if a.null() { + jump_to_pos!(self, pos); + continue; + } else { + self.stack.pop().unwrap(); + } + } Instruction::JumpIfNeq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); From 5dc2664ef03d377e808c04f5c095ee637a565d67 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 31 Dec 2023 23:48:53 +0100 Subject: [PATCH 056/142] Update comparison functions to accept arbitrary number of arguments --- .../forms/comparison_of_numbers.rs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/src/byte_compile/forms/comparison_of_numbers.rs b/src/byte_compile/forms/comparison_of_numbers.rs index 0d08d7fc..ccb440c5 100644 --- a/src/byte_compile/forms/comparison_of_numbers.rs +++ b/src/byte_compile/forms/comparison_of_numbers.rs @@ -1,20 +1,46 @@ -use crate::{byte_compile::Compiler, vm::Instruction, Error, TulispObject}; +use crate::{ + byte_compile::Compiler, + vm::{Instruction, Pos}, + Error, TulispObject, +}; + +fn compile_fn_compare( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, + instruction: Instruction, +) -> Result, Error> { + if !compiler.keep_result { + return Ok(vec![]); + } + let label = TulispObject::symbol("_".to_string(), false); + let mut result = vec![]; + let args = args.base_iter().collect::>(); + if args.len() < 2 { + return Err(Error::new( + crate::ErrorKind::ArityMismatch, + format!("{} requires at least 2 arguments.", name), + )); + } + for items in args.windows(2) { + result.append(&mut compiler.compile_expr(&items[1])?); + result.append(&mut compiler.compile_expr(&items[0])?); + result.push(instruction.clone()); + result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); + } + result.pop(); + if args.len() > 2 { + result.push(Instruction::Label(label)); + } + Ok(result) +} pub(super) fn compile_fn_lt( compiler: &mut Compiler<'_>, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Lt); - Ok(result) - } - }) + compile_fn_compare(compiler, name, args, Instruction::Lt) } pub(super) fn compile_fn_le( @@ -22,16 +48,7 @@ pub(super) fn compile_fn_le( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::LtEq); - Ok(result) - } - }) + compile_fn_compare(compiler, name, args, Instruction::LtEq) } pub(super) fn compile_fn_gt( @@ -39,16 +56,7 @@ pub(super) fn compile_fn_gt( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::Gt); - Ok(result) - } - }) + compile_fn_compare(compiler, name, args, Instruction::Gt) } pub(super) fn compile_fn_ge( @@ -56,16 +64,7 @@ pub(super) fn compile_fn_ge( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - result.push(Instruction::GtEq); - Ok(result) - } - }) + compile_fn_compare(compiler, name, args, Instruction::GtEq) } pub(super) fn compile_fn_eq( From c1ad925e272e0c10244142479a5314e01f80a783 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 01:25:14 +0100 Subject: [PATCH 057/142] Detect use of unbound symbols --- src/byte_compile/byte_compile.rs | 19 +++++++++++++++---- src/byte_compile/forms/other_functions.rs | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index defc3e16..5d9df2d3 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -32,15 +32,15 @@ impl<'a> Compiler<'a> { } } - pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> usize { + pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> (usize, bool) { let addr = &symbol.addr_as_usize(); if let Some(scopes) = self.symbol_to_binding_idx.get(addr) { - *scopes.last().unwrap() + (*scopes.last().unwrap(), false) } else { self.bindings.push(VMBindings::new(symbol.to_string())); let idx = self.bindings.len() - 1; self.symbol_to_binding_idx.insert(*addr, vec![idx]); - idx + (idx, true) } } @@ -119,7 +119,18 @@ impl<'a> Compiler<'a> { TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(self.get_symbol_idx(expr))]), + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load({ + match self.get_symbol_idx(expr) { + (_, true) => { + return Err(Error::new( + crate::ErrorKind::SyntaxError, + format!("Unbound symbol: {}", expr), + ) + .with_trace(expr.clone())) + } + (idx, false) => idx, + } + })]), } } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index ce4ab5aa..5777d9f6 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -29,9 +29,9 @@ pub(super) fn compile_fn_setq( compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { let mut result = compiler.compile_expr_keep_result(arg2)?; if compiler.keep_result { - result.push(Instruction::Store(compiler.get_symbol_idx(arg1))); + result.push(Instruction::Store(compiler.get_symbol_idx(arg1).0)); } else { - result.push(Instruction::StorePop(compiler.get_symbol_idx(arg1))); + result.push(Instruction::StorePop(compiler.get_symbol_idx(arg1).0)); } Ok(result) }) From 6d21090103b91b9897c05da696fc91bffb5283f8 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 01:25:51 +0100 Subject: [PATCH 058/142] Add a `let*` compiler, and add an alias to `let` --- src/byte_compile/forms/mod.rs | 2 + src/byte_compile/forms/other_functions.rs | 66 ++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index cbc2db0a..ba7398db 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -49,6 +49,8 @@ impl VMFunctions { ("setq", other_functions::compile_fn_setq), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), + ("let", other_functions::compile_fn_let_star), + ("let*", other_functions::compile_fn_let_star), // lists ("cons", other_functions::compile_fn_cons), ("list", other_functions::compile_fn_list), diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 5777d9f6..6532ae58 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -1,8 +1,9 @@ use crate::{ byte_compile::Compiler, + destruct_bind, parse::mark_tail_calls, vm::{Instruction, Pos}, - Error, TulispObject, + Error, ErrorKind, TulispObject, }; pub(super) fn compile_fn_print( @@ -156,6 +157,69 @@ pub(super) fn compile_fn_defun( }) } +pub(super) fn compile_fn_let_star( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(name, args, true, |ctx, varlist, body| { + let mut result = vec![]; + let mut params = vec![]; + let mut symbols = vec![]; + for varitem in varlist.base_iter() { + if varitem.symbolp() { + let param = ctx.begin_scope(&varitem); + params.push(param); + result.append(&mut vec![ + Instruction::Push(TulispObject::nil()), + Instruction::BeginScope(param), + ]); + + symbols.push(varitem); + } else if varitem.consp() { + let varitem_clone = varitem.clone(); + destruct_bind!((&optional name value &rest rest) = varitem_clone); + if name.null() { + return Err(Error::new( + ErrorKind::Undefined, + "let varitem requires name".to_string(), + ) + .with_trace(varitem)); + } + if !rest.null() { + return Err(Error::new( + ErrorKind::Undefined, + "let varitem has too many values".to_string(), + ) + .with_trace(varitem)); + } + let param = ctx.begin_scope(&name); + params.push(param); + result.append( + &mut ctx + .compile_expr_keep_result(&value) + .map_err(|e| e.with_trace(value))?, + ); + result.push(Instruction::BeginScope(param)); + } else { + return Err(Error::new( + ErrorKind::SyntaxError, + format!( + "varitems inside a let-varlist should be a var or a binding: {}", + varitem + ), + ) + .with_trace(varitem)); + } + } + result.append(&mut ctx.compile_progn(body)?); + for param in params { + result.push(Instruction::EndScope(param)); + } + Ok(result) + }) +} + pub(super) fn compile_fn_progn( compiler: &mut Compiler<'_>, _name: &TulispObject, From a0cf459959d47caca41d3ddc547e5e5ab75da68f Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 01:38:50 +0100 Subject: [PATCH 059/142] Remove nested optimization of jumps --- src/byte_compile/forms/conditionals.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index f2762013..6e5cd83b 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -26,13 +26,6 @@ fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) -> Instruct result.pop(); Instruction::JumpIfNeq(tgt_pos) } - Some(Instruction::Label(_)) => { - let label = result.pop().unwrap(); - let ret = optimize_jump_if_nil(result, tgt_pos); - result.push(label); - - ret - } _ => Instruction::JumpIfNil(tgt_pos), } } From 026b09ba2491c7e48ff44ef8a72d80151a3de138 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 01:42:14 +0100 Subject: [PATCH 060/142] Make `defmacro` and `macroexpand` as no-op in the vm --- src/byte_compile/forms/mod.rs | 3 +++ src/byte_compile/forms/other_functions.rs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index ba7398db..ce35009a 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -58,6 +58,9 @@ impl VMFunctions { ("if", conditionals::compile_fn_if), ("cond", conditionals::compile_fn_cond), ("while", conditionals::compile_fn_while), + // noop + ("defmacro", other_functions::compile_fn_noop), + ("macroexpand", other_functions::compile_fn_noop), } VMFunctions { functions } } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 6532ae58..64d6d035 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -227,3 +227,11 @@ pub(super) fn compile_fn_progn( ) -> Result, Error> { Ok(compiler.compile_progn(args)?) } + +pub(crate) fn compile_fn_noop( + _compiler: &mut Compiler<'_>, + _name: &TulispObject, + _args: &TulispObject, +) -> Result, Error> { + Ok(vec![]) +} From 33e17848432e7b8b06f95b1c889dfc00a621006b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 04:45:06 +0100 Subject: [PATCH 061/142] Hold fundamental type values directly on stack --- examples/fib-tail.lisp | 2 +- examples/tail-recursion.lisp | 3 +- src/byte_compile/byte_compile.rs | 40 +++- src/byte_compile/forms/conditionals.rs | 4 +- src/byte_compile/forms/other_functions.rs | 2 +- src/vm.rs | 255 ++++++++++++++++------ 6 files changed, 232 insertions(+), 74 deletions(-) diff --git a/examples/fib-tail.lisp b/examples/fib-tail.lisp index 973f2fb5..33ec7196 100644 --- a/examples/fib-tail.lisp +++ b/examples/fib-tail.lisp @@ -6,4 +6,4 @@ "Tail recursive Fib" (fib-impl n 0 1)) -(princ (fib 30)) +(print (fib 30)) diff --git a/examples/tail-recursion.lisp b/examples/tail-recursion.lisp index 567f052f..0898c981 100644 --- a/examples/tail-recursion.lisp +++ b/examples/tail-recursion.lisp @@ -2,4 +2,5 @@ (if (equal counter 0) lis (build (- counter 1) (cons counter lis)))) -(princ (build 1000000 '())) +(print (build 1000000 '())) + diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 5d9df2d3..8db3983d 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -88,30 +88,54 @@ impl<'a> Compiler<'a> { pub(crate) fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { match &*expr.inner_ref() { - TulispValue::Int { .. } - | TulispValue::Float { .. } - | TulispValue::String { .. } + TulispValue::Int { value } => { + if self.keep_result { + return Ok(vec![Instruction::Push(value.clone().into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::Float { value } => { + if self.keep_result { + return Ok(vec![Instruction::Push(value.clone().into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::Nil => { + if self.keep_result { + return Ok(vec![Instruction::Push(false.into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::T => { + if self.keep_result { + return Ok(vec![Instruction::Push(true.into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::String { .. } | TulispValue::Lambda { .. } | TulispValue::Func(_) | TulispValue::Macro(_) | TulispValue::Defmacro { .. } | TulispValue::Any(_) | TulispValue::Bounce { .. } - | TulispValue::Nil | TulispValue::Sharpquote { .. } | TulispValue::Backquote { .. } | TulispValue::Unquote { .. } - | TulispValue::Splice { .. } - | TulispValue::T => { + | TulispValue::Splice { .. } => { if self.keep_result { - return Ok(vec![Instruction::Push(expr.clone())]); + return Ok(vec![Instruction::Push(expr.clone().into())]); } else { return Ok(vec![]); } } TulispValue::Quote { value } => { if self.keep_result { - return Ok(vec![Instruction::Push(value.clone())]); + return Ok(vec![Instruction::Push(value.clone().into())]); } else { return Ok(vec![]); } diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index 6e5cd83b..5b2ccb0a 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -44,7 +44,7 @@ pub(super) fn compile_fn_if( result.push(res); result.append(&mut then); if else_.is_empty() && ctx.keep_result { - else_.push(Instruction::Push(TulispObject::nil())); + else_.push(Instruction::Push(false.into())); } result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); result.append(&mut else_); @@ -77,7 +77,7 @@ pub(super) fn compile_fn_cond( result.push(Instruction::Jump(Pos::Label(cond_end.clone()))); } if compiler.keep_result { - result.push(Instruction::Push(TulispObject::nil())); + result.push(Instruction::Push(false.into())); } result.push(Instruction::Label(cond_end)); Ok(result) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 64d6d035..67993032 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -171,7 +171,7 @@ pub(super) fn compile_fn_let_star( let param = ctx.begin_scope(&varitem); params.push(param); result.append(&mut vec![ - Instruction::Push(TulispObject::nil()), + Instruction::Push(false.into()), Instruction::BeginScope(param), ]); diff --git a/src/vm.rs b/src/vm.rs index 649ee4e7..2daa0e29 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -3,45 +3,73 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; use crate::{value::TulispFn, Error, ErrorKind, TulispContext, TulispObject}; macro_rules! compare_ops { - ($oper:expr) => {{ - |selfobj: &TulispObject, other: &TulispObject| -> Result { - if selfobj.floatp() { - let s: f64 = selfobj.as_float().unwrap(); - let o: f64 = other.try_into()?; - Ok($oper(&s, &o)) - } else if other.floatp() { - let o: f64 = other.as_float().unwrap(); - let s: f64 = selfobj.try_into()?; - Ok($oper(&s, &o)) - } else { - let s: i64 = selfobj.try_into()?; - let o: i64 = other.try_into()?; - Ok($oper(&s, &o)) + ($name:ident, $oper:expr) => { + fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { + match selfobj { + VMStackValue::Float(f1) => match other { + VMStackValue::Float(f2) => Ok($oper(f1, f2)), + VMStackValue::Int(i2) => Ok($oper(f1, &(*i2 as f64))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + VMStackValue::Int(i1) => match other { + VMStackValue::Float(f2) => Ok($oper(&(*i1 as f64), f2)), + VMStackValue::Int(i2) => Ok($oper(i1, i2)), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", selfobj), + )), } } - }}; + }; } +compare_ops!(cmp_lt, std::cmp::PartialOrd::lt); +compare_ops!(cmp_le, std::cmp::PartialOrd::le); +compare_ops!(cmp_gt, std::cmp::PartialOrd::gt); +compare_ops!(cmp_ge, std::cmp::PartialOrd::ge); + macro_rules! binary_ops { - ($oper:expr) => {{ - |selfobj: &TulispObject, other: &TulispObject| -> Result { - if selfobj.floatp() { - let s: f64 = selfobj.as_float().unwrap(); - let o: f64 = other.try_into()?; - Ok($oper(&s, &o).into()) - } else if other.floatp() { - let o: f64 = other.as_float().unwrap(); - let s: f64 = selfobj.try_into()?; - Ok($oper(&s, &o).into()) - } else { - let s: i64 = selfobj.try_into()?; - let o: i64 = other.try_into()?; - Ok($oper(&s, &o).into()) + ($name:ident, $oper:expr) => { + fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { + match selfobj { + VMStackValue::Float(f1) => match other { + VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(f1, f2))), + VMStackValue::Int(i2) => Ok(VMStackValue::Float($oper(f1, &(*i2 as f64)))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + VMStackValue::Int(i1) => match other { + VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(&(*i1 as f64), f2))), + VMStackValue::Int(i2) => Ok(VMStackValue::Int($oper(i1, i2))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", selfobj), + )), } } - }}; + }; } +binary_ops!(arith_add, std::ops::Add::add); +binary_ops!(arith_sub, std::ops::Sub::sub); +binary_ops!(arith_mul, std::ops::Mul::mul); +binary_ops!(arith_div, std::ops::Div::div); + #[derive(Clone)] pub enum Pos { Abs(usize), @@ -61,9 +89,9 @@ impl Display for Pos { /// A single instruction in the VM. #[derive(Clone)] -pub enum Instruction { +pub(crate) enum Instruction { // stack - Push(TulispObject), + Push(VMStackValue), Pop, // variables StorePop(usize), @@ -97,7 +125,10 @@ pub enum Instruction { Jump(Pos), // functions Label(TulispObject), - RustCall { func: Rc }, + #[allow(dead_code)] + RustCall { + func: Rc, + }, Call(Pos), Ret, // lists @@ -145,10 +176,11 @@ impl Display for Instruction { } } -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone)] pub(crate) struct VMBindings { + #[allow(dead_code)] name: String, - items: Vec, + items: Vec, } impl VMBindings { @@ -159,7 +191,7 @@ impl VMBindings { } } - pub fn set(&mut self, to_set: TulispObject) { + pub fn set(&mut self, to_set: VMStackValue) { if self.items.is_empty() { self.items.push(to_set); } else { @@ -167,7 +199,7 @@ impl VMBindings { } } - pub fn set_scope(&mut self, to_set: TulispObject) { + pub fn set_scope(&mut self, to_set: VMStackValue) { self.items.push(to_set); } @@ -180,7 +212,7 @@ impl VMBindings { !self.items.is_empty() } - pub fn get(&self) -> Result { + pub fn get(&self) -> Result { if self.items.is_empty() { return Err(Error::new( ErrorKind::TypeMismatch, @@ -214,8 +246,112 @@ impl Bytecode { } } +#[derive(Clone)] +pub(crate) enum VMStackValue { + TulispObject(TulispObject), + Bool(bool), + Float(f64), + Int(i64), +} + +macro_rules! impl_from_for_stack_value { + ($name:ident, $type:ty) => { + impl From<$type> for VMStackValue { + fn from(val: $type) -> Self { + VMStackValue::$name(val) + } + } + }; +} + +impl_from_for_stack_value!(Float, f64); +impl_from_for_stack_value!(Int, i64); +impl_from_for_stack_value!(Bool, bool); +impl_from_for_stack_value!(TulispObject, TulispObject); + +impl Into for VMStackValue { + fn into(self) -> TulispObject { + match self { + VMStackValue::TulispObject(obj) => obj, + VMStackValue::Bool(b) => b.into(), + VMStackValue::Float(fl) => fl.into(), + VMStackValue::Int(i) => i.into(), + } + } +} + +impl VMStackValue { + pub fn null(&self) -> bool { + match self { + VMStackValue::TulispObject(obj) => obj.null(), + VMStackValue::Bool(b) => !b, + VMStackValue::Float(_) | VMStackValue::Int(_) => false, + } + } + + pub fn equal(&self, other: &VMStackValue) -> bool { + match self { + VMStackValue::TulispObject(obj) => obj.equal(other), + VMStackValue::Bool(b) => match other { + VMStackValue::Bool(b2) => b == b2, + _ => false, + }, + VMStackValue::Float(fl) => match other { + VMStackValue::Float(fl2) => fl == fl2, + _ => false, + }, + VMStackValue::Int(i) => match other { + VMStackValue::Int(i2) => i == i2, + _ => false, + }, + } + } + + pub fn eq(&self, other: &VMStackValue) -> bool { + match self { + VMStackValue::TulispObject(obj) => obj.eq(other), + VMStackValue::Bool(b) => match other { + VMStackValue::Bool(b2) => b == b2, + _ => false, + }, + VMStackValue::Float(fl) => match other { + VMStackValue::Float(fl2) => fl == fl2, + _ => false, + }, + VMStackValue::Int(i) => match other { + VMStackValue::Int(i2) => i == i2, + _ => false, + }, + } + } +} + +impl std::ops::Deref for VMStackValue { + type Target = TulispObject; + + fn deref(&self) -> &Self::Target { + match self { + VMStackValue::TulispObject(obj) => obj, + VMStackValue::Bool(_) => panic!("Expected TulispObject"), + VMStackValue::Float(_) => panic!("Expected TulispObject"), + VMStackValue::Int(_) => panic!("Expected TulispObject"), + } + } +} + +impl Display for VMStackValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VMStackValue::TulispObject(obj) => write!(f, "{}", obj), + VMStackValue::Bool(b) => write!(f, "{}", b), + VMStackValue::Float(fl) => write!(f, "{}", fl), + VMStackValue::Int(i) => write!(f, "{}", i), + } + } +} + pub struct Machine { - stack: Vec, + stack: Vec, bytecode: Bytecode, labels: HashMap, // TulispObject.addr -> instruction index pc: usize, @@ -277,7 +413,7 @@ impl Machine { pub fn run(&mut self, ctx: &mut TulispContext) -> Result { self.run_impl(ctx, 0)?; - Ok(self.stack.pop().unwrap()) + Ok(self.stack.pop().unwrap().into()) } fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { @@ -292,22 +428,22 @@ impl Machine { Instruction::Add => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(binary_ops!(std::ops::Add::add)(&a, &b)?); + self.stack.push(arith_add(&a, &b)?); } Instruction::Sub => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(binary_ops!(std::ops::Sub::sub)(&a, &b)?); + self.stack.push(arith_sub(&a, &b)?); } Instruction::Mul => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(binary_ops!(std::ops::Mul::mul)(&a, &b)?); + self.stack.push(arith_mul(&a, &b)?); } Instruction::Div => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(binary_ops!(std::ops::Div::div)(&a, &b)?); + self.stack.push(arith_div(&a, &b)?); } Instruction::PrintPop => { let a = self.stack.pop().unwrap(); @@ -344,7 +480,7 @@ impl Machine { Instruction::JumpIfLt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - if compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)? { + if cmp_lt(&a, &b)? { jump_to_pos!(self, pos); continue; } @@ -352,7 +488,7 @@ impl Machine { Instruction::JumpIfLtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - if compare_ops!(std::cmp::PartialOrd::le)(&a, &b)? { + if cmp_le(&a, &b)? { jump_to_pos!(self, pos); continue; } @@ -360,7 +496,7 @@ impl Machine { Instruction::JumpIfGt(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - if compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)? { + if cmp_gt(&a, &b)? { jump_to_pos!(self, pos); continue; } @@ -368,7 +504,7 @@ impl Machine { Instruction::JumpIfGtEq(pos) => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - if compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)? { + if cmp_ge(&a, &b)? { jump_to_pos!(self, pos); continue; } @@ -390,26 +526,22 @@ impl Machine { Instruction::Lt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack - .push(compare_ops!(std::cmp::PartialOrd::lt)(&a, &b)?.into()); + self.stack.push(cmp_lt(&a, &b)?.into()); } Instruction::LtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack - .push(compare_ops!(std::cmp::PartialOrd::le)(&a, &b)?.into()); + self.stack.push(cmp_le(&a, &b)?.into()); } Instruction::Gt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack - .push(compare_ops!(std::cmp::PartialOrd::gt)(&a, &b)?.into()); + self.stack.push(cmp_gt(&a, &b)?.into()); } Instruction::GtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack - .push(compare_ops!(std::cmp::PartialOrd::ge)(&a, &b)?.into()); + self.stack.push(cmp_ge(&a, &b)?.into()); } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); @@ -421,7 +553,7 @@ impl Machine { } Instruction::Load(obj) => { let a = self.bytecode.bindings[*obj].get().unwrap(); - self.stack.push(a); + self.stack.push(a.into()); } Instruction::BeginScope(obj) => { let a = self.stack.pop().unwrap(); @@ -441,21 +573,22 @@ impl Machine { Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { let args = self.stack.pop().unwrap(); - self.stack.push(func(ctx, &args)?); + self.stack.push(func(ctx, &args)?.into()); } Instruction::Label(_) => {} Instruction::Cons => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(TulispObject::cons(a, b)); + self.stack + .push(TulispObject::cons(a.into(), b.into()).into()); } Instruction::List(len) => { let mut list = TulispObject::nil(); for _ in 0..*len { let a = self.stack.pop().unwrap(); - list = TulispObject::cons(a, list); + list = TulispObject::cons(a.into(), list); } - self.stack.push(list); + self.stack.push(list.into()); } } self.pc += 1; From 2b21595ffcec5b861af281a9584358b7dbdd616e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 1 Jan 2024 14:40:49 +0100 Subject: [PATCH 062/142] Remove `Deref` for `VMStackValue` --- src/vm.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 2daa0e29..39bcea7c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -291,7 +291,12 @@ impl VMStackValue { pub fn equal(&self, other: &VMStackValue) -> bool { match self { - VMStackValue::TulispObject(obj) => obj.equal(other), + VMStackValue::TulispObject(obj1) => match other { + VMStackValue::TulispObject(obj2) => obj1.equal(obj2), + VMStackValue::Bool(b2) => obj1.equal(&(*b2).into()), + VMStackValue::Float(fl2) => obj1.equal(&(*fl2).into()), + VMStackValue::Int(i2) => obj1.equal(&(*i2).into()), + }, VMStackValue::Bool(b) => match other { VMStackValue::Bool(b2) => b == b2, _ => false, @@ -309,7 +314,10 @@ impl VMStackValue { pub fn eq(&self, other: &VMStackValue) -> bool { match self { - VMStackValue::TulispObject(obj) => obj.eq(other), + VMStackValue::TulispObject(obj1) => match other { + VMStackValue::TulispObject(obj2) => obj1.eq(obj2), + _ => false, + }, VMStackValue::Bool(b) => match other { VMStackValue::Bool(b2) => b == b2, _ => false, @@ -326,19 +334,6 @@ impl VMStackValue { } } -impl std::ops::Deref for VMStackValue { - type Target = TulispObject; - - fn deref(&self) -> &Self::Target { - match self { - VMStackValue::TulispObject(obj) => obj, - VMStackValue::Bool(_) => panic!("Expected TulispObject"), - VMStackValue::Float(_) => panic!("Expected TulispObject"), - VMStackValue::Int(_) => panic!("Expected TulispObject"), - } - } -} - impl Display for VMStackValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -572,7 +567,10 @@ impl Machine { } Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { - let args = self.stack.pop().unwrap(); + let args = match self.stack.pop().unwrap() { + VMStackValue::TulispObject(obj) => obj, + e => panic!("Expected TulispObject as arg to rustcall. got {}", e), + }; self.stack.push(func(ctx, &args)?.into()); } Instruction::Label(_) => {} From 01617565f6cfeaf3b68a68cf7ee2463521965cfd Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 6 Jan 2024 02:05:27 +0100 Subject: [PATCH 063/142] Remove unnecessary assignments --- src/vm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 39bcea7c..21bd3b93 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -540,11 +540,11 @@ impl Machine { } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); - let _ = self.bytecode.bindings[*obj].set(a); + self.bytecode.bindings[*obj].set(a); } Instruction::Store(obj) => { let a = self.stack.last().unwrap(); - let _ = self.bytecode.bindings[*obj].set(a.clone()); + self.bytecode.bindings[*obj].set(a.clone()); } Instruction::Load(obj) => { let a = self.bytecode.bindings[*obj].get().unwrap(); @@ -552,11 +552,11 @@ impl Machine { } Instruction::BeginScope(obj) => { let a = self.stack.pop().unwrap(); - let _ = self.bytecode.bindings[*obj].set_scope(a); + self.bytecode.bindings[*obj].set_scope(a); } Instruction::EndScope(obj) => { if self.bytecode.bindings[*obj].items.len() > 1 { - let _ = self.bytecode.bindings[*obj].unset(); + self.bytecode.bindings[*obj].unset(); } } Instruction::Call(pos) => { From 4536a8ef1f48cbf22d414f774d6f4af6a8f29ec8 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 15:00:28 +0100 Subject: [PATCH 064/142] Add basic backquote compiler with splicing support for in-place lists --- src/byte_compile/byte_compile.rs | 74 +++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 8db3983d..bda5c027 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ vm::{Bytecode, Instruction, VMBindings}, - Error, TulispContext, TulispObject, TulispValue, + Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; use super::forms::VMFunctions; @@ -122,18 +122,15 @@ impl<'a> Compiler<'a> { | TulispValue::Macro(_) | TulispValue::Defmacro { .. } | TulispValue::Any(_) - | TulispValue::Bounce { .. } - | TulispValue::Sharpquote { .. } - | TulispValue::Backquote { .. } - | TulispValue::Unquote { .. } - | TulispValue::Splice { .. } => { + | TulispValue::Bounce { .. } => { if self.keep_result { return Ok(vec![Instruction::Push(expr.clone().into())]); } else { return Ok(vec![]); } } - TulispValue::Quote { value } => { + TulispValue::Backquote { value } => self.compile_back_quote(value), + TulispValue::Quote { value } | TulispValue::Sharpquote { value } => { if self.keep_result { return Ok(vec![Instruction::Push(value.clone().into())]); } else { @@ -155,6 +152,13 @@ impl<'a> Compiler<'a> { (idx, false) => idx, } })]), + TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { + return Err(Error::new( + crate::ErrorKind::SyntaxError, + "Unquote/Splice must be within a backquoted list.".to_string(), + ) + .with_trace(expr.clone())); + } } } @@ -179,4 +183,60 @@ impl<'a> Compiler<'a> { self.keep_result = keep_result; ret } + + fn compile_back_quote(&mut self, value: &TulispObject) -> Result, Error> { + if !self.keep_result { + return Ok(vec![]); + } + if !value.consp() { + return Ok(vec![Instruction::Push(value.clone().into())]); + } + let mut result = vec![]; + + let mut value = value.clone(); + let mut items = 0; + loop { + value.car_and_then(|first| { + let first_inner = &*first.inner_ref(); + if let TulispValue::Unquote { value } = first_inner { + items += 1; + result.append(&mut self.compile_expr(&value)?); + } else if let TulispValue::Splice { value } = first_inner { + result.append(&mut self.compile_expr(&value)?); + let list_inst = result.pop().unwrap(); + if let Instruction::List(n) = list_inst { + items += n; + } else { + if !value.consp() { + return Err(Error::new( + ErrorKind::SyntaxError, + format!("Can only splice a list: {}", value), + ) + .with_trace(first.clone())); + } + } + } else { + items += 1; + result.append(&mut self.compile_back_quote(first)?); + } + Ok(()) + })?; + let rest = value.cdr()?; + if let TulispValue::Unquote { value } = &*rest.inner_ref() { + items += 1; + result.append(&mut self.compile_expr(&value)?); + break; + } + if !rest.consp() { + if !rest.null() { + items += 1; + result.push(Instruction::Push(rest.clone().into())); + } + break; + } + value = rest; + } + result.push(Instruction::List(items)); + Ok(result) + } } From e004f8da3333db5e83f9249f32020de1e9bac50d Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 16:47:45 +0100 Subject: [PATCH 065/142] Reverse operand order for `Cons` instruction To make it consistent with the `List` instruction --- src/byte_compile/forms/other_functions.rs | 4 ++-- src/vm.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 67993032..a2397fcc 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -47,8 +47,8 @@ pub(super) fn compile_fn_cons( if !compiler.keep_result { return Ok(vec![]); } - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); + let mut result = compiler.compile_expr(arg1)?; + result.append(&mut compiler.compile_expr(arg2)?); result.push(Instruction::Cons); Ok(result) }) diff --git a/src/vm.rs b/src/vm.rs index 21bd3b93..0dc6f19c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -575,8 +575,8 @@ impl Machine { } Instruction::Label(_) => {} Instruction::Cons => { - let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); + let a = self.stack.pop().unwrap(); self.stack .push(TulispObject::cons(a.into(), b.into()).into()); } From 0a9beb786039271b02d46ab7e5ffc5d6f6ad0a71 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 16:52:51 +0100 Subject: [PATCH 066/142] Add an `Append` instruction and a corresponding compiler function --- src/byte_compile/forms/mod.rs | 1 + src/byte_compile/forms/other_functions.rs | 16 ++++++++++++++++ src/vm.rs | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index ce35009a..fa7dad4a 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -54,6 +54,7 @@ impl VMFunctions { // lists ("cons", other_functions::compile_fn_cons), ("list", other_functions::compile_fn_list), + ("append", other_functions::compile_fn_append), // conditionals ("if", conditionals::compile_fn_if), ("cond", conditionals::compile_fn_cond), diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index a2397fcc..8e7e0dee 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -76,6 +76,22 @@ pub(super) fn compile_fn_list( Ok(result) } +pub(super) fn compile_fn_append( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + if !compiler.keep_result { + return Ok(vec![]); + } + compiler.compile_2_arg_call(_name, args, false, |compiler, arg1, arg2, _| { + let mut result = compiler.compile_expr(arg1)?; + result.append(&mut compiler.compile_expr(arg2)?); + result.push(Instruction::Append); + Ok(result) + }) +} + fn compile_fn_defun_bounce_call( compiler: &mut Compiler<'_>, name: &TulispObject, diff --git a/src/vm.rs b/src/vm.rs index 0dc6f19c..e54e169d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -134,6 +134,7 @@ pub(crate) enum Instruction { // lists Cons, List(usize), + Append, } impl Display for Instruction { @@ -172,6 +173,7 @@ impl Display for Instruction { Instruction::Label(name) => write!(f, "{}:", name), Instruction::Cons => write!(f, " cons"), Instruction::List(len) => write!(f, " list {}", len), + Instruction::Append => write!(f, " append"), } } } @@ -588,6 +590,13 @@ impl Machine { } self.stack.push(list.into()); } + Instruction::Append => { + let a: TulispObject = self.stack.pop().unwrap().into(); + let a = a.deep_copy().unwrap(); + let b: TulispObject = self.stack.pop().unwrap().into(); + b.append(a)?; + self.stack.push(b.into()); + } } self.pc += 1; } From 31390c35cbb29a18fe274abde7373487e4eb853e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 16:57:39 +0100 Subject: [PATCH 067/142] Support splicing of variables in backquote expressions --- src/byte_compile/byte_compile.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index bda5c027..ffed4be1 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -195,6 +195,7 @@ impl<'a> Compiler<'a> { let mut value = value.clone(); let mut items = 0; + let mut need_append = false; loop { value.car_and_then(|first| { let first_inner = &*first.inner_ref(); @@ -206,11 +207,22 @@ impl<'a> Compiler<'a> { let list_inst = result.pop().unwrap(); if let Instruction::List(n) = list_inst { items += n; + } else if let Instruction::Load(idx) = list_inst { + result.push(Instruction::List(items)); + if need_append { + result.push(Instruction::Append); + } + result.append(&mut vec![Instruction::Load(idx), Instruction::Append]); + need_append = true; + items = 0; } else { if !value.consp() { return Err(Error::new( ErrorKind::SyntaxError, - format!("Can only splice a list: {}", value), + format!( + "Can only splice an inplace-list or a variable binding: {}", + value + ), ) .with_trace(first.clone())); } @@ -237,6 +249,9 @@ impl<'a> Compiler<'a> { value = rest; } result.push(Instruction::List(items)); + if need_append { + result.push(Instruction::Append); + } Ok(result) } } From c70e9f52ea82708b8572ccceb5a10345bf31ca09 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 16:58:16 +0100 Subject: [PATCH 068/142] Support splicing in cdr of cons cells --- src/byte_compile/byte_compile.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index ffed4be1..20e8b280 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -195,6 +195,7 @@ impl<'a> Compiler<'a> { let mut value = value.clone(); let mut items = 0; + let mut need_list = true; let mut need_append = false; loop { value.car_and_then(|first| { @@ -235,20 +236,24 @@ impl<'a> Compiler<'a> { })?; let rest = value.cdr()?; if let TulispValue::Unquote { value } = &*rest.inner_ref() { - items += 1; result.append(&mut self.compile_expr(&value)?); + result.push(Instruction::Cons); + need_list = false; break; } if !rest.consp() { if !rest.null() { - items += 1; result.push(Instruction::Push(rest.clone().into())); + result.push(Instruction::Cons); + need_list = false; } break; } value = rest; } - result.push(Instruction::List(items)); + if need_list { + result.push(Instruction::List(items)); + } if need_append { result.push(Instruction::Append); } From b384932926487bd35791e18249339ca2b65f374e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Jan 2024 17:16:03 +0100 Subject: [PATCH 069/142] Extract fundamental values when converting TulispObject to stack value --- src/vm.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index e54e169d..4d725417 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; -use crate::{value::TulispFn, Error, ErrorKind, TulispContext, TulispObject}; +use crate::{value::TulispFn, Error, ErrorKind, TulispContext, TulispObject, TulispValue}; macro_rules! compare_ops { ($name:ident, $oper:expr) => { @@ -269,7 +269,19 @@ macro_rules! impl_from_for_stack_value { impl_from_for_stack_value!(Float, f64); impl_from_for_stack_value!(Int, i64); impl_from_for_stack_value!(Bool, bool); -impl_from_for_stack_value!(TulispObject, TulispObject); + +impl From for VMStackValue { + fn from(val: TulispObject) -> Self { + match &*val.inner_ref() { + TulispValue::Int { value } => return VMStackValue::Int(*value), + TulispValue::Float { value } => return VMStackValue::Float(*value), + TulispValue::T => return VMStackValue::Bool(true), + TulispValue::Nil => return VMStackValue::Bool(false), + _ => {} + } + VMStackValue::TulispObject(val) + } +} impl Into for VMStackValue { fn into(self) -> TulispObject { From 6da645f45ad6e51ba01ee24b6342c3d4968e153d Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 14:06:25 +0100 Subject: [PATCH 070/142] Handle splicing results of arbitrary operations in backquote compiler --- src/byte_compile/byte_compile.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 20e8b280..16796242 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -204,11 +204,13 @@ impl<'a> Compiler<'a> { items += 1; result.append(&mut self.compile_expr(&value)?); } else if let TulispValue::Splice { value } = first_inner { - result.append(&mut self.compile_expr(&value)?); - let list_inst = result.pop().unwrap(); + let mut splice_result = self.compile_expr(&value)?; + let list_inst = splice_result.pop().unwrap(); if let Instruction::List(n) = list_inst { + result.append(&mut splice_result); items += n; } else if let Instruction::Load(idx) = list_inst { + result.append(&mut splice_result); result.push(Instruction::List(items)); if need_append { result.push(Instruction::Append); @@ -227,6 +229,15 @@ impl<'a> Compiler<'a> { ) .with_trace(first.clone())); } + result.push(Instruction::List(items)); + if need_append { + result.push(Instruction::Append); + } + result.append(&mut splice_result); + result.push(list_inst); + result.push(Instruction::Append); + need_append = true; + items = 0; } } else { items += 1; From 56cce48c98967b402ceb45aa9e2743aca7034d2c Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 14:35:40 +0100 Subject: [PATCH 071/142] Add cxr instructions and compiler --- src/byte_compile/forms/list_elements.rs | 52 ++++++++++++ src/byte_compile/forms/mod.rs | 32 ++++++++ src/vm.rs | 105 ++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 src/byte_compile/forms/list_elements.rs diff --git a/src/byte_compile/forms/list_elements.rs b/src/byte_compile/forms/list_elements.rs new file mode 100644 index 00000000..e96e6dd3 --- /dev/null +++ b/src/byte_compile/forms/list_elements.rs @@ -0,0 +1,52 @@ +use crate::{ + byte_compile::Compiler, + vm::{Instruction, InstructionCxr}, + Error, ErrorKind, TulispObject, +}; + +pub(super) fn compile_fn_cxr( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = compiler.compile_1_arg_call(name, args, false, |compiler, arg1, _| { + compiler.compile_expr(arg1) + })?; + if compiler.keep_result { + let name = name.to_string(); + match name.as_str() { + "car" => result.push(Instruction::Cxr(InstructionCxr::Car)), + "cdr" => result.push(Instruction::Cxr(InstructionCxr::Cdr)), + "caar" => result.push(Instruction::Cxr(InstructionCxr::Caar)), + "cadr" => result.push(Instruction::Cxr(InstructionCxr::Cadr)), + "cdar" => result.push(Instruction::Cxr(InstructionCxr::Cdar)), + "cddr" => result.push(Instruction::Cxr(InstructionCxr::Cddr)), + "caaar" => result.push(Instruction::Cxr(InstructionCxr::Caaar)), + "caadr" => result.push(Instruction::Cxr(InstructionCxr::Caadr)), + "cadar" => result.push(Instruction::Cxr(InstructionCxr::Cadar)), + "caddr" => result.push(Instruction::Cxr(InstructionCxr::Caddr)), + "cdaar" => result.push(Instruction::Cxr(InstructionCxr::Cdaar)), + "cdadr" => result.push(Instruction::Cxr(InstructionCxr::Cdadr)), + "cddar" => result.push(Instruction::Cxr(InstructionCxr::Cddar)), + "cdddr" => result.push(Instruction::Cxr(InstructionCxr::Cdddr)), + "caaaar" => result.push(Instruction::Cxr(InstructionCxr::Caaaar)), + "caaadr" => result.push(Instruction::Cxr(InstructionCxr::Caaadr)), + "caadar" => result.push(Instruction::Cxr(InstructionCxr::Caadar)), + "caaddr" => result.push(Instruction::Cxr(InstructionCxr::Caaddr)), + "cadaar" => result.push(Instruction::Cxr(InstructionCxr::Cadaar)), + "cadadr" => result.push(Instruction::Cxr(InstructionCxr::Cadadr)), + "caddar" => result.push(Instruction::Cxr(InstructionCxr::Caddar)), + "cadddr" => result.push(Instruction::Cxr(InstructionCxr::Cadddr)), + "cdaaar" => result.push(Instruction::Cxr(InstructionCxr::Cdaaar)), + "cdaadr" => result.push(Instruction::Cxr(InstructionCxr::Cdaadr)), + "cdadar" => result.push(Instruction::Cxr(InstructionCxr::Cdadar)), + "cdaddr" => result.push(Instruction::Cxr(InstructionCxr::Cdaddr)), + "cddaar" => result.push(Instruction::Cxr(InstructionCxr::Cddaar)), + "cddadr" => result.push(Instruction::Cxr(InstructionCxr::Cddadr)), + "cdddar" => result.push(Instruction::Cxr(InstructionCxr::Cdddar)), + "cddddr" => result.push(Instruction::Cxr(InstructionCxr::Cddddr)), + _ => return Err(Error::new(ErrorKind::Undefined, "unknown cxr".to_string())), + } + } + Ok(result) +} diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index fa7dad4a..62e80592 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -8,6 +8,7 @@ mod arithmetic_operations; mod common; mod comparison_of_numbers; mod conditionals; +mod list_elements; mod other_functions; type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; @@ -55,6 +56,37 @@ impl VMFunctions { ("cons", other_functions::compile_fn_cons), ("list", other_functions::compile_fn_list), ("append", other_functions::compile_fn_append), + // cxr + ("car", list_elements::compile_fn_cxr), + ("cdr", list_elements::compile_fn_cxr), + ("caar", list_elements::compile_fn_cxr), + ("cadr", list_elements::compile_fn_cxr), + ("cdar", list_elements::compile_fn_cxr), + ("cddr", list_elements::compile_fn_cxr), + ("caaar", list_elements::compile_fn_cxr), + ("caadr", list_elements::compile_fn_cxr), + ("cadar", list_elements::compile_fn_cxr), + ("caddr", list_elements::compile_fn_cxr), + ("cdaar", list_elements::compile_fn_cxr), + ("cdadr", list_elements::compile_fn_cxr), + ("cddar", list_elements::compile_fn_cxr), + ("cdddr", list_elements::compile_fn_cxr), + ("caaaar", list_elements::compile_fn_cxr), + ("caaadr", list_elements::compile_fn_cxr), + ("caadar", list_elements::compile_fn_cxr), + ("caaddr", list_elements::compile_fn_cxr), + ("cadaar", list_elements::compile_fn_cxr), + ("cadadr", list_elements::compile_fn_cxr), + ("caddar", list_elements::compile_fn_cxr), + ("cadddr", list_elements::compile_fn_cxr), + ("cdaaar", list_elements::compile_fn_cxr), + ("cdaadr", list_elements::compile_fn_cxr), + ("cdadar", list_elements::compile_fn_cxr), + ("cdaddr", list_elements::compile_fn_cxr), + ("cddaar", list_elements::compile_fn_cxr), + ("cddadr", list_elements::compile_fn_cxr), + ("cdddar", list_elements::compile_fn_cxr), + ("cddddr", list_elements::compile_fn_cxr), // conditionals ("if", conditionals::compile_fn_if), ("cond", conditionals::compile_fn_cond), diff --git a/src/vm.rs b/src/vm.rs index 4d725417..fbdb85d3 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -87,6 +87,40 @@ impl Display for Pos { } } +#[derive(Clone, Copy)] +pub(crate) enum InstructionCxr { + Car, + Cdr, + Caar, + Cadr, + Cdar, + Cddr, + Caaar, + Caadr, + Cadar, + Caddr, + Cdaar, + Cdadr, + Cddar, + Cdddr, + Caaaar, + Caaadr, + Caadar, + Caaddr, + Cadaar, + Cadadr, + Caddar, + Cadddr, + Cdaaar, + Cdaadr, + Cdadar, + Cdaddr, + Cddaar, + Cddadr, + Cdddar, + Cddddr, +} + /// A single instruction in the VM. #[derive(Clone)] pub(crate) enum Instruction { @@ -135,6 +169,7 @@ pub(crate) enum Instruction { Cons, List(usize), Append, + Cxr(InstructionCxr), } impl Display for Instruction { @@ -174,6 +209,38 @@ impl Display for Instruction { Instruction::Cons => write!(f, " cons"), Instruction::List(len) => write!(f, " list {}", len), Instruction::Append => write!(f, " append"), + Instruction::Cxr(cxr) => match cxr { + InstructionCxr::Car => write!(f, " car"), + InstructionCxr::Cdr => write!(f, " cdr"), + InstructionCxr::Caar => write!(f, " caar"), + InstructionCxr::Cadr => write!(f, " cadr"), + InstructionCxr::Cdar => write!(f, " cdar"), + InstructionCxr::Cddr => write!(f, " cddr"), + InstructionCxr::Caaar => write!(f, " caaar"), + InstructionCxr::Caadr => write!(f, " caadr"), + InstructionCxr::Cadar => write!(f, " cadar"), + InstructionCxr::Caddr => write!(f, " caddr"), + InstructionCxr::Cdaar => write!(f, " cdaar"), + InstructionCxr::Cdadr => write!(f, " cdadr"), + InstructionCxr::Cddar => write!(f, " cddar"), + InstructionCxr::Cdddr => write!(f, " cdddr"), + InstructionCxr::Caaaar => write!(f, " caaaar"), + InstructionCxr::Caaadr => write!(f, " caaadr"), + InstructionCxr::Caadar => write!(f, " caadar"), + InstructionCxr::Caaddr => write!(f, " caaddr"), + InstructionCxr::Cadaar => write!(f, " cadaar"), + InstructionCxr::Cadadr => write!(f, " cadadr"), + InstructionCxr::Caddar => write!(f, " caddar"), + InstructionCxr::Cadddr => write!(f, " cadddr"), + InstructionCxr::Cdaaar => write!(f, " cdaaar"), + InstructionCxr::Cdaadr => write!(f, " cdaadr"), + InstructionCxr::Cdadar => write!(f, " cdadar"), + InstructionCxr::Cdaddr => write!(f, " cdaddr"), + InstructionCxr::Cddaar => write!(f, " cddaar"), + InstructionCxr::Cddadr => write!(f, " cddadr"), + InstructionCxr::Cdddar => write!(f, " cdddar"), + InstructionCxr::Cddddr => write!(f, " cddddr"), + }, } } } @@ -609,6 +676,44 @@ impl Machine { b.append(a)?; self.stack.push(b.into()); } + Instruction::Cxr(cxr) => { + let a: TulispObject = self.stack.pop().unwrap().into(); + self.stack.push( + match cxr { + InstructionCxr::Car => a.car().unwrap(), + InstructionCxr::Cdr => a.cdr().unwrap(), + InstructionCxr::Caar => a.caar().unwrap(), + InstructionCxr::Cadr => a.cadr().unwrap(), + InstructionCxr::Cdar => a.cdar().unwrap(), + InstructionCxr::Cddr => a.cddr().unwrap(), + InstructionCxr::Caaar => a.caaar().unwrap(), + InstructionCxr::Caadr => a.caadr().unwrap(), + InstructionCxr::Cadar => a.cadar().unwrap(), + InstructionCxr::Caddr => a.caddr().unwrap(), + InstructionCxr::Cdaar => a.cdaar().unwrap(), + InstructionCxr::Cdadr => a.cdadr().unwrap(), + InstructionCxr::Cddar => a.cddar().unwrap(), + InstructionCxr::Cdddr => a.cdddr().unwrap(), + InstructionCxr::Caaaar => a.caaaar().unwrap(), + InstructionCxr::Caaadr => a.caaadr().unwrap(), + InstructionCxr::Caadar => a.caadar().unwrap(), + InstructionCxr::Caaddr => a.caaddr().unwrap(), + InstructionCxr::Cadaar => a.cadaar().unwrap(), + InstructionCxr::Cadadr => a.cadadr().unwrap(), + InstructionCxr::Caddar => a.caddar().unwrap(), + InstructionCxr::Cadddr => a.cadddr().unwrap(), + InstructionCxr::Cdaaar => a.cdaaar().unwrap(), + InstructionCxr::Cdaadr => a.cdaadr().unwrap(), + InstructionCxr::Cdadar => a.cdadar().unwrap(), + InstructionCxr::Cdaddr => a.cdaddr().unwrap(), + InstructionCxr::Cddaar => a.cddaar().unwrap(), + InstructionCxr::Cddadr => a.cddadr().unwrap(), + InstructionCxr::Cdddar => a.cdddar().unwrap(), + InstructionCxr::Cddddr => a.cddddr().unwrap(), + } + .into(), + ) + } } self.pc += 1; } From 72191a5b7980a4ed71d7f8ef072b30b991bde146 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 17:50:59 +0100 Subject: [PATCH 072/142] Update append to support multiple arguments --- src/byte_compile/byte_compile.rs | 10 +++++----- src/byte_compile/forms/other_functions.rs | 15 +++++++-------- src/vm.rs | 17 +++++++++-------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index 16796242..fa8b60c6 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -213,9 +213,9 @@ impl<'a> Compiler<'a> { result.append(&mut splice_result); result.push(Instruction::List(items)); if need_append { - result.push(Instruction::Append); + result.push(Instruction::Append(2)); } - result.append(&mut vec![Instruction::Load(idx), Instruction::Append]); + result.append(&mut vec![Instruction::Load(idx), Instruction::Append(2)]); need_append = true; items = 0; } else { @@ -231,11 +231,11 @@ impl<'a> Compiler<'a> { } result.push(Instruction::List(items)); if need_append { - result.push(Instruction::Append); + result.push(Instruction::Append(2)); } result.append(&mut splice_result); result.push(list_inst); - result.push(Instruction::Append); + result.push(Instruction::Append(2)); need_append = true; items = 0; } @@ -266,7 +266,7 @@ impl<'a> Compiler<'a> { result.push(Instruction::List(items)); } if need_append { - result.push(Instruction::Append); + result.push(Instruction::Append(2)); } Ok(result) } diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index 8e7e0dee..db5a49fe 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -81,15 +81,14 @@ pub(super) fn compile_fn_append( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - if !compiler.keep_result { - return Ok(vec![]); + let mut result = vec![]; + let mut len = 0; + for arg in args.base_iter() { + result.append(&mut compiler.compile_expr(&arg)?); + len += 1; } - compiler.compile_2_arg_call(_name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr(arg1)?; - result.append(&mut compiler.compile_expr(arg2)?); - result.push(Instruction::Append); - Ok(result) - }) + result.push(Instruction::Append(len)); + Ok(result) } fn compile_fn_defun_bounce_call( diff --git a/src/vm.rs b/src/vm.rs index fbdb85d3..4c18e8c2 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -168,7 +168,7 @@ pub(crate) enum Instruction { // lists Cons, List(usize), - Append, + Append(usize), Cxr(InstructionCxr), } @@ -208,7 +208,7 @@ impl Display for Instruction { Instruction::Label(name) => write!(f, "{}:", name), Instruction::Cons => write!(f, " cons"), Instruction::List(len) => write!(f, " list {}", len), - Instruction::Append => write!(f, " append"), + Instruction::Append(len) => write!(f, " append {}", len), Instruction::Cxr(cxr) => match cxr { InstructionCxr::Car => write!(f, " car"), InstructionCxr::Cdr => write!(f, " cdr"), @@ -669,12 +669,13 @@ impl Machine { } self.stack.push(list.into()); } - Instruction::Append => { - let a: TulispObject = self.stack.pop().unwrap().into(); - let a = a.deep_copy().unwrap(); - let b: TulispObject = self.stack.pop().unwrap().into(); - b.append(a)?; - self.stack.push(b.into()); + Instruction::Append(len) => { + let list = TulispObject::nil(); + + for elt in self.stack.drain(self.stack.len() - *len..) { + list.append(>::into(elt).deep_copy()?)?; + } + self.stack.push(list.into()); } Instruction::Cxr(cxr) => { let a: TulispObject = self.stack.pop().unwrap().into(); From 2560707a0c09a460e66ba42b1feb10382609814e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 19:23:09 +0100 Subject: [PATCH 073/142] Improve `scope` instruction performance by eliminate `Vec::pop` --- src/vm.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 4c18e8c2..99638a96 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -268,12 +268,12 @@ impl VMBindings { } } - pub fn set_scope(&mut self, to_set: VMStackValue) { - self.items.push(to_set); + pub fn set_scope(&mut self, to_set: &VMStackValue) { + self.items.push(to_set.to_owned()); } pub fn unset(&mut self) { - self.items.pop(); + self.items.truncate(self.items.len() - 1); } #[allow(dead_code)] @@ -632,8 +632,9 @@ impl Machine { self.stack.push(a.into()); } Instruction::BeginScope(obj) => { - let a = self.stack.pop().unwrap(); + let a = self.stack.last().unwrap(); self.bytecode.bindings[*obj].set_scope(a); + self.stack.truncate(self.stack.len() - 1); } Instruction::EndScope(obj) => { if self.bytecode.bindings[*obj].items.len() > 1 { From 992b6983dda7f9bea640e2739eb16e48f4b482e6 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 19:26:44 +0100 Subject: [PATCH 074/142] Improve arithmetic instruction performance by better stack access --- src/vm.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 99638a96..80c1d85f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -499,27 +499,43 @@ impl Machine { match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Pop => { - self.stack.pop().unwrap(); + self.stack.pop(); } Instruction::Add => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - self.stack.push(arith_add(&a, &b)?); + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let vv = arith_add(a, b)?; + self.stack.truncate(minus2); + self.stack.push(vv); } Instruction::Sub => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - self.stack.push(arith_sub(&a, &b)?); + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let vv = arith_sub(a, b)?; + self.stack.truncate(minus2); + self.stack.push(vv); } Instruction::Mul => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - self.stack.push(arith_mul(&a, &b)?); + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let vv = arith_mul(a, b)?; + self.stack.truncate(minus2); + self.stack.push(vv); } Instruction::Div => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - self.stack.push(arith_div(&a, &b)?); + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let vv = arith_div(a, b)?; + self.stack.truncate(minus2); + self.stack.push(vv); } Instruction::PrintPop => { let a = self.stack.pop().unwrap(); From 6eb07cba06cb51407a030f0514035ae880f6eeea Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 19:37:05 +0100 Subject: [PATCH 075/142] Improve conditional jump performance by better stack access --- src/vm.rs | 58 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 80c1d85f..b92c644b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -546,8 +546,10 @@ impl Machine { println!("{}", a); } Instruction::JumpIfNil(pos) => { - let a = self.stack.pop().unwrap(); - if a.null() { + let a = self.stack.last().unwrap(); + let cmp = a.null(); + self.stack.truncate(self.stack.len() - 1); + if cmp { jump_to_pos!(self, pos); continue; } @@ -558,45 +560,65 @@ impl Machine { jump_to_pos!(self, pos); continue; } else { - self.stack.pop().unwrap(); + self.stack.truncate(self.stack.len() - 1); } } Instruction::JumpIfNeq(pos) => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - if !a.eq(&b) { + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let cmp = !a.eq(&b); + self.stack.truncate(minus2); + if cmp { jump_to_pos!(self, pos); continue; } } Instruction::JumpIfLt(pos) => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - if cmp_lt(&a, &b)? { + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let cmp = cmp_lt(a, b)?; + self.stack.truncate(minus2); + if cmp { jump_to_pos!(self, pos); continue; } } Instruction::JumpIfLtEq(pos) => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - if cmp_le(&a, &b)? { + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let cmp = cmp_le(a, b)?; + self.stack.truncate(minus2); + if cmp { jump_to_pos!(self, pos); continue; } } Instruction::JumpIfGt(pos) => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - if cmp_gt(&a, &b)? { + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let cmp = cmp_gt(a, b)?; + self.stack.truncate(minus2); + if cmp { jump_to_pos!(self, pos); continue; } } Instruction::JumpIfGtEq(pos) => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - if cmp_ge(&a, &b)? { + let minus2 = self.stack.len() - 2; + let [ref b, ref a] = self.stack[minus2..] else { + unreachable!() + }; + let cmp = cmp_ge(a, b)?; + self.stack.truncate(minus2); + if cmp { jump_to_pos!(self, pos); continue; } From 50926f982e3455db00b56bd74b779d6b66b85f3c Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 20:09:43 +0100 Subject: [PATCH 076/142] Group `BinaryOp`s under a sub instruction set --- .../forms/arithmetic_operations.rs | 18 +++--- src/vm.rs | 62 +++++++------------ 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/byte_compile/forms/arithmetic_operations.rs index d6bfa1e7..61f3623e 100644 --- a/src/byte_compile/forms/arithmetic_operations.rs +++ b/src/byte_compile/forms/arithmetic_operations.rs @@ -1,4 +1,8 @@ -use crate::{byte_compile::Compiler, vm::Instruction, Error, ErrorKind, TulispObject}; +use crate::{ + byte_compile::Compiler, + vm::{Instruction, InstructionBinaryOp}, + Error, ErrorKind, TulispObject, +}; pub(super) fn compile_fn_plus( compiler: &mut Compiler<'_>, @@ -20,7 +24,7 @@ pub(super) fn compile_fn_plus( result.append(&mut compiler.compile_expr(arg)?); } for _ in 0..args.len() - 1 { - result.push(Instruction::Add); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Add)); } Ok(result) } @@ -46,11 +50,11 @@ pub(super) fn compile_fn_minus( } if args.len() == 1 { result.push(Instruction::Push((-1).into())); - result.push(Instruction::Mul); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Mul)); return Ok(result); } for _ in 0..args.len() - 1 { - result.push(Instruction::Sub); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Sub)); } Ok(result) } @@ -75,7 +79,7 @@ pub(super) fn compile_fn_mul( result.append(&mut compiler.compile_expr(arg)?); } for _ in 0..args.len() - 1 { - result.push(Instruction::Mul); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Mul)); } Ok(result) } @@ -101,11 +105,11 @@ pub(super) fn compile_fn_div( } if args.len() == 1 { result.push(Instruction::Push(1.into())); - result.push(Instruction::Div); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Div)); return Ok(result); } for _ in 0..args.len() - 1 { - result.push(Instruction::Div); + result.push(Instruction::BinaryOp(InstructionBinaryOp::Div)); } Ok(result) } diff --git a/src/vm.rs b/src/vm.rs index b92c644b..f0dcc974 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -121,6 +121,14 @@ pub(crate) enum InstructionCxr { Cddddr, } +#[derive(Clone, Copy)] +pub(crate) enum InstructionBinaryOp { + Add, + Sub, + Mul, + Div, +} + /// A single instruction in the VM. #[derive(Clone)] pub(crate) enum Instruction { @@ -134,10 +142,7 @@ pub(crate) enum Instruction { BeginScope(usize), EndScope(usize), // arithmetic - Add, - Sub, - Mul, - Div, + BinaryOp(InstructionBinaryOp), // io PrintPop, Print, @@ -182,10 +187,12 @@ impl Display for Instruction { Instruction::Load(obj) => write!(f, " load {}", obj), Instruction::BeginScope(obj) => write!(f, " begin_scope {}", obj), Instruction::EndScope(obj) => write!(f, " end_scope {}", obj), - Instruction::Add => write!(f, " add"), - Instruction::Sub => write!(f, " sub"), - Instruction::Mul => write!(f, " mul"), - Instruction::Div => write!(f, " div"), + Instruction::BinaryOp(op) => match op { + InstructionBinaryOp::Add => write!(f, " add"), + InstructionBinaryOp::Sub => write!(f, " sub"), + InstructionBinaryOp::Mul => write!(f, " mul"), + InstructionBinaryOp::Div => write!(f, " div"), + }, Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), @@ -501,40 +508,17 @@ impl Machine { Instruction::Pop => { self.stack.pop(); } - Instruction::Add => { - let minus2 = self.stack.len() - 2; - let [ref b, ref a] = self.stack[minus2..] else { + Instruction::BinaryOp(op) => { + let [ref b, ref a] = self.stack[(self.stack.len() - 2)..] else { unreachable!() }; - let vv = arith_add(a, b)?; - self.stack.truncate(minus2); - self.stack.push(vv); - } - Instruction::Sub => { - let minus2 = self.stack.len() - 2; - let [ref b, ref a] = self.stack[minus2..] else { - unreachable!() - }; - let vv = arith_sub(a, b)?; - self.stack.truncate(minus2); - self.stack.push(vv); - } - Instruction::Mul => { - let minus2 = self.stack.len() - 2; - let [ref b, ref a] = self.stack[minus2..] else { - unreachable!() + let vv = match op { + InstructionBinaryOp::Add => arith_add(&a, &b)?, + InstructionBinaryOp::Sub => arith_sub(&a, &b)?, + InstructionBinaryOp::Mul => arith_mul(&a, &b)?, + InstructionBinaryOp::Div => arith_div(&a, &b)?, }; - let vv = arith_mul(a, b)?; - self.stack.truncate(minus2); - self.stack.push(vv); - } - Instruction::Div => { - let minus2 = self.stack.len() - 2; - let [ref b, ref a] = self.stack[minus2..] else { - unreachable!() - }; - let vv = arith_div(a, b)?; - self.stack.truncate(minus2); + self.stack.truncate(self.stack.len() - 2); self.stack.push(vv); } Instruction::PrintPop => { From 4a15ba954f9453dd65950016cff47dde58ac8ea8 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 20:24:52 +0100 Subject: [PATCH 077/142] Remove old hand-written bytecode programs --- src/vm.rs | 99 ------------------------------------------------------- 1 file changed, 99 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index f0dcc974..cab2ca5a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -744,102 +744,3 @@ impl Machine { Ok(()) } } - -mod programs { - // use super::*; - - // use crate::{list, TulispContext, TulispObject, TulispValue}; - - // #[allow(dead_code)] - // pub(super) fn print_range(from: i64, to: i64) -> Vec { - // let i = TulispObject::symbol("i".to_string(), false); - // let n = TulispObject::symbol("n".to_string(), false); - // vec![ - // // print numbers 1 to 10 - // Instruction::Push(from.into()), // 0 - // Instruction::StorePop(i.clone()), // 1 - // Instruction::Push(to.into()), // 2 - // Instruction::StorePop(n.clone()), // 3 - // // loop: - // Instruction::Load(i.clone()), // 4 - // Instruction::PrintPop, // 5 - // Instruction::Push(1.into()), // 6 - // Instruction::Load(i.clone()), // 7 - // if from < to { - // Instruction::Add - // } else { - // Instruction::Sub - // }, // 8 - // Instruction::StorePop(i.clone()), // 9 - // Instruction::Load(i.clone()), // 10 - // Instruction::Load(n.clone()), // 11 - // if from < to { - // Instruction::JumpIfGtEq(Pos::Abs(4)) - // } else { - // Instruction::JumpIfLtEq(Pos::Abs(4)) - // }, // 12 - // ] - // } - - // #[allow(dead_code)] - // pub(super) fn fib(num: i64) -> Vec { - // let n = TulispObject::symbol("n".to_string(), false); - // let fib = TulispObject::symbol("fib".to_string(), false); - // let main = TulispObject::symbol("main".to_string(), false); - // vec![ - // Instruction::Jump(Pos::Label(main.clone())), // 0 - // Instruction::Label(fib.clone()), // 1 - // Instruction::Push(2.into()), // 2 - // Instruction::Load(n.clone()), // 3 - // Instruction::JumpIfGt(Pos::Rel(2)), // 4 - // Instruction::Push(1.into()), // 5 - // Instruction::Ret, // 6 - // Instruction::Push(1.into()), // 7 - // Instruction::Load(n.clone()), // 8 - // Instruction::Sub, // 9 - // Instruction::Call { - // pos: Pos::Label(fib.clone()), - // params: vec![n.clone()], - // }, // 10 - // Instruction::Push(2.into()), // 11 - // Instruction::Load(n.clone()), // 12 - // Instruction::Sub, // 13 - // Instruction::Call { - // pos: Pos::Label(fib.clone()), - // params: vec![n.clone()], - // }, // 14 - // Instruction::Add, // 15 - // Instruction::Ret, // 16 - // Instruction::Label(main.clone()), // 17 - // Instruction::Push(num.into()), // 18 - // Instruction::Call { - // pos: Pos::Label(fib.clone()), - // params: vec![n.clone()], - // }, // 19 - // Instruction::PrintPop, // 20 - // ] - // } - - // #[allow(dead_code)] - // pub(super) fn rustcall_dotimes(ctx: &mut TulispContext, num: i64) -> Vec { - // let var = TulispObject::symbol("var".to_string(), false); - // let args = list!( - // list!(var.clone(), num.into()).unwrap(), - // list!(ctx.intern("print"), var).unwrap() - // ) - // .unwrap(); - // vec![ - // Instruction::Push(args), // 0 - // Instruction::RustCall { - // func: { - // let obj = ctx.intern("dotimes").get().unwrap(); - // if let TulispValue::Func(ref func) = obj.clone_inner() { - // func.clone() - // } else { - // panic!("Expected function") - // } - // }, - // }, - // ] - // } -} From 0a6f39701e4e5645447e440a61e31e4acb419e44 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 20:59:39 +0100 Subject: [PATCH 078/142] Move interpreter into `bytecode` module --- src/byte_compile/byte_compile.rs | 2 +- .../forms/arithmetic_operations.rs | 2 +- src/byte_compile/forms/common.rs | 4 +- .../forms/comparison_of_numbers.rs | 2 +- src/byte_compile/forms/conditionals.rs | 2 +- src/byte_compile/forms/list_elements.rs | 2 +- src/byte_compile/forms/mod.rs | 2 +- src/byte_compile/forms/other_functions.rs | 2 +- src/{vm.rs => bytecode/interpreter.rs} | 207 ++--------------- src/bytecode/mod.rs | 6 + src/bytecode/stack_value.rs | 212 ++++++++++++++++++ src/context.rs | 14 +- src/lib.rs | 2 +- 13 files changed, 255 insertions(+), 204 deletions(-) rename src/{vm.rs => bytecode/interpreter.rs} (74%) create mode 100644 src/bytecode/mod.rs create mode 100644 src/bytecode/stack_value.rs diff --git a/src/byte_compile/byte_compile.rs b/src/byte_compile/byte_compile.rs index fa8b60c6..fa12193a 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/byte_compile/byte_compile.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::{ - vm::{Bytecode, Instruction, VMBindings}, + bytecode::{Bytecode, Instruction, VMBindings}, Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/byte_compile/forms/arithmetic_operations.rs index 61f3623e..ab3b84df 100644 --- a/src/byte_compile/forms/arithmetic_operations.rs +++ b/src/byte_compile/forms/arithmetic_operations.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - vm::{Instruction, InstructionBinaryOp}, + bytecode::{Instruction, InstructionBinaryOp}, Error, ErrorKind, TulispObject, }; diff --git a/src/byte_compile/forms/common.rs b/src/byte_compile/forms/common.rs index e6cf6aba..be14f948 100644 --- a/src/byte_compile/forms/common.rs +++ b/src/byte_compile/forms/common.rs @@ -1,4 +1,6 @@ -use crate::{byte_compile::Compiler, vm::Instruction, Error, ErrorKind, TulispObject, TulispValue}; +use crate::{ + byte_compile::Compiler, bytecode::Instruction, Error, ErrorKind, TulispObject, TulispValue, +}; impl Compiler<'_> { pub(crate) fn compile_1_arg_call( diff --git a/src/byte_compile/forms/comparison_of_numbers.rs b/src/byte_compile/forms/comparison_of_numbers.rs index ccb440c5..7b512766 100644 --- a/src/byte_compile/forms/comparison_of_numbers.rs +++ b/src/byte_compile/forms/comparison_of_numbers.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - vm::{Instruction, Pos}, + bytecode::{Instruction, Pos}, Error, TulispObject, }; diff --git a/src/byte_compile/forms/conditionals.rs b/src/byte_compile/forms/conditionals.rs index 5b2ccb0a..04e387c5 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/byte_compile/forms/conditionals.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - vm::{Instruction, Pos}, + bytecode::{Instruction, Pos}, Error, TulispObject, }; diff --git a/src/byte_compile/forms/list_elements.rs b/src/byte_compile/forms/list_elements.rs index e96e6dd3..84e0912f 100644 --- a/src/byte_compile/forms/list_elements.rs +++ b/src/byte_compile/forms/list_elements.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - vm::{Instruction, InstructionCxr}, + bytecode::{Instruction, InstructionCxr}, Error, ErrorKind, TulispObject, }; diff --git a/src/byte_compile/forms/mod.rs b/src/byte_compile/forms/mod.rs index 62e80592..ba5adce3 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/byte_compile/forms/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{vm::Instruction, Error, ErrorKind, TulispContext, TulispObject}; +use crate::{bytecode::Instruction, Error, ErrorKind, TulispContext, TulispObject}; use super::{byte_compile::CompileResult, Compiler}; diff --git a/src/byte_compile/forms/other_functions.rs b/src/byte_compile/forms/other_functions.rs index db5a49fe..9c743442 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/byte_compile/forms/other_functions.rs @@ -1,8 +1,8 @@ use crate::{ byte_compile::Compiler, + bytecode::{Instruction, Pos}, destruct_bind, parse::mark_tail_calls, - vm::{Instruction, Pos}, Error, ErrorKind, TulispObject, }; diff --git a/src/vm.rs b/src/bytecode/interpreter.rs similarity index 74% rename from src/vm.rs rename to src/bytecode/interpreter.rs index cab2ca5a..4e056639 100644 --- a/src/vm.rs +++ b/src/bytecode/interpreter.rs @@ -1,74 +1,8 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; -use crate::{value::TulispFn, Error, ErrorKind, TulispContext, TulispObject, TulispValue}; - -macro_rules! compare_ops { - ($name:ident, $oper:expr) => { - fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { - match selfobj { - VMStackValue::Float(f1) => match other { - VMStackValue::Float(f2) => Ok($oper(f1, f2)), - VMStackValue::Int(i2) => Ok($oper(f1, &(*i2 as f64))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - VMStackValue::Int(i1) => match other { - VMStackValue::Float(f2) => Ok($oper(&(*i1 as f64), f2)), - VMStackValue::Int(i2) => Ok($oper(i1, i2)), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", selfobj), - )), - } - } - }; -} - -compare_ops!(cmp_lt, std::cmp::PartialOrd::lt); -compare_ops!(cmp_le, std::cmp::PartialOrd::le); -compare_ops!(cmp_gt, std::cmp::PartialOrd::gt); -compare_ops!(cmp_ge, std::cmp::PartialOrd::ge); - -macro_rules! binary_ops { - ($name:ident, $oper:expr) => { - fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { - match selfobj { - VMStackValue::Float(f1) => match other { - VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(f1, f2))), - VMStackValue::Int(i2) => Ok(VMStackValue::Float($oper(f1, &(*i2 as f64)))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - VMStackValue::Int(i1) => match other { - VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(&(*i1 as f64), f2))), - VMStackValue::Int(i2) => Ok(VMStackValue::Int($oper(i1, i2))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", selfobj), - )), - } - } - }; -} - -binary_ops!(arith_add, std::ops::Add::add); -binary_ops!(arith_sub, std::ops::Sub::sub); -binary_ops!(arith_mul, std::ops::Mul::mul); -binary_ops!(arith_div, std::ops::Div::div); +use crate::{ + bytecode::VMStackValue, value::TulispFn, Error, ErrorKind, TulispContext, TulispObject, +}; #[derive(Clone)] pub enum Pos { @@ -322,117 +256,6 @@ impl Bytecode { } } -#[derive(Clone)] -pub(crate) enum VMStackValue { - TulispObject(TulispObject), - Bool(bool), - Float(f64), - Int(i64), -} - -macro_rules! impl_from_for_stack_value { - ($name:ident, $type:ty) => { - impl From<$type> for VMStackValue { - fn from(val: $type) -> Self { - VMStackValue::$name(val) - } - } - }; -} - -impl_from_for_stack_value!(Float, f64); -impl_from_for_stack_value!(Int, i64); -impl_from_for_stack_value!(Bool, bool); - -impl From for VMStackValue { - fn from(val: TulispObject) -> Self { - match &*val.inner_ref() { - TulispValue::Int { value } => return VMStackValue::Int(*value), - TulispValue::Float { value } => return VMStackValue::Float(*value), - TulispValue::T => return VMStackValue::Bool(true), - TulispValue::Nil => return VMStackValue::Bool(false), - _ => {} - } - VMStackValue::TulispObject(val) - } -} - -impl Into for VMStackValue { - fn into(self) -> TulispObject { - match self { - VMStackValue::TulispObject(obj) => obj, - VMStackValue::Bool(b) => b.into(), - VMStackValue::Float(fl) => fl.into(), - VMStackValue::Int(i) => i.into(), - } - } -} - -impl VMStackValue { - pub fn null(&self) -> bool { - match self { - VMStackValue::TulispObject(obj) => obj.null(), - VMStackValue::Bool(b) => !b, - VMStackValue::Float(_) | VMStackValue::Int(_) => false, - } - } - - pub fn equal(&self, other: &VMStackValue) -> bool { - match self { - VMStackValue::TulispObject(obj1) => match other { - VMStackValue::TulispObject(obj2) => obj1.equal(obj2), - VMStackValue::Bool(b2) => obj1.equal(&(*b2).into()), - VMStackValue::Float(fl2) => obj1.equal(&(*fl2).into()), - VMStackValue::Int(i2) => obj1.equal(&(*i2).into()), - }, - VMStackValue::Bool(b) => match other { - VMStackValue::Bool(b2) => b == b2, - _ => false, - }, - VMStackValue::Float(fl) => match other { - VMStackValue::Float(fl2) => fl == fl2, - _ => false, - }, - VMStackValue::Int(i) => match other { - VMStackValue::Int(i2) => i == i2, - _ => false, - }, - } - } - - pub fn eq(&self, other: &VMStackValue) -> bool { - match self { - VMStackValue::TulispObject(obj1) => match other { - VMStackValue::TulispObject(obj2) => obj1.eq(obj2), - _ => false, - }, - VMStackValue::Bool(b) => match other { - VMStackValue::Bool(b2) => b == b2, - _ => false, - }, - VMStackValue::Float(fl) => match other { - VMStackValue::Float(fl2) => fl == fl2, - _ => false, - }, - VMStackValue::Int(i) => match other { - VMStackValue::Int(i2) => i == i2, - _ => false, - }, - } - } -} - -impl Display for VMStackValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VMStackValue::TulispObject(obj) => write!(f, "{}", obj), - VMStackValue::Bool(b) => write!(f, "{}", b), - VMStackValue::Float(fl) => write!(f, "{}", fl), - VMStackValue::Int(i) => write!(f, "{}", i), - } - } -} - pub struct Machine { stack: Vec, bytecode: Bytecode, @@ -513,10 +336,10 @@ impl Machine { unreachable!() }; let vv = match op { - InstructionBinaryOp::Add => arith_add(&a, &b)?, - InstructionBinaryOp::Sub => arith_sub(&a, &b)?, - InstructionBinaryOp::Mul => arith_mul(&a, &b)?, - InstructionBinaryOp::Div => arith_div(&a, &b)?, + InstructionBinaryOp::Add => a.add(b)?, + InstructionBinaryOp::Sub => a.sub(b)?, + InstructionBinaryOp::Mul => a.mul(b)?, + InstructionBinaryOp::Div => a.div(b)?, }; self.stack.truncate(self.stack.len() - 2); self.stack.push(vv); @@ -564,7 +387,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = cmp_lt(a, b)?; + let cmp = a.lt(b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -576,7 +399,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = cmp_le(a, b)?; + let cmp = a.le(b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -588,7 +411,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = cmp_gt(a, b)?; + let cmp = a.gt(b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -600,7 +423,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = cmp_ge(a, b)?; + let cmp = a.ge(b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -624,22 +447,22 @@ impl Machine { Instruction::Lt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(cmp_lt(&a, &b)?.into()); + self.stack.push(a.lt(&b)?.into()); } Instruction::LtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(cmp_le(&a, &b)?.into()); + self.stack.push(a.le(&b)?.into()); } Instruction::Gt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(cmp_gt(&a, &b)?.into()); + self.stack.push(a.gt(&b)?.into()); } Instruction::GtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(cmp_ge(&a, &b)?.into()); + self.stack.push(a.ge(&b)?.into()); } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs new file mode 100644 index 00000000..6b6dba5e --- /dev/null +++ b/src/bytecode/mod.rs @@ -0,0 +1,6 @@ +mod interpreter; +pub(crate) use interpreter::{ + Bytecode, Instruction, InstructionBinaryOp, InstructionCxr, Machine, Pos, VMBindings, +}; +mod stack_value; +pub(crate) use stack_value::VMStackValue; diff --git a/src/bytecode/stack_value.rs b/src/bytecode/stack_value.rs new file mode 100644 index 00000000..fa4eb52f --- /dev/null +++ b/src/bytecode/stack_value.rs @@ -0,0 +1,212 @@ +use crate::{Error, ErrorKind, TulispObject, TulispValue}; + +#[derive(Clone)] +pub(crate) enum VMStackValue { + TulispObject(TulispObject), + Bool(bool), + Float(f64), + Int(i64), +} + +macro_rules! impl_from_for_stack_value { + ($name:ident, $type:ty) => { + impl From<$type> for VMStackValue { + fn from(val: $type) -> Self { + VMStackValue::$name(val) + } + } + }; +} + +impl_from_for_stack_value!(Float, f64); +impl_from_for_stack_value!(Int, i64); +impl_from_for_stack_value!(Bool, bool); + +impl From for VMStackValue { + fn from(val: TulispObject) -> Self { + match &*val.inner_ref() { + TulispValue::Int { value } => return VMStackValue::Int(*value), + TulispValue::Float { value } => return VMStackValue::Float(*value), + TulispValue::T => return VMStackValue::Bool(true), + TulispValue::Nil => return VMStackValue::Bool(false), + _ => {} + } + VMStackValue::TulispObject(val) + } +} + +impl Into for VMStackValue { + fn into(self) -> TulispObject { + match self { + VMStackValue::TulispObject(obj) => obj, + VMStackValue::Bool(b) => b.into(), + VMStackValue::Float(fl) => fl.into(), + VMStackValue::Int(i) => i.into(), + } + } +} + +impl VMStackValue { + pub fn null(&self) -> bool { + match self { + VMStackValue::TulispObject(obj) => obj.null(), + VMStackValue::Bool(b) => !b, + VMStackValue::Float(_) | VMStackValue::Int(_) => false, + } + } + + pub fn add(&self, other: &VMStackValue) -> Result { + add(self, other) + } + + pub fn sub(&self, other: &VMStackValue) -> Result { + sub(self, other) + } + + pub fn mul(&self, other: &VMStackValue) -> Result { + mul(self, other) + } + + pub fn div(&self, other: &VMStackValue) -> Result { + div(self, other) + } + + pub fn lt(&self, other: &VMStackValue) -> Result { + lt(self, other) + } + + pub fn le(&self, other: &VMStackValue) -> Result { + le(self, other) + } + + pub fn gt(&self, other: &VMStackValue) -> Result { + gt(self, other) + } + + pub fn ge(&self, other: &VMStackValue) -> Result { + ge(self, other) + } + + pub fn equal(&self, other: &VMStackValue) -> bool { + match self { + VMStackValue::TulispObject(obj1) => match other { + VMStackValue::TulispObject(obj2) => obj1.equal(obj2), + VMStackValue::Bool(b2) => obj1.equal(&(*b2).into()), + VMStackValue::Float(fl2) => obj1.equal(&(*fl2).into()), + VMStackValue::Int(i2) => obj1.equal(&(*i2).into()), + }, + VMStackValue::Bool(b) => match other { + VMStackValue::Bool(b2) => b == b2, + _ => false, + }, + VMStackValue::Float(fl) => match other { + VMStackValue::Float(fl2) => fl == fl2, + _ => false, + }, + VMStackValue::Int(i) => match other { + VMStackValue::Int(i2) => i == i2, + _ => false, + }, + } + } + + pub fn eq(&self, other: &VMStackValue) -> bool { + match self { + VMStackValue::TulispObject(obj1) => match other { + VMStackValue::TulispObject(obj2) => obj1.eq(obj2), + _ => false, + }, + VMStackValue::Bool(b) => match other { + VMStackValue::Bool(b2) => b == b2, + _ => false, + }, + VMStackValue::Float(fl) => match other { + VMStackValue::Float(fl2) => fl == fl2, + _ => false, + }, + VMStackValue::Int(i) => match other { + VMStackValue::Int(i2) => i == i2, + _ => false, + }, + } + } +} + +impl std::fmt::Display for VMStackValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VMStackValue::TulispObject(obj) => write!(f, "{}", obj), + VMStackValue::Bool(b) => write!(f, "{}", b), + VMStackValue::Float(fl) => write!(f, "{}", fl), + VMStackValue::Int(i) => write!(f, "{}", i), + } + } +} + +macro_rules! compare_ops { + ($name:ident, $oper:expr) => { + fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { + match selfobj { + VMStackValue::Float(f1) => match other { + VMStackValue::Float(f2) => Ok($oper(f1, f2)), + VMStackValue::Int(i2) => Ok($oper(f1, &(*i2 as f64))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + VMStackValue::Int(i1) => match other { + VMStackValue::Float(f2) => Ok($oper(&(*i1 as f64), f2)), + VMStackValue::Int(i2) => Ok($oper(i1, i2)), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", selfobj), + )), + } + } + }; +} + +compare_ops!(lt, std::cmp::PartialOrd::lt); +compare_ops!(le, std::cmp::PartialOrd::le); +compare_ops!(gt, std::cmp::PartialOrd::gt); +compare_ops!(ge, std::cmp::PartialOrd::ge); + +macro_rules! binary_ops { + ($name:ident, $oper:expr) => { + fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { + match selfobj { + VMStackValue::Float(f1) => match other { + VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(f1, f2))), + VMStackValue::Int(i2) => Ok(VMStackValue::Float($oper(f1, &(*i2 as f64)))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + VMStackValue::Int(i1) => match other { + VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(&(*i1 as f64), f2))), + VMStackValue::Int(i2) => Ok(VMStackValue::Int($oper(i1, i2))), + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", other), + )), + }, + _ => Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected number, found: {}", selfobj), + )), + } + } + }; +} + +binary_ops!(add, std::ops::Add::add); +binary_ops!(sub, std::ops::Sub::sub); +binary_ops!(mul, std::ops::Mul::mul); +binary_ops!(div, std::ops::Div::div); diff --git a/src/context.rs b/src/context.rs index ef0cb697..7f47120f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,11 +3,12 @@ use std::{collections::HashMap, fs}; use crate::{ builtin, byte_compile::Compiler, + bytecode, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, parse::parse, - vm, TulispObject, + TulispObject, }; #[derive(Debug, Default, Clone)] @@ -197,7 +198,7 @@ impl TulispContext { pub fn vm_eval_string(&mut self, string: &str) -> Result { let vv = parse(self, 0, string)?; let bytecode = Compiler::new(self).compile(&vv)?; - vm::Machine::new(bytecode).run(self) + bytecode::Machine::new(bytecode).run(self) } pub fn vm_eval_file(&mut self, filename: &str) -> Result { @@ -210,10 +211,17 @@ impl TulispContext { self.filenames.push(filename.to_owned()); let string: &str = &contents; + let start = std::time::Instant::now(); let vv = parse(self, self.filenames.len() - 1, string)?; + println!("Parsing took: {:?}", start.elapsed()); + let start = std::time::Instant::now(); let bytecode = Compiler::new(self).compile(&vv)?; + println!("Compiling took: {:?}", start.elapsed()); bytecode.print(); - vm::Machine::new(bytecode).run(self) + let start = std::time::Instant::now(); + let ret = bytecode::Machine::new(bytecode).run(self); + println!("Running took: {:?}", start.elapsed()); + ret } pub(crate) fn get_filename(&self, file_id: usize) -> String { diff --git a/src/lib.rs b/src/lib.rs index b61c43b2..45e0c90e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,10 +82,10 @@ A list of currently available builtin functions can be found [here](builtin). */ mod byte_compile; +pub(crate) mod bytecode; mod eval; mod macros; mod parse; -pub mod vm; pub use tulisp_proc_macros::{tulisp_add_func, tulisp_add_macro, tulisp_fn, tulisp_fn_no_eval}; From 68cde747c922be0137dca01e0deeccd9e578629f Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 21:15:26 +0100 Subject: [PATCH 079/142] Move instructions out of the interpreter --- .../forms/arithmetic_operations.rs | 14 +- src/byte_compile/forms/list_elements.rs | 62 ++--- src/bytecode/instruction.rs | 185 +++++++++++++ src/bytecode/interpreter.rs | 257 +++--------------- src/bytecode/mod.rs | 8 +- 5 files changed, 266 insertions(+), 260 deletions(-) create mode 100644 src/bytecode/instruction.rs diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/byte_compile/forms/arithmetic_operations.rs index ab3b84df..1df990a0 100644 --- a/src/byte_compile/forms/arithmetic_operations.rs +++ b/src/byte_compile/forms/arithmetic_operations.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - bytecode::{Instruction, InstructionBinaryOp}, + bytecode::{instruction::BinaryOp, Instruction}, Error, ErrorKind, TulispObject, }; @@ -24,7 +24,7 @@ pub(super) fn compile_fn_plus( result.append(&mut compiler.compile_expr(arg)?); } for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(InstructionBinaryOp::Add)); + result.push(Instruction::BinaryOp(BinaryOp::Add)); } Ok(result) } @@ -50,11 +50,11 @@ pub(super) fn compile_fn_minus( } if args.len() == 1 { result.push(Instruction::Push((-1).into())); - result.push(Instruction::BinaryOp(InstructionBinaryOp::Mul)); + result.push(Instruction::BinaryOp(BinaryOp::Mul)); return Ok(result); } for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(InstructionBinaryOp::Sub)); + result.push(Instruction::BinaryOp(BinaryOp::Sub)); } Ok(result) } @@ -79,7 +79,7 @@ pub(super) fn compile_fn_mul( result.append(&mut compiler.compile_expr(arg)?); } for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(InstructionBinaryOp::Mul)); + result.push(Instruction::BinaryOp(BinaryOp::Mul)); } Ok(result) } @@ -105,11 +105,11 @@ pub(super) fn compile_fn_div( } if args.len() == 1 { result.push(Instruction::Push(1.into())); - result.push(Instruction::BinaryOp(InstructionBinaryOp::Div)); + result.push(Instruction::BinaryOp(BinaryOp::Div)); return Ok(result); } for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(InstructionBinaryOp::Div)); + result.push(Instruction::BinaryOp(BinaryOp::Div)); } Ok(result) } diff --git a/src/byte_compile/forms/list_elements.rs b/src/byte_compile/forms/list_elements.rs index 84e0912f..2e70a5d1 100644 --- a/src/byte_compile/forms/list_elements.rs +++ b/src/byte_compile/forms/list_elements.rs @@ -1,6 +1,6 @@ use crate::{ byte_compile::Compiler, - bytecode::{Instruction, InstructionCxr}, + bytecode::{instruction::Cxr, Instruction}, Error, ErrorKind, TulispObject, }; @@ -15,36 +15,36 @@ pub(super) fn compile_fn_cxr( if compiler.keep_result { let name = name.to_string(); match name.as_str() { - "car" => result.push(Instruction::Cxr(InstructionCxr::Car)), - "cdr" => result.push(Instruction::Cxr(InstructionCxr::Cdr)), - "caar" => result.push(Instruction::Cxr(InstructionCxr::Caar)), - "cadr" => result.push(Instruction::Cxr(InstructionCxr::Cadr)), - "cdar" => result.push(Instruction::Cxr(InstructionCxr::Cdar)), - "cddr" => result.push(Instruction::Cxr(InstructionCxr::Cddr)), - "caaar" => result.push(Instruction::Cxr(InstructionCxr::Caaar)), - "caadr" => result.push(Instruction::Cxr(InstructionCxr::Caadr)), - "cadar" => result.push(Instruction::Cxr(InstructionCxr::Cadar)), - "caddr" => result.push(Instruction::Cxr(InstructionCxr::Caddr)), - "cdaar" => result.push(Instruction::Cxr(InstructionCxr::Cdaar)), - "cdadr" => result.push(Instruction::Cxr(InstructionCxr::Cdadr)), - "cddar" => result.push(Instruction::Cxr(InstructionCxr::Cddar)), - "cdddr" => result.push(Instruction::Cxr(InstructionCxr::Cdddr)), - "caaaar" => result.push(Instruction::Cxr(InstructionCxr::Caaaar)), - "caaadr" => result.push(Instruction::Cxr(InstructionCxr::Caaadr)), - "caadar" => result.push(Instruction::Cxr(InstructionCxr::Caadar)), - "caaddr" => result.push(Instruction::Cxr(InstructionCxr::Caaddr)), - "cadaar" => result.push(Instruction::Cxr(InstructionCxr::Cadaar)), - "cadadr" => result.push(Instruction::Cxr(InstructionCxr::Cadadr)), - "caddar" => result.push(Instruction::Cxr(InstructionCxr::Caddar)), - "cadddr" => result.push(Instruction::Cxr(InstructionCxr::Cadddr)), - "cdaaar" => result.push(Instruction::Cxr(InstructionCxr::Cdaaar)), - "cdaadr" => result.push(Instruction::Cxr(InstructionCxr::Cdaadr)), - "cdadar" => result.push(Instruction::Cxr(InstructionCxr::Cdadar)), - "cdaddr" => result.push(Instruction::Cxr(InstructionCxr::Cdaddr)), - "cddaar" => result.push(Instruction::Cxr(InstructionCxr::Cddaar)), - "cddadr" => result.push(Instruction::Cxr(InstructionCxr::Cddadr)), - "cdddar" => result.push(Instruction::Cxr(InstructionCxr::Cdddar)), - "cddddr" => result.push(Instruction::Cxr(InstructionCxr::Cddddr)), + "car" => result.push(Instruction::Cxr(Cxr::Car)), + "cdr" => result.push(Instruction::Cxr(Cxr::Cdr)), + "caar" => result.push(Instruction::Cxr(Cxr::Caar)), + "cadr" => result.push(Instruction::Cxr(Cxr::Cadr)), + "cdar" => result.push(Instruction::Cxr(Cxr::Cdar)), + "cddr" => result.push(Instruction::Cxr(Cxr::Cddr)), + "caaar" => result.push(Instruction::Cxr(Cxr::Caaar)), + "caadr" => result.push(Instruction::Cxr(Cxr::Caadr)), + "cadar" => result.push(Instruction::Cxr(Cxr::Cadar)), + "caddr" => result.push(Instruction::Cxr(Cxr::Caddr)), + "cdaar" => result.push(Instruction::Cxr(Cxr::Cdaar)), + "cdadr" => result.push(Instruction::Cxr(Cxr::Cdadr)), + "cddar" => result.push(Instruction::Cxr(Cxr::Cddar)), + "cdddr" => result.push(Instruction::Cxr(Cxr::Cdddr)), + "caaaar" => result.push(Instruction::Cxr(Cxr::Caaaar)), + "caaadr" => result.push(Instruction::Cxr(Cxr::Caaadr)), + "caadar" => result.push(Instruction::Cxr(Cxr::Caadar)), + "caaddr" => result.push(Instruction::Cxr(Cxr::Caaddr)), + "cadaar" => result.push(Instruction::Cxr(Cxr::Cadaar)), + "cadadr" => result.push(Instruction::Cxr(Cxr::Cadadr)), + "caddar" => result.push(Instruction::Cxr(Cxr::Caddar)), + "cadddr" => result.push(Instruction::Cxr(Cxr::Cadddr)), + "cdaaar" => result.push(Instruction::Cxr(Cxr::Cdaaar)), + "cdaadr" => result.push(Instruction::Cxr(Cxr::Cdaadr)), + "cdadar" => result.push(Instruction::Cxr(Cxr::Cdadar)), + "cdaddr" => result.push(Instruction::Cxr(Cxr::Cdaddr)), + "cddaar" => result.push(Instruction::Cxr(Cxr::Cddaar)), + "cddadr" => result.push(Instruction::Cxr(Cxr::Cddadr)), + "cdddar" => result.push(Instruction::Cxr(Cxr::Cdddar)), + "cddddr" => result.push(Instruction::Cxr(Cxr::Cddddr)), _ => return Err(Error::new(ErrorKind::Undefined, "unknown cxr".to_string())), } } diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs new file mode 100644 index 00000000..ffe224e1 --- /dev/null +++ b/src/bytecode/instruction.rs @@ -0,0 +1,185 @@ +use std::rc::Rc; + +use crate::{bytecode::VMStackValue, value::TulispFn, TulispObject}; + +#[derive(Clone)] +pub(crate) enum Pos { + Abs(usize), + Rel(isize), + Label(TulispObject), +} + +impl std::fmt::Display for Pos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Pos::Abs(p) => write!(f, "{}", p), + Pos::Rel(p) => write!(f, ". {}", p), + Pos::Label(p) => write!(f, "{}", p), + } + } +} + +#[derive(Clone, Copy)] +pub(crate) enum Cxr { + Car, + Cdr, + Caar, + Cadr, + Cdar, + Cddr, + Caaar, + Caadr, + Cadar, + Caddr, + Cdaar, + Cdadr, + Cddar, + Cdddr, + Caaaar, + Caaadr, + Caadar, + Caaddr, + Cadaar, + Cadadr, + Caddar, + Cadddr, + Cdaaar, + Cdaadr, + Cdadar, + Cdaddr, + Cddaar, + Cddadr, + Cdddar, + Cddddr, +} + +#[derive(Clone, Copy)] +pub(crate) enum BinaryOp { + Add, + Sub, + Mul, + Div, +} + +/// A single instruction in the VM. +#[derive(Clone)] +pub(crate) enum Instruction { + // stack + Push(VMStackValue), + Pop, + // variables + StorePop(usize), + Store(usize), + Load(usize), + BeginScope(usize), + EndScope(usize), + // arithmetic + BinaryOp(BinaryOp), + // io + PrintPop, + Print, + // comparison + Equal, + Eq, + Lt, + LtEq, + Gt, + GtEq, + // control flow + JumpIfNil(Pos), + JumpIfNilElsePop(Pos), + JumpIfNeq(Pos), + JumpIfLt(Pos), + JumpIfLtEq(Pos), + JumpIfGt(Pos), + JumpIfGtEq(Pos), + Jump(Pos), + // functions + Label(TulispObject), + #[allow(dead_code)] + RustCall { + func: Rc, + }, + Call(Pos), + Ret, + // lists + Cons, + List(usize), + Append(usize), + Cxr(Cxr), +} + +impl std::fmt::Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Instruction::Push(obj) => write!(f, " push {}", obj), + Instruction::Pop => write!(f, " pop"), + Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), + Instruction::Store(obj) => write!(f, " store {}", obj), + Instruction::Load(obj) => write!(f, " load {}", obj), + Instruction::BeginScope(obj) => write!(f, " begin_scope {}", obj), + Instruction::EndScope(obj) => write!(f, " end_scope {}", obj), + Instruction::BinaryOp(op) => match op { + BinaryOp::Add => write!(f, " add"), + BinaryOp::Sub => write!(f, " sub"), + BinaryOp::Mul => write!(f, " mul"), + BinaryOp::Div => write!(f, " div"), + }, + Instruction::PrintPop => write!(f, " print_pop"), + Instruction::Print => write!(f, " print"), + Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), + Instruction::JumpIfNilElsePop(pos) => write!(f, " jnil_else_pop {}", pos), + Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), + Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), + Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), + Instruction::JumpIfGt(pos) => write!(f, " jgt {}", pos), + Instruction::JumpIfGtEq(pos) => write!(f, " jge {}", pos), + Instruction::Equal => write!(f, " equal"), + Instruction::Eq => write!(f, " ceq"), + Instruction::Lt => write!(f, " clt"), + Instruction::LtEq => write!(f, " cle"), + Instruction::Gt => write!(f, " cgt"), + Instruction::GtEq => write!(f, " cge"), + Instruction::Jump(pos) => write!(f, " jmp {}", pos), + Instruction::Call(pos) => write!(f, " call {}", pos), + Instruction::Ret => write!(f, " ret"), + Instruction::RustCall { .. } => write!(f, " rustcall"), + Instruction::Label(name) => write!(f, "{}:", name), + Instruction::Cons => write!(f, " cons"), + Instruction::List(len) => write!(f, " list {}", len), + Instruction::Append(len) => write!(f, " append {}", len), + Instruction::Cxr(cxr) => match cxr { + Cxr::Car => write!(f, " car"), + Cxr::Cdr => write!(f, " cdr"), + Cxr::Caar => write!(f, " caar"), + Cxr::Cadr => write!(f, " cadr"), + Cxr::Cdar => write!(f, " cdar"), + Cxr::Cddr => write!(f, " cddr"), + Cxr::Caaar => write!(f, " caaar"), + Cxr::Caadr => write!(f, " caadr"), + Cxr::Cadar => write!(f, " cadar"), + Cxr::Caddr => write!(f, " caddr"), + Cxr::Cdaar => write!(f, " cdaar"), + Cxr::Cdadr => write!(f, " cdadr"), + Cxr::Cddar => write!(f, " cddar"), + Cxr::Cdddr => write!(f, " cdddr"), + Cxr::Caaaar => write!(f, " caaaar"), + Cxr::Caaadr => write!(f, " caaadr"), + Cxr::Caadar => write!(f, " caadar"), + Cxr::Caaddr => write!(f, " caaddr"), + Cxr::Cadaar => write!(f, " cadaar"), + Cxr::Cadadr => write!(f, " cadadr"), + Cxr::Caddar => write!(f, " caddar"), + Cxr::Cadddr => write!(f, " cadddr"), + Cxr::Cdaaar => write!(f, " cdaaar"), + Cxr::Cdaadr => write!(f, " cdaadr"), + Cxr::Cdadar => write!(f, " cdadar"), + Cxr::Cdaddr => write!(f, " cdaddr"), + Cxr::Cddaar => write!(f, " cddaar"), + Cxr::Cddadr => write!(f, " cddadr"), + Cxr::Cdddar => write!(f, " cdddar"), + Cxr::Cddddr => write!(f, " cddddr"), + }, + } + } +} diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 4e056639..1090d78b 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,190 +1,9 @@ -use std::{collections::HashMap, fmt::Display, rc::Rc}; - +use super::{instruction, Instruction}; use crate::{ - bytecode::VMStackValue, value::TulispFn, Error, ErrorKind, TulispContext, TulispObject, + bytecode::{Pos, VMStackValue}, + Error, ErrorKind, TulispContext, TulispObject, }; - -#[derive(Clone)] -pub enum Pos { - Abs(usize), - Rel(isize), - Label(TulispObject), -} - -impl Display for Pos { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Pos::Abs(p) => write!(f, "{}", p), - Pos::Rel(p) => write!(f, ". {}", p), - Pos::Label(p) => write!(f, "{}", p), - } - } -} - -#[derive(Clone, Copy)] -pub(crate) enum InstructionCxr { - Car, - Cdr, - Caar, - Cadr, - Cdar, - Cddr, - Caaar, - Caadr, - Cadar, - Caddr, - Cdaar, - Cdadr, - Cddar, - Cdddr, - Caaaar, - Caaadr, - Caadar, - Caaddr, - Cadaar, - Cadadr, - Caddar, - Cadddr, - Cdaaar, - Cdaadr, - Cdadar, - Cdaddr, - Cddaar, - Cddadr, - Cdddar, - Cddddr, -} - -#[derive(Clone, Copy)] -pub(crate) enum InstructionBinaryOp { - Add, - Sub, - Mul, - Div, -} - -/// A single instruction in the VM. -#[derive(Clone)] -pub(crate) enum Instruction { - // stack - Push(VMStackValue), - Pop, - // variables - StorePop(usize), - Store(usize), - Load(usize), - BeginScope(usize), - EndScope(usize), - // arithmetic - BinaryOp(InstructionBinaryOp), - // io - PrintPop, - Print, - // comparison - Equal, - Eq, - Lt, - LtEq, - Gt, - GtEq, - // control flow - JumpIfNil(Pos), - JumpIfNilElsePop(Pos), - JumpIfNeq(Pos), - JumpIfLt(Pos), - JumpIfLtEq(Pos), - JumpIfGt(Pos), - JumpIfGtEq(Pos), - Jump(Pos), - // functions - Label(TulispObject), - #[allow(dead_code)] - RustCall { - func: Rc, - }, - Call(Pos), - Ret, - // lists - Cons, - List(usize), - Append(usize), - Cxr(InstructionCxr), -} - -impl Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Instruction::Push(obj) => write!(f, " push {}", obj), - Instruction::Pop => write!(f, " pop"), - Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), - Instruction::Store(obj) => write!(f, " store {}", obj), - Instruction::Load(obj) => write!(f, " load {}", obj), - Instruction::BeginScope(obj) => write!(f, " begin_scope {}", obj), - Instruction::EndScope(obj) => write!(f, " end_scope {}", obj), - Instruction::BinaryOp(op) => match op { - InstructionBinaryOp::Add => write!(f, " add"), - InstructionBinaryOp::Sub => write!(f, " sub"), - InstructionBinaryOp::Mul => write!(f, " mul"), - InstructionBinaryOp::Div => write!(f, " div"), - }, - Instruction::PrintPop => write!(f, " print_pop"), - Instruction::Print => write!(f, " print"), - Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), - Instruction::JumpIfNilElsePop(pos) => write!(f, " jnil_else_pop {}", pos), - Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), - Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), - Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), - Instruction::JumpIfGt(pos) => write!(f, " jgt {}", pos), - Instruction::JumpIfGtEq(pos) => write!(f, " jge {}", pos), - Instruction::Equal => write!(f, " equal"), - Instruction::Eq => write!(f, " ceq"), - Instruction::Lt => write!(f, " clt"), - Instruction::LtEq => write!(f, " cle"), - Instruction::Gt => write!(f, " cgt"), - Instruction::GtEq => write!(f, " cge"), - Instruction::Jump(pos) => write!(f, " jmp {}", pos), - Instruction::Call(pos) => write!(f, " call {}", pos), - Instruction::Ret => write!(f, " ret"), - Instruction::RustCall { .. } => write!(f, " rustcall"), - Instruction::Label(name) => write!(f, "{}:", name), - Instruction::Cons => write!(f, " cons"), - Instruction::List(len) => write!(f, " list {}", len), - Instruction::Append(len) => write!(f, " append {}", len), - Instruction::Cxr(cxr) => match cxr { - InstructionCxr::Car => write!(f, " car"), - InstructionCxr::Cdr => write!(f, " cdr"), - InstructionCxr::Caar => write!(f, " caar"), - InstructionCxr::Cadr => write!(f, " cadr"), - InstructionCxr::Cdar => write!(f, " cdar"), - InstructionCxr::Cddr => write!(f, " cddr"), - InstructionCxr::Caaar => write!(f, " caaar"), - InstructionCxr::Caadr => write!(f, " caadr"), - InstructionCxr::Cadar => write!(f, " cadar"), - InstructionCxr::Caddr => write!(f, " caddr"), - InstructionCxr::Cdaar => write!(f, " cdaar"), - InstructionCxr::Cdadr => write!(f, " cdadr"), - InstructionCxr::Cddar => write!(f, " cddar"), - InstructionCxr::Cdddr => write!(f, " cdddr"), - InstructionCxr::Caaaar => write!(f, " caaaar"), - InstructionCxr::Caaadr => write!(f, " caaadr"), - InstructionCxr::Caadar => write!(f, " caadar"), - InstructionCxr::Caaddr => write!(f, " caaddr"), - InstructionCxr::Cadaar => write!(f, " cadaar"), - InstructionCxr::Cadadr => write!(f, " cadadr"), - InstructionCxr::Caddar => write!(f, " caddar"), - InstructionCxr::Cadddr => write!(f, " cadddr"), - InstructionCxr::Cdaaar => write!(f, " cdaaar"), - InstructionCxr::Cdaadr => write!(f, " cdaadr"), - InstructionCxr::Cdadar => write!(f, " cdadar"), - InstructionCxr::Cdaddr => write!(f, " cdaddr"), - InstructionCxr::Cddaar => write!(f, " cddaar"), - InstructionCxr::Cddadr => write!(f, " cddadr"), - InstructionCxr::Cdddar => write!(f, " cdddar"), - InstructionCxr::Cddddr => write!(f, " cddddr"), - }, - } - } -} +use std::collections::HashMap; #[derive(Default, Clone)] pub(crate) struct VMBindings { @@ -336,10 +155,10 @@ impl Machine { unreachable!() }; let vv = match op { - InstructionBinaryOp::Add => a.add(b)?, - InstructionBinaryOp::Sub => a.sub(b)?, - InstructionBinaryOp::Mul => a.mul(b)?, - InstructionBinaryOp::Div => a.div(b)?, + instruction::BinaryOp::Add => a.add(b)?, + instruction::BinaryOp::Sub => a.sub(b)?, + instruction::BinaryOp::Mul => a.mul(b)?, + instruction::BinaryOp::Div => a.div(b)?, }; self.stack.truncate(self.stack.len() - 2); self.stack.push(vv); @@ -527,36 +346,36 @@ impl Machine { let a: TulispObject = self.stack.pop().unwrap().into(); self.stack.push( match cxr { - InstructionCxr::Car => a.car().unwrap(), - InstructionCxr::Cdr => a.cdr().unwrap(), - InstructionCxr::Caar => a.caar().unwrap(), - InstructionCxr::Cadr => a.cadr().unwrap(), - InstructionCxr::Cdar => a.cdar().unwrap(), - InstructionCxr::Cddr => a.cddr().unwrap(), - InstructionCxr::Caaar => a.caaar().unwrap(), - InstructionCxr::Caadr => a.caadr().unwrap(), - InstructionCxr::Cadar => a.cadar().unwrap(), - InstructionCxr::Caddr => a.caddr().unwrap(), - InstructionCxr::Cdaar => a.cdaar().unwrap(), - InstructionCxr::Cdadr => a.cdadr().unwrap(), - InstructionCxr::Cddar => a.cddar().unwrap(), - InstructionCxr::Cdddr => a.cdddr().unwrap(), - InstructionCxr::Caaaar => a.caaaar().unwrap(), - InstructionCxr::Caaadr => a.caaadr().unwrap(), - InstructionCxr::Caadar => a.caadar().unwrap(), - InstructionCxr::Caaddr => a.caaddr().unwrap(), - InstructionCxr::Cadaar => a.cadaar().unwrap(), - InstructionCxr::Cadadr => a.cadadr().unwrap(), - InstructionCxr::Caddar => a.caddar().unwrap(), - InstructionCxr::Cadddr => a.cadddr().unwrap(), - InstructionCxr::Cdaaar => a.cdaaar().unwrap(), - InstructionCxr::Cdaadr => a.cdaadr().unwrap(), - InstructionCxr::Cdadar => a.cdadar().unwrap(), - InstructionCxr::Cdaddr => a.cdaddr().unwrap(), - InstructionCxr::Cddaar => a.cddaar().unwrap(), - InstructionCxr::Cddadr => a.cddadr().unwrap(), - InstructionCxr::Cdddar => a.cdddar().unwrap(), - InstructionCxr::Cddddr => a.cddddr().unwrap(), + instruction::Cxr::Car => a.car().unwrap(), + instruction::Cxr::Cdr => a.cdr().unwrap(), + instruction::Cxr::Caar => a.caar().unwrap(), + instruction::Cxr::Cadr => a.cadr().unwrap(), + instruction::Cxr::Cdar => a.cdar().unwrap(), + instruction::Cxr::Cddr => a.cddr().unwrap(), + instruction::Cxr::Caaar => a.caaar().unwrap(), + instruction::Cxr::Caadr => a.caadr().unwrap(), + instruction::Cxr::Cadar => a.cadar().unwrap(), + instruction::Cxr::Caddr => a.caddr().unwrap(), + instruction::Cxr::Cdaar => a.cdaar().unwrap(), + instruction::Cxr::Cdadr => a.cdadr().unwrap(), + instruction::Cxr::Cddar => a.cddar().unwrap(), + instruction::Cxr::Cdddr => a.cdddr().unwrap(), + instruction::Cxr::Caaaar => a.caaaar().unwrap(), + instruction::Cxr::Caaadr => a.caaadr().unwrap(), + instruction::Cxr::Caadar => a.caadar().unwrap(), + instruction::Cxr::Caaddr => a.caaddr().unwrap(), + instruction::Cxr::Cadaar => a.cadaar().unwrap(), + instruction::Cxr::Cadadr => a.cadadr().unwrap(), + instruction::Cxr::Caddar => a.caddar().unwrap(), + instruction::Cxr::Cadddr => a.cadddr().unwrap(), + instruction::Cxr::Cdaaar => a.cdaaar().unwrap(), + instruction::Cxr::Cdaadr => a.cdaadr().unwrap(), + instruction::Cxr::Cdadar => a.cdadar().unwrap(), + instruction::Cxr::Cdaddr => a.cdaddr().unwrap(), + instruction::Cxr::Cddaar => a.cddaar().unwrap(), + instruction::Cxr::Cddadr => a.cddadr().unwrap(), + instruction::Cxr::Cdddar => a.cdddar().unwrap(), + instruction::Cxr::Cddddr => a.cddddr().unwrap(), } .into(), ) diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index 6b6dba5e..26170f2b 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -1,6 +1,8 @@ +pub(crate) mod instruction; +pub(crate) use instruction::{Instruction, Pos}; + mod interpreter; -pub(crate) use interpreter::{ - Bytecode, Instruction, InstructionBinaryOp, InstructionCxr, Machine, Pos, VMBindings, -}; +pub(crate) use interpreter::{Bytecode, Machine, VMBindings}; + mod stack_value; pub(crate) use stack_value::VMStackValue; From 5281f365ad9e81cba053bfcc2614e4efebc20713 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 21:28:47 +0100 Subject: [PATCH 080/142] Move compiler to bytecode/compiler --- src/byte_compile/mod.rs | 3 --- .../byte_compile.rs => bytecode/compiler/compiler.rs} | 2 -- .../compiler}/forms/arithmetic_operations.rs | 3 +-- src/{byte_compile => bytecode/compiler}/forms/common.rs | 3 ++- .../compiler}/forms/comparison_of_numbers.rs | 3 +-- .../compiler}/forms/conditionals.rs | 3 +-- .../compiler}/forms/list_elements.rs | 3 +-- src/{byte_compile => bytecode/compiler}/forms/mod.rs | 5 +++-- .../compiler}/forms/other_functions.rs | 3 +-- src/bytecode/compiler/mod.rs | 3 +++ src/bytecode/mod.rs | 3 +++ src/context.rs | 3 +-- src/lib.rs | 1 - 13 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 src/byte_compile/mod.rs rename src/{byte_compile/byte_compile.rs => bytecode/compiler/compiler.rs} (99%) rename src/{byte_compile => bytecode/compiler}/forms/arithmetic_operations.rs (97%) rename src/{byte_compile => bytecode/compiler}/forms/common.rs (96%) rename src/{byte_compile => bytecode/compiler}/forms/comparison_of_numbers.rs (97%) rename src/{byte_compile => bytecode/compiler}/forms/conditionals.rs (98%) rename src/{byte_compile => bytecode/compiler}/forms/list_elements.rs (97%) rename src/{byte_compile => bytecode/compiler}/forms/mod.rs (97%) rename src/{byte_compile => bytecode/compiler}/forms/other_functions.rs (99%) create mode 100644 src/bytecode/compiler/mod.rs diff --git a/src/byte_compile/mod.rs b/src/byte_compile/mod.rs deleted file mode 100644 index e08c1d9e..00000000 --- a/src/byte_compile/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod byte_compile; -mod forms; -pub(crate) use byte_compile::Compiler; diff --git a/src/byte_compile/byte_compile.rs b/src/bytecode/compiler/compiler.rs similarity index 99% rename from src/byte_compile/byte_compile.rs rename to src/bytecode/compiler/compiler.rs index fa12193a..2c02d66d 100644 --- a/src/byte_compile/byte_compile.rs +++ b/src/bytecode/compiler/compiler.rs @@ -7,8 +7,6 @@ use crate::{ use super::forms::VMFunctions; -pub(crate) type CompileResult = Result, Error>; - #[allow(dead_code)] pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, diff --git a/src/byte_compile/forms/arithmetic_operations.rs b/src/bytecode/compiler/forms/arithmetic_operations.rs similarity index 97% rename from src/byte_compile/forms/arithmetic_operations.rs rename to src/bytecode/compiler/forms/arithmetic_operations.rs index 1df990a0..5b44ab3f 100644 --- a/src/byte_compile/forms/arithmetic_operations.rs +++ b/src/bytecode/compiler/forms/arithmetic_operations.rs @@ -1,6 +1,5 @@ use crate::{ - byte_compile::Compiler, - bytecode::{instruction::BinaryOp, Instruction}, + bytecode::{instruction::BinaryOp, Compiler, Instruction}, Error, ErrorKind, TulispObject, }; diff --git a/src/byte_compile/forms/common.rs b/src/bytecode/compiler/forms/common.rs similarity index 96% rename from src/byte_compile/forms/common.rs rename to src/bytecode/compiler/forms/common.rs index be14f948..36e39e99 100644 --- a/src/byte_compile/forms/common.rs +++ b/src/bytecode/compiler/forms/common.rs @@ -1,5 +1,6 @@ use crate::{ - byte_compile::Compiler, bytecode::Instruction, Error, ErrorKind, TulispObject, TulispValue, + bytecode::{Compiler, Instruction}, + Error, ErrorKind, TulispObject, TulispValue, }; impl Compiler<'_> { diff --git a/src/byte_compile/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs similarity index 97% rename from src/byte_compile/forms/comparison_of_numbers.rs rename to src/bytecode/compiler/forms/comparison_of_numbers.rs index 7b512766..66824195 100644 --- a/src/byte_compile/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -1,6 +1,5 @@ use crate::{ - byte_compile::Compiler, - bytecode::{Instruction, Pos}, + bytecode::{Compiler, Instruction, Pos}, Error, TulispObject, }; diff --git a/src/byte_compile/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs similarity index 98% rename from src/byte_compile/forms/conditionals.rs rename to src/bytecode/compiler/forms/conditionals.rs index 04e387c5..8a66a337 100644 --- a/src/byte_compile/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -1,6 +1,5 @@ use crate::{ - byte_compile::Compiler, - bytecode::{Instruction, Pos}, + bytecode::{Compiler, Instruction, Pos}, Error, TulispObject, }; diff --git a/src/byte_compile/forms/list_elements.rs b/src/bytecode/compiler/forms/list_elements.rs similarity index 97% rename from src/byte_compile/forms/list_elements.rs rename to src/bytecode/compiler/forms/list_elements.rs index 2e70a5d1..df13575e 100644 --- a/src/byte_compile/forms/list_elements.rs +++ b/src/bytecode/compiler/forms/list_elements.rs @@ -1,6 +1,5 @@ use crate::{ - byte_compile::Compiler, - bytecode::{instruction::Cxr, Instruction}, + bytecode::{instruction::Cxr, Compiler, Instruction}, Error, ErrorKind, TulispObject, }; diff --git a/src/byte_compile/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs similarity index 97% rename from src/byte_compile/forms/mod.rs rename to src/bytecode/compiler/forms/mod.rs index ba5adce3..bde55b0e 100644 --- a/src/byte_compile/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{bytecode::Instruction, Error, ErrorKind, TulispContext, TulispObject}; -use super::{byte_compile::CompileResult, Compiler}; +use super::Compiler; mod arithmetic_operations; mod common; @@ -11,7 +11,8 @@ mod conditionals; mod list_elements; mod other_functions; -type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> CompileResult; +type FnCallCompiler = + fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>; pub(crate) struct VMFunctions { // TulispObject.addr() -> implementation diff --git a/src/byte_compile/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs similarity index 99% rename from src/byte_compile/forms/other_functions.rs rename to src/bytecode/compiler/forms/other_functions.rs index 9c743442..c76972a9 100644 --- a/src/byte_compile/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -1,6 +1,5 @@ use crate::{ - byte_compile::Compiler, - bytecode::{Instruction, Pos}, + bytecode::{Compiler, Instruction, Pos}, destruct_bind, parse::mark_tail_calls, Error, ErrorKind, TulispObject, diff --git a/src/bytecode/compiler/mod.rs b/src/bytecode/compiler/mod.rs new file mode 100644 index 00000000..eee63f27 --- /dev/null +++ b/src/bytecode/compiler/mod.rs @@ -0,0 +1,3 @@ +mod compiler; +mod forms; +pub(crate) use compiler::Compiler; diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index 26170f2b..cb2bf95b 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -6,3 +6,6 @@ pub(crate) use interpreter::{Bytecode, Machine, VMBindings}; mod stack_value; pub(crate) use stack_value::VMStackValue; + +mod compiler; +pub(crate) use compiler::Compiler; diff --git a/src/context.rs b/src/context.rs index 7f47120f..71a403f2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,8 +2,7 @@ use std::{collections::HashMap, fs}; use crate::{ builtin, - byte_compile::Compiler, - bytecode, + bytecode::{self, Compiler}, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, diff --git a/src/lib.rs b/src/lib.rs index 45e0c90e..9502a802 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,6 @@ A list of currently available builtin functions can be found [here](builtin). a `TulispContext` object, so that they can be called from lisp code. */ -mod byte_compile; pub(crate) mod bytecode; mod eval; mod macros; From b95a4006892e2ec922efa5c7d7cd09f9f67f2173 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 13 Jan 2024 21:35:09 +0100 Subject: [PATCH 081/142] Simplify instruction imports --- src/bytecode/interpreter.rs | 87 ++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 1090d78b..ccb211db 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,4 +1,4 @@ -use super::{instruction, Instruction}; +use super::Instruction; use crate::{ bytecode::{Pos, VMStackValue}, Error, ErrorKind, TulispContext, TulispObject, @@ -154,11 +154,15 @@ impl Machine { let [ref b, ref a] = self.stack[(self.stack.len() - 2)..] else { unreachable!() }; - let vv = match op { - instruction::BinaryOp::Add => a.add(b)?, - instruction::BinaryOp::Sub => a.sub(b)?, - instruction::BinaryOp::Mul => a.mul(b)?, - instruction::BinaryOp::Div => a.div(b)?, + + let vv = { + use crate::bytecode::instruction::BinaryOp::*; + match op { + Add => a.add(b)?, + Sub => a.sub(b)?, + Mul => a.mul(b)?, + Div => a.div(b)?, + } }; self.stack.truncate(self.stack.len() - 2); self.stack.push(vv); @@ -344,41 +348,42 @@ impl Machine { } Instruction::Cxr(cxr) => { let a: TulispObject = self.stack.pop().unwrap().into(); - self.stack.push( - match cxr { - instruction::Cxr::Car => a.car().unwrap(), - instruction::Cxr::Cdr => a.cdr().unwrap(), - instruction::Cxr::Caar => a.caar().unwrap(), - instruction::Cxr::Cadr => a.cadr().unwrap(), - instruction::Cxr::Cdar => a.cdar().unwrap(), - instruction::Cxr::Cddr => a.cddr().unwrap(), - instruction::Cxr::Caaar => a.caaar().unwrap(), - instruction::Cxr::Caadr => a.caadr().unwrap(), - instruction::Cxr::Cadar => a.cadar().unwrap(), - instruction::Cxr::Caddr => a.caddr().unwrap(), - instruction::Cxr::Cdaar => a.cdaar().unwrap(), - instruction::Cxr::Cdadr => a.cdadr().unwrap(), - instruction::Cxr::Cddar => a.cddar().unwrap(), - instruction::Cxr::Cdddr => a.cdddr().unwrap(), - instruction::Cxr::Caaaar => a.caaaar().unwrap(), - instruction::Cxr::Caaadr => a.caaadr().unwrap(), - instruction::Cxr::Caadar => a.caadar().unwrap(), - instruction::Cxr::Caaddr => a.caaddr().unwrap(), - instruction::Cxr::Cadaar => a.cadaar().unwrap(), - instruction::Cxr::Cadadr => a.cadadr().unwrap(), - instruction::Cxr::Caddar => a.caddar().unwrap(), - instruction::Cxr::Cadddr => a.cadddr().unwrap(), - instruction::Cxr::Cdaaar => a.cdaaar().unwrap(), - instruction::Cxr::Cdaadr => a.cdaadr().unwrap(), - instruction::Cxr::Cdadar => a.cdadar().unwrap(), - instruction::Cxr::Cdaddr => a.cdaddr().unwrap(), - instruction::Cxr::Cddaar => a.cddaar().unwrap(), - instruction::Cxr::Cddadr => a.cddadr().unwrap(), - instruction::Cxr::Cdddar => a.cdddar().unwrap(), - instruction::Cxr::Cddddr => a.cddddr().unwrap(), - } - .into(), - ) + + self.stack.push({ + use crate::bytecode::instruction::Cxr::*; + VMStackValue::from(match cxr { + Car => a.car().unwrap(), + Cdr => a.cdr().unwrap(), + Caar => a.caar().unwrap(), + Cadr => a.cadr().unwrap(), + Cdar => a.cdar().unwrap(), + Cddr => a.cddr().unwrap(), + Caaar => a.caaar().unwrap(), + Caadr => a.caadr().unwrap(), + Cadar => a.cadar().unwrap(), + Caddr => a.caddr().unwrap(), + Cdaar => a.cdaar().unwrap(), + Cdadr => a.cdadr().unwrap(), + Cddar => a.cddar().unwrap(), + Cdddr => a.cdddr().unwrap(), + Caaaar => a.caaaar().unwrap(), + Caaadr => a.caaadr().unwrap(), + Caadar => a.caadar().unwrap(), + Caaddr => a.caaddr().unwrap(), + Cadaar => a.cadaar().unwrap(), + Cadadr => a.cadadr().unwrap(), + Caddar => a.caddar().unwrap(), + Cadddr => a.cadddr().unwrap(), + Cdaaar => a.cdaaar().unwrap(), + Cdaadr => a.cdaadr().unwrap(), + Cdadar => a.cdadar().unwrap(), + Cdaddr => a.cdaddr().unwrap(), + Cddaar => a.cddaar().unwrap(), + Cddadr => a.cddadr().unwrap(), + Cdddar => a.cdddar().unwrap(), + Cddddr => a.cddddr().unwrap(), + }) + }) } } self.pc += 1; From e236927c26119cf9556ed057ea19c8047cede185 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 14 Jan 2024 18:53:18 +0100 Subject: [PATCH 082/142] Add a compiler for the `quote` special-form --- src/bytecode/compiler/forms/mod.rs | 1 + src/bytecode/compiler/forms/other_functions.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index bde55b0e..39583308 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -48,6 +48,7 @@ impl VMFunctions { ("/", arithmetic_operations::compile_fn_div), // other functions ("print", other_functions::compile_fn_print), + ("quote", other_functions::compile_fn_quote), ("setq", other_functions::compile_fn_setq), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index c76972a9..fa97ff96 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -21,6 +21,20 @@ pub(super) fn compile_fn_print( }) } +pub(super) fn compile_fn_quote( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { + if compiler.keep_result { + return Ok(vec![Instruction::Push(arg.clone().into())]); + } else { + return Ok(vec![]); + } + }) +} + pub(super) fn compile_fn_setq( compiler: &mut Compiler<'_>, name: &TulispObject, From 4a31204cd1710593dc6630f8d9cb96d53b1e52b7 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 14 Jan 2024 19:02:05 +0100 Subject: [PATCH 083/142] Lisp-like printing of booleans --- src/bytecode/stack_value.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bytecode/stack_value.rs b/src/bytecode/stack_value.rs index fa4eb52f..a9e5b428 100644 --- a/src/bytecode/stack_value.rs +++ b/src/bytecode/stack_value.rs @@ -136,7 +136,9 @@ impl std::fmt::Display for VMStackValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { VMStackValue::TulispObject(obj) => write!(f, "{}", obj), - VMStackValue::Bool(b) => write!(f, "{}", b), + VMStackValue::Bool(b) => { + write!(f, "{}", if *b { TulispValue::T } else { TulispValue::Nil }) + } VMStackValue::Float(fl) => write!(f, "{}", fl), VMStackValue::Int(i) => write!(f, "{}", i), } From 235a1161645b2ad2489793931aad0671924c1529 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 14 Jan 2024 20:01:53 +0100 Subject: [PATCH 084/142] Add a `Null` instruction and a `not` compiler --- src/bytecode/compiler/forms/conditionals.rs | 14 ++++++++++++++ src/bytecode/compiler/forms/mod.rs | 1 + src/bytecode/instruction.rs | 3 +++ src/bytecode/interpreter.rs | 5 +++++ 4 files changed, 23 insertions(+) diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 8a66a337..816ca900 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -98,3 +98,17 @@ pub(super) fn compile_fn_while( Ok(result) }) } + +pub(super) fn compile_fn_not( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(_name, args, false, |compiler, arg, _| { + let mut result = compiler.compile_expr(arg)?; + if compiler.keep_result { + result.push(Instruction::Null); + } + Ok(result) + }) +} diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 39583308..13b4cb87 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -93,6 +93,7 @@ impl VMFunctions { ("if", conditionals::compile_fn_if), ("cond", conditionals::compile_fn_cond), ("while", conditionals::compile_fn_while), + ("not", conditionals::compile_fn_not), // noop ("defmacro", other_functions::compile_fn_noop), ("macroexpand", other_functions::compile_fn_noop), diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index ffe224e1..b7138ca2 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -85,6 +85,8 @@ pub(crate) enum Instruction { LtEq, Gt, GtEq, + // predicates + Null, // control flow JumpIfNil(Pos), JumpIfNilElsePop(Pos), @@ -127,6 +129,7 @@ impl std::fmt::Display for Instruction { }, Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), + Instruction::Null => write!(f, " null"), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), Instruction::JumpIfNilElsePop(pos) => write!(f, " jnil_else_pop {}", pos), Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index ccb211db..58e6b73a 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -385,6 +385,11 @@ impl Machine { }) }) } + // predicates + Instruction::Null => { + let a = self.stack.last().unwrap().null(); + *self.stack.last_mut().unwrap() = a.into(); + } } self.pc += 1; } From cd1f874a5000a36f98d77f6fe76024793ae02929 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 14 Jan 2024 20:02:28 +0100 Subject: [PATCH 085/142] Add an `and` compiler --- src/bytecode/compiler/forms/conditionals.rs | 29 +++++++++++++++++++++ src/bytecode/compiler/forms/mod.rs | 1 + 2 files changed, 30 insertions(+) diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 816ca900..4deb4ffd 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -112,3 +112,32 @@ pub(super) fn compile_fn_not( Ok(result) }) } + +pub(super) fn compile_fn_and( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + let label = TulispObject::symbol("end-and".to_string(), false); + let mut need_label = false; + for item in args.base_iter() { + let expr_result = &mut compiler.compile_expr(&item)?; + if !expr_result.is_empty() { + result.append(expr_result); + if compiler.keep_result { + result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); + } else { + result.push(Instruction::JumpIfNil(Pos::Label(label.clone()))); + } + need_label = true; + } + } + if need_label { + if compiler.keep_result { + result.push(Instruction::Push(true.into())) + } + result.push(Instruction::Label(label.into())); + } + Ok(result) +} diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 13b4cb87..be632834 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -93,6 +93,7 @@ impl VMFunctions { ("if", conditionals::compile_fn_if), ("cond", conditionals::compile_fn_cond), ("while", conditionals::compile_fn_while), + ("and", conditionals::compile_fn_and), ("not", conditionals::compile_fn_not), // noop ("defmacro", other_functions::compile_fn_noop), From b7dab0cd8245f747db479a8f45db0c96c9ec0bc2 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 3 Feb 2024 11:47:41 +0100 Subject: [PATCH 086/142] Use the last value in `and` when all previous values are truthy --- src/bytecode/compiler/forms/conditionals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 4deb4ffd..30316310 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -135,7 +135,7 @@ pub(super) fn compile_fn_and( } if need_label { if compiler.keep_result { - result.push(Instruction::Push(true.into())) + result.pop(); } result.push(Instruction::Label(label.into())); } From e13fd1e5a8659fd0434e387e9ae8a2eb67261a4b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 3 Feb 2024 11:49:23 +0100 Subject: [PATCH 087/142] Add a `or` compiler --- src/bytecode/compiler/forms/conditionals.rs | 29 +++++++++++++++++++++ src/bytecode/compiler/forms/mod.rs | 1 + src/bytecode/instruction.rs | 4 +++ src/bytecode/interpreter.rs | 18 +++++++++++++ 4 files changed, 52 insertions(+) diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 30316310..3db9f99b 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -141,3 +141,32 @@ pub(super) fn compile_fn_and( } Ok(result) } + +pub(super) fn compile_fn_or( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + let label = TulispObject::symbol("end-or".to_string(), false); + let mut need_label = false; + for item in args.base_iter() { + let expr_result = &mut compiler.compile_expr(&item)?; + if !expr_result.is_empty() { + result.append(expr_result); + if compiler.keep_result { + result.push(Instruction::JumpIfNotNilElsePop(Pos::Label(label.clone()))); + } else { + result.push(Instruction::JumpIfNotNil(Pos::Label(label.clone()))); + } + need_label = true; + } + } + if need_label { + if compiler.keep_result { + result.push(Instruction::Push(false.into())) + } + result.push(Instruction::Label(label.into())); + } + Ok(result) +} diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index be632834..607d5bf9 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -94,6 +94,7 @@ impl VMFunctions { ("cond", conditionals::compile_fn_cond), ("while", conditionals::compile_fn_while), ("and", conditionals::compile_fn_and), + ("or", conditionals::compile_fn_or), ("not", conditionals::compile_fn_not), // noop ("defmacro", other_functions::compile_fn_noop), diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index b7138ca2..097e7b9d 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -89,7 +89,9 @@ pub(crate) enum Instruction { Null, // control flow JumpIfNil(Pos), + JumpIfNotNil(Pos), JumpIfNilElsePop(Pos), + JumpIfNotNilElsePop(Pos), JumpIfNeq(Pos), JumpIfLt(Pos), JumpIfLtEq(Pos), @@ -131,7 +133,9 @@ impl std::fmt::Display for Instruction { Instruction::Print => write!(f, " print"), Instruction::Null => write!(f, " null"), Instruction::JumpIfNil(pos) => write!(f, " jnil {}", pos), + Instruction::JumpIfNotNil(pos) => write!(f, " jnnil {}", pos), Instruction::JumpIfNilElsePop(pos) => write!(f, " jnil_else_pop {}", pos), + Instruction::JumpIfNotNilElsePop(pos) => write!(f, " jnnil_else_pop {}", pos), Instruction::JumpIfNeq(pos) => write!(f, " jne {}", pos), Instruction::JumpIfLt(pos) => write!(f, " jlt {}", pos), Instruction::JumpIfLtEq(pos) => write!(f, " jle {}", pos), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 58e6b73a..c175c127 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -184,6 +184,15 @@ impl Machine { continue; } } + Instruction::JumpIfNotNil(pos) => { + let a = self.stack.last().unwrap(); + let cmp = !a.null(); + self.stack.truncate(self.stack.len() - 1); + if cmp { + jump_to_pos!(self, pos); + continue; + } + } Instruction::JumpIfNilElsePop(pos) => { let a = self.stack.last().unwrap(); if a.null() { @@ -193,6 +202,15 @@ impl Machine { self.stack.truncate(self.stack.len() - 1); } } + Instruction::JumpIfNotNilElsePop(pos) => { + let a = self.stack.last().unwrap(); + if !a.null() { + jump_to_pos!(self, pos); + continue; + } else { + self.stack.truncate(self.stack.len() - 1); + } + } Instruction::JumpIfNeq(pos) => { let minus2 = self.stack.len() - 2; let [ref b, ref a] = self.stack[minus2..] else { From 68ba5735556193ed0cf51e85f96d26e97cfc65b0 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 13:51:55 +0100 Subject: [PATCH 088/142] Remove separate binding storage, store values in TulispObjects directly This makes the VM slower and takes away the possibility to have lexical scoping, but that probably needs a different approach anyway - one with garbage collection or some other way of cleanup. But for now, this allows for variable bindings to be shared between the tree walking interpreter and the virtual machine. --- src/bytecode/compiler/compiler.rs | 53 ++------------ .../compiler/forms/other_functions.rs | 23 +++--- src/bytecode/instruction.rs | 10 +-- src/bytecode/interpreter.rs | 70 +++---------------- src/bytecode/mod.rs | 2 +- 5 files changed, 28 insertions(+), 130 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 2c02d66d..a5d411e7 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::{ - bytecode::{Bytecode, Instruction, VMBindings}, + bytecode::{Bytecode, Instruction}, Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; @@ -11,8 +11,7 @@ use super::forms::VMFunctions; pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, pub functions: VMFunctions, - pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx - pub bindings: Vec, + pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx pub symbol_to_binding_idx: HashMap>, pub keep_result: bool, } @@ -24,46 +23,13 @@ impl<'a> Compiler<'a> { ctx, functions, defun_args: HashMap::new(), - bindings: Vec::new(), symbol_to_binding_idx: HashMap::new(), keep_result: true, } } - pub fn get_symbol_idx(&mut self, symbol: &TulispObject) -> (usize, bool) { - let addr = &symbol.addr_as_usize(); - if let Some(scopes) = self.symbol_to_binding_idx.get(addr) { - (*scopes.last().unwrap(), false) - } else { - self.bindings.push(VMBindings::new(symbol.to_string())); - let idx = self.bindings.len() - 1; - self.symbol_to_binding_idx.insert(*addr, vec![idx]); - (idx, true) - } - } - - pub fn begin_scope(&mut self, symbol: &TulispObject) -> usize { - let addr = &symbol.addr_as_usize(); - self.bindings.push(VMBindings::new(symbol.to_string())); - let idx = self.bindings.len() - 1; - if let Some(scopes) = self.symbol_to_binding_idx.get_mut(addr) { - scopes.push(idx); - } else { - self.symbol_to_binding_idx - .insert(*addr, vec![self.bindings.len() - 1]); - } - idx - } - - pub fn end_scope(&mut self, symbol: &TulispObject) { - let addr = &symbol.addr_as_usize(); - if let Some(scopes) = self.symbol_to_binding_idx.get_mut(addr) { - scopes.pop(); - } - } - pub fn compile(mut self, value: &TulispObject) -> Result { - Ok(Bytecode::new(self.compile_progn(value)?, self.bindings)) + Ok(Bytecode::new(self.compile_progn(value)?)) } pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { @@ -138,18 +104,7 @@ impl<'a> Compiler<'a> { TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load({ - match self.get_symbol_idx(expr) { - (_, true) => { - return Err(Error::new( - crate::ErrorKind::SyntaxError, - format!("Unbound symbol: {}", expr), - ) - .with_trace(expr.clone())) - } - (idx, false) => idx, - } - })]), + TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { return Err(Error::new( crate::ErrorKind::SyntaxError, diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index fa97ff96..033103f3 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -43,9 +43,9 @@ pub(super) fn compile_fn_setq( compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { let mut result = compiler.compile_expr_keep_result(arg2)?; if compiler.keep_result { - result.push(Instruction::Store(compiler.get_symbol_idx(arg1).0)); + result.push(Instruction::Store(arg1.clone())); } else { - result.push(Instruction::StorePop(compiler.get_symbol_idx(arg1).0)); + result.push(Instruction::StorePop(arg1.clone())); } Ok(result) }) @@ -115,7 +115,7 @@ fn compile_fn_defun_bounce_call( } let params = &compiler.defun_args[&name.addr_as_usize()]; for param in params { - result.push(Instruction::StorePop(*param)) + result.push(Instruction::StorePop(param.clone())) } result.push(Instruction::Jump(Pos::Label(name.clone()))); return Ok(result); @@ -132,11 +132,11 @@ pub(super) fn compile_fn_defun_call( } let params = &compiler.defun_args[&name.addr_as_usize()]; for param in params { - result.push(Instruction::BeginScope(*param)) + result.push(Instruction::BeginScope(param.clone())) } result.push(Instruction::Call(Pos::Label(name.clone()))); for param in params { - result.push(Instruction::EndScope(*param)); + result.push(Instruction::EndScope(param.clone())); } if !compiler.keep_result { result.push(Instruction::Pop); @@ -156,7 +156,7 @@ pub(super) fn compile_fn_defun( let mut params = vec![]; let args = args.base_iter().collect::>(); for arg in args.iter().rev() { - params.push(ctx.begin_scope(&arg)); + params.push(arg.clone()); } ctx.defun_args.insert(name.addr_as_usize(), params); @@ -171,9 +171,6 @@ pub(super) fn compile_fn_defun( e })?; let mut body = ctx.compile_progn_keep_result(&body)?; - for arg in args.iter().rev() { - ctx.end_scope(&arg); - } let mut result = vec![ Instruction::Jump(Pos::Rel(body.len() as isize + 2)), @@ -196,8 +193,8 @@ pub(super) fn compile_fn_let_star( let mut symbols = vec![]; for varitem in varlist.base_iter() { if varitem.symbolp() { - let param = ctx.begin_scope(&varitem); - params.push(param); + let param = varitem.clone(); + params.push(param.clone()); result.append(&mut vec![ Instruction::Push(false.into()), Instruction::BeginScope(param), @@ -221,8 +218,8 @@ pub(super) fn compile_fn_let_star( ) .with_trace(varitem)); } - let param = ctx.begin_scope(&name); - params.push(param); + let param = name.clone(); + params.push(param.clone()); result.append( &mut ctx .compile_expr_keep_result(&value) diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 097e7b9d..9f6a059f 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -68,11 +68,11 @@ pub(crate) enum Instruction { Push(VMStackValue), Pop, // variables - StorePop(usize), - Store(usize), - Load(usize), - BeginScope(usize), - EndScope(usize), + StorePop(TulispObject), + Store(TulispObject), + Load(TulispObject), + BeginScope(TulispObject), + EndScope(TulispObject), // arithmetic BinaryOp(BinaryOp), // io diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index c175c127..b8a32bee 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,68 +1,17 @@ use super::Instruction; use crate::{ bytecode::{Pos, VMStackValue}, - Error, ErrorKind, TulispContext, TulispObject, + Error, TulispContext, TulispObject, }; use std::collections::HashMap; -#[derive(Default, Clone)] -pub(crate) struct VMBindings { - #[allow(dead_code)] - name: String, - items: Vec, -} - -impl VMBindings { - pub(crate) fn new(name: String) -> Self { - Self { - name, - items: Vec::new(), - } - } - - pub fn set(&mut self, to_set: VMStackValue) { - if self.items.is_empty() { - self.items.push(to_set); - } else { - *self.items.last_mut().unwrap() = to_set; - } - } - - pub fn set_scope(&mut self, to_set: &VMStackValue) { - self.items.push(to_set.to_owned()); - } - - pub fn unset(&mut self) { - self.items.truncate(self.items.len() - 1); - } - - #[allow(dead_code)] - pub fn boundp(&self) -> bool { - !self.items.is_empty() - } - - pub fn get(&self) -> Result { - if self.items.is_empty() { - return Err(Error::new( - ErrorKind::TypeMismatch, - format!("Variable definition is void: {}", self.name), - )); - } - return Ok(self.items.last().unwrap().clone()); - } -} - pub(crate) struct Bytecode { instructions: Vec, - bindings: Vec, } impl Bytecode { - pub(crate) fn new(instructions: Vec, bindings: Vec) -> Self { - Self { - instructions, - bindings, - } + pub(crate) fn new(instructions: Vec) -> Self { + Self { instructions } } #[allow(dead_code)] @@ -71,7 +20,6 @@ impl Bytecode { for (i, instr) in self.instructions.iter().enumerate() { println!("{:<40} # {}", instr.to_string(), i); } - println!("Number of bindings: {}", self.bindings.len()); } } @@ -307,25 +255,23 @@ impl Machine { } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); - self.bytecode.bindings[*obj].set(a); + obj.set(a.into()).unwrap(); } Instruction::Store(obj) => { let a = self.stack.last().unwrap(); - self.bytecode.bindings[*obj].set(a.clone()); + obj.set(a.clone().into()).unwrap(); } Instruction::Load(obj) => { - let a = self.bytecode.bindings[*obj].get().unwrap(); + let a = obj.get()?; self.stack.push(a.into()); } Instruction::BeginScope(obj) => { let a = self.stack.last().unwrap(); - self.bytecode.bindings[*obj].set_scope(a); + obj.set_scope(a.clone().into()).unwrap(); self.stack.truncate(self.stack.len() - 1); } Instruction::EndScope(obj) => { - if self.bytecode.bindings[*obj].items.len() > 1 { - self.bytecode.bindings[*obj].unset(); - } + obj.unset().unwrap(); } Instruction::Call(pos) => { let pc = self.pc; diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index cb2bf95b..9fdfbf72 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -2,7 +2,7 @@ pub(crate) mod instruction; pub(crate) use instruction::{Instruction, Pos}; mod interpreter; -pub(crate) use interpreter::{Bytecode, Machine, VMBindings}; +pub(crate) use interpreter::{Bytecode, Machine}; mod stack_value; pub(crate) use stack_value::VMStackValue; From 377281245201d472f90f084338f1f21a7ce9197e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 14:15:18 +0100 Subject: [PATCH 089/142] Remove `VMStackValue` There's no reason to have them if there's no local array storage to put them in when bound to variables. --- src/bytecode/instruction.rs | 4 +- src/bytecode/interpreter.rs | 82 ++++++++++---- src/bytecode/mod.rs | 3 - src/bytecode/stack_value.rs | 214 ------------------------------------ 4 files changed, 60 insertions(+), 243 deletions(-) delete mode 100644 src/bytecode/stack_value.rs diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 9f6a059f..ece7ee2d 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{bytecode::VMStackValue, value::TulispFn, TulispObject}; +use crate::{value::TulispFn, TulispObject}; #[derive(Clone)] pub(crate) enum Pos { @@ -65,7 +65,7 @@ pub(crate) enum BinaryOp { #[derive(Clone)] pub(crate) enum Instruction { // stack - Push(VMStackValue), + Push(TulispObject), Pop, // variables StorePop(TulispObject), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index b8a32bee..260e6446 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,14 +1,51 @@ use super::Instruction; -use crate::{ - bytecode::{Pos, VMStackValue}, - Error, TulispContext, TulispObject, -}; +use crate::{bytecode::Pos, Error, TulispContext, TulispObject}; use std::collections::HashMap; pub(crate) struct Bytecode { instructions: Vec, } +macro_rules! binary_ops { + ($oper:expr) => {{ + |selfobj: &TulispObject, other: &TulispObject| -> Result { + if selfobj.floatp() { + let s: f64 = selfobj.as_float().unwrap(); + let o: f64 = other.try_into()?; + Ok($oper(&s, &o).into()) + } else if other.floatp() { + let o: f64 = other.as_float().unwrap(); + let s: f64 = selfobj.try_into()?; + Ok($oper(&s, &o).into()) + } else { + let s: i64 = selfobj.try_into()?; + let o: i64 = other.try_into()?; + Ok($oper(&s, &o).into()) + } + } + }}; +} + +macro_rules! compare_ops { + ($oper:expr) => {{ + |selfobj: &TulispObject, other: &TulispObject| -> Result { + if selfobj.floatp() { + let s: f64 = selfobj.as_float().unwrap(); + let o: f64 = other.try_into()?; + Ok($oper(&s, &o)) + } else if other.floatp() { + let o: f64 = other.as_float().unwrap(); + let s: f64 = selfobj.try_into()?; + Ok($oper(&s, &o)) + } else { + let s: i64 = selfobj.try_into()?; + let o: i64 = other.try_into()?; + Ok($oper(&s, &o)) + } + } + }}; +} + impl Bytecode { pub(crate) fn new(instructions: Vec) -> Self { Self { instructions } @@ -24,7 +61,7 @@ impl Bytecode { } pub struct Machine { - stack: Vec, + stack: Vec, bytecode: Bytecode, labels: HashMap, // TulispObject.addr -> instruction index pc: usize, @@ -106,10 +143,10 @@ impl Machine { let vv = { use crate::bytecode::instruction::BinaryOp::*; match op { - Add => a.add(b)?, - Sub => a.sub(b)?, - Mul => a.mul(b)?, - Div => a.div(b)?, + Add => binary_ops!(|a, b| a + b)(a, b)?, + Sub => binary_ops!(|a, b| a - b)(a, b)?, + Mul => binary_ops!(|a, b| a * b)(a, b)?, + Div => binary_ops!(|a, b| a / b)(a, b)?, } }; self.stack.truncate(self.stack.len() - 2); @@ -176,7 +213,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = a.lt(b)?; + let cmp = compare_ops!(|a, b| a < b)(a, b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -188,7 +225,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = a.le(b)?; + let cmp = compare_ops!(|a, b| a <= b)(a, b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -200,7 +237,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = a.gt(b)?; + let cmp = compare_ops!(|a, b| a > b)(a, b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -212,7 +249,7 @@ impl Machine { let [ref b, ref a] = self.stack[minus2..] else { unreachable!() }; - let cmp = a.ge(b)?; + let cmp = compare_ops!(|a, b| a >= b)(a, b)?; self.stack.truncate(minus2); if cmp { jump_to_pos!(self, pos); @@ -236,22 +273,22 @@ impl Machine { Instruction::Lt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(a.lt(&b)?.into()); + self.stack.push(compare_ops!(|a, b| a < b)(&a, &b)?.into()); } Instruction::LtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(a.le(&b)?.into()); + self.stack.push(compare_ops!(|a, b| a <= b)(&a, &b)?.into()); } Instruction::Gt => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(a.gt(&b)?.into()); + self.stack.push(compare_ops!(|a, b| a > b)(&a, &b)?.into()); } Instruction::GtEq => { let a = self.stack.pop().unwrap(); let b = self.stack.pop().unwrap(); - self.stack.push(a.ge(&b)?.into()); + self.stack.push(compare_ops!(|a, b| a >= b)(&a, &b)?.into()); } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); @@ -281,10 +318,7 @@ impl Machine { } Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { - let args = match self.stack.pop().unwrap() { - VMStackValue::TulispObject(obj) => obj, - e => panic!("Expected TulispObject as arg to rustcall. got {}", e), - }; + let args = self.stack.pop().unwrap(); self.stack.push(func(ctx, &args)?.into()); } Instruction::Label(_) => {} @@ -306,7 +340,7 @@ impl Machine { let list = TulispObject::nil(); for elt in self.stack.drain(self.stack.len() - *len..) { - list.append(>::into(elt).deep_copy()?)?; + list.append(elt.deep_copy().unwrap())?; } self.stack.push(list.into()); } @@ -315,7 +349,7 @@ impl Machine { self.stack.push({ use crate::bytecode::instruction::Cxr::*; - VMStackValue::from(match cxr { + match cxr { Car => a.car().unwrap(), Cdr => a.cdr().unwrap(), Caar => a.caar().unwrap(), @@ -346,7 +380,7 @@ impl Machine { Cddadr => a.cddadr().unwrap(), Cdddar => a.cdddar().unwrap(), Cddddr => a.cddddr().unwrap(), - }) + } }) } // predicates diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index 9fdfbf72..a026d865 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -4,8 +4,5 @@ pub(crate) use instruction::{Instruction, Pos}; mod interpreter; pub(crate) use interpreter::{Bytecode, Machine}; -mod stack_value; -pub(crate) use stack_value::VMStackValue; - mod compiler; pub(crate) use compiler::Compiler; diff --git a/src/bytecode/stack_value.rs b/src/bytecode/stack_value.rs deleted file mode 100644 index a9e5b428..00000000 --- a/src/bytecode/stack_value.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::{Error, ErrorKind, TulispObject, TulispValue}; - -#[derive(Clone)] -pub(crate) enum VMStackValue { - TulispObject(TulispObject), - Bool(bool), - Float(f64), - Int(i64), -} - -macro_rules! impl_from_for_stack_value { - ($name:ident, $type:ty) => { - impl From<$type> for VMStackValue { - fn from(val: $type) -> Self { - VMStackValue::$name(val) - } - } - }; -} - -impl_from_for_stack_value!(Float, f64); -impl_from_for_stack_value!(Int, i64); -impl_from_for_stack_value!(Bool, bool); - -impl From for VMStackValue { - fn from(val: TulispObject) -> Self { - match &*val.inner_ref() { - TulispValue::Int { value } => return VMStackValue::Int(*value), - TulispValue::Float { value } => return VMStackValue::Float(*value), - TulispValue::T => return VMStackValue::Bool(true), - TulispValue::Nil => return VMStackValue::Bool(false), - _ => {} - } - VMStackValue::TulispObject(val) - } -} - -impl Into for VMStackValue { - fn into(self) -> TulispObject { - match self { - VMStackValue::TulispObject(obj) => obj, - VMStackValue::Bool(b) => b.into(), - VMStackValue::Float(fl) => fl.into(), - VMStackValue::Int(i) => i.into(), - } - } -} - -impl VMStackValue { - pub fn null(&self) -> bool { - match self { - VMStackValue::TulispObject(obj) => obj.null(), - VMStackValue::Bool(b) => !b, - VMStackValue::Float(_) | VMStackValue::Int(_) => false, - } - } - - pub fn add(&self, other: &VMStackValue) -> Result { - add(self, other) - } - - pub fn sub(&self, other: &VMStackValue) -> Result { - sub(self, other) - } - - pub fn mul(&self, other: &VMStackValue) -> Result { - mul(self, other) - } - - pub fn div(&self, other: &VMStackValue) -> Result { - div(self, other) - } - - pub fn lt(&self, other: &VMStackValue) -> Result { - lt(self, other) - } - - pub fn le(&self, other: &VMStackValue) -> Result { - le(self, other) - } - - pub fn gt(&self, other: &VMStackValue) -> Result { - gt(self, other) - } - - pub fn ge(&self, other: &VMStackValue) -> Result { - ge(self, other) - } - - pub fn equal(&self, other: &VMStackValue) -> bool { - match self { - VMStackValue::TulispObject(obj1) => match other { - VMStackValue::TulispObject(obj2) => obj1.equal(obj2), - VMStackValue::Bool(b2) => obj1.equal(&(*b2).into()), - VMStackValue::Float(fl2) => obj1.equal(&(*fl2).into()), - VMStackValue::Int(i2) => obj1.equal(&(*i2).into()), - }, - VMStackValue::Bool(b) => match other { - VMStackValue::Bool(b2) => b == b2, - _ => false, - }, - VMStackValue::Float(fl) => match other { - VMStackValue::Float(fl2) => fl == fl2, - _ => false, - }, - VMStackValue::Int(i) => match other { - VMStackValue::Int(i2) => i == i2, - _ => false, - }, - } - } - - pub fn eq(&self, other: &VMStackValue) -> bool { - match self { - VMStackValue::TulispObject(obj1) => match other { - VMStackValue::TulispObject(obj2) => obj1.eq(obj2), - _ => false, - }, - VMStackValue::Bool(b) => match other { - VMStackValue::Bool(b2) => b == b2, - _ => false, - }, - VMStackValue::Float(fl) => match other { - VMStackValue::Float(fl2) => fl == fl2, - _ => false, - }, - VMStackValue::Int(i) => match other { - VMStackValue::Int(i2) => i == i2, - _ => false, - }, - } - } -} - -impl std::fmt::Display for VMStackValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VMStackValue::TulispObject(obj) => write!(f, "{}", obj), - VMStackValue::Bool(b) => { - write!(f, "{}", if *b { TulispValue::T } else { TulispValue::Nil }) - } - VMStackValue::Float(fl) => write!(f, "{}", fl), - VMStackValue::Int(i) => write!(f, "{}", i), - } - } -} - -macro_rules! compare_ops { - ($name:ident, $oper:expr) => { - fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { - match selfobj { - VMStackValue::Float(f1) => match other { - VMStackValue::Float(f2) => Ok($oper(f1, f2)), - VMStackValue::Int(i2) => Ok($oper(f1, &(*i2 as f64))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - VMStackValue::Int(i1) => match other { - VMStackValue::Float(f2) => Ok($oper(&(*i1 as f64), f2)), - VMStackValue::Int(i2) => Ok($oper(i1, i2)), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", selfobj), - )), - } - } - }; -} - -compare_ops!(lt, std::cmp::PartialOrd::lt); -compare_ops!(le, std::cmp::PartialOrd::le); -compare_ops!(gt, std::cmp::PartialOrd::gt); -compare_ops!(ge, std::cmp::PartialOrd::ge); - -macro_rules! binary_ops { - ($name:ident, $oper:expr) => { - fn $name(selfobj: &VMStackValue, other: &VMStackValue) -> Result { - match selfobj { - VMStackValue::Float(f1) => match other { - VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(f1, f2))), - VMStackValue::Int(i2) => Ok(VMStackValue::Float($oper(f1, &(*i2 as f64)))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - VMStackValue::Int(i1) => match other { - VMStackValue::Float(f2) => Ok(VMStackValue::Float($oper(&(*i1 as f64), f2))), - VMStackValue::Int(i2) => Ok(VMStackValue::Int($oper(i1, i2))), - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", other), - )), - }, - _ => Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected number, found: {}", selfobj), - )), - } - } - }; -} - -binary_ops!(add, std::ops::Add::add); -binary_ops!(sub, std::ops::Sub::sub); -binary_ops!(mul, std::ops::Mul::mul); -binary_ops!(div, std::ops::Div::div); From 6ba18e2064dca9db5e47cb46fa7d68a6f8906f14 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 14:43:44 +0100 Subject: [PATCH 090/142] Call functions from the tree walker when not known in the vm --- src/bytecode/compiler/forms/mod.rs | 25 ++++++++++++------- .../compiler/forms/other_functions.rs | 8 ------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 607d5bf9..32d65338 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -96,9 +96,6 @@ impl VMFunctions { ("and", conditionals::compile_fn_and), ("or", conditionals::compile_fn_or), ("not", conditionals::compile_fn_not), - // noop - ("defmacro", other_functions::compile_fn_noop), - ("macroexpand", other_functions::compile_fn_noop), } VMFunctions { functions } } @@ -112,12 +109,22 @@ impl Compiler<'_> { let name = cons.car(); let args = cons.cdr(); if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { - compiler(self, &name, &args) - } else { - Err(Error::new( - ErrorKind::Undefined, - format!("undefined function: {}", name), - )) + return compiler(self, &name, &args); } + if let Ok(func) = self.ctx.eval(&name) { + match &*func.inner_ref() { + crate::value::TulispValue::Func(func) => { + return Ok(vec![ + Instruction::Push(args.clone().into()), + Instruction::RustCall { func: func.clone() }, + ]); + } + _ => {} + } + } + Err(Error::new( + ErrorKind::Undefined, + format!("undefined function: {}", name), + )) } } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 033103f3..6712a4a6 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -252,11 +252,3 @@ pub(super) fn compile_fn_progn( ) -> Result, Error> { Ok(compiler.compile_progn(args)?) } - -pub(crate) fn compile_fn_noop( - _compiler: &mut Compiler<'_>, - _name: &TulispObject, - _args: &TulispObject, -) -> Result, Error> { - Ok(vec![]) -} From ce6ad56a2f11853143e2e32491d33b49ef7f240b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 15:35:56 +0100 Subject: [PATCH 091/142] Move `Bytecode` into a separate submodule --- src/bytecode/bytecode.rs | 19 +++++++++++++++++++ src/bytecode/interpreter.rs | 20 +------------------- src/bytecode/mod.rs | 5 ++++- 3 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 src/bytecode/bytecode.rs diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs new file mode 100644 index 00000000..a1503817 --- /dev/null +++ b/src/bytecode/bytecode.rs @@ -0,0 +1,19 @@ +use super::Instruction; + +pub(crate) struct Bytecode { + pub(crate) instructions: Vec, +} + +impl Bytecode { + pub(crate) fn new(instructions: Vec) -> Self { + Self { instructions } + } + + #[allow(dead_code)] + pub(crate) fn print(&self) { + println!("start:"); + for (i, instr) in self.instructions.iter().enumerate() { + println!("{:<40} # {}", instr.to_string(), i); + } + } +} diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 260e6446..3ae3cfa9 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,11 +1,7 @@ -use super::Instruction; +use super::{bytecode::Bytecode, Instruction}; use crate::{bytecode::Pos, Error, TulispContext, TulispObject}; use std::collections::HashMap; -pub(crate) struct Bytecode { - instructions: Vec, -} - macro_rules! binary_ops { ($oper:expr) => {{ |selfobj: &TulispObject, other: &TulispObject| -> Result { @@ -46,20 +42,6 @@ macro_rules! compare_ops { }}; } -impl Bytecode { - pub(crate) fn new(instructions: Vec) -> Self { - Self { instructions } - } - - #[allow(dead_code)] - pub(crate) fn print(&self) { - println!("start:"); - for (i, instr) in self.instructions.iter().enumerate() { - println!("{:<40} # {}", instr.to_string(), i); - } - } -} - pub struct Machine { stack: Vec, bytecode: Bytecode, diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index a026d865..a85cc076 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -1,8 +1,11 @@ +mod bytecode; +pub(crate) use bytecode::Bytecode; + pub(crate) mod instruction; pub(crate) use instruction::{Instruction, Pos}; mod interpreter; -pub(crate) use interpreter::{Bytecode, Machine}; +pub(crate) use interpreter::Machine; mod compiler; pub(crate) use compiler::Compiler; From 43dd84fc684898e1cfc14948518279a4a1df00dd Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 15:46:05 +0100 Subject: [PATCH 092/142] Rename global namespace in Bytecode This would make more sense when we add individual bytecode programs individual functions. --- src/bytecode/bytecode.rs | 8 +++++--- src/bytecode/interpreter.rs | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index a1503817..4b29527e 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,18 +1,20 @@ use super::Instruction; pub(crate) struct Bytecode { - pub(crate) instructions: Vec, + pub(crate) global: Vec, } impl Bytecode { pub(crate) fn new(instructions: Vec) -> Self { - Self { instructions } + Self { + global: instructions, + } } #[allow(dead_code)] pub(crate) fn print(&self) { println!("start:"); - for (i, instr) in self.instructions.iter().enumerate() { + for (i, instr) in self.global.iter().enumerate() { println!("{:<40} # {}", instr.to_string(), i); } } diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 3ae3cfa9..7e535d6d 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -73,7 +73,7 @@ impl Machine { pub(crate) fn new(bytecode: Bytecode) -> Self { Machine { stack: Vec::new(), - labels: Self::locate_labels(&bytecode.instructions), + labels: Self::locate_labels(&bytecode.global), bytecode, // program: programs::print_range(92, 100), // program: programs::fib(30), @@ -99,7 +99,7 @@ impl Machine { } println!( "\nDepth: {}: PC: {}; Executing: {}", - recursion_depth, self.pc, self.bytecode.instructions[self.pc] + recursion_depth, self.pc, self.bytecode.global[self.pc] ); } @@ -109,9 +109,9 @@ impl Machine { } fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { - while self.pc < self.bytecode.instructions.len() { + while self.pc < self.bytecode.global.len() { // self.print_stack(recursion_depth); - let instr = &mut self.bytecode.instructions[self.pc]; + let instr = &mut self.bytecode.global[self.pc]; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Pop => { From b997e4138f6ec8c17f2205bd8c624a09ed4e728e Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 17:47:37 +0100 Subject: [PATCH 093/142] Compile functions into their own instruction sets This would allow overwriting functions individually. --- src/bytecode/bytecode.rs | 18 ++-- src/bytecode/compiler/compiler.rs | 5 +- .../compiler/forms/other_functions.rs | 20 ++--- src/bytecode/instruction.rs | 2 +- src/bytecode/interpreter.rs | 89 ++++++++++++------- 5 files changed, 84 insertions(+), 50 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 4b29527e..7d519571 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,14 +1,16 @@ +use std::{collections::HashMap, rc::Rc}; + use super::Instruction; +#[derive(Default)] pub(crate) struct Bytecode { - pub(crate) global: Vec, + pub(crate) global: Rc>, + pub(crate) functions: HashMap>>, } impl Bytecode { - pub(crate) fn new(instructions: Vec) -> Self { - Self { - global: instructions, - } + pub(crate) fn new() -> Self { + Self::default() } #[allow(dead_code)] @@ -17,5 +19,11 @@ impl Bytecode { for (i, instr) in self.global.iter().enumerate() { println!("{:<40} # {}", instr.to_string(), i); } + for (name, instr) in &self.functions { + println!("\n{}:", name); + for (i, instr) in instr.iter().enumerate() { + println!("{:<40} # {}", instr.to_string(), i); + } + } } } diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index a5d411e7..dc29c5ba 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -13,6 +13,7 @@ pub(crate) struct Compiler<'a> { pub functions: VMFunctions, pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx pub symbol_to_binding_idx: HashMap>, + pub bytecode: Bytecode, pub keep_result: bool, } @@ -24,12 +25,14 @@ impl<'a> Compiler<'a> { functions, defun_args: HashMap::new(), symbol_to_binding_idx: HashMap::new(), + bytecode: Bytecode::new(), keep_result: true, } } pub fn compile(mut self, value: &TulispObject) -> Result { - Ok(Bytecode::new(self.compile_progn(value)?)) + self.bytecode.global = self.compile_progn(value)?.into(); + Ok(self.bytecode) } pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 6712a4a6..da0086ee 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -134,7 +134,7 @@ pub(super) fn compile_fn_defun_call( for param in params { result.push(Instruction::BeginScope(param.clone())) } - result.push(Instruction::Call(Pos::Label(name.clone()))); + result.push(Instruction::Call(name.addr_as_usize())); for param in params { result.push(Instruction::EndScope(param.clone())); } @@ -149,7 +149,7 @@ pub(super) fn compile_fn_defun( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, true, |ctx, name, args, body| { + let mut res = compiler.compile_2_arg_call(name, args, true, |ctx, name, args, body| { ctx.functions .functions .insert(name.addr_as_usize(), compile_fn_defun_call); @@ -170,16 +170,16 @@ pub(super) fn compile_fn_defun( println!("mark_tail_calls error: {:?}", e); e })?; - let mut body = ctx.compile_progn_keep_result(&body)?; - - let mut result = vec![ - Instruction::Jump(Pos::Rel(body.len() as isize + 2)), - Instruction::Label(name.clone()), - ]; - result.append(&mut body); + let mut result = ctx.compile_progn_keep_result(&body)?; result.push(Instruction::Ret); + result.push(Instruction::Call(name.addr_as_usize())); Ok(result) - }) + })?; + let Some(Instruction::Call(addr)) = res.pop() else { + unreachable!() + }; + compiler.bytecode.functions.insert(addr, res.into()); + Ok(vec![]) } pub(super) fn compile_fn_let_star( diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index ece7ee2d..66ba20cc 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -104,7 +104,7 @@ pub(crate) enum Instruction { RustCall { func: Rc, }, - Call(Pos), + Call(usize), Ret, // lists Cons, diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 7e535d6d..1d116c38 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -46,22 +46,21 @@ pub struct Machine { stack: Vec, bytecode: Bytecode, labels: HashMap, // TulispObject.addr -> instruction index - pc: usize, } macro_rules! jump_to_pos { - ($self:ident, $pos:ident) => { - $self.pc = { + ($self: ident, $pc:ident, $pos:ident) => { + $pc = { match $pos { Pos::Abs(p) => *p, Pos::Rel(p) => { - let abs_pos = ($self.pc as isize + *p + 1) as usize; - *$pos = Pos::Abs(abs_pos); + let abs_pos = ($pc as isize + *p + 1) as usize; + // *$pos = Pos::Abs(abs_pos); abs_pos } Pos::Label(p) => { let abs_pos = *$self.labels.get(&p.addr_as_usize()).unwrap(); - *$pos = Pos::Abs(abs_pos); + // *$pos = Pos::Abs(abs_pos); // TODO: uncomment abs_pos } } @@ -71,47 +70,73 @@ macro_rules! jump_to_pos { impl Machine { pub(crate) fn new(bytecode: Bytecode) -> Self { + let labels = Self::locate_labels(&bytecode); Machine { stack: Vec::new(), - labels: Self::locate_labels(&bytecode.global), + labels, bytecode, // program: programs::print_range(92, 100), // program: programs::fib(30), - pc: 0, } } - fn locate_labels(program: &Vec) -> HashMap { + fn locate_labels(bytecode: &Bytecode) -> HashMap { + // TODO: intern-soft and make sure that the labels are unique let mut labels = HashMap::new(); - for (i, instr) in program.iter().enumerate() { + for (i, instr) in bytecode.global.iter().enumerate() { if let Instruction::Label(name) = instr { labels.insert(name.addr_as_usize(), i + 1); } } + for (_, instr) in &bytecode.functions { + for (i, instr) in instr.iter().enumerate() { + if let Instruction::Label(name) = instr { + labels.insert(name.addr_as_usize(), i + 1); + } + } + } labels } #[allow(dead_code)] - fn print_stack(&self, recursion_depth: u32) { + fn print_stack(&self, func: Option, pc: usize, recursion_depth: u32) { println!("Stack:"); for obj in self.stack.iter() { println!(" {}", obj); } println!( "\nDepth: {}: PC: {}; Executing: {}", - recursion_depth, self.pc, self.bytecode.global[self.pc] + recursion_depth, + pc, + if let Some(func) = func { + self.bytecode.functions.get(&func).unwrap()[pc].clone() + } else { + self.bytecode.global[pc].clone() + } ); } pub fn run(&mut self, ctx: &mut TulispContext) -> Result { - self.run_impl(ctx, 0)?; + self.run_impl(ctx, None, 0)?; Ok(self.stack.pop().unwrap().into()) } - fn run_impl(&mut self, ctx: &mut TulispContext, recursion_depth: u32) -> Result<(), Error> { - while self.pc < self.bytecode.global.len() { - // self.print_stack(recursion_depth); - let instr = &mut self.bytecode.global[self.pc]; + fn run_impl( + &mut self, + ctx: &mut TulispContext, + func: Option, + recursion_depth: u32, + ) -> Result<(), Error> { + let mut pc: usize = 0; + let program = if let Some(func) = func { + self.bytecode.functions.get(&func).unwrap().clone() + } else { + self.bytecode.global.clone() + }; + let program_size = program.len(); + while pc < program_size { + // self.print_stack(func, pc, recursion_depth); + let instr = &program[pc]; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Pop => { @@ -147,7 +172,7 @@ impl Machine { let cmp = a.null(); self.stack.truncate(self.stack.len() - 1); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } @@ -156,14 +181,14 @@ impl Machine { let cmp = !a.null(); self.stack.truncate(self.stack.len() - 1); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } Instruction::JumpIfNilElsePop(pos) => { let a = self.stack.last().unwrap(); if a.null() { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } else { self.stack.truncate(self.stack.len() - 1); @@ -172,7 +197,7 @@ impl Machine { Instruction::JumpIfNotNilElsePop(pos) => { let a = self.stack.last().unwrap(); if !a.null() { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } else { self.stack.truncate(self.stack.len() - 1); @@ -186,7 +211,7 @@ impl Machine { let cmp = !a.eq(&b); self.stack.truncate(minus2); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } @@ -198,7 +223,7 @@ impl Machine { let cmp = compare_ops!(|a, b| a < b)(a, b)?; self.stack.truncate(minus2); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } @@ -210,7 +235,7 @@ impl Machine { let cmp = compare_ops!(|a, b| a <= b)(a, b)?; self.stack.truncate(minus2); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } @@ -222,7 +247,7 @@ impl Machine { let cmp = compare_ops!(|a, b| a > b)(a, b)?; self.stack.truncate(minus2); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } @@ -234,12 +259,12 @@ impl Machine { let cmp = compare_ops!(|a, b| a >= b)(a, b)?; self.stack.truncate(minus2); if cmp { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } } Instruction::Jump(pos) => { - jump_to_pos!(self, pos); + jump_to_pos!(self, pc, pos); continue; } Instruction::Equal => { @@ -292,11 +317,9 @@ impl Machine { Instruction::EndScope(obj) => { obj.unset().unwrap(); } - Instruction::Call(pos) => { - let pc = self.pc; - jump_to_pos!(self, pos); - self.run_impl(ctx, recursion_depth + 1)?; - self.pc = pc; + Instruction::Call(func) => { + let func = Some(*func); + self.run_impl(ctx, func, recursion_depth + 1)?; } Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { @@ -371,7 +394,7 @@ impl Machine { *self.stack.last_mut().unwrap() = a.into(); } } - self.pc += 1; + pc += 1; } Ok(()) } From fca2845c388e436bb5e38f69a7e32b11216ae23c Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 21:08:12 +0100 Subject: [PATCH 094/142] Make instructions modifiable again --- src/bytecode/bytecode.rs | 10 +++++----- src/bytecode/compiler/compiler.rs | 4 ++-- .../compiler/forms/other_functions.rs | 7 ++++++- src/bytecode/instruction.rs | 5 +---- src/bytecode/interpreter.rs | 19 +++++++++++-------- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 7d519571..599766f3 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use super::Instruction; #[derive(Default)] pub(crate) struct Bytecode { - pub(crate) global: Rc>, - pub(crate) functions: HashMap>>, + pub(crate) global: Rc>>, + pub(crate) functions: HashMap>>>, } impl Bytecode { @@ -16,12 +16,12 @@ impl Bytecode { #[allow(dead_code)] pub(crate) fn print(&self) { println!("start:"); - for (i, instr) in self.global.iter().enumerate() { + for (i, instr) in self.global.borrow().iter().enumerate() { println!("{:<40} # {}", instr.to_string(), i); } for (name, instr) in &self.functions { println!("\n{}:", name); - for (i, instr) in instr.iter().enumerate() { + for (i, instr) in instr.borrow().iter().enumerate() { println!("{:<40} # {}", instr.to_string(), i); } } diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index dc29c5ba..aa0ae9ec 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ bytecode::{Bytecode, Instruction}, @@ -31,7 +31,7 @@ impl<'a> Compiler<'a> { } pub fn compile(mut self, value: &TulispObject) -> Result { - self.bytecode.global = self.compile_progn(value)?.into(); + self.bytecode.global = Rc::new(RefCell::new(self.compile_progn(value)?)); Ok(self.bytecode) } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index da0086ee..982552ec 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -1,3 +1,5 @@ +use std::{cell::RefCell, rc::Rc}; + use crate::{ bytecode::{Compiler, Instruction, Pos}, destruct_bind, @@ -178,7 +180,10 @@ pub(super) fn compile_fn_defun( let Some(Instruction::Call(addr)) = res.pop() else { unreachable!() }; - compiler.bytecode.functions.insert(addr, res.into()); + compiler + .bytecode + .functions + .insert(addr, Rc::new(RefCell::new(res))); Ok(vec![]) } diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 66ba20cc..da6b36de 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -100,10 +100,7 @@ pub(crate) enum Instruction { Jump(Pos), // functions Label(TulispObject), - #[allow(dead_code)] - RustCall { - func: Rc, - }, + RustCall { func: Rc }, Call(usize), Ret, // lists diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 1d116c38..8b7304ae 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -55,12 +55,12 @@ macro_rules! jump_to_pos { Pos::Abs(p) => *p, Pos::Rel(p) => { let abs_pos = ($pc as isize + *p + 1) as usize; - // *$pos = Pos::Abs(abs_pos); + *$pos = Pos::Abs(abs_pos); abs_pos } Pos::Label(p) => { let abs_pos = *$self.labels.get(&p.addr_as_usize()).unwrap(); - // *$pos = Pos::Abs(abs_pos); // TODO: uncomment + *$pos = Pos::Abs(abs_pos); // TODO: uncomment abs_pos } } @@ -83,13 +83,13 @@ impl Machine { fn locate_labels(bytecode: &Bytecode) -> HashMap { // TODO: intern-soft and make sure that the labels are unique let mut labels = HashMap::new(); - for (i, instr) in bytecode.global.iter().enumerate() { + for (i, instr) in bytecode.global.borrow().iter().enumerate() { if let Instruction::Label(name) = instr { labels.insert(name.addr_as_usize(), i + 1); } } for (_, instr) in &bytecode.functions { - for (i, instr) in instr.iter().enumerate() { + for (i, instr) in instr.borrow().iter().enumerate() { if let Instruction::Label(name) = instr { labels.insert(name.addr_as_usize(), i + 1); } @@ -109,9 +109,9 @@ impl Machine { recursion_depth, pc, if let Some(func) = func { - self.bytecode.functions.get(&func).unwrap()[pc].clone() + self.bytecode.functions.get(&func).unwrap().borrow()[pc].clone() } else { - self.bytecode.global[pc].clone() + self.bytecode.global.borrow()[pc].clone() } ); } @@ -133,10 +133,11 @@ impl Machine { } else { self.bytecode.global.clone() }; - let program_size = program.len(); + let program_size = program.borrow().len(); + let mut instr_ref = program.borrow_mut(); while pc < program_size { // self.print_stack(func, pc, recursion_depth); - let instr = &program[pc]; + let instr = &mut instr_ref[pc]; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), Instruction::Pop => { @@ -319,7 +320,9 @@ impl Machine { } Instruction::Call(func) => { let func = Some(*func); + drop(instr_ref); self.run_impl(ctx, func, recursion_depth + 1)?; + instr_ref = program.borrow_mut(); } Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { From a32ad2e8f1e6ee2e335945f5832ad4f14a40dc8b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 9 Feb 2024 21:16:06 +0100 Subject: [PATCH 095/142] Revert back to better formatted output for fib.lisp --- examples/fib.lisp | 6 +++--- src/bytecode/interpreter.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/fib.lisp b/examples/fib.lisp index add265ae..71200cf1 100644 --- a/examples/fib.lisp +++ b/examples/fib.lisp @@ -4,7 +4,7 @@ (+ (fib (- n 1)) (fib (- n 2))))) -;; (let ((tgt 30)) -;; (print (format "\n (fib %d)\n %d\n" tgt (fib tgt)))) +(let* ((tgt 30) + (res (fib tgt))) + (print (format "\n (fib %d)\n %d\n" tgt res))) -(print (fib 30)) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 8b7304ae..2d5874f6 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -162,11 +162,11 @@ impl Machine { } Instruction::PrintPop => { let a = self.stack.pop().unwrap(); - println!("{}", a); + println!("{}", a.fmt_string()); } Instruction::Print => { let a = self.stack.last().unwrap(); - println!("{}", a); + println!("{}", a.fmt_string()); } Instruction::JumpIfNil(pos) => { let a = self.stack.last().unwrap(); From 288b5cf656767b710eb43fff4d3c12d52f75c51c Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 00:33:52 +0100 Subject: [PATCH 096/142] Add a `LoadFile` instruction --- src/bytecode/bytecode.rs | 6 +++++ src/bytecode/compiler/forms/mod.rs | 1 + .../compiler/forms/other_functions.rs | 12 ++++++++++ src/bytecode/instruction.rs | 2 ++ src/bytecode/interpreter.rs | 18 +++++++++++++- src/context.rs | 24 +++++++++++-------- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 599766f3..4fa3b6fd 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -26,4 +26,10 @@ impl Bytecode { } } } + + pub(crate) fn import_functions(&mut self, other: &Bytecode) { + for (name, instr) in &other.functions { + self.functions.insert(*name, instr.clone()); + } + } } diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 32d65338..6a266304 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -47,6 +47,7 @@ impl VMFunctions { ("*", arithmetic_operations::compile_fn_mul), ("/", arithmetic_operations::compile_fn_div), // other functions + ("load", other_functions::compile_fn_load_file), ("print", other_functions::compile_fn_print), ("quote", other_functions::compile_fn_quote), ("setq", other_functions::compile_fn_setq), diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 982552ec..64828465 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -257,3 +257,15 @@ pub(super) fn compile_fn_progn( ) -> Result, Error> { Ok(compiler.compile_progn(args)?) } + +pub(super) fn compile_fn_load_file( + compiler: &mut Compiler<'_>, + _name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_1_arg_call(_name, args, true, |compiler, arg, _| { + let mut result = compiler.compile_expr_keep_result(&arg)?; + result.push(Instruction::LoadFile); + Ok(result) + }) +} diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index da6b36de..12585d07 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -76,6 +76,7 @@ pub(crate) enum Instruction { // arithmetic BinaryOp(BinaryOp), // io + LoadFile, PrintPop, Print, // comparison @@ -126,6 +127,7 @@ impl std::fmt::Display for Instruction { BinaryOp::Mul => write!(f, " mul"), BinaryOp::Div => write!(f, " div"), }, + Instruction::LoadFile => write!(f, " load_file"), Instruction::PrintPop => write!(f, " print_pop"), Instruction::Print => write!(f, " print"), Instruction::Null => write!(f, " null"), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 2d5874f6..44e522b5 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,4 +1,4 @@ -use super::{bytecode::Bytecode, Instruction}; +use super::{bytecode::Bytecode, Compiler, Instruction}; use crate::{bytecode::Pos, Error, TulispContext, TulispObject}; use std::collections::HashMap; @@ -160,6 +160,22 @@ impl Machine { self.stack.truncate(self.stack.len() - 2); self.stack.push(vv); } + Instruction::LoadFile => { + let filename = self.stack.pop().unwrap(); + let filename = filename + .as_string() + .map_err(|err| err.with_trace(filename))?; + let ast = ctx.parse_file(&filename)?; + let bytecode = Compiler::new(ctx).compile(&ast)?; + // TODO: support global code in modules + if bytecode.global.borrow().len() > 0 { + return Err(Error::new( + crate::ErrorKind::Undefined, + "Cannot load a file with global code".to_string(), + )); + } + self.bytecode.import_functions(&bytecode); + } Instruction::PrintPop => { let a = self.stack.pop().unwrap(); println!("{}", a.fmt_string()); diff --git a/src/context.rs b/src/context.rs index 71a403f2..1c22b36f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -201,17 +201,8 @@ impl TulispContext { } pub fn vm_eval_file(&mut self, filename: &str) -> Result { - let contents = fs::read_to_string(filename).map_err(|e| { - Error::new( - crate::ErrorKind::Undefined, - format!("Unable to read file: {filename}. Error: {e}"), - ) - })?; - self.filenames.push(filename.to_owned()); - - let string: &str = &contents; let start = std::time::Instant::now(); - let vv = parse(self, self.filenames.len() - 1, string)?; + let vv = self.parse_file(filename)?; println!("Parsing took: {:?}", start.elapsed()); let start = std::time::Instant::now(); let bytecode = Compiler::new(self).compile(&vv)?; @@ -226,4 +217,17 @@ impl TulispContext { pub(crate) fn get_filename(&self, file_id: usize) -> String { self.filenames[file_id].clone() } + + pub(crate) fn parse_file(&mut self, filename: &str) -> Result { + let contents = fs::read_to_string(filename).map_err(|e| { + Error::new( + crate::ErrorKind::Undefined, + format!("Unable to read file: {filename}. Error: {e}"), + ) + })?; + self.filenames.push(filename.to_owned()); + + let string: &str = &contents; + parse(self, self.filenames.len() - 1, string) + } } From 24e900e805610c833fe3a1c75259a15b4eacd2a7 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 01:39:53 +0100 Subject: [PATCH 097/142] Rewrite how function arguments are set at call time By doing more at runtime, we are able to compile calls to functions that haven't been declared yet. This incurs a small perf cost, and reduces compile time checks, but those can be optimized separately. --- examples/fib.lisp | 1 - src/bytecode/bytecode.rs | 8 ++++-- src/bytecode/compiler/compiler.rs | 1 + src/bytecode/compiler/forms/mod.rs | 7 ++--- .../compiler/forms/other_functions.rs | 25 ++++++++++------- src/bytecode/instruction.rs | 11 ++++++-- src/bytecode/interpreter.rs | 28 +++++++++++++++++-- 7 files changed, 57 insertions(+), 24 deletions(-) diff --git a/examples/fib.lisp b/examples/fib.lisp index 71200cf1..3f9a02c2 100644 --- a/examples/fib.lisp +++ b/examples/fib.lisp @@ -7,4 +7,3 @@ (let* ((tgt 30) (res (fib tgt))) (print (format "\n (fib %d)\n %d\n" tgt res))) - diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 4fa3b6fd..238635ab 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,11 +1,14 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use crate::TulispObject; + use super::Instruction; #[derive(Default)] pub(crate) struct Bytecode { pub(crate) global: Rc>>, pub(crate) functions: HashMap>>>, + pub(crate) defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx } impl Bytecode { @@ -28,8 +31,7 @@ impl Bytecode { } pub(crate) fn import_functions(&mut self, other: &Bytecode) { - for (name, instr) in &other.functions { - self.functions.insert(*name, instr.clone()); - } + self.functions.extend(other.functions.clone()); + self.defun_args.extend(other.defun_args.clone()); } } diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index aa0ae9ec..f7b606ed 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -32,6 +32,7 @@ impl<'a> Compiler<'a> { pub fn compile(mut self, value: &TulispObject) -> Result { self.bytecode.global = Rc::new(RefCell::new(self.compile_progn(value)?)); + self.bytecode.defun_args = self.defun_args; Ok(self.bytecode) } diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 6a266304..79daf8d6 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{bytecode::Instruction, Error, ErrorKind, TulispContext, TulispObject}; +use crate::{bytecode::Instruction, Error, TulispContext, TulispObject}; use super::Compiler; @@ -123,9 +123,6 @@ impl Compiler<'_> { _ => {} } } - Err(Error::new( - ErrorKind::Undefined, - format!("undefined function: {}", name), - )) + other_functions::compile_fn_defun_call(self, &name, &args) } } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 64828465..a3e812e2 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -115,6 +115,8 @@ fn compile_fn_defun_bounce_call( for arg in args.cdr()?.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); } + // TODO: use .get() instead of indexing, to not crash if the function is not + // found let params = &compiler.defun_args[&name.addr_as_usize()]; for param in params { result.push(Instruction::StorePop(param.clone())) @@ -132,14 +134,11 @@ pub(super) fn compile_fn_defun_call( for arg in args.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); } - let params = &compiler.defun_args[&name.addr_as_usize()]; - for param in params { - result.push(Instruction::BeginScope(param.clone())) - } - result.push(Instruction::Call(name.addr_as_usize())); - for param in params { - result.push(Instruction::EndScope(param.clone())); - } + let params = compiler.defun_args.get(&name.addr_as_usize()); + result.push(Instruction::Call { + addr: name.addr_as_usize(), + args: params.cloned(), + }); if !compiler.keep_result { result.push(Instruction::Pop); } @@ -174,10 +173,16 @@ pub(super) fn compile_fn_defun( })?; let mut result = ctx.compile_progn_keep_result(&body)?; result.push(Instruction::Ret); - result.push(Instruction::Call(name.addr_as_usize())); + + // This use of a `List` instruction is a hack to get the address of the + // function we just compiled so we can insert it into the + // bytecode.functions map. The instruction is discarded as soon as it is + // read, and isn't part of the compiler's output. + result.push(Instruction::List(name.addr_as_usize())); + Ok(result) })?; - let Some(Instruction::Call(addr)) = res.pop() else { + let Some(Instruction::List(addr)) = res.pop() else { unreachable!() }; compiler diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 12585d07..19ea6d12 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -101,8 +101,13 @@ pub(crate) enum Instruction { Jump(Pos), // functions Label(TulispObject), - RustCall { func: Rc }, - Call(usize), + RustCall { + func: Rc, + }, + Call { + addr: usize, + args: Option>, + }, Ret, // lists Cons, @@ -147,7 +152,7 @@ impl std::fmt::Display for Instruction { Instruction::Gt => write!(f, " cgt"), Instruction::GtEq => write!(f, " cge"), Instruction::Jump(pos) => write!(f, " jmp {}", pos), - Instruction::Call(pos) => write!(f, " call {}", pos), + Instruction::Call { addr, .. } => write!(f, " call {}", addr), Instruction::Ret => write!(f, " ret"), Instruction::RustCall { .. } => write!(f, " rustcall"), Instruction::Label(name) => write!(f, "{}:", name), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 44e522b5..2790a48e 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -334,11 +334,35 @@ impl Machine { Instruction::EndScope(obj) => { obj.unset().unwrap(); } - Instruction::Call(func) => { - let func = Some(*func); + Instruction::Call { addr, args } => { + let addr = *addr; + let func = Some(addr); + if args.is_none() { + if let Some(items) = self.bytecode.defun_args.get(&addr) { + *args = Some(items.clone()); + } else { + return Err(Error::new( + crate::ErrorKind::Undefined, + // TODO: err name with span instead of addr + format!("undefined function: {}", addr), + )); + } + } + + if let Some(args) = args { + for arg in args.iter() { + arg.set_scope(self.stack.pop().unwrap()).unwrap(); + } + } + drop(instr_ref); self.run_impl(ctx, func, recursion_depth + 1)?; instr_ref = program.borrow_mut(); + if let Some(args) = self.bytecode.defun_args.get(&addr) { + for arg in args.iter() { + arg.unset().unwrap(); + } + } } Instruction::Ret => return Ok(()), Instruction::RustCall { func } => { From edb2526f72c3db8d33781c26ae98f17d3b6ce175 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 02:09:03 +0100 Subject: [PATCH 098/142] Declare `defmacro` to be a noop in the VM `defmacro`s are executed and expanded during parsing. They don't need to be around during execution in the VM. --- src/bytecode/compiler/forms/mod.rs | 2 ++ src/bytecode/compiler/forms/other_functions.rs | 8 ++++++++ src/bytecode/interpreter.rs | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 79daf8d6..8b312b83 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -97,6 +97,8 @@ impl VMFunctions { ("and", conditionals::compile_fn_and), ("or", conditionals::compile_fn_or), ("not", conditionals::compile_fn_not), + // noop + ("defmacro", other_functions::compile_fn_noop), } VMFunctions { functions } } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index a3e812e2..d08aae59 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -274,3 +274,11 @@ pub(super) fn compile_fn_load_file( Ok(result) }) } + +pub(super) fn compile_fn_noop( + _compiler: &mut Compiler<'_>, + _name: &TulispObject, + _args: &TulispObject, +) -> Result, Error> { + Ok(vec![]) +} diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 2790a48e..1e9c59c5 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -136,7 +136,10 @@ impl Machine { let program_size = program.borrow().len(); let mut instr_ref = program.borrow_mut(); while pc < program_size { + // drop(instr_ref); // self.print_stack(func, pc, recursion_depth); + // instr_ref = program.borrow_mut(); + let instr = &mut instr_ref[pc]; match instr { Instruction::Push(obj) => self.stack.push(obj.clone()), @@ -174,6 +177,8 @@ impl Machine { "Cannot load a file with global code".to_string(), )); } + // bytecode.print(); + self.labels.extend(Self::locate_labels(&bytecode)); self.bytecode.import_functions(&bytecode); } Instruction::PrintPop => { From 120ca65d6a8dcfab07d4a64b3e7ef2c86e58ee44 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 12:19:55 +0100 Subject: [PATCH 099/142] Compile calls to `set` --- src/bytecode/compiler/forms/mod.rs | 1 + src/bytecode/compiler/forms/other_functions.rs | 17 +++++++++++++++++ src/bytecode/instruction.rs | 4 ++++ src/bytecode/interpreter.rs | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 8b312b83..7956bbe4 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -51,6 +51,7 @@ impl VMFunctions { ("print", other_functions::compile_fn_print), ("quote", other_functions::compile_fn_quote), ("setq", other_functions::compile_fn_setq), + ("set", other_functions::compile_fn_set), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), ("let", other_functions::compile_fn_let_star), diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index d08aae59..22ebfb03 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -53,6 +53,23 @@ pub(super) fn compile_fn_setq( }) } +pub(super) fn compile_fn_set( + compiler: &mut Compiler<'_>, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { + let mut result = compiler.compile_expr_keep_result(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + if compiler.keep_result { + result.push(Instruction::Set); + } else { + result.push(Instruction::SetPop); + } + Ok(result) + }) +} + pub(super) fn compile_fn_cons( compiler: &mut Compiler<'_>, name: &TulispObject, diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 19ea6d12..ab745405 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -68,6 +68,8 @@ pub(crate) enum Instruction { Push(TulispObject), Pop, // variables + Set, + SetPop, StorePop(TulispObject), Store(TulispObject), Load(TulispObject), @@ -121,6 +123,8 @@ impl std::fmt::Display for Instruction { match self { Instruction::Push(obj) => write!(f, " push {}", obj), Instruction::Pop => write!(f, " pop"), + Instruction::Set => write!(f, " set"), + Instruction::SetPop => write!(f, " set_pop"), Instruction::StorePop(obj) => write!(f, " store_pop {}", obj), Instruction::Store(obj) => write!(f, " store {}", obj), Instruction::Load(obj) => write!(f, " load {}", obj), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 1e9c59c5..2b67a79e 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -319,6 +319,24 @@ impl Machine { let b = self.stack.pop().unwrap(); self.stack.push(compare_ops!(|a, b| a >= b)(&a, &b)?.into()); } + Instruction::Set => { + let minus2 = self.stack.len() - 2; + let [ref value, ref variable] = self.stack[minus2..] else { + unreachable!() + }; + variable.set(value.clone()).unwrap(); + // remove just the variable from the stack, keep the value + self.stack.truncate(self.stack.len() - 1); + } + Instruction::SetPop => { + let minus2 = self.stack.len() - 2; + let [ref value, ref variable] = self.stack[minus2..] else { + unreachable!() + }; + variable.set(value.clone()).unwrap(); + // remove both variable and value from stack. + self.stack.truncate(minus2); + } Instruction::StorePop(obj) => { let a = self.stack.pop().unwrap(); obj.set(a.into()).unwrap(); From 42a468811428dcb775fa3111080a80842c880803 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 12:36:28 +0100 Subject: [PATCH 100/142] Display function names in errors and bytecode outputs --- src/bytecode/compiler/forms/mod.rs | 5 ++++- src/bytecode/compiler/forms/other_functions.rs | 2 +- src/bytecode/instruction.rs | 7 ++++--- src/bytecode/interpreter.rs | 12 ++++++------ 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 7956bbe4..2c73a003 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -120,7 +120,10 @@ impl Compiler<'_> { crate::value::TulispValue::Func(func) => { return Ok(vec![ Instruction::Push(args.clone().into()), - Instruction::RustCall { func: func.clone() }, + Instruction::RustCall { + name: name.clone(), + func: func.clone(), + }, ]); } _ => {} diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 22ebfb03..beeb674a 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -153,7 +153,7 @@ pub(super) fn compile_fn_defun_call( } let params = compiler.defun_args.get(&name.addr_as_usize()); result.push(Instruction::Call { - addr: name.addr_as_usize(), + name: name.clone(), args: params.cloned(), }); if !compiler.keep_result { diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index ab745405..3419a916 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -104,10 +104,11 @@ pub(crate) enum Instruction { // functions Label(TulispObject), RustCall { + name: TulispObject, func: Rc, }, Call { - addr: usize, + name: TulispObject, args: Option>, }, Ret, @@ -156,9 +157,9 @@ impl std::fmt::Display for Instruction { Instruction::Gt => write!(f, " cgt"), Instruction::GtEq => write!(f, " cge"), Instruction::Jump(pos) => write!(f, " jmp {}", pos), - Instruction::Call { addr, .. } => write!(f, " call {}", addr), + Instruction::Call { name, .. } => write!(f, " call {}", name), Instruction::Ret => write!(f, " ret"), - Instruction::RustCall { .. } => write!(f, " rustcall"), + Instruction::RustCall { name, .. } => write!(f, " rustcall {}", name), Instruction::Label(name) => write!(f, "{}:", name), Instruction::Cons => write!(f, " cons"), Instruction::List(len) => write!(f, " list {}", len), diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 2b67a79e..b73f3f29 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -357,8 +357,8 @@ impl Machine { Instruction::EndScope(obj) => { obj.unset().unwrap(); } - Instruction::Call { addr, args } => { - let addr = *addr; + Instruction::Call { name, args } => { + let addr = name.addr_as_usize(); let func = Some(addr); if args.is_none() { if let Some(items) = self.bytecode.defun_args.get(&addr) { @@ -366,9 +366,9 @@ impl Machine { } else { return Err(Error::new( crate::ErrorKind::Undefined, - // TODO: err name with span instead of addr - format!("undefined function: {}", addr), - )); + format!("undefined function: {}", name), + ) + .with_trace(name.clone())); } } @@ -388,7 +388,7 @@ impl Machine { } } Instruction::Ret => return Ok(()), - Instruction::RustCall { func } => { + Instruction::RustCall { func, .. } => { let args = self.stack.pop().unwrap(); self.stack.push(func(ctx, &args)?.into()); } From aca245334dfe45c8144eb895d58109d31f68c394 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 15:02:33 +0100 Subject: [PATCH 101/142] Implement `Display` for Bytecode, rather than a custom `print` method --- src/bytecode/bytecode.rs | 26 ++++++++++++++------------ src/bytecode/interpreter.rs | 2 +- src/context.rs | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 238635ab..43b07c95 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; use crate::TulispObject; @@ -11,23 +11,25 @@ pub(crate) struct Bytecode { pub(crate) defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx } -impl Bytecode { - pub(crate) fn new() -> Self { - Self::default() - } - - #[allow(dead_code)] - pub(crate) fn print(&self) { - println!("start:"); +impl fmt::Display for Bytecode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "start:\n")?; for (i, instr) in self.global.borrow().iter().enumerate() { - println!("{:<40} # {}", instr.to_string(), i); + write!(f, "{:<40} # {}\n", instr.to_string(), i)?; } for (name, instr) in &self.functions { - println!("\n{}:", name); + write!(f, "\n{}:", name)?; for (i, instr) in instr.borrow().iter().enumerate() { - println!("{:<40} # {}", instr.to_string(), i); + write!(f, "\n{:<40} # {}", instr.to_string(), i)?; } } + Ok(()) + } +} + +impl Bytecode { + pub(crate) fn new() -> Self { + Self::default() } pub(crate) fn import_functions(&mut self, other: &Bytecode) { diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index b73f3f29..2bbd408e 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -177,7 +177,7 @@ impl Machine { "Cannot load a file with global code".to_string(), )); } - // bytecode.print(); + // println!("{}", bytecode); self.labels.extend(Self::locate_labels(&bytecode)); self.bytecode.import_functions(&bytecode); } diff --git a/src/context.rs b/src/context.rs index 1c22b36f..d4fe4bc6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -207,7 +207,7 @@ impl TulispContext { let start = std::time::Instant::now(); let bytecode = Compiler::new(self).compile(&vv)?; println!("Compiling took: {:?}", start.elapsed()); - bytecode.print(); + println!("{}", bytecode); let start = std::time::Instant::now(); let ret = bytecode::Machine::new(bytecode).run(self); println!("Running took: {:?}", start.elapsed()); From fe9914b1716b72c9c690c47bcd190c35dd955491 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 10 Feb 2024 15:22:34 +0100 Subject: [PATCH 102/142] Use sequentially numbered labels instead of arbitrary ones --- src/bytecode/compiler/compiler.rs | 7 +++++++ src/bytecode/compiler/forms/comparison_of_numbers.rs | 2 +- src/bytecode/compiler/forms/conditionals.rs | 6 +++--- src/bytecode/instruction.rs | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index f7b606ed..68a8bd90 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -15,6 +15,7 @@ pub(crate) struct Compiler<'a> { pub symbol_to_binding_idx: HashMap>, pub bytecode: Bytecode, pub keep_result: bool, + label_counter: usize, } impl<'a> Compiler<'a> { @@ -27,9 +28,15 @@ impl<'a> Compiler<'a> { symbol_to_binding_idx: HashMap::new(), bytecode: Bytecode::new(), keep_result: true, + label_counter: 0, } } + pub fn new_label(&mut self) -> TulispObject { + self.label_counter += 1; + TulispObject::symbol(format!(":{}", self.label_counter), true) + } + pub fn compile(mut self, value: &TulispObject) -> Result { self.bytecode.global = Rc::new(RefCell::new(self.compile_progn(value)?)); self.bytecode.defun_args = self.defun_args; diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index 66824195..5e29c718 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -12,7 +12,7 @@ fn compile_fn_compare( if !compiler.keep_result { return Ok(vec![]); } - let label = TulispObject::symbol("_".to_string(), false); + let label = compiler.new_label(); let mut result = vec![]; let args = args.base_iter().collect::>(); if args.len() < 2 { diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 3db9f99b..52b29f9a 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -57,7 +57,7 @@ pub(super) fn compile_fn_cond( args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; - let cond_end = TulispObject::symbol("cond-end".to_string(), true); + let cond_end = compiler.new_label(); for branch in args.base_iter() { result.append( @@ -119,7 +119,7 @@ pub(super) fn compile_fn_and( args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; - let label = TulispObject::symbol("end-and".to_string(), false); + let label = compiler.new_label(); let mut need_label = false; for item in args.base_iter() { let expr_result = &mut compiler.compile_expr(&item)?; @@ -148,7 +148,7 @@ pub(super) fn compile_fn_or( args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; - let label = TulispObject::symbol("end-or".to_string(), false); + let label = compiler.new_label(); let mut need_label = false; for item in args.base_iter() { let expr_result = &mut compiler.compile_expr(&item)?; diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 3419a916..74c6d825 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -160,7 +160,7 @@ impl std::fmt::Display for Instruction { Instruction::Call { name, .. } => write!(f, " call {}", name), Instruction::Ret => write!(f, " ret"), Instruction::RustCall { name, .. } => write!(f, " rustcall {}", name), - Instruction::Label(name) => write!(f, "{}:", name), + Instruction::Label(name) => write!(f, "{}", name), Instruction::Cons => write!(f, " cons"), Instruction::List(len) => write!(f, " list {}", len), Instruction::Append(len) => write!(f, " append {}", len), From 85634f90fa8bae50447d73226ac205e7c8eb3943 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 02:37:48 +0100 Subject: [PATCH 103/142] Load values only when necessary This exposed a bug in the `set` implementation, where variables always need to be loaded, because `set` has side-effects. --- src/bytecode/compiler/compiler.rs | 8 +++++++- src/bytecode/compiler/forms/other_functions.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 68a8bd90..a97d8710 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -115,7 +115,13 @@ impl<'a> Compiler<'a> { TulispValue::List { cons, .. } => self .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), - TulispValue::Symbol { .. } => Ok(vec![Instruction::Load(expr.clone())]), + TulispValue::Symbol { .. } => { + if self.keep_result { + return Ok(vec![Instruction::Load(expr.clone())]); + } else { + return Ok(vec![]); + } + } TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { return Err(Error::new( crate::ErrorKind::SyntaxError, diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index beeb674a..29722a93 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -60,7 +60,7 @@ pub(super) fn compile_fn_set( ) -> Result, Error> { compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { let mut result = compiler.compile_expr_keep_result(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); + result.append(&mut compiler.compile_expr_keep_result(arg1)?); if compiler.keep_result { result.push(Instruction::Set); } else { From 1c20eec760b3254134f4087635b6c56542f635ef Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 03:04:15 +0100 Subject: [PATCH 104/142] Allow side-effects of arguments when compiling numeric comparisons --- .../compiler/forms/comparison_of_numbers.rs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index 5e29c718..2d3185e3 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -9,9 +9,6 @@ fn compile_fn_compare( args: &TulispObject, instruction: Instruction, ) -> Result, Error> { - if !compiler.keep_result { - return Ok(vec![]); - } let label = compiler.new_label(); let mut result = vec![]; let args = args.base_iter().collect::>(); @@ -24,12 +21,16 @@ fn compile_fn_compare( for items in args.windows(2) { result.append(&mut compiler.compile_expr(&items[1])?); result.append(&mut compiler.compile_expr(&items[0])?); - result.push(instruction.clone()); - result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); + if compiler.keep_result { + result.push(instruction.clone()); + result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); + } } - result.pop(); - if args.len() > 2 { - result.push(Instruction::Label(label)); + if compiler.keep_result { + result.pop(); + if args.len() > 2 { + result.push(Instruction::Label(label)); + } } Ok(result) } @@ -72,14 +73,12 @@ pub(super) fn compile_fn_eq( args: &TulispObject, ) -> Result, Error> { compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + if compiler.keep_result { result.push(Instruction::Eq); - Ok(result) } + Ok(result) }) } @@ -89,13 +88,11 @@ pub(super) fn compile_fn_equal( args: &TulispObject, ) -> Result, Error> { compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - Ok(vec![]) - } else { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); + let mut result = compiler.compile_expr(arg2)?; + result.append(&mut compiler.compile_expr(arg1)?); + if compiler.keep_result { result.push(Instruction::Equal); - Ok(result) } + Ok(result) }) } From 91d41e6aad55717fe0e34119501f699599bd582b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 03:05:51 +0100 Subject: [PATCH 105/142] Evaluate keywords to themselves --- src/bytecode/compiler/compiler.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index a97d8710..2617d92c 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -116,11 +116,14 @@ impl<'a> Compiler<'a> { .compile_form(cons) .map_err(|e| e.with_trace(expr.clone())), TulispValue::Symbol { .. } => { - if self.keep_result { - return Ok(vec![Instruction::Load(expr.clone())]); - } else { + if !self.keep_result { return Ok(vec![]); } + return Ok(vec![if expr.keywordp() { + Instruction::Push(expr.clone()) + } else { + Instruction::Load(expr.clone()) + }]); } TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { return Err(Error::new( From cba6c1dc9f4a537e1b577b0acf8dfb47e1e503b6 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 03:07:46 +0100 Subject: [PATCH 106/142] Add unit tests for comparison of numbers --- src/bytecode/bytecode.rs | 5 +- .../compiler/forms/comparison_of_numbers.rs | 101 ++++++++++++++++++ src/context.rs | 19 +++- 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 43b07c95..4642da94 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -13,12 +13,11 @@ pub(crate) struct Bytecode { impl fmt::Display for Bytecode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "start:\n")?; for (i, instr) in self.global.borrow().iter().enumerate() { - write!(f, "{:<40} # {}\n", instr.to_string(), i)?; + write!(f, "\n{:<40} # {}", instr.to_string(), i)?; } for (name, instr) in &self.functions { - write!(f, "\n{}:", name)?; + write!(f, "\n\n{}:", name)?; for (i, instr) in instr.borrow().iter().enumerate() { write!(f, "\n{:<40} # {}", instr.to_string(), i)?; } diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index 2d3185e3..d357c162 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -96,3 +96,104 @@ pub(super) fn compile_fn_equal( Ok(result) }) } + +#[cfg(test)] +mod tests { + use crate::TulispObject; + + #[test] + fn test_compare_two_variables() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(> 15 10)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert!(bytecode.global.borrow().is_empty()); + assert_eq!(bytecode.functions.len(), 0); + + let program = "(> 15 10)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 10 # 0 + push 15 # 1 + cgt # 2"# + ); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + assert!(!output.equal(&TulispObject::nil())); + + let program = "(> 10 15)"; + + let bytecode = ctx.compile_string(program, true).unwrap(); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + assert!(!output.equal(&TulispObject::t())); + } + + #[test] + fn test_compare_multiple_variables() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(< a b c 10)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert!(bytecode.global.borrow().is_empty()); + assert_eq!(bytecode.functions.len(), 0); + + let bytecode = ctx.compile_string(program, true).unwrap(); + + assert_eq!( + bytecode.to_string(), + r#" + load b # 0 + load a # 1 + clt # 2 + jnil_else_pop :1 # 3 + load c # 4 + load b # 5 + clt # 6 + jnil_else_pop :1 # 7 + push 10 # 8 + load c # 9 + clt # 10 +:1 # 11"# + ); + } + + #[test] + fn test_compare_side_effects() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(<= (setq a 5) 8 10)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 5 # 0 + store_pop a # 1"# + ); + + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 8 # 0 + push 5 # 1 + store a # 2 + cle # 3 + jnil_else_pop :1 # 4 + push 10 # 5 + push 8 # 6 + cle # 7 +:1 # 8"# + ); + + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + + let program = "(<= (setq a 5) 8 5)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + } +} diff --git a/src/context.rs b/src/context.rs index d4fe4bc6..6cf91317 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, fs}; use crate::{ builtin, - bytecode::{self, Compiler}, + bytecode::{self, Bytecode, Compiler}, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, @@ -230,4 +230,21 @@ impl TulispContext { let string: &str = &contents; parse(self, self.filenames.len() - 1, string) } + + #[allow(dead_code)] + pub(crate) fn compile_string( + &mut self, + string: &str, + keep_result: bool, + ) -> Result { + let vv = parse(self, 0, string)?; + let mut compiler = Compiler::new(self); + compiler.keep_result = keep_result; + compiler.compile(&vv) + } + + #[allow(dead_code)] + pub(crate) fn run_bytecode(&mut self, bytecode: Bytecode) -> Result { + bytecode::Machine::new(bytecode).run(self) + } } From 0846af9f5f762c418d744b56e7af3ad7c8c11b92 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 16:16:51 +0100 Subject: [PATCH 107/142] Add unit tests for equality predicates --- .../compiler/forms/comparison_of_numbers.rs | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index d357c162..300b657f 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -196,4 +196,137 @@ mod tests { let output = ctx.run_bytecode(bytecode).unwrap(); assert!(output.equal(&TulispObject::nil())); } + + #[test] + fn test_compare_eq() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(eq 'a 'a)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push a # 0 + push a # 1 + ceq # 2"# + ); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + + let program = "(eq 'a 'b)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push b # 0 + push a # 1 + ceq # 2"# + ); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + + let program = "(eq 'a 'a)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert!(bytecode.global.borrow().is_empty()); + assert_eq!(bytecode.functions.len(), 0); + } + + #[test] + fn test_compare_eq_side_effects() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(eq (setq a 'w) 'w)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + println!("{}", bytecode.to_string()); + assert_eq!( + bytecode.to_string(), + r#" + push w # 0 + store_pop a # 1"# + ); + + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push w # 0 + push w # 1 + store a # 2 + ceq # 3"# + ); + + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + + let program = "(eq (setq a 'w) 'x)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + } + + #[test] + fn test_compare_equal() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(equal 5 5)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 5 # 0 + push 5 # 1 + equal # 2"# + ); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + + let program = "(equal 5 6)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 6 # 0 + push 5 # 1 + equal # 2"# + ); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + + let program = "(equal 5 5)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert!(bytecode.global.borrow().is_empty()); + assert_eq!(bytecode.functions.len(), 0); + } + + #[test] + fn test_compare_equal_side_effects() { + let ctx = &mut crate::TulispContext::new(); + + let program = "(equal (setq a 5) 5)"; + let bytecode = ctx.compile_string(program, false).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 5 # 0 + store_pop a # 1"# + ); + + let bytecode = ctx.compile_string(program, true).unwrap(); + assert_eq!( + bytecode.to_string(), + r#" + push 5 # 0 + push 5 # 1 + store a # 2 + equal # 3"# + ); + + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::t())); + + let program = "(equal (setq a 5) 6)"; + let bytecode = ctx.compile_string(program, true).unwrap(); + let output = ctx.run_bytecode(bytecode).unwrap(); + assert!(output.equal(&TulispObject::nil())); + } } From 29d56cf8bc49708b496e8e1ef17e5f144eaa8da4 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 16:25:07 +0100 Subject: [PATCH 108/142] Allow side-effects of arguments when compiling arithmetic operations Even though the result of the arithmetic operations are not going to be kept. --- .../compiler/forms/arithmetic_operations.rs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/bytecode/compiler/forms/arithmetic_operations.rs b/src/bytecode/compiler/forms/arithmetic_operations.rs index 5b44ab3f..8b0d74cb 100644 --- a/src/bytecode/compiler/forms/arithmetic_operations.rs +++ b/src/bytecode/compiler/forms/arithmetic_operations.rs @@ -8,9 +8,6 @@ pub(super) fn compile_fn_plus( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - if !compiler.keep_result { - return Ok(vec![]); - } let mut result = vec![]; let args = args.base_iter().collect::>(); if args.is_empty() { @@ -22,8 +19,10 @@ pub(super) fn compile_fn_plus( for arg in args.iter().rev() { result.append(&mut compiler.compile_expr(arg)?); } - for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(BinaryOp::Add)); + if compiler.keep_result { + for _ in 0..args.len() - 1 { + result.push(Instruction::BinaryOp(BinaryOp::Add)); + } } Ok(result) } @@ -33,9 +32,6 @@ pub(super) fn compile_fn_minus( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - if !compiler.keep_result { - return Ok(vec![]); - } let mut result = vec![]; let args = args.base_iter().collect::>(); if args.is_empty() { @@ -48,12 +44,16 @@ pub(super) fn compile_fn_minus( result.append(&mut compiler.compile_expr(arg)?); } if args.len() == 1 { - result.push(Instruction::Push((-1).into())); - result.push(Instruction::BinaryOp(BinaryOp::Mul)); + if compiler.keep_result { + result.push(Instruction::Push((-1).into())); + result.push(Instruction::BinaryOp(BinaryOp::Mul)); + } return Ok(result); } - for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(BinaryOp::Sub)); + if compiler.keep_result { + for _ in 0..args.len() - 1 { + result.push(Instruction::BinaryOp(BinaryOp::Sub)); + } } Ok(result) } @@ -63,9 +63,6 @@ pub(super) fn compile_fn_mul( _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - if !compiler.keep_result { - return Ok(vec![]); - } let mut result = vec![]; let args = args.base_iter().collect::>(); if args.is_empty() { @@ -77,8 +74,10 @@ pub(super) fn compile_fn_mul( for arg in args.iter().rev() { result.append(&mut compiler.compile_expr(arg)?); } - for _ in 0..args.len() - 1 { - result.push(Instruction::BinaryOp(BinaryOp::Mul)); + if compiler.keep_result { + for _ in 0..args.len() - 1 { + result.push(Instruction::BinaryOp(BinaryOp::Mul)); + } } Ok(result) } From a60186aae3333e5369b67a4e8e04d3abefd86376 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 17:21:24 +0100 Subject: [PATCH 109/142] Improve code generation for more functions wrt side effects --- src/bytecode/compiler/forms/conditionals.rs | 2 +- .../compiler/forms/other_functions.rs | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 52b29f9a..9453c31b 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -43,7 +43,7 @@ pub(super) fn compile_fn_if( result.push(res); result.append(&mut then); if else_.is_empty() && ctx.keep_result { - else_.push(Instruction::Push(false.into())); + else_.push(Instruction::Push(TulispObject::nil())); } result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); result.append(&mut else_); diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 29722a93..7d99e0a9 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -76,12 +76,11 @@ pub(super) fn compile_fn_cons( args: &TulispObject, ) -> Result, Error> { compiler.compile_2_arg_call(name, args, true, |compiler, arg1, arg2, _| { - if !compiler.keep_result { - return Ok(vec![]); - } let mut result = compiler.compile_expr(arg1)?; result.append(&mut compiler.compile_expr(arg2)?); - result.push(Instruction::Cons); + if compiler.keep_result { + result.push(Instruction::Cons); + } Ok(result) }) } @@ -95,16 +94,15 @@ pub(super) fn compile_fn_list( return compile_fn_defun_bounce_call(compiler, &name, args); } - if !compiler.keep_result { - return Ok(vec![]); - } let mut result = vec![]; let mut len = 0; for arg in args.base_iter() { result.append(&mut compiler.compile_expr(&arg)?); len += 1; } - result.push(Instruction::List(len)); + if compiler.keep_result { + result.push(Instruction::List(len)); + } Ok(result) } @@ -119,7 +117,9 @@ pub(super) fn compile_fn_append( result.append(&mut compiler.compile_expr(&arg)?); len += 1; } - result.push(Instruction::Append(len)); + if compiler.keep_result { + result.push(Instruction::Append(len)); + } Ok(result) } @@ -264,7 +264,11 @@ pub(super) fn compile_fn_let_star( .with_trace(varitem)); } } - result.append(&mut ctx.compile_progn(body)?); + let mut body = ctx.compile_progn(body)?; + if body.is_empty() { + return Ok(vec![]); + } + result.append(&mut body); for param in params { result.push(Instruction::EndScope(param)); } From 9497cec7e4eb9c6109af39ff5c8fc880dcb55cfd Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 11 Feb 2024 20:33:55 +0100 Subject: [PATCH 110/142] Offload runtime macro-expansions to the tree walking interpreter --- src/bytecode/compiler/compiler.rs | 4 ++-- src/bytecode/compiler/forms/mod.rs | 29 ++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 2617d92c..2c6b1bcc 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -112,8 +112,8 @@ impl<'a> Compiler<'a> { return Ok(vec![]); } } - TulispValue::List { cons, .. } => self - .compile_form(cons) + TulispValue::List { .. } => self + .compile_form(expr) .map_err(|e| e.with_trace(expr.clone())), TulispValue::Symbol { .. } => { if !self.keep_result { diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 2c73a003..9cdd53b9 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{bytecode::Instruction, Error, TulispContext, TulispObject}; +use crate::{bytecode::Instruction, Error, TulispContext, TulispObject, TulispValue}; use super::Compiler; @@ -106,26 +106,37 @@ impl VMFunctions { } impl Compiler<'_> { - pub(super) fn compile_form( - &mut self, - cons: &crate::cons::Cons, - ) -> Result, Error> { - let name = cons.car(); - let args = cons.cdr(); + pub(super) fn compile_form(&mut self, form: &TulispObject) -> Result, Error> { + let name = form.car()?; + let args = form.cdr()?; if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { return compiler(self, &name, &args); } if let Ok(func) = self.ctx.eval(&name) { match &*func.inner_ref() { - crate::value::TulispValue::Func(func) => { + TulispValue::Func(func) => { return Ok(vec![ - Instruction::Push(args.clone().into()), + Instruction::Push(args.clone()), Instruction::RustCall { name: name.clone(), func: func.clone(), }, ]); } + TulispValue::Defmacro { .. } | TulispValue::Macro(..) => { + let eval = self.ctx.intern("eval"); + return Ok(vec![ + Instruction::Push(form.clone()), + Instruction::RustCall { + name: eval.clone(), + func: if let TulispValue::Func(func) = &*eval.get()?.inner_ref() { + func.clone() + } else { + unreachable!() + }, + }, + ]); + } _ => {} } } From 7a58d2d00bd9995a225a06067ea39352f96280d7 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 12 Feb 2024 17:32:56 +0100 Subject: [PATCH 111/142] Support optional and rest defun parameters --- src/bytecode/bytecode.rs | 5 +- src/bytecode/compiler/compiler.rs | 9 +- .../compiler/forms/other_functions.rs | 108 ++++++++++++++++-- src/bytecode/compiler/mod.rs | 2 +- src/bytecode/instruction.rs | 7 +- src/bytecode/interpreter.rs | 48 ++++++-- 6 files changed, 151 insertions(+), 28 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index 4642da94..e4849662 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,14 +1,13 @@ use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; -use crate::TulispObject; - use super::Instruction; +use crate::bytecode::compiler::VMDefunParams; #[derive(Default)] pub(crate) struct Bytecode { pub(crate) global: Rc>>, pub(crate) functions: HashMap>>>, - pub(crate) defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx + pub(crate) defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx } impl fmt::Display for Bytecode { diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 2c6b1bcc..189bac32 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -7,11 +7,18 @@ use crate::{ use super::forms::VMFunctions; +#[derive(Clone)] +pub(crate) struct VMDefunParams { + pub required: Vec, + pub optional: Vec, + pub rest: Option, +} + #[allow(dead_code)] pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, pub functions: VMFunctions, - pub defun_args: HashMap>, // fn_name.addr_as_usize() -> arg symbol idx + pub defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx pub symbol_to_binding_idx: HashMap>, pub bytecode: Bytecode, pub keep_result: bool, diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 7d99e0a9..fb7b9daf 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ - bytecode::{Compiler, Instruction, Pos}, + bytecode::{compiler::VMDefunParams, Compiler, Instruction, Pos}, destruct_bind, parse::mark_tail_calls, Error, ErrorKind, TulispObject, @@ -129,16 +129,52 @@ fn compile_fn_defun_bounce_call( args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; + let params = compiler.defun_args[&name.addr_as_usize()].clone(); + let mut args_count = 0; + // cdr because the first element is `Bounce`. for arg in args.cdr()?.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); + args_count += 1; } - // TODO: use .get() instead of indexing, to not crash if the function is not - // found - let params = &compiler.defun_args[&name.addr_as_usize()]; - for param in params { + let mut optional_count = 0; + let left_args = args_count - params.required.len(); + if left_args > params.optional.len() { + if params.rest.is_none() { + return Err(Error::new( + ErrorKind::ArityMismatch, + format!( + "defun bounce call: too many arguments. expected: {} got: {}", + params.required.len() + params.optional.len(), + args_count + ), + ) + .with_trace(args.clone())); + } + result.push(Instruction::List(left_args - params.optional.len())); + optional_count = params.optional.len(); + } else if params.rest.is_some() { + result.push(Instruction::Push(TulispObject::nil())); + } + if let Some(param) = ¶ms.rest { result.push(Instruction::StorePop(param.clone())) } - result.push(Instruction::Jump(Pos::Label(name.clone()))); + if left_args <= params.optional.len() && left_args > 0 { + optional_count = left_args; + } + + for (ii, param) in params.optional.iter().enumerate().rev() { + if ii >= optional_count { + result.push(Instruction::Push(TulispObject::nil())); + result.push(Instruction::StorePop(param.clone())) + } else { + result.push(Instruction::StorePop(param.clone())); + } + } + + for param in params.required.iter().rev() { + result.push(Instruction::StorePop(param.clone())) + } + result.push(Instruction::Jump(Pos::Abs(0))); return Ok(result); } @@ -148,13 +184,17 @@ pub(super) fn compile_fn_defun_call( args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; + let mut args_count = 0; for arg in args.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); + args_count += 1; } - let params = compiler.defun_args.get(&name.addr_as_usize()); result.push(Instruction::Call { name: name.clone(), - args: params.cloned(), + args_count, + params: None, + optional_count: 0, + rest_count: 0, }); if !compiler.keep_result { result.push(Instruction::Pop); @@ -171,13 +211,57 @@ pub(super) fn compile_fn_defun( ctx.functions .functions .insert(name.addr_as_usize(), compile_fn_defun_call); - let mut params = vec![]; + let mut required = vec![]; + let mut optional = vec![]; + let mut rest = None; let args = args.base_iter().collect::>(); - for arg in args.iter().rev() { - params.push(arg.clone()); + let mut is_optional = false; + let mut is_rest = false; + let optional_symbol = ctx.ctx.intern("&optional"); + let rest_symbol = ctx.ctx.intern("&rest"); + for arg in args.iter() { + if arg.eq(&optional_symbol) { + if is_rest { + return Err(Error::new( + ErrorKind::Undefined, + "optional after rest".to_string(), + ) + .with_trace(arg.clone())); + } + is_optional = true; + } else if arg.eq(&rest_symbol) { + if is_rest { + return Err( + Error::new(ErrorKind::Undefined, "rest after rest".to_string()) + .with_trace(arg.clone()), + ); + } + is_optional = false; + is_rest = true; + } else if is_optional { + optional.push(arg.clone()); + } else if is_rest { + if rest.is_some() { + return Err(Error::new( + ErrorKind::Undefined, + "multiple rest arguments".to_string(), + ) + .with_trace(arg.clone())); + } + rest = Some(arg.clone()); + } else { + required.push(arg.clone()); + } } - ctx.defun_args.insert(name.addr_as_usize(), params); + ctx.defun_args.insert( + name.addr_as_usize(), + VMDefunParams { + required, + optional, + rest, + }, + ); // TODO: replace with `is_string` let body = if body.car()?.as_string().is_ok() { body.cdr()? diff --git a/src/bytecode/compiler/mod.rs b/src/bytecode/compiler/mod.rs index eee63f27..9a823612 100644 --- a/src/bytecode/compiler/mod.rs +++ b/src/bytecode/compiler/mod.rs @@ -1,3 +1,3 @@ mod compiler; mod forms; -pub(crate) use compiler::Compiler; +pub(crate) use compiler::{Compiler, VMDefunParams}; diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 74c6d825..deef7b8d 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -2,6 +2,8 @@ use std::rc::Rc; use crate::{value::TulispFn, TulispObject}; +use super::compiler::VMDefunParams; + #[derive(Clone)] pub(crate) enum Pos { Abs(usize), @@ -109,7 +111,10 @@ pub(crate) enum Instruction { }, Call { name: TulispObject, - args: Option>, + args_count: usize, + params: Option, + optional_count: usize, + rest_count: usize, }, Ret, // lists diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 2bbd408e..3b87d1b4 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -357,12 +357,25 @@ impl Machine { Instruction::EndScope(obj) => { obj.unset().unwrap(); } - Instruction::Call { name, args } => { + Instruction::Call { + name, + params, + args_count, + optional_count, + rest_count, + } => { let addr = name.addr_as_usize(); let func = Some(addr); - if args.is_none() { + if params.is_none() { if let Some(items) = self.bytecode.defun_args.get(&addr) { - *args = Some(items.clone()); + *params = Some(items.clone()); + let left_args = *args_count - items.required.len(); + if left_args > items.optional.len() { + *rest_count = left_args - items.optional.len(); + *optional_count = items.optional.len(); + } else if left_args > 0 { + *optional_count = left_args + } } else { return Err(Error::new( crate::ErrorKind::Undefined, @@ -372,19 +385,34 @@ impl Machine { } } - if let Some(args) = args { - for arg in args.iter() { - arg.set_scope(self.stack.pop().unwrap()).unwrap(); + let Some(params) = params else { unreachable!() }; + let mut set_params = vec![]; + if let Some(rest) = ¶ms.rest { + let mut rest_value = TulispObject::nil(); + for _ in 0..*rest_count { + rest_value = TulispObject::cons(self.stack.pop().unwrap(), rest_value); } + rest.set_scope(rest_value).unwrap(); + set_params.push(rest.clone()); + } + for (ii, arg) in params.optional.iter().enumerate().rev() { + if ii >= *optional_count { + arg.set_scope(TulispObject::nil()).unwrap(); + continue; + } + arg.set_scope(self.stack.pop().unwrap()).unwrap(); + set_params.push(arg.clone()); + } + for arg in params.required.iter().rev() { + arg.set_scope(self.stack.pop().unwrap()).unwrap(); + set_params.push(arg.clone()); } drop(instr_ref); self.run_impl(ctx, func, recursion_depth + 1)?; instr_ref = program.borrow_mut(); - if let Some(args) = self.bytecode.defun_args.get(&addr) { - for arg in args.iter() { - arg.unset().unwrap(); - } + for arg in set_params.iter() { + arg.unset().unwrap(); } } Instruction::Ret => return Ok(()), From 6baa16dfb118b5eb11fea13fd6ad1b212d553247 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 13:32:07 +0100 Subject: [PATCH 112/142] Compile inplace calls to lambdas --- src/bytecode/compiler/forms/other_functions.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index fb7b9daf..13c83811 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ bytecode::{compiler::VMDefunParams, Compiler, Instruction, Pos}, - destruct_bind, + destruct_bind, list, parse::mark_tail_calls, Error, ErrorKind, TulispObject, }; @@ -185,6 +185,9 @@ pub(super) fn compile_fn_defun_call( ) -> Result, Error> { let mut result = vec![]; let mut args_count = 0; + if name.consp() && name.car_and_then(|name| Ok(name.eq(&compiler.ctx.intern("lambda"))))? { + compile_fn_defun(compiler, &name.car()?, &list!(name.clone() ,@name.cdr()?)?)?; + } for arg in args.base_iter() { result.append(&mut compiler.compile_expr_keep_result(&arg)?); args_count += 1; From dda5dde57a61a48e3215800fb70926380d7af949 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 16:20:35 +0100 Subject: [PATCH 113/142] Drop references to expr before calling `compile_form` This is because compile_form might try to get a unique mutable reference to expr when trying to expand macros. --- src/bytecode/compiler/compiler.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 189bac32..744bd1a1 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -69,7 +69,8 @@ impl<'a> Compiler<'a> { } pub(crate) fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { - match &*expr.inner_ref() { + let expr_ref = expr.inner_ref(); + match &*expr_ref { TulispValue::Int { value } => { if self.keep_result { return Ok(vec![Instruction::Push(value.clone().into())]); @@ -119,9 +120,11 @@ impl<'a> Compiler<'a> { return Ok(vec![]); } } - TulispValue::List { .. } => self - .compile_form(expr) - .map_err(|e| e.with_trace(expr.clone())), + TulispValue::List { .. } => { + drop(expr_ref); + self.compile_form(expr) + .map_err(|e| e.with_trace(expr.clone())) + } TulispValue::Symbol { .. } => { if !self.keep_result { return Ok(vec![]); From 9be59477fa2ce9595d0b82e19255ecf119f44c62 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 16:27:13 +0100 Subject: [PATCH 114/142] Fix macroexpansion in unquote expressions This should happen in the parser and needs to be fixed there and removed from here. --- src/bytecode/compiler/forms/mod.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 9cdd53b9..455fbef0 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; -use crate::{bytecode::Instruction, Error, TulispContext, TulispObject, TulispValue}; +use crate::{ + bytecode::Instruction, eval::macroexpand, Error, TulispContext, TulispObject, TulispValue, +}; use super::Compiler; @@ -124,18 +126,10 @@ impl Compiler<'_> { ]); } TulispValue::Defmacro { .. } | TulispValue::Macro(..) => { - let eval = self.ctx.intern("eval"); - return Ok(vec![ - Instruction::Push(form.clone()), - Instruction::RustCall { - name: eval.clone(), - func: if let TulispValue::Func(func) = &*eval.get()?.inner_ref() { - func.clone() - } else { - unreachable!() - }, - }, - ]); + // TODO: this should not be necessary, this should be + // handled in the parser instead. + let form = macroexpand(self.ctx, form.clone())?; + return self.compile_expr(&form); } _ => {} } From 1df9d9bbe716122387e0585f2cd01243de29fead Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 16:28:51 +0100 Subject: [PATCH 115/142] Keep result from rust calls only when necessary --- src/bytecode/compiler/forms/mod.rs | 1 + src/bytecode/instruction.rs | 1 + src/bytecode/interpreter.rs | 9 +++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 455fbef0..d67af1ab 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -122,6 +122,7 @@ impl Compiler<'_> { Instruction::RustCall { name: name.clone(), func: func.clone(), + keep_result: self.keep_result, }, ]); } diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index deef7b8d..c3265f8c 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -108,6 +108,7 @@ pub(crate) enum Instruction { RustCall { name: TulispObject, func: Rc, + keep_result: bool, }, Call { name: TulispObject, diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 3b87d1b4..a415390c 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -416,9 +416,14 @@ impl Machine { } } Instruction::Ret => return Ok(()), - Instruction::RustCall { func, .. } => { + Instruction::RustCall { + func, keep_result, .. + } => { let args = self.stack.pop().unwrap(); - self.stack.push(func(ctx, &args)?.into()); + let result = func(ctx, &args)?; + if *keep_result { + self.stack.push(result); + } } Instruction::Label(_) => {} Instruction::Cons => { From 9d6cf2e524b302ea2d6c642a2f2821c94c3d57c1 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 18:06:42 +0100 Subject: [PATCH 116/142] Rename `VMFunctions` -> `VMCompilers` --- src/bytecode/compiler/compiler.rs | 6 +++--- src/bytecode/compiler/forms/mod.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 744bd1a1..00f2b795 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -5,7 +5,7 @@ use crate::{ Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; -use super::forms::VMFunctions; +use super::forms::VMCompilers; #[derive(Clone)] pub(crate) struct VMDefunParams { @@ -17,7 +17,7 @@ pub(crate) struct VMDefunParams { #[allow(dead_code)] pub(crate) struct Compiler<'a> { pub ctx: &'a mut TulispContext, - pub functions: VMFunctions, + pub functions: VMCompilers, pub defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx pub symbol_to_binding_idx: HashMap>, pub bytecode: Bytecode, @@ -27,7 +27,7 @@ pub(crate) struct Compiler<'a> { impl<'a> Compiler<'a> { pub fn new(ctx: &'a mut TulispContext) -> Self { - let functions = VMFunctions::new(ctx); + let functions = VMCompilers::new(ctx); Compiler { ctx, functions, diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index d67af1ab..20973153 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -16,7 +16,7 @@ mod other_functions; type FnCallCompiler = fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>; -pub(crate) struct VMFunctions { +pub(crate) struct VMCompilers { // TulispObject.addr() -> implementation pub functions: HashMap, } @@ -32,7 +32,7 @@ macro_rules! map_fn_call_compilers { }; } -impl VMFunctions { +impl VMCompilers { pub fn new(ctx: &mut TulispContext) -> Self { let mut functions = HashMap::new(); map_fn_call_compilers! { @@ -103,7 +103,7 @@ impl VMFunctions { // noop ("defmacro", other_functions::compile_fn_noop), } - VMFunctions { functions } + VMCompilers { functions } } } From 1f38417ff18e23cf7427433a456164cc42ae29e4 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 13 Feb 2024 18:07:05 +0100 Subject: [PATCH 117/142] Expand macros when parsing unquote values --- src/parse.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parse.rs b/src/parse.rs index 092f3cb9..c2e740a1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -466,7 +466,10 @@ impl Parser<'_, '_> { } }; Ok(Some( - TulispValue::Unquote { value: next }.into_ref(Some(span)), + TulispValue::Unquote { + value: macroexpand(self.ctx, next)?, + } + .into_ref(Some(span)), )) } Token::Splice { span } => { From 038aa601f144b889c36c523aced54dde73fb4e83 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Wed, 14 Feb 2024 00:53:56 +0100 Subject: [PATCH 118/142] Reuse Compiler so previous compiled functions are not lost --- src/bytecode/bytecode.rs | 2 +- src/bytecode/compiler/compiler.rs | 416 +++++++++--------- .../compiler/forms/arithmetic_operations.rs | 24 +- src/bytecode/compiler/forms/common.rs | 15 +- .../compiler/forms/comparison_of_numbers.rs | 64 +-- src/bytecode/compiler/forms/conditionals.rs | 68 +-- src/bytecode/compiler/forms/list_elements.rs | 12 +- src/bytecode/compiler/forms/mod.rs | 68 +-- .../compiler/forms/other_functions.rs | 126 +++--- src/bytecode/compiler/mod.rs | 4 +- src/bytecode/interpreter.rs | 4 +- src/bytecode/mod.rs | 2 +- src/context.rs | 38 +- 13 files changed, 457 insertions(+), 386 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index e4849662..f794f84a 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; use super::Instruction; use crate::bytecode::compiler::VMDefunParams; -#[derive(Default)] +#[derive(Default, Clone)] pub(crate) struct Bytecode { pub(crate) global: Rc>>, pub(crate) functions: HashMap>>>, diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 00f2b795..4f2dba27 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -5,7 +5,7 @@ use crate::{ Error, ErrorKind, TulispContext, TulispObject, TulispValue, }; -use super::forms::VMCompilers; +use super::forms::{compile_form, VMCompilers}; #[derive(Clone)] pub(crate) struct VMDefunParams { @@ -15,9 +15,8 @@ pub(crate) struct VMDefunParams { } #[allow(dead_code)] -pub(crate) struct Compiler<'a> { - pub ctx: &'a mut TulispContext, - pub functions: VMCompilers, +pub(crate) struct Compiler { + pub vm_compilers: VMCompilers, pub defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx pub symbol_to_binding_idx: HashMap>, pub bytecode: Bytecode, @@ -25,12 +24,10 @@ pub(crate) struct Compiler<'a> { label_counter: usize, } -impl<'a> Compiler<'a> { - pub fn new(ctx: &'a mut TulispContext) -> Self { - let functions = VMCompilers::new(ctx); +impl Compiler { + pub fn new(vm_compilers: VMCompilers) -> Self { Compiler { - ctx, - functions, + vm_compilers, defun_args: HashMap::new(), symbol_to_binding_idx: HashMap::new(), bytecode: Bytecode::new(), @@ -43,214 +40,241 @@ impl<'a> Compiler<'a> { self.label_counter += 1; TulispObject::symbol(format!(":{}", self.label_counter), true) } +} - pub fn compile(mut self, value: &TulispObject) -> Result { - self.bytecode.global = Rc::new(RefCell::new(self.compile_progn(value)?)); - self.bytecode.defun_args = self.defun_args; - Ok(self.bytecode) - } +pub fn compile(ctx: &mut TulispContext, value: &TulispObject) -> Result { + let output = compile_progn(ctx, value)?; + let compiler = ctx.compiler.as_mut().unwrap(); + compiler.bytecode.global = Rc::new(RefCell::new(output)); + compiler + .bytecode + .defun_args + .extend(compiler.defun_args.clone()); + Ok(compiler.bytecode.clone()) +} - pub fn compile_progn(&mut self, value: &TulispObject) -> Result, Error> { - let mut result = vec![]; - let mut prev = None; - let keep_result = self.keep_result; - for expr in value.base_iter() { - if let Some(prev) = prev { - self.keep_result = false; - result.append(&mut self.compile_expr(&prev)?); - } - prev = Some(expr); - } - self.keep_result = keep_result; +pub fn compile_progn( + ctx: &mut TulispContext, + value: &TulispObject, +) -> Result, Error> { + let mut result = vec![]; + let mut prev = None; + let compiler = ctx.compiler.as_mut().unwrap(); + let keep_result = compiler.keep_result; + compiler.keep_result = false; + #[allow(dropping_references)] + drop(compiler); + for expr in value.base_iter() { if let Some(prev) = prev { - result.append(&mut self.compile_expr(&prev)?); + result.append(&mut compile_expr(ctx, &prev)?); } - Ok(result) + prev = Some(expr); + } + let compiler = ctx.compiler.as_mut().unwrap(); + compiler.keep_result = keep_result; + #[allow(dropping_references)] + drop(compiler); + if let Some(prev) = prev { + result.append(&mut compile_expr(ctx, &prev)?); } + Ok(result) +} - pub(crate) fn compile_expr(&mut self, expr: &TulispObject) -> Result, Error> { - let expr_ref = expr.inner_ref(); - match &*expr_ref { - TulispValue::Int { value } => { - if self.keep_result { - return Ok(vec![Instruction::Push(value.clone().into())]); - } else { - return Ok(vec![]); - } - } - TulispValue::Float { value } => { - if self.keep_result { - return Ok(vec![Instruction::Push(value.clone().into())]); - } else { - return Ok(vec![]); - } - } - TulispValue::Nil => { - if self.keep_result { - return Ok(vec![Instruction::Push(false.into())]); - } else { - return Ok(vec![]); - } - } - TulispValue::T => { - if self.keep_result { - return Ok(vec![Instruction::Push(true.into())]); - } else { - return Ok(vec![]); - } - } - TulispValue::String { .. } - | TulispValue::Lambda { .. } - | TulispValue::Func(_) - | TulispValue::Macro(_) - | TulispValue::Defmacro { .. } - | TulispValue::Any(_) - | TulispValue::Bounce { .. } => { - if self.keep_result { - return Ok(vec![Instruction::Push(expr.clone().into())]); - } else { - return Ok(vec![]); - } - } - TulispValue::Backquote { value } => self.compile_back_quote(value), - TulispValue::Quote { value } | TulispValue::Sharpquote { value } => { - if self.keep_result { - return Ok(vec![Instruction::Push(value.clone().into())]); +pub(crate) fn compile_expr_keep_result( + ctx: &mut TulispContext, + expr: &TulispObject, +) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); + let keep_result = compiler.keep_result; + compiler.keep_result = true; + #[allow(dropping_references)] + drop(compiler); + let ret = compile_expr(ctx, expr); + ctx.compiler.as_mut().unwrap().keep_result = keep_result; + ret +} + +pub(crate) fn compile_progn_keep_result( + ctx: &mut TulispContext, + expr: &TulispObject, +) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); + let keep_result = compiler.keep_result; + compiler.keep_result = true; + #[allow(dropping_references)] + drop(compiler); + let ret = compile_progn(ctx, expr); + ctx.compiler.as_mut().unwrap().keep_result = keep_result; + ret +} + +fn compile_back_quote( + ctx: &mut TulispContext, + value: &TulispObject, +) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); + if !compiler.keep_result { + return Ok(vec![]); + } + if !value.consp() { + return Ok(vec![Instruction::Push(value.clone().into())]); + } + let mut result = vec![]; + + let mut value = value.clone(); + let mut items = 0; + let mut need_list = true; + let mut need_append = false; + loop { + value.car_and_then(|first| { + let first_inner = &*first.inner_ref(); + if let TulispValue::Unquote { value } = first_inner { + items += 1; + result.append(&mut compile_expr(ctx, &value)?); + } else if let TulispValue::Splice { value } = first_inner { + let mut splice_result = compile_expr(ctx, &value)?; + let list_inst = splice_result.pop().unwrap(); + if let Instruction::List(n) = list_inst { + result.append(&mut splice_result); + items += n; + } else if let Instruction::Load(idx) = list_inst { + result.append(&mut splice_result); + result.push(Instruction::List(items)); + if need_append { + result.push(Instruction::Append(2)); + } + result.append(&mut vec![Instruction::Load(idx), Instruction::Append(2)]); + need_append = true; + items = 0; } else { - return Ok(vec![]); - } - } - TulispValue::List { .. } => { - drop(expr_ref); - self.compile_form(expr) - .map_err(|e| e.with_trace(expr.clone())) - } - TulispValue::Symbol { .. } => { - if !self.keep_result { - return Ok(vec![]); + if !value.consp() { + return Err(Error::new( + ErrorKind::SyntaxError, + format!( + "Can only splice an inplace-list or a variable binding: {}", + value + ), + ) + .with_trace(first.clone())); + } + result.push(Instruction::List(items)); + if need_append { + result.push(Instruction::Append(2)); + } + result.append(&mut splice_result); + result.push(list_inst); + result.push(Instruction::Append(2)); + need_append = true; + items = 0; } - return Ok(vec![if expr.keywordp() { - Instruction::Push(expr.clone()) - } else { - Instruction::Load(expr.clone()) - }]); + } else { + items += 1; + result.append(&mut compile_back_quote(ctx, first)?); } - TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { - return Err(Error::new( - crate::ErrorKind::SyntaxError, - "Unquote/Splice must be within a backquoted list.".to_string(), - ) - .with_trace(expr.clone())); + Ok(()) + })?; + let rest = value.cdr()?; + if let TulispValue::Unquote { value } = &*rest.inner_ref() { + result.append(&mut compile_expr(ctx, &value)?); + result.push(Instruction::Cons); + need_list = false; + break; + } + if !rest.consp() { + if !rest.null() { + result.push(Instruction::Push(rest.clone().into())); + result.push(Instruction::Cons); + need_list = false; } + break; } + value = rest; } - - pub(crate) fn compile_expr_keep_result( - &mut self, - expr: &TulispObject, - ) -> Result, Error> { - let keep_result = self.keep_result; - self.keep_result = true; - let ret = self.compile_expr(expr); - self.keep_result = keep_result; - ret + if need_list { + result.push(Instruction::List(items)); } - - pub(crate) fn compile_progn_keep_result( - &mut self, - expr: &TulispObject, - ) -> Result, Error> { - let keep_result = self.keep_result; - self.keep_result = true; - let ret = self.compile_progn(expr); - self.keep_result = keep_result; - ret + if need_append { + result.push(Instruction::Append(2)); } + Ok(result) +} - fn compile_back_quote(&mut self, value: &TulispObject) -> Result, Error> { - if !self.keep_result { - return Ok(vec![]); +pub(crate) fn compile_expr( + ctx: &mut TulispContext, + expr: &TulispObject, +) -> Result, Error> { + let expr_ref = expr.inner_ref(); + let compiler = ctx.compiler.as_mut().unwrap(); + match &*expr_ref { + TulispValue::Int { value } => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(value.clone().into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::Float { value } => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(value.clone().into())]); + } else { + return Ok(vec![]); + } } - if !value.consp() { - return Ok(vec![Instruction::Push(value.clone().into())]); + TulispValue::Nil => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(false.into())]); + } else { + return Ok(vec![]); + } } - let mut result = vec![]; - - let mut value = value.clone(); - let mut items = 0; - let mut need_list = true; - let mut need_append = false; - loop { - value.car_and_then(|first| { - let first_inner = &*first.inner_ref(); - if let TulispValue::Unquote { value } = first_inner { - items += 1; - result.append(&mut self.compile_expr(&value)?); - } else if let TulispValue::Splice { value } = first_inner { - let mut splice_result = self.compile_expr(&value)?; - let list_inst = splice_result.pop().unwrap(); - if let Instruction::List(n) = list_inst { - result.append(&mut splice_result); - items += n; - } else if let Instruction::Load(idx) = list_inst { - result.append(&mut splice_result); - result.push(Instruction::List(items)); - if need_append { - result.push(Instruction::Append(2)); - } - result.append(&mut vec![Instruction::Load(idx), Instruction::Append(2)]); - need_append = true; - items = 0; - } else { - if !value.consp() { - return Err(Error::new( - ErrorKind::SyntaxError, - format!( - "Can only splice an inplace-list or a variable binding: {}", - value - ), - ) - .with_trace(first.clone())); - } - result.push(Instruction::List(items)); - if need_append { - result.push(Instruction::Append(2)); - } - result.append(&mut splice_result); - result.push(list_inst); - result.push(Instruction::Append(2)); - need_append = true; - items = 0; - } - } else { - items += 1; - result.append(&mut self.compile_back_quote(first)?); - } - Ok(()) - })?; - let rest = value.cdr()?; - if let TulispValue::Unquote { value } = &*rest.inner_ref() { - result.append(&mut self.compile_expr(&value)?); - result.push(Instruction::Cons); - need_list = false; - break; + TulispValue::T => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(true.into())]); + } else { + return Ok(vec![]); } - if !rest.consp() { - if !rest.null() { - result.push(Instruction::Push(rest.clone().into())); - result.push(Instruction::Cons); - need_list = false; - } - break; + } + TulispValue::String { .. } + | TulispValue::Lambda { .. } + | TulispValue::Func(_) + | TulispValue::Macro(_) + | TulispValue::Defmacro { .. } + | TulispValue::Any(_) + | TulispValue::Bounce { .. } => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(expr.clone().into())]); + } else { + return Ok(vec![]); } - value = rest; } - if need_list { - result.push(Instruction::List(items)); + TulispValue::Backquote { value } => compile_back_quote(ctx, value), + TulispValue::Quote { value } | TulispValue::Sharpquote { value } => { + if compiler.keep_result { + return Ok(vec![Instruction::Push(value.clone().into())]); + } else { + return Ok(vec![]); + } + } + TulispValue::List { .. } => { + drop(expr_ref); + compile_form(ctx, expr).map_err(|e| e.with_trace(expr.clone())) + } + TulispValue::Symbol { .. } => { + if !compiler.keep_result { + return Ok(vec![]); + } + return Ok(vec![if expr.keywordp() { + Instruction::Push(expr.clone()) + } else { + Instruction::Load(expr.clone()) + }]); } - if need_append { - result.push(Instruction::Append(2)); + TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { + return Err(Error::new( + crate::ErrorKind::SyntaxError, + "Unquote/Splice must be within a backquoted list.".to_string(), + ) + .with_trace(expr.clone())); } - Ok(result) } } diff --git a/src/bytecode/compiler/forms/arithmetic_operations.rs b/src/bytecode/compiler/forms/arithmetic_operations.rs index 8b0d74cb..04ade702 100644 --- a/src/bytecode/compiler/forms/arithmetic_operations.rs +++ b/src/bytecode/compiler/forms/arithmetic_operations.rs @@ -1,10 +1,10 @@ use crate::{ - bytecode::{instruction::BinaryOp, Compiler, Instruction}, - Error, ErrorKind, TulispObject, + bytecode::{compiler::compiler::compile_expr, instruction::BinaryOp, Instruction}, + Error, ErrorKind, TulispContext, TulispObject, }; pub(super) fn compile_fn_plus( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { @@ -17,8 +17,9 @@ pub(super) fn compile_fn_plus( )); } for arg in args.iter().rev() { - result.append(&mut compiler.compile_expr(arg)?); + result.append(&mut compile_expr(ctx, arg)?); } + let compiler = ctx.compiler.as_mut().unwrap(); if compiler.keep_result { for _ in 0..args.len() - 1 { result.push(Instruction::BinaryOp(BinaryOp::Add)); @@ -28,7 +29,7 @@ pub(super) fn compile_fn_plus( } pub(super) fn compile_fn_minus( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { @@ -41,8 +42,9 @@ pub(super) fn compile_fn_minus( )); } for arg in args.iter().rev() { - result.append(&mut compiler.compile_expr(arg)?); + result.append(&mut compile_expr(ctx, arg)?); } + let compiler = ctx.compiler.as_mut().unwrap(); if args.len() == 1 { if compiler.keep_result { result.push(Instruction::Push((-1).into())); @@ -59,7 +61,7 @@ pub(super) fn compile_fn_minus( } pub(super) fn compile_fn_mul( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { @@ -72,8 +74,9 @@ pub(super) fn compile_fn_mul( )); } for arg in args.iter().rev() { - result.append(&mut compiler.compile_expr(arg)?); + result.append(&mut compile_expr(ctx, arg)?); } + let compiler = ctx.compiler.as_mut().unwrap(); if compiler.keep_result { for _ in 0..args.len() - 1 { result.push(Instruction::BinaryOp(BinaryOp::Mul)); @@ -83,10 +86,11 @@ pub(super) fn compile_fn_mul( } pub(super) fn compile_fn_div( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); if !compiler.keep_result { return Ok(vec![]); } @@ -99,7 +103,7 @@ pub(super) fn compile_fn_div( )); } for arg in args.iter().rev() { - result.append(&mut compiler.compile_expr(arg)?); + result.append(&mut compile_expr(ctx, arg)?); } if args.len() == 1 { result.push(Instruction::Push(1.into())); diff --git a/src/bytecode/compiler/forms/common.rs b/src/bytecode/compiler/forms/common.rs index 36e39e99..7cf89331 100644 --- a/src/bytecode/compiler/forms/common.rs +++ b/src/bytecode/compiler/forms/common.rs @@ -1,15 +1,16 @@ -use crate::{ - bytecode::{Compiler, Instruction}, - Error, ErrorKind, TulispObject, TulispValue, -}; +use crate::{bytecode::Instruction, Error, ErrorKind, TulispContext, TulispObject, TulispValue}; -impl Compiler<'_> { +impl TulispContext { pub(crate) fn compile_1_arg_call( &mut self, name: &TulispObject, args: &TulispObject, has_rest: bool, - lambda: fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>, + lambda: fn( + &mut TulispContext, + &TulispObject, + &TulispObject, + ) -> Result, Error>, ) -> Result, Error> { if args.null() { return Err(Error::new( @@ -40,7 +41,7 @@ impl Compiler<'_> { args: &TulispObject, has_rest: bool, lambda: fn( - &mut Compiler, + &mut TulispContext, &TulispObject, &TulispObject, &TulispObject, diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index 300b657f..986c245e 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -1,15 +1,19 @@ use crate::{ - bytecode::{Compiler, Instruction, Pos}, - Error, TulispObject, + bytecode::{compiler::compiler::compile_expr, Instruction, Pos}, + Error, TulispContext, TulispObject, }; fn compile_fn_compare( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, instruction: Instruction, ) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); let label = compiler.new_label(); + let keep_result = compiler.keep_result; + #[allow(dropping_references)] + drop(compiler); let mut result = vec![]; let args = args.base_iter().collect::>(); if args.len() < 2 { @@ -19,14 +23,14 @@ fn compile_fn_compare( )); } for items in args.windows(2) { - result.append(&mut compiler.compile_expr(&items[1])?); - result.append(&mut compiler.compile_expr(&items[0])?); - if compiler.keep_result { + result.append(&mut compile_expr(ctx, &items[1])?); + result.append(&mut compile_expr(ctx, &items[0])?); + if keep_result { result.push(instruction.clone()); result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); } } - if compiler.keep_result { + if keep_result { result.pop(); if args.len() > 2 { result.push(Instruction::Label(label)); @@ -36,46 +40,46 @@ fn compile_fn_compare( } pub(super) fn compile_fn_lt( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compile_fn_compare(compiler, name, args, Instruction::Lt) + compile_fn_compare(ctx, name, args, Instruction::Lt) } pub(super) fn compile_fn_le( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compile_fn_compare(compiler, name, args, Instruction::LtEq) + compile_fn_compare(ctx, name, args, Instruction::LtEq) } pub(super) fn compile_fn_gt( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compile_fn_compare(compiler, name, args, Instruction::Gt) + compile_fn_compare(ctx, name, args, Instruction::Gt) } pub(super) fn compile_fn_ge( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compile_fn_compare(compiler, name, args, Instruction::GtEq) + compile_fn_compare(ctx, name, args, Instruction::GtEq) } pub(super) fn compile_fn_eq( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - if compiler.keep_result { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr(ctx, arg2)?; + result.append(&mut compile_expr(ctx, arg1)?); + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Eq); } Ok(result) @@ -83,14 +87,14 @@ pub(super) fn compile_fn_eq( } pub(super) fn compile_fn_equal( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr(arg2)?; - result.append(&mut compiler.compile_expr(arg1)?); - if compiler.keep_result { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr(ctx, arg2)?; + result.append(&mut compile_expr(ctx, arg1)?); + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Equal); } Ok(result) @@ -148,15 +152,15 @@ mod tests { load b # 0 load a # 1 clt # 2 - jnil_else_pop :1 # 3 + jnil_else_pop :2 # 3 load c # 4 load b # 5 clt # 6 - jnil_else_pop :1 # 7 + jnil_else_pop :2 # 7 push 10 # 8 load c # 9 clt # 10 -:1 # 11"# +:2 # 11"# ); } @@ -181,11 +185,11 @@ mod tests { push 5 # 1 store a # 2 cle # 3 - jnil_else_pop :1 # 4 + jnil_else_pop :2 # 4 push 10 # 5 push 8 # 6 cle # 7 -:1 # 8"# +:2 # 8"# ); let output = ctx.run_bytecode(bytecode).unwrap(); diff --git a/src/bytecode/compiler/forms/conditionals.rs b/src/bytecode/compiler/forms/conditionals.rs index 9453c31b..73df4f6f 100644 --- a/src/bytecode/compiler/forms/conditionals.rs +++ b/src/bytecode/compiler/forms/conditionals.rs @@ -1,6 +1,9 @@ use crate::{ - bytecode::{Compiler, Instruction, Pos}, - Error, TulispObject, + bytecode::{ + compiler::compiler::{compile_expr, compile_expr_keep_result, compile_progn}, + Instruction, Pos, + }, + Error, TulispContext, TulispObject, }; fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) -> Instruction { @@ -30,19 +33,19 @@ fn optimize_jump_if_nil(result: &mut Vec, tgt_pos: Pos) -> Instruct } pub(super) fn compile_fn_if( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut then = ctx.compile_expr(then)?; - let mut else_ = ctx.compile_progn(else_)?; + ctx.compile_2_arg_call(name, args, true, |ctx, cond, then, else_| { + let mut result = compile_expr_keep_result(ctx, cond)?; + let mut then = compile_expr(ctx, then)?; + let mut else_ = compile_progn(ctx, else_)?; let res = optimize_jump_if_nil(&mut result, Pos::Rel(then.len() as isize + 1)); result.push(res); result.append(&mut then); - if else_.is_empty() && ctx.keep_result { + if else_.is_empty() && ctx.compiler.as_ref().unwrap().keep_result { else_.push(Instruction::Push(TulispObject::nil())); } result.push(Instruction::Jump(Pos::Rel(else_.len() as isize))); @@ -52,19 +55,19 @@ pub(super) fn compile_fn_if( } pub(super) fn compile_fn_cond( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; - let cond_end = compiler.new_label(); + let cond_end = ctx.compiler.as_mut().unwrap().new_label(); for branch in args.base_iter() { result.append( - &mut compiler + &mut ctx .compile_1_arg_call(&"cond-branch".into(), &branch, true, |ctx, cond, body| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut body = ctx.compile_progn(body)?; + let mut result = compile_expr_keep_result(ctx, cond)?; + let mut body = compile_progn(ctx, body)?; let res = optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); result.push(res); @@ -75,6 +78,7 @@ pub(super) fn compile_fn_cond( ); result.push(Instruction::Jump(Pos::Label(cond_end.clone()))); } + let compiler = ctx.compiler.as_mut().unwrap(); if compiler.keep_result { result.push(Instruction::Push(false.into())); } @@ -83,13 +87,13 @@ pub(super) fn compile_fn_cond( } pub(super) fn compile_fn_while( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(name, args, true, |ctx, cond, body| { - let mut result = ctx.compile_expr_keep_result(cond)?; - let mut body = ctx.compile_progn(body)?; + ctx.compile_1_arg_call(name, args, true, |ctx, cond, body| { + let mut result = compile_expr_keep_result(ctx, cond)?; + let mut body = compile_progn(ctx, body)?; let res = optimize_jump_if_nil(&mut result, Pos::Rel(body.len() as isize + 1)); result.push(res); @@ -100,13 +104,13 @@ pub(super) fn compile_fn_while( } pub(super) fn compile_fn_not( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(_name, args, false, |compiler, arg, _| { - let mut result = compiler.compile_expr(arg)?; - if compiler.keep_result { + ctx.compile_1_arg_call(_name, args, false, |ctx, arg, _| { + let mut result = compile_expr(ctx, arg)?; + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Null); } Ok(result) @@ -114,18 +118,22 @@ pub(super) fn compile_fn_not( } pub(super) fn compile_fn_and( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; + let compiler = ctx.compiler.as_mut().unwrap(); let label = compiler.new_label(); + let keep_result = compiler.keep_result; + #[allow(dropping_references)] + drop(compiler); let mut need_label = false; for item in args.base_iter() { - let expr_result = &mut compiler.compile_expr(&item)?; + let expr_result = &mut compile_expr(ctx, &item)?; if !expr_result.is_empty() { result.append(expr_result); - if compiler.keep_result { + if keep_result { result.push(Instruction::JumpIfNilElsePop(Pos::Label(label.clone()))); } else { result.push(Instruction::JumpIfNil(Pos::Label(label.clone()))); @@ -134,7 +142,7 @@ pub(super) fn compile_fn_and( } } if need_label { - if compiler.keep_result { + if keep_result { result.pop(); } result.push(Instruction::Label(label.into())); @@ -143,18 +151,20 @@ pub(super) fn compile_fn_and( } pub(super) fn compile_fn_or( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; + let compiler = ctx.compiler.as_mut().unwrap(); let label = compiler.new_label(); + let keep_result = compiler.keep_result; let mut need_label = false; for item in args.base_iter() { - let expr_result = &mut compiler.compile_expr(&item)?; + let expr_result = &mut compile_expr(ctx, &item)?; if !expr_result.is_empty() { result.append(expr_result); - if compiler.keep_result { + if keep_result { result.push(Instruction::JumpIfNotNilElsePop(Pos::Label(label.clone()))); } else { result.push(Instruction::JumpIfNotNil(Pos::Label(label.clone()))); @@ -163,7 +173,7 @@ pub(super) fn compile_fn_or( } } if need_label { - if compiler.keep_result { + if keep_result { result.push(Instruction::Push(false.into())) } result.push(Instruction::Label(label.into())); diff --git a/src/bytecode/compiler/forms/list_elements.rs b/src/bytecode/compiler/forms/list_elements.rs index df13575e..4bd9bba7 100644 --- a/src/bytecode/compiler/forms/list_elements.rs +++ b/src/bytecode/compiler/forms/list_elements.rs @@ -1,16 +1,16 @@ use crate::{ - bytecode::{instruction::Cxr, Compiler, Instruction}, - Error, ErrorKind, TulispObject, + bytecode::{compiler::compiler::compile_expr, instruction::Cxr, Instruction}, + Error, ErrorKind, TulispContext, TulispObject, }; pub(super) fn compile_fn_cxr( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - let mut result = compiler.compile_1_arg_call(name, args, false, |compiler, arg1, _| { - compiler.compile_expr(arg1) - })?; + let mut result = + ctx.compile_1_arg_call(name, args, false, |ctx, arg1, _| compile_expr(ctx, arg1))?; + let compiler = ctx.compiler.as_mut().unwrap(); if compiler.keep_result { let name = name.to_string(); match name.as_str() { diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 20973153..fc8faa3d 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -4,7 +4,7 @@ use crate::{ bytecode::Instruction, eval::macroexpand, Error, TulispContext, TulispObject, TulispValue, }; -use super::Compiler; +use super::compiler::compile_expr; mod arithmetic_operations; mod common; @@ -14,7 +14,7 @@ mod list_elements; mod other_functions; type FnCallCompiler = - fn(&mut Compiler, &TulispObject, &TulispObject) -> Result, Error>; + fn(&mut TulispContext, &TulispObject, &TulispObject) -> Result, Error>; pub(crate) struct VMCompilers { // TulispObject.addr() -> implementation @@ -107,34 +107,44 @@ impl VMCompilers { } } -impl Compiler<'_> { - pub(super) fn compile_form(&mut self, form: &TulispObject) -> Result, Error> { - let name = form.car()?; - let args = form.cdr()?; - if let Some(compiler) = self.functions.functions.get(&name.addr_as_usize()) { - return compiler(self, &name, &args); - } - if let Ok(func) = self.ctx.eval(&name) { - match &*func.inner_ref() { - TulispValue::Func(func) => { - return Ok(vec![ - Instruction::Push(args.clone()), - Instruction::RustCall { - name: name.clone(), - func: func.clone(), - keep_result: self.keep_result, - }, - ]); - } - TulispValue::Defmacro { .. } | TulispValue::Macro(..) => { - // TODO: this should not be necessary, this should be - // handled in the parser instead. - let form = macroexpand(self.ctx, form.clone())?; - return self.compile_expr(&form); - } - _ => {} +pub(super) fn compile_form( + ctx: &mut TulispContext, + form: &TulispObject, +) -> Result, Error> { + let name = form.car()?; + let args = form.cdr()?; + if let Some(compiler) = ctx + .compiler + .as_ref() + .unwrap() + .vm_compilers + .functions + .get(&name.addr_as_usize()) + { + return compiler(ctx, &name, &args); + } + if let Ok(func) = ctx.eval(&name) { + match &*func.inner_ref() { + TulispValue::Func(func) => { + let compiler = ctx.compiler.as_mut().unwrap(); + return Ok(vec![ + Instruction::Push(args.clone()), + Instruction::RustCall { + name: name.clone(), + func: func.clone(), + keep_result: compiler.keep_result, + }, + ]); } + TulispValue::Defmacro { .. } | TulispValue::Macro(..) => { + // TODO: this should not be necessary, this should be + // handled in the parser instead. + let form = macroexpand(ctx, form.clone())?; + return compile_expr(ctx, &form); + } + _ => {} } - other_functions::compile_fn_defun_call(self, &name, &args) } + + other_functions::compile_fn_defun_call(ctx, &name, &args) } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 13c83811..452eeac3 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -1,20 +1,28 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ - bytecode::{compiler::VMDefunParams, Compiler, Instruction, Pos}, + bytecode::{ + compiler::{ + compiler::{ + compile_expr, compile_expr_keep_result, compile_progn, compile_progn_keep_result, + }, + VMDefunParams, + }, + Instruction, Pos, + }, destruct_bind, list, parse::mark_tail_calls, - Error, ErrorKind, TulispObject, + Error, ErrorKind, TulispContext, TulispObject, }; pub(super) fn compile_fn_print( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { - let mut result = compiler.compile_expr_keep_result(arg)?; - if compiler.keep_result { + ctx.compile_1_arg_call(name, args, false, |ctx, arg, _| { + let mut result = compile_expr_keep_result(ctx, arg)?; + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Print); } else { result.push(Instruction::PrintPop); @@ -24,11 +32,12 @@ pub(super) fn compile_fn_print( } pub(super) fn compile_fn_quote( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(name, args, false, |compiler, arg, _| { + ctx.compile_1_arg_call(name, args, false, |ctx, arg, _| { + let compiler = ctx.compiler.as_mut().unwrap(); if compiler.keep_result { return Ok(vec![Instruction::Push(arg.clone().into())]); } else { @@ -38,13 +47,13 @@ pub(super) fn compile_fn_quote( } pub(super) fn compile_fn_setq( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr_keep_result(arg2)?; - if compiler.keep_result { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr_keep_result(ctx, arg2)?; + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Store(arg1.clone())); } else { result.push(Instruction::StorePop(arg1.clone())); @@ -54,14 +63,14 @@ pub(super) fn compile_fn_setq( } pub(super) fn compile_fn_set( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, false, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr_keep_result(arg2)?; - result.append(&mut compiler.compile_expr_keep_result(arg1)?); - if compiler.keep_result { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr_keep_result(ctx, arg2)?; + result.append(&mut compile_expr_keep_result(ctx, arg1)?); + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Set); } else { result.push(Instruction::SetPop); @@ -71,14 +80,14 @@ pub(super) fn compile_fn_set( } pub(super) fn compile_fn_cons( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_2_arg_call(name, args, true, |compiler, arg1, arg2, _| { - let mut result = compiler.compile_expr(arg1)?; - result.append(&mut compiler.compile_expr(arg2)?); - if compiler.keep_result { + ctx.compile_2_arg_call(name, args, true, |ctx, arg1, arg2, _| { + let mut result = compile_expr(ctx, arg1)?; + result.append(&mut compile_expr(ctx, arg2)?); + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Cons); } Ok(result) @@ -86,54 +95,56 @@ pub(super) fn compile_fn_cons( } pub(super) fn compile_fn_list( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { if let Some(name) = args.is_bounced() { - return compile_fn_defun_bounce_call(compiler, &name, args); + return compile_fn_defun_bounce_call(ctx, &name, args); } let mut result = vec![]; let mut len = 0; for arg in args.base_iter() { - result.append(&mut compiler.compile_expr(&arg)?); + result.append(&mut compile_expr(ctx, &arg)?); len += 1; } - if compiler.keep_result { + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::List(len)); } Ok(result) } pub(super) fn compile_fn_append( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; let mut len = 0; for arg in args.base_iter() { - result.append(&mut compiler.compile_expr(&arg)?); + result.append(&mut compile_expr(ctx, &arg)?); len += 1; } - if compiler.keep_result { + if ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Append(len)); } Ok(result) } fn compile_fn_defun_bounce_call( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { + let compiler = ctx.compiler.as_mut().unwrap(); + let mut result = vec![]; let params = compiler.defun_args[&name.addr_as_usize()].clone(); let mut args_count = 0; // cdr because the first element is `Bounce`. for arg in args.cdr()?.base_iter() { - result.append(&mut compiler.compile_expr_keep_result(&arg)?); + result.append(&mut compile_expr_keep_result(ctx, &arg)?); args_count += 1; } let mut optional_count = 0; @@ -179,17 +190,18 @@ fn compile_fn_defun_bounce_call( } pub(super) fn compile_fn_defun_call( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut result = vec![]; let mut args_count = 0; - if name.consp() && name.car_and_then(|name| Ok(name.eq(&compiler.ctx.intern("lambda"))))? { - compile_fn_defun(compiler, &name.car()?, &list!(name.clone() ,@name.cdr()?)?)?; + if name.consp() && name.car_and_then(|name| Ok(name.eq(&ctx.intern("lambda"))))? { + compile_fn_defun(ctx, &name.car()?, &list!(name.clone() ,@name.cdr()?)?)?; } + for arg in args.base_iter() { - result.append(&mut compiler.compile_expr_keep_result(&arg)?); + result.append(&mut compile_expr_keep_result(ctx, &arg)?); args_count += 1; } result.push(Instruction::Call { @@ -199,19 +211,24 @@ pub(super) fn compile_fn_defun_call( optional_count: 0, rest_count: 0, }); - if !compiler.keep_result { + if !ctx.compiler.as_ref().unwrap().keep_result { result.push(Instruction::Pop); } Ok(result) } pub(super) fn compile_fn_defun( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - let mut res = compiler.compile_2_arg_call(name, args, true, |ctx, name, args, body| { - ctx.functions + let mut res = ctx.compile_2_arg_call(name, args, true, |ctx, name, args, body| { + let optional_symbol = ctx.intern("&optional"); + let rest_symbol = ctx.intern("&rest"); + + let compiler = ctx.compiler.as_mut().unwrap(); + compiler + .vm_compilers .functions .insert(name.addr_as_usize(), compile_fn_defun_call); let mut required = vec![]; @@ -220,8 +237,6 @@ pub(super) fn compile_fn_defun( let args = args.base_iter().collect::>(); let mut is_optional = false; let mut is_rest = false; - let optional_symbol = ctx.ctx.intern("&optional"); - let rest_symbol = ctx.ctx.intern("&rest"); for arg in args.iter() { if arg.eq(&optional_symbol) { if is_rest { @@ -257,7 +272,7 @@ pub(super) fn compile_fn_defun( } } - ctx.defun_args.insert( + compiler.defun_args.insert( name.addr_as_usize(), VMDefunParams { required, @@ -271,11 +286,11 @@ pub(super) fn compile_fn_defun( } else { body.clone() }; - let body = mark_tail_calls(ctx.ctx, name.clone(), body).map_err(|e| { + let body = mark_tail_calls(ctx, name.clone(), body).map_err(|e| { println!("mark_tail_calls error: {:?}", e); e })?; - let mut result = ctx.compile_progn_keep_result(&body)?; + let mut result = compile_progn_keep_result(ctx, &body)?; result.push(Instruction::Ret); // This use of a `List` instruction is a hack to get the address of the @@ -289,6 +304,7 @@ pub(super) fn compile_fn_defun( let Some(Instruction::List(addr)) = res.pop() else { unreachable!() }; + let compiler = ctx.compiler.as_mut().unwrap(); compiler .bytecode .functions @@ -297,11 +313,11 @@ pub(super) fn compile_fn_defun( } pub(super) fn compile_fn_let_star( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(name, args, true, |ctx, varlist, body| { + ctx.compile_1_arg_call(name, args, true, |ctx, varlist, body| { let mut result = vec![]; let mut params = vec![]; let mut symbols = vec![]; @@ -335,9 +351,7 @@ pub(super) fn compile_fn_let_star( let param = name.clone(); params.push(param.clone()); result.append( - &mut ctx - .compile_expr_keep_result(&value) - .map_err(|e| e.with_trace(value))?, + &mut compile_expr_keep_result(ctx, &value).map_err(|e| e.with_trace(value))?, ); result.push(Instruction::BeginScope(param)); } else { @@ -351,7 +365,7 @@ pub(super) fn compile_fn_let_star( .with_trace(varitem)); } } - let mut body = ctx.compile_progn(body)?; + let mut body = compile_progn(ctx, body)?; if body.is_empty() { return Ok(vec![]); } @@ -364,27 +378,27 @@ pub(super) fn compile_fn_let_star( } pub(super) fn compile_fn_progn( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - Ok(compiler.compile_progn(args)?) + Ok(compile_progn(ctx, args)?) } pub(super) fn compile_fn_load_file( - compiler: &mut Compiler<'_>, + ctx: &mut TulispContext, _name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - compiler.compile_1_arg_call(_name, args, true, |compiler, arg, _| { - let mut result = compiler.compile_expr_keep_result(&arg)?; + ctx.compile_1_arg_call(_name, args, true, |ctx, arg, _| { + let mut result = compile_expr_keep_result(ctx, &arg)?; result.push(Instruction::LoadFile); Ok(result) }) } pub(super) fn compile_fn_noop( - _compiler: &mut Compiler<'_>, + _ctx: &mut TulispContext, _name: &TulispObject, _args: &TulispObject, ) -> Result, Error> { diff --git a/src/bytecode/compiler/mod.rs b/src/bytecode/compiler/mod.rs index 9a823612..3265d98a 100644 --- a/src/bytecode/compiler/mod.rs +++ b/src/bytecode/compiler/mod.rs @@ -1,3 +1,5 @@ mod compiler; mod forms; -pub(crate) use compiler::{Compiler, VMDefunParams}; +pub(crate) use compiler::{compile, Compiler, VMDefunParams}; + +pub(crate) use forms::VMCompilers; diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index a415390c..c3e12bac 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,4 +1,4 @@ -use super::{bytecode::Bytecode, Compiler, Instruction}; +use super::{bytecode::Bytecode, compile, Instruction}; use crate::{bytecode::Pos, Error, TulispContext, TulispObject}; use std::collections::HashMap; @@ -169,7 +169,7 @@ impl Machine { .as_string() .map_err(|err| err.with_trace(filename))?; let ast = ctx.parse_file(&filename)?; - let bytecode = Compiler::new(ctx).compile(&ast)?; + let bytecode = compile(ctx, &ast)?; // TODO: support global code in modules if bytecode.global.borrow().len() > 0 { return Err(Error::new( diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index a85cc076..04291e56 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -8,4 +8,4 @@ mod interpreter; pub(crate) use interpreter::Machine; mod compiler; -pub(crate) use compiler::Compiler; +pub(crate) use compiler::{compile, Compiler, VMCompilers}; diff --git a/src/context.rs b/src/context.rs index 6cf91317..1a43edf0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, fs}; use crate::{ builtin, - bytecode::{self, Bytecode, Compiler}, + bytecode::{self, compile, Bytecode, Compiler}, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, @@ -10,6 +10,8 @@ use crate::{ TulispObject, }; +use crate::bytecode::VMCompilers; + #[derive(Debug, Default, Clone)] pub(crate) struct Scope { pub scope: Vec, @@ -41,6 +43,7 @@ impl Scope { pub struct TulispContext { obarray: HashMap, pub(crate) filenames: Vec, + pub(crate) compiler: Option, } impl Default for TulispContext { @@ -55,9 +58,12 @@ impl TulispContext { let mut ctx = Self { obarray: HashMap::new(), filenames: vec!["".to_string()], + compiler: None, }; builtin::functions::add(&mut ctx); builtin::macros::add(&mut ctx); + let vm_compilers = VMCompilers::new(&mut ctx); + ctx.compiler = Some(Compiler::new(vm_compilers)); ctx } @@ -181,22 +187,13 @@ impl TulispContext { /// Parses and evaluates the contents of the given file and returns the /// value. pub fn eval_file(&mut self, filename: &str) -> Result { - let contents = fs::read_to_string(filename).map_err(|e| { - Error::new( - crate::ErrorKind::Undefined, - format!("Unable to read file: {filename}. Error: {e}"), - ) - })?; - self.filenames.push(filename.to_owned()); - - let string: &str = &contents; - let vv = parse(self, self.filenames.len() - 1, string)?; + let vv = self.parse_file(filename)?; self.eval_progn(&vv) } pub fn vm_eval_string(&mut self, string: &str) -> Result { let vv = parse(self, 0, string)?; - let bytecode = Compiler::new(self).compile(&vv)?; + let bytecode = compile(self, &vv)?; bytecode::Machine::new(bytecode).run(self) } @@ -205,9 +202,9 @@ impl TulispContext { let vv = self.parse_file(filename)?; println!("Parsing took: {:?}", start.elapsed()); let start = std::time::Instant::now(); - let bytecode = Compiler::new(self).compile(&vv)?; + let bytecode = compile(self, &vv)?; println!("Compiling took: {:?}", start.elapsed()); - println!("{}", bytecode); + // println!("{}", bytecode); let start = std::time::Instant::now(); let ret = bytecode::Machine::new(bytecode).run(self); println!("Running took: {:?}", start.elapsed()); @@ -225,10 +222,15 @@ impl TulispContext { format!("Unable to read file: {filename}. Error: {e}"), ) })?; - self.filenames.push(filename.to_owned()); + let idx = if let Some(idx) = self.filenames.iter().position(|x| x == filename) { + idx + } else { + self.filenames.push(filename.to_owned()); + self.filenames.len() - 1 + }; let string: &str = &contents; - parse(self, self.filenames.len() - 1, string) + parse(self, idx, string) } #[allow(dead_code)] @@ -238,9 +240,9 @@ impl TulispContext { keep_result: bool, ) -> Result { let vv = parse(self, 0, string)?; - let mut compiler = Compiler::new(self); + let compiler = self.compiler.as_mut().unwrap(); compiler.keep_result = keep_result; - compiler.compile(&vv) + compile(self, &vv) } #[allow(dead_code)] From b70cfbb886b08964845b4200a26a270b0bbd32fa Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Wed, 14 Feb 2024 02:23:27 +0100 Subject: [PATCH 119/142] Compile calls to `plist-get` --- src/bytecode/compiler/forms/mod.rs | 2 ++ src/bytecode/compiler/forms/plist.rs | 19 +++++++++++++++++++ src/bytecode/instruction.rs | 2 ++ src/bytecode/interpreter.rs | 10 +++++++++- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/bytecode/compiler/forms/plist.rs diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index fc8faa3d..96856b12 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -12,6 +12,7 @@ mod comparison_of_numbers; mod conditionals; mod list_elements; mod other_functions; +mod plist; type FnCallCompiler = fn(&mut TulispContext, &TulispObject, &TulispObject) -> Result, Error>; @@ -62,6 +63,7 @@ impl VMCompilers { ("cons", other_functions::compile_fn_cons), ("list", other_functions::compile_fn_list), ("append", other_functions::compile_fn_append), + ("plist-get", plist::compile_fn_plist_get), // cxr ("car", list_elements::compile_fn_cxr), ("cdr", list_elements::compile_fn_cxr), diff --git a/src/bytecode/compiler/forms/plist.rs b/src/bytecode/compiler/forms/plist.rs new file mode 100644 index 00000000..2ae81fd4 --- /dev/null +++ b/src/bytecode/compiler/forms/plist.rs @@ -0,0 +1,19 @@ +use crate::{ + bytecode::{compiler::compiler::compile_expr, Instruction}, + Error, TulispContext, TulispObject, +}; + +pub(super) fn compile_fn_plist_get( + ctx: &mut TulispContext, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + ctx.compile_2_arg_call(name, args, false, |ctx, plist, property, _| { + let mut result = compile_expr(ctx, property)?; + result.append(&mut compile_expr(ctx, plist)?); + if ctx.compiler.as_ref().unwrap().keep_result { + result.push(Instruction::PlistGet); + } + Ok(result) + }) +} diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index c3265f8c..502d4c6c 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -123,6 +123,7 @@ pub(crate) enum Instruction { List(usize), Append(usize), Cxr(Cxr), + PlistGet, } impl std::fmt::Display for Instruction { @@ -202,6 +203,7 @@ impl std::fmt::Display for Instruction { Cxr::Cdddar => write!(f, " cdddar"), Cxr::Cddddr => write!(f, " cddddr"), }, + Instruction::PlistGet => write!(f, " plist_get"), } } } diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index c3e12bac..0878801a 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,5 +1,5 @@ use super::{bytecode::Bytecode, compile, Instruction}; -use crate::{bytecode::Pos, Error, TulispContext, TulispObject}; +use crate::{bytecode::Pos, lists, Error, TulispContext, TulispObject}; use std::collections::HashMap; macro_rules! binary_ops { @@ -487,6 +487,14 @@ impl Machine { } }) } + Instruction::PlistGet => { + let [ref key, ref plist] = self.stack[(self.stack.len() - 2)..] else { + unreachable!() + }; + let value = lists::plist_get(plist, key)?; + self.stack.truncate(self.stack.len() - 2); + self.stack.push(value); + } // predicates Instruction::Null => { let a = self.stack.last().unwrap().null(); From e552e14db30bc97012246fe8b9e53ce45483c076 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 25 Feb 2024 19:11:41 +0100 Subject: [PATCH 120/142] Eliminate function lookup everytime when calling a function Instead hold onto an `Rc` to the instructions and pass that to the runtime. --- .../compiler/forms/other_functions.rs | 1 + src/bytecode/instruction.rs | 3 ++- src/bytecode/interpreter.rs | 24 +++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 452eeac3..0a9569e4 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -207,6 +207,7 @@ pub(super) fn compile_fn_defun_call( result.push(Instruction::Call { name: name.clone(), args_count, + instructions: None, params: None, optional_count: 0, rest_count: 0, diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 502d4c6c..e9d7b9c3 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{cell::RefCell, rc::Rc}; use crate::{value::TulispFn, TulispObject}; @@ -113,6 +113,7 @@ pub(crate) enum Instruction { Call { name: TulispObject, args_count: usize, + instructions: Option>>>, params: Option, optional_count: usize, rest_count: usize, diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 0878801a..edc5904b 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,6 +1,6 @@ use super::{bytecode::Bytecode, compile, Instruction}; use crate::{bytecode::Pos, lists, Error, TulispContext, TulispObject}; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; macro_rules! binary_ops { ($oper:expr) => {{ @@ -117,22 +117,17 @@ impl Machine { } pub fn run(&mut self, ctx: &mut TulispContext) -> Result { - self.run_impl(ctx, None, 0)?; + self.run_impl(ctx, &self.bytecode.global.clone(), 0)?; Ok(self.stack.pop().unwrap().into()) } fn run_impl( &mut self, ctx: &mut TulispContext, - func: Option, + program: &Rc>>, recursion_depth: u32, ) -> Result<(), Error> { let mut pc: usize = 0; - let program = if let Some(func) = func { - self.bytecode.functions.get(&func).unwrap().clone() - } else { - self.bytecode.global.clone() - }; let program_size = program.borrow().len(); let mut instr_ref = program.borrow_mut(); while pc < program_size { @@ -361,12 +356,18 @@ impl Machine { name, params, args_count, + instructions, optional_count, rest_count, } => { - let addr = name.addr_as_usize(); - let func = Some(addr); + if instructions.is_none() { + let addr = name.addr_as_usize(); + let instrs = self.bytecode.functions.get(&addr).unwrap().clone(); + *instructions = Some(instrs); + } + let instructions = instructions.as_ref().unwrap().clone(); if params.is_none() { + let addr = name.addr_as_usize(); if let Some(items) = self.bytecode.defun_args.get(&addr) { *params = Some(items.clone()); let left_args = *args_count - items.required.len(); @@ -407,9 +408,8 @@ impl Machine { arg.set_scope(self.stack.pop().unwrap()).unwrap(); set_params.push(arg.clone()); } - drop(instr_ref); - self.run_impl(ctx, func, recursion_depth + 1)?; + self.run_impl(ctx, &instructions, recursion_depth + 1)?; instr_ref = program.borrow_mut(); for arg in set_params.iter() { arg.unset().unwrap(); From 268294d7fe003055abefbf60dff03e90140ed661 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 02:53:42 +0200 Subject: [PATCH 121/142] Intern keywords once in context --- .../compiler/forms/other_functions.rs | 9 +-- src/context.rs | 55 ++++++++++++++++--- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 0a9569e4..5b9dbe45 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -196,7 +196,7 @@ pub(super) fn compile_fn_defun_call( ) -> Result, Error> { let mut result = vec![]; let mut args_count = 0; - if name.consp() && name.car_and_then(|name| Ok(name.eq(&ctx.intern("lambda"))))? { + if name.consp() && name.car_and_then(|name| Ok(name.eq(&ctx.keywords.lambda)))? { compile_fn_defun(ctx, &name.car()?, &list!(name.clone() ,@name.cdr()?)?)?; } @@ -224,9 +224,6 @@ pub(super) fn compile_fn_defun( args: &TulispObject, ) -> Result, Error> { let mut res = ctx.compile_2_arg_call(name, args, true, |ctx, name, args, body| { - let optional_symbol = ctx.intern("&optional"); - let rest_symbol = ctx.intern("&rest"); - let compiler = ctx.compiler.as_mut().unwrap(); compiler .vm_compilers @@ -239,7 +236,7 @@ pub(super) fn compile_fn_defun( let mut is_optional = false; let mut is_rest = false; for arg in args.iter() { - if arg.eq(&optional_symbol) { + if arg.eq(&ctx.keywords.amp_optional) { if is_rest { return Err(Error::new( ErrorKind::Undefined, @@ -248,7 +245,7 @@ pub(super) fn compile_fn_defun( .with_trace(arg.clone())); } is_optional = true; - } else if arg.eq(&rest_symbol) { + } else if arg.eq(&ctx.keywords.amp_rest) { if is_rest { return Err( Error::new(ErrorKind::Undefined, "rest after rest".to_string()) diff --git a/src/context.rs b/src/context.rs index dad1f4ed..ddfb335d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,17 +1,43 @@ use std::{collections::HashMap, fs, rc::Rc}; use crate::{ - builtin, - bytecode::{self, compile, Bytecode, Compiler}, - error::Error, - eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, - list, - parse::parse, - TulispObject, TulispValue, + builtin, bytecode::{self, compile, Bytecode, Compiler}, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, parse::parse, TulispObject, TulispValue }; use crate::bytecode::VMCompilers; +macro_rules! intern_from_obarray { + ($( #[$meta:meta] )* + $vis:vis struct $struct_name:ident { + $($name:ident : $symbol:literal),+ $(,)? + }) => { + $( #[$meta] )* + $vis struct $struct_name { + $(pub $name: $crate::TulispObject),+ + } + + impl $struct_name { + fn from_obarray(obarray: &mut std::collections::HashMap) -> Self { + $struct_name { + $($name: intern_from_obarray!(@intern obarray, $symbol)),+ + } + } + } + }; + + (@intern $obarray:ident, $name:literal) => { + if let Some(sym) = $obarray.get($name) { + sym.clone_without_span() + } else { + let name = $name.to_string(); + let constant = name.starts_with(':'); + let sym = TulispObject::symbol(name.clone(), constant); + $obarray.insert(name, sym.clone()); + sym + } + } +} + #[derive(Debug, Default, Clone)] pub(crate) struct Scope { pub scope: Vec, @@ -32,6 +58,15 @@ impl Scope { } } +intern_from_obarray! { + #[derive(Clone)] + pub(crate) struct Keywords { + amp_optional: "&optional", + amp_rest: "&rest", + lambda: "lambda", + } +} + /// Represents an instance of the _Tulisp_ interpreter. /// /// Owns the @@ -44,6 +79,7 @@ pub struct TulispContext { obarray: HashMap, pub(crate) filenames: Vec, pub(crate) compiler: Option, + pub(crate) keywords: Keywords, } impl Default for TulispContext { @@ -55,10 +91,13 @@ impl Default for TulispContext { impl TulispContext { /// Creates a TulispContext with an empty global scope. pub fn new() -> Self { + let mut obarray = HashMap::new(); + let keywords = Keywords::from_obarray(&mut obarray); let mut ctx = Self { - obarray: HashMap::new(), + obarray, filenames: vec!["".to_string()], compiler: None, + keywords, }; builtin::functions::add(&mut ctx); builtin::macros::add(&mut ctx); From c4a95410daef5a11639eedcb2cb844392488d998 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 12:25:43 +0200 Subject: [PATCH 122/142] Simplify function execution from the VM To help with implementing funcall later. --- src/bytecode/interpreter.rs | 93 ++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index edc5904b..18c0aaa1 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,4 +1,4 @@ -use super::{bytecode::Bytecode, compile, Instruction}; +use super::{bytecode::Bytecode, compile, compiler::VMDefunParams, Instruction}; use crate::{bytecode::Pos, lists, Error, TulispContext, TulispObject}; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -42,6 +42,26 @@ macro_rules! compare_ops { }}; } +struct SetParams(Vec); + +impl SetParams { + fn new() -> Self { + Self(Vec::new()) + } + + fn push(&mut self, obj: TulispObject) { + self.0.push(obj); + } +} + +impl Drop for SetParams { + fn drop(&mut self) { + for obj in self.0.iter() { + obj.unset().unwrap(); + } + } +} + pub struct Machine { stack: Vec, bytecode: Bytecode, @@ -365,7 +385,6 @@ impl Machine { let instrs = self.bytecode.functions.get(&addr).unwrap().clone(); *instructions = Some(instrs); } - let instructions = instructions.as_ref().unwrap().clone(); if params.is_none() { let addr = name.addr_as_usize(); if let Some(items) = self.bytecode.defun_args.get(&addr) { @@ -386,34 +405,14 @@ impl Machine { } } + let instructions = instructions.as_ref().unwrap().clone(); let Some(params) = params else { unreachable!() }; - let mut set_params = vec![]; - if let Some(rest) = ¶ms.rest { - let mut rest_value = TulispObject::nil(); - for _ in 0..*rest_count { - rest_value = TulispObject::cons(self.stack.pop().unwrap(), rest_value); - } - rest.set_scope(rest_value).unwrap(); - set_params.push(rest.clone()); - } - for (ii, arg) in params.optional.iter().enumerate().rev() { - if ii >= *optional_count { - arg.set_scope(TulispObject::nil()).unwrap(); - continue; - } - arg.set_scope(self.stack.pop().unwrap()).unwrap(); - set_params.push(arg.clone()); - } - for arg in params.required.iter().rev() { - arg.set_scope(self.stack.pop().unwrap()).unwrap(); - set_params.push(arg.clone()); - } + + let params = self.init_defun_args(¶ms, optional_count, rest_count); drop(instr_ref); - self.run_impl(ctx, &instructions, recursion_depth + 1)?; + self.run_function(ctx, &instructions, recursion_depth + 1)?; instr_ref = program.borrow_mut(); - for arg in set_params.iter() { - arg.unset().unwrap(); - } + drop(params); } Instruction::Ret => return Ok(()), Instruction::RustCall { @@ -505,4 +504,44 @@ impl Machine { } Ok(()) } + + fn init_defun_args( + &mut self, + params: &VMDefunParams, + optional_count: &usize, + rest_count: &usize, + ) -> SetParams { + let mut set_params = SetParams::new(); + if let Some(rest) = ¶ms.rest { + let mut rest_value = TulispObject::nil(); + for _ in 0..*rest_count { + rest_value = TulispObject::cons(self.stack.pop().unwrap(), rest_value); + } + rest.set_scope(rest_value).unwrap(); + set_params.push(rest.clone()); + } + for (ii, arg) in params.optional.iter().enumerate().rev() { + if ii >= *optional_count { + arg.set_scope(TulispObject::nil()).unwrap(); + continue; + } + arg.set_scope(self.stack.pop().unwrap()).unwrap(); + set_params.push(arg.clone()); + } + for arg in params.required.iter().rev() { + arg.set_scope(self.stack.pop().unwrap()).unwrap(); + set_params.push(arg.clone()); + } + set_params + } + + fn run_function( + &mut self, + ctx: &mut TulispContext, + instructions: &Rc>>, + recursion_depth: u32, + ) -> Result<(), Error> { + self.run_impl(ctx, &instructions, recursion_depth)?; + Ok(()) + } } From 6db95e4c7adcf65e50e845a8a7afdaf909ba60b5 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 12:58:53 +0200 Subject: [PATCH 123/142] Update VM error messages to match the tree walker --- .../compiler/forms/comparison_of_numbers.rs | 4 ++-- src/bytecode/interpreter.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/bytecode/compiler/forms/comparison_of_numbers.rs b/src/bytecode/compiler/forms/comparison_of_numbers.rs index 986c245e..bf742b9e 100644 --- a/src/bytecode/compiler/forms/comparison_of_numbers.rs +++ b/src/bytecode/compiler/forms/comparison_of_numbers.rs @@ -18,8 +18,8 @@ fn compile_fn_compare( let args = args.base_iter().collect::>(); if args.len() < 2 { return Err(Error::new( - crate::ErrorKind::ArityMismatch, - format!("{} requires at least 2 arguments.", name), + crate::ErrorKind::OutOfRange, // TODO: change to ArityMismatch + format!("{} requires at least 2 arguments", name), )); } for items in args.windows(2) { diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 18c0aaa1..cabb261f 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -25,6 +25,20 @@ macro_rules! binary_ops { macro_rules! compare_ops { ($oper:expr) => {{ |selfobj: &TulispObject, other: &TulispObject| -> Result { + if !selfobj.numberp() { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, + format!("Expected number, found: {selfobj}"), + ) + .with_trace(selfobj.clone())); + } + if !other.numberp() { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, + format!("Expected number, found: {other}"), + ) + .with_trace(other.clone())); + } if selfobj.floatp() { let s: f64 = selfobj.as_float().unwrap(); let o: f64 = other.try_into()?; From d8d7fde01609b8a442871197a4284446d1f8e066 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 13:12:41 +0200 Subject: [PATCH 124/142] Handle divide by zero --- src/bytecode/interpreter.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index cabb261f..150df19b 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -186,7 +186,15 @@ impl Machine { Add => binary_ops!(|a, b| a + b)(a, b)?, Sub => binary_ops!(|a, b| a - b)(a, b)?, Mul => binary_ops!(|a, b| a * b)(a, b)?, - Div => binary_ops!(|a, b| a / b)(a, b)?, + Div => { + if b.integerp() && b.as_int().unwrap() == 0 { + return Err(Error::new( + crate::ErrorKind::Undefined, + "Division by zero".to_string(), + )); + } + binary_ops!(|a, b| a / b)(a, b)? + }, } }; self.stack.truncate(self.stack.len() - 2); From 69c2165409083ecb42fdadf443a5fac9f8fc1512 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 15:24:38 +0200 Subject: [PATCH 125/142] Update VM error messages to match tree walker --- src/bytecode/compiler/forms/common.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/bytecode/compiler/forms/common.rs b/src/bytecode/compiler/forms/common.rs index 7cf89331..d9819908 100644 --- a/src/bytecode/compiler/forms/common.rs +++ b/src/bytecode/compiler/forms/common.rs @@ -14,11 +14,11 @@ impl TulispContext { ) -> Result, Error> { if args.null() { return Err(Error::new( - ErrorKind::ArityMismatch, + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { - format!("{} requires at least 1 argument.", name) + format!("{} requires at least 1 argument", name) } else { - format!("{} requires 1 argument.", name) + format!("{} requires exactly 1 argument", name) }, )); } @@ -26,8 +26,8 @@ impl TulispContext { args.cdr_and_then(|rest| { if !has_rest && !rest.null() { return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} accepts only 1 argument.", name), + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + format!("{} requires exactly 1 argument", name), )); } lambda(self, arg1, rest) @@ -49,21 +49,21 @@ impl TulispContext { ) -> Result, Error> { let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { return Err(Error::new( - ErrorKind::ArityMismatch, + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { - format!("{} requires at least 2 arguments.", name) + format!("{} requires at least 2 arguments", name) } else { - format!("{} requires 2 arguments.", name) + format!("{} requires exactly 2 arguments", name) }, )); }; if args.cdr().null() { return Err(Error::new( - ErrorKind::ArityMismatch, + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { - format!("{} requires at least 2 arguments.", name) + format!("{} requires at least 2 arguments", name) } else { - format!("{} requires 2 arguments.", name) + format!("{} requires exactly 2 arguments", name) }, )); } @@ -72,8 +72,8 @@ impl TulispContext { args.cdr().cdr_and_then(|rest| { if !has_rest && !rest.null() { return Err(Error::new( - ErrorKind::ArityMismatch, - format!("{} accepts only 2 arguments.", name), + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + format!("{} requires exactly 2 arguments", name), )); } lambda(self, arg1, arg2, rest) From 4bccab7ed183bb74431ecbec071cf8c29834b8c4 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 15:24:58 +0200 Subject: [PATCH 126/142] Fix compilation issue for calls to `cons` --- src/bytecode/compiler/forms/other_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 5b9dbe45..7c9ced90 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -84,7 +84,7 @@ pub(super) fn compile_fn_cons( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { - ctx.compile_2_arg_call(name, args, true, |ctx, arg1, arg2, _| { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { let mut result = compile_expr(ctx, arg1)?; result.append(&mut compile_expr(ctx, arg2)?); if ctx.compiler.as_ref().unwrap().keep_result { From fbf7eb1d4d7cd1e143e184f383d56e5ba7c81585 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 17:43:57 +0200 Subject: [PATCH 127/142] Remove unused variable --- src/bytecode/compiler/compiler.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 1f97ed98..f634db45 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -18,7 +18,6 @@ pub(crate) struct VMDefunParams { pub(crate) struct Compiler { pub vm_compilers: VMCompilers, pub defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx - pub symbol_to_binding_idx: HashMap>, pub bytecode: Bytecode, pub keep_result: bool, label_counter: usize, @@ -29,7 +28,6 @@ impl Compiler { Compiler { vm_compilers, defun_args: HashMap::new(), - symbol_to_binding_idx: HashMap::new(), bytecode: Bytecode::new(), keep_result: true, label_counter: 0, From d182089fdb5e12978230f3dc0986f4f2f607517b Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 18:14:17 +0200 Subject: [PATCH 128/142] Group defun instructions and params into `CompiledDefun` type --- src/bytecode/bytecode.rs | 14 ++-- src/bytecode/compiler/compiler.rs | 6 +- src/bytecode/compiler/forms/common.rs | 14 ++-- .../compiler/forms/other_functions.rs | 34 ++++++---- src/bytecode/instruction.rs | 7 +- src/bytecode/interpreter.rs | 67 ++++++++++++------- 6 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index f794f84a..b1220160 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -3,11 +3,16 @@ use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; use super::Instruction; use crate::bytecode::compiler::VMDefunParams; +#[derive(Default, Clone)] +pub(crate) struct CompiledDefun { + pub(crate) instructions: Rc>>, + pub(crate) params: VMDefunParams, +} + #[derive(Default, Clone)] pub(crate) struct Bytecode { pub(crate) global: Rc>>, - pub(crate) functions: HashMap>>>, - pub(crate) defun_args: HashMap, // fn_name.addr_as_usize() -> arg symbol idx + pub(crate) functions: HashMap, // key: fn_name.addr_as_usize() } impl fmt::Display for Bytecode { @@ -15,9 +20,9 @@ impl fmt::Display for Bytecode { for (i, instr) in self.global.borrow().iter().enumerate() { write!(f, "\n{:<40} # {}", instr.to_string(), i)?; } - for (name, instr) in &self.functions { + for (name, func) in &self.functions { write!(f, "\n\n{}:", name)?; - for (i, instr) in instr.borrow().iter().enumerate() { + for (i, instr) in func.instructions.borrow().iter().enumerate() { write!(f, "\n{:<40} # {}", instr.to_string(), i)?; } } @@ -32,6 +37,5 @@ impl Bytecode { pub(crate) fn import_functions(&mut self, other: &Bytecode) { self.functions.extend(other.functions.clone()); - self.defun_args.extend(other.defun_args.clone()); } } diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index f634db45..2ebd43f3 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -7,7 +7,7 @@ use crate::{ use super::forms::{compile_form, VMCompilers}; -#[derive(Clone)] +#[derive(Default, Clone)] pub(crate) struct VMDefunParams { pub required: Vec, pub optional: Vec, @@ -44,10 +44,6 @@ pub fn compile(ctx: &mut TulispContext, value: &TulispObject) -> Result Result, Error> { if args.null() { return Err(Error::new( - ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { format!("{} requires at least 1 argument", name) } else { @@ -26,7 +26,7 @@ impl TulispContext { args.cdr_and_then(|rest| { if !has_rest && !rest.null() { return Err(Error::new( - ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch format!("{} requires exactly 1 argument", name), )); } @@ -40,7 +40,7 @@ impl TulispContext { name: &TulispObject, args: &TulispObject, has_rest: bool, - lambda: fn( + mut lambda: impl FnMut( &mut TulispContext, &TulispObject, &TulispObject, @@ -49,7 +49,7 @@ impl TulispContext { ) -> Result, Error> { let TulispValue::List { cons: args, .. } = &*args.inner_ref() else { return Err(Error::new( - ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { format!("{} requires at least 2 arguments", name) } else { @@ -59,7 +59,7 @@ impl TulispContext { }; if args.cdr().null() { return Err(Error::new( - ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch if has_rest { format!("{} requires at least 2 arguments", name) } else { @@ -72,7 +72,7 @@ impl TulispContext { args.cdr().cdr_and_then(|rest| { if !has_rest && !rest.null() { return Err(Error::new( - ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + ErrorKind::TypeMismatch, // TODO: change to ArityMismatch format!("{} requires exactly 2 arguments", name), )); } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 7c9ced90..cc7f933a 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -2,6 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ bytecode::{ + bytecode::CompiledDefun, compiler::{ compiler::{ compile_expr, compile_expr_keep_result, compile_progn, compile_progn_keep_result, @@ -207,8 +208,7 @@ pub(super) fn compile_fn_defun_call( result.push(Instruction::Call { name: name.clone(), args_count, - instructions: None, - params: None, + function: None, optional_count: 0, rest_count: 0, }); @@ -223,6 +223,11 @@ pub(super) fn compile_fn_defun( name: &TulispObject, args: &TulispObject, ) -> Result, Error> { + let mut defun_params = VMDefunParams { + required: vec![], + optional: vec![], + rest: None, + }; let mut res = ctx.compile_2_arg_call(name, args, true, |ctx, name, args, body| { let compiler = ctx.compiler.as_mut().unwrap(); compiler @@ -270,14 +275,12 @@ pub(super) fn compile_fn_defun( } } - compiler.defun_args.insert( - name.addr_as_usize(), - VMDefunParams { - required, - optional, - rest, - }, - ); + defun_params = VMDefunParams { + required, + optional, + rest, + }; + // TODO: replace with `is_string` let body = if body.car()?.as_string().is_ok() { body.cdr()? @@ -303,10 +306,13 @@ pub(super) fn compile_fn_defun( unreachable!() }; let compiler = ctx.compiler.as_mut().unwrap(); - compiler - .bytecode - .functions - .insert(addr, Rc::new(RefCell::new(res))); + compiler.bytecode.functions.insert( + addr, + CompiledDefun { + instructions: Rc::new(RefCell::new(res)), + params: defun_params, + }, + ); Ok(vec![]) } diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index e9d7b9c3..2560e7a3 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -1,8 +1,8 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Rc; use crate::{value::TulispFn, TulispObject}; -use super::compiler::VMDefunParams; +use super::bytecode::CompiledDefun; #[derive(Clone)] pub(crate) enum Pos { @@ -113,8 +113,7 @@ pub(crate) enum Instruction { Call { name: TulispObject, args_count: usize, - instructions: Option>>>, - params: Option, + function: Option, optional_count: usize, rest_count: usize, }, diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 150df19b..e8be632f 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -5,6 +5,20 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; macro_rules! binary_ops { ($oper:expr) => {{ |selfobj: &TulispObject, other: &TulispObject| -> Result { + if !selfobj.numberp() { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, + format!("Expected number, found: {selfobj}"), + ) + .with_trace(selfobj.clone())); + } + if !other.numberp() { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, + format!("Expected number, found: {other}"), + ) + .with_trace(other.clone())); + } if selfobj.floatp() { let s: f64 = selfobj.as_float().unwrap(); let o: f64 = other.try_into()?; @@ -122,8 +136,8 @@ impl Machine { labels.insert(name.addr_as_usize(), i + 1); } } - for (_, instr) in &bytecode.functions { - for (i, instr) in instr.borrow().iter().enumerate() { + for (_, func) in &bytecode.functions { + for (i, instr) in func.instructions.borrow().iter().enumerate() { if let Instruction::Label(name) = instr { labels.insert(name.addr_as_usize(), i + 1); } @@ -143,7 +157,13 @@ impl Machine { recursion_depth, pc, if let Some(func) = func { - self.bytecode.functions.get(&func).unwrap().borrow()[pc].clone() + self.bytecode + .functions + .get(&func) + .unwrap() + .instructions + .borrow()[pc] + .clone() } else { self.bytecode.global.borrow()[pc].clone() } @@ -194,7 +214,7 @@ impl Machine { )); } binary_ops!(|a, b| a / b)(a, b)? - }, + } } }; self.stack.truncate(self.stack.len() - 2); @@ -396,41 +416,38 @@ impl Machine { } Instruction::Call { name, - params, + function, args_count, - instructions, optional_count, rest_count, } => { - if instructions.is_none() { - let addr = name.addr_as_usize(); - let instrs = self.bytecode.functions.get(&addr).unwrap().clone(); - *instructions = Some(instrs); - } - if params.is_none() { + if function.is_none() { let addr = name.addr_as_usize(); - if let Some(items) = self.bytecode.defun_args.get(&addr) { - *params = Some(items.clone()); - let left_args = *args_count - items.required.len(); - if left_args > items.optional.len() { - *rest_count = left_args - items.optional.len(); - *optional_count = items.optional.len(); - } else if left_args > 0 { - *optional_count = left_args - } - } else { + let Some(func) = self.bytecode.functions.get(&addr) else { return Err(Error::new( crate::ErrorKind::Undefined, format!("undefined function: {}", name), ) .with_trace(name.clone())); + }; + let func = func.clone(); + + let left_args = *args_count - func.params.required.len(); + if left_args > func.params.optional.len() { + *rest_count = left_args - func.params.optional.len(); + *optional_count = func.params.optional.len(); + } else if left_args > 0 { + *optional_count = left_args } + *function = Some(func); } - let instructions = instructions.as_ref().unwrap().clone(); - let Some(params) = params else { unreachable!() }; + let instructions = function.as_ref().unwrap().instructions.clone(); + let Some(function) = function.as_ref() else { + unreachable!() + }; - let params = self.init_defun_args(¶ms, optional_count, rest_count); + let params = self.init_defun_args(&function.params, optional_count, rest_count); drop(instr_ref); self.run_function(ctx, &instructions, recursion_depth + 1)?; instr_ref = program.borrow_mut(); From 9de62449d6c2922f1389a03e6681d2a313bf2065 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 18:19:33 +0200 Subject: [PATCH 129/142] Improve formatting --- .../functions/comparison_of_strings.rs | 26 ++++++++++++------- src/builtin/functions/mod.rs | 2 +- src/builtin/functions/sequences.rs | 6 ++--- src/context.rs | 8 +++++- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/builtin/functions/comparison_of_strings.rs b/src/builtin/functions/comparison_of_strings.rs index 07145666..701cdce2 100644 --- a/src/builtin/functions/comparison_of_strings.rs +++ b/src/builtin/functions/comparison_of_strings.rs @@ -1,4 +1,4 @@ -use crate::{TulispContext, TulispObject, Error, ErrorKind, destruct_bind, TulispValue}; +use crate::{destruct_bind, Error, ErrorKind, TulispContext, TulispObject, TulispValue}; fn string_cmp( ctx: &mut TulispContext, @@ -11,19 +11,19 @@ fn string_cmp( let string1 = string1.inner_ref(); let string2 = string2.inner_ref(); match (&*string1, &*string2) { - (TulispValue::String { value:string1 }, TulispValue::String { value:string2 }) => { + (TulispValue::String { value: string1 }, TulispValue::String { value: string2 }) => { return Ok(oper(string1, string2).into()); - }, + } (_, _) => { return Err(Error::new( ErrorKind::TypeMismatch, - "Both arguments need to be strings".to_string() - ).with_trace(if string1.stringp() { + "Both arguments need to be strings".to_string(), + ) + .with_trace(if string1.stringp() { arg2.clone() } else { arg1.clone() - } - )) + })) } } } @@ -32,7 +32,13 @@ pub(crate) fn add(ctx: &mut TulispContext) { ctx.add_special_form("string<", |ctx, args| string_cmp(ctx, args, PartialOrd::lt)); ctx.add_special_form("string>", |ctx, args| string_cmp(ctx, args, PartialOrd::gt)); ctx.add_special_form("string=", |ctx, args| string_cmp(ctx, args, PartialEq::eq)); - ctx.add_special_form("string-lessp", |ctx, args| string_cmp(ctx, args, PartialOrd::lt)); - ctx.add_special_form("string-greaterp", |ctx, args| string_cmp(ctx, args, PartialOrd::gt)); - ctx.add_special_form("string-equal", |ctx, args| string_cmp(ctx, args, PartialEq::eq)); + ctx.add_special_form("string-lessp", |ctx, args| { + string_cmp(ctx, args, PartialOrd::lt) + }); + ctx.add_special_form("string-greaterp", |ctx, args| { + string_cmp(ctx, args, PartialOrd::gt) + }); + ctx.add_special_form("string-equal", |ctx, args| { + string_cmp(ctx, args, PartialEq::eq) + }); } diff --git a/src/builtin/functions/mod.rs b/src/builtin/functions/mod.rs index 77b0d1fc..1982967f 100644 --- a/src/builtin/functions/mod.rs +++ b/src/builtin/functions/mod.rs @@ -54,6 +54,7 @@ macro_rules! intern_set_func { } pub(crate) mod common; +mod comparison_of_strings; mod conditionals; mod equality_predicates; mod functions; @@ -61,7 +62,6 @@ mod hash_table; mod list_elements; mod numbers; mod sequences; -mod comparison_of_strings; pub(crate) fn add(ctx: &mut TulispContext) { conditionals::add(ctx); diff --git a/src/builtin/functions/sequences.rs b/src/builtin/functions/sequences.rs index 88159617..d36e87ce 100644 --- a/src/builtin/functions/sequences.rs +++ b/src/builtin/functions/sequences.rs @@ -84,9 +84,9 @@ pub(crate) fn add(ctx: &mut TulispContext) { if let Some(err) = err { return Err(err); } - let ret = vec - .iter() - .fold(TulispObject::nil(), |v1, v2| TulispObject::cons(v2.clone(), v1)); + let ret = vec.iter().fold(TulispObject::nil(), |v1, v2| { + TulispObject::cons(v2.clone(), v1) + }); Ok(ret) } } diff --git a/src/context.rs b/src/context.rs index ddfb335d..d4baf0a1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,13 @@ use std::{collections::HashMap, fs, rc::Rc}; use crate::{ - builtin, bytecode::{self, compile, Bytecode, Compiler}, error::Error, eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, list, parse::parse, TulispObject, TulispValue + builtin, + bytecode::{self, compile, Bytecode, Compiler}, + error::Error, + eval::{eval, eval_and_then, eval_basic, funcall, DummyEval}, + list, + parse::parse, + TulispObject, TulispValue, }; use crate::bytecode::VMCompilers; From 0aeb44dd58f94974093d89a6ca9ce3261ad6fa03 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 18:21:46 +0200 Subject: [PATCH 130/142] Improve variable names in `defun` compiler This differentiates the `defun` keyword which is the name of the function being called, from the name of the defun being compiled. --- src/bytecode/compiler/forms/other_functions.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index cc7f933a..537aabda 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -220,7 +220,7 @@ pub(super) fn compile_fn_defun_call( pub(super) fn compile_fn_defun( ctx: &mut TulispContext, - name: &TulispObject, + defun_kw: &TulispObject, args: &TulispObject, ) -> Result, Error> { let mut defun_params = VMDefunParams { @@ -228,12 +228,12 @@ pub(super) fn compile_fn_defun( optional: vec![], rest: None, }; - let mut res = ctx.compile_2_arg_call(name, args, true, |ctx, name, args, body| { + let mut res = ctx.compile_2_arg_call(defun_kw, args, true, |ctx, defun_name, args, body| { let compiler = ctx.compiler.as_mut().unwrap(); compiler .vm_compilers .functions - .insert(name.addr_as_usize(), compile_fn_defun_call); + .insert(defun_name.addr_as_usize(), compile_fn_defun_call); let mut required = vec![]; let mut optional = vec![]; let mut rest = None; @@ -287,7 +287,7 @@ pub(super) fn compile_fn_defun( } else { body.clone() }; - let body = mark_tail_calls(ctx, name.clone(), body).map_err(|e| { + let body = mark_tail_calls(ctx, defun_name.clone(), body).map_err(|e| { println!("mark_tail_calls error: {:?}", e); e })?; @@ -298,7 +298,7 @@ pub(super) fn compile_fn_defun( // function we just compiled so we can insert it into the // bytecode.functions map. The instruction is discarded as soon as it is // read, and isn't part of the compiler's output. - result.push(Instruction::List(name.addr_as_usize())); + result.push(Instruction::List(defun_name.addr_as_usize())); Ok(result) })?; From de62f34f9b4202e2e0e997ffd30bf1899fd5f907 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 18:26:55 +0200 Subject: [PATCH 131/142] Improve test, to be compatible with both VM and tree walker They were both complaining about different `j`s earlier. --- tests/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 51bb11a6..acae0bae 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -474,10 +474,10 @@ fn test_eval() -> Result<(), Error> { "# } tulisp_assert! { - program: "(let ((j 10)) (+ j j))(+ j j)", + program: "(let ((j 10)) (+ j j))(+ j 1)", error: r#"ERR TypeMismatch: Variable definition is void: j :1.26-1.27: at j -:1.23-1.30: at (+ j j) +:1.23-1.30: at (+ j 1) "# } Ok(()) From 8ba26f66ca132375dc4ee5f2ce71cba654639e8a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 21:30:49 +0200 Subject: [PATCH 132/142] Simplify `defun` compilation further --- src/bytecode/compiler/forms/other_functions.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 537aabda..4514a34b 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -234,9 +234,6 @@ pub(super) fn compile_fn_defun( .vm_compilers .functions .insert(defun_name.addr_as_usize(), compile_fn_defun_call); - let mut required = vec![]; - let mut optional = vec![]; - let mut rest = None; let args = args.base_iter().collect::>(); let mut is_optional = false; let mut is_rest = false; @@ -260,27 +257,21 @@ pub(super) fn compile_fn_defun( is_optional = false; is_rest = true; } else if is_optional { - optional.push(arg.clone()); + defun_params.optional.push(arg.clone()); } else if is_rest { - if rest.is_some() { + if defun_params.rest.is_some() { return Err(Error::new( ErrorKind::Undefined, "multiple rest arguments".to_string(), ) .with_trace(arg.clone())); } - rest = Some(arg.clone()); + defun_params.rest = Some(arg.clone()); } else { - required.push(arg.clone()); + defun_params.required.push(arg.clone()); } } - defun_params = VMDefunParams { - required, - optional, - rest, - }; - // TODO: replace with `is_string` let body = if body.car()?.as_string().is_ok() { body.cdr()? From d6ee93dfd1e68407956360fe53aedd9cd5d4aa90 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 21:38:53 +0200 Subject: [PATCH 133/142] Check at compile-time that let variables are symbols --- src/builtin/functions/functions.rs | 3 ++- src/bytecode/compiler/forms/other_functions.rs | 7 +++++++ tests/tests.rs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/builtin/functions/functions.rs b/src/builtin/functions/functions.rs index ce2a1a86..c2a2c2c7 100644 --- a/src/builtin/functions/functions.rs +++ b/src/builtin/functions/functions.rs @@ -265,7 +265,8 @@ pub(crate) fn add(ctx: &mut TulispContext) { "varitems inside a let-varlist should be a var or a binding: {}", varitem ), - )); + ) + .with_trace(varitem.clone())); }; } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 4514a34b..382dff76 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -336,6 +336,13 @@ pub(super) fn compile_fn_let_star( ) .with_trace(varitem)); } + if !name.symbolp() { + return Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected Symbol: Can't assign to {}", name), + ) + .with_trace(name)); + } if !rest.null() { return Err(Error::new( ErrorKind::Undefined, diff --git a/tests/tests.rs b/tests/tests.rs index acae0bae..8b94f7e2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -825,6 +825,7 @@ fn test_let() -> Result<(), Error> { tulisp_assert! { program: "(let (18 (vv (+ 55 1)) (jj 20)) (+ vv jj 1))", error: r#"ERR SyntaxError: varitems inside a let-varlist should be a var or a binding: 18 +:1.7-1.9: at 18 :1.1-1.45: at (let (18 (vv (+ 55 1)) (jj 20)) (+ vv jj 1)) "# } From 9b7cafee3a5d9c7cb7c01868ed7989c10e2339d2 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 22:35:05 +0200 Subject: [PATCH 134/142] Add a `name` parameter to `CompiledDefun` This will be needed when implementing `funcall` on them. --- src/bytecode/bytecode.rs | 3 ++- .../compiler/forms/other_functions.rs | 25 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/bytecode/bytecode.rs b/src/bytecode/bytecode.rs index b1220160..a7db3f5c 100644 --- a/src/bytecode/bytecode.rs +++ b/src/bytecode/bytecode.rs @@ -1,10 +1,11 @@ use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; use super::Instruction; -use crate::bytecode::compiler::VMDefunParams; +use crate::{bytecode::compiler::VMDefunParams, TulispObject}; #[derive(Default, Clone)] pub(crate) struct CompiledDefun { + pub(crate) name: TulispObject, pub(crate) instructions: Rc>>, pub(crate) params: VMDefunParams, } diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index 382dff76..d5db9d68 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -228,7 +228,9 @@ pub(super) fn compile_fn_defun( optional: vec![], rest: None, }; + let mut fn_name = TulispObject::nil(); let mut res = ctx.compile_2_arg_call(defun_kw, args, true, |ctx, defun_name, args, body| { + fn_name = defun_name.clone(); let compiler = ctx.compiler.as_mut().unwrap(); compiler .vm_compilers @@ -285,25 +287,18 @@ pub(super) fn compile_fn_defun( let mut result = compile_progn_keep_result(ctx, &body)?; result.push(Instruction::Ret); - // This use of a `List` instruction is a hack to get the address of the - // function we just compiled so we can insert it into the - // bytecode.functions map. The instruction is discarded as soon as it is - // read, and isn't part of the compiler's output. - result.push(Instruction::List(defun_name.addr_as_usize())); - Ok(result) })?; - let Some(Instruction::List(addr)) = res.pop() else { - unreachable!() + let function = CompiledDefun { + name: fn_name.clone(), + instructions: Rc::new(RefCell::new(res)), + params: defun_params, }; let compiler = ctx.compiler.as_mut().unwrap(); - compiler.bytecode.functions.insert( - addr, - CompiledDefun { - instructions: Rc::new(RefCell::new(res)), - params: defun_params, - }, - ); + compiler + .bytecode + .functions + .insert(fn_name.addr_as_usize(), function); Ok(vec![]) } From fdba3fa3a76c91d2e58f7122850185b5668b3417 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 22:46:53 +0200 Subject: [PATCH 135/142] Reuse `Machine` instance from `TulispContext` Without this a `Machine` instance that has all the state won't be available to make `funcall`s on. --- src/bytecode/interpreter.rs | 19 ++++++++++++------- src/context.rs | 20 +++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index e8be632f..2f5d5fb7 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -117,14 +117,11 @@ macro_rules! jump_to_pos { } impl Machine { - pub(crate) fn new(bytecode: Bytecode) -> Self { - let labels = Self::locate_labels(&bytecode); + pub(crate) fn new() -> Self { Machine { stack: Vec::new(), - labels, - bytecode, - // program: programs::print_range(92, 100), - // program: programs::fib(30), + bytecode: Bytecode::new(), + labels: HashMap::new(), } } @@ -170,7 +167,15 @@ impl Machine { ); } - pub fn run(&mut self, ctx: &mut TulispContext) -> Result { + pub fn run( + &mut self, + ctx: &mut TulispContext, + bytecode: Bytecode, + ) -> Result { + let labels = Self::locate_labels(&bytecode); + self.labels.extend(labels); + self.bytecode.import_functions(&bytecode); + self.bytecode.global = bytecode.global; self.run_impl(ctx, &self.bytecode.global.clone(), 0)?; Ok(self.stack.pop().unwrap().into()) } diff --git a/src/context.rs b/src/context.rs index d4baf0a1..b06ba1ff 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fs, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fs, rc::Rc}; use crate::{ builtin, @@ -86,6 +86,7 @@ pub struct TulispContext { pub(crate) filenames: Vec, pub(crate) compiler: Option, pub(crate) keywords: Keywords, + pub(crate) vm: Rc>, } impl Default for TulispContext { @@ -104,6 +105,7 @@ impl TulispContext { filenames: vec!["".to_string()], compiler: None, keywords, + vm: Rc::new(RefCell::new(bytecode::Machine::new())), }; builtin::functions::add(&mut ctx); builtin::macros::add(&mut ctx); @@ -261,7 +263,10 @@ impl TulispContext { pub fn vm_eval_string(&mut self, string: &str) -> Result { let vv = parse(self, 0, string)?; let bytecode = compile(self, &vv)?; - bytecode::Machine::new(bytecode).run(self) + let vm = self.vm.clone(); + let res = vm.borrow_mut().run(self, bytecode); + drop(vm); + res } pub fn vm_eval_file(&mut self, filename: &str) -> Result { @@ -273,9 +278,11 @@ impl TulispContext { println!("Compiling took: {:?}", start.elapsed()); // println!("{}", bytecode); let start = std::time::Instant::now(); - let ret = bytecode::Machine::new(bytecode).run(self); + let vm = self.vm.clone(); + let res = vm.borrow_mut().run(self, bytecode); + drop(vm); println!("Running took: {:?}", start.elapsed()); - ret + res } pub(crate) fn get_filename(&self, file_id: usize) -> String { @@ -314,6 +321,9 @@ impl TulispContext { #[allow(dead_code)] pub(crate) fn run_bytecode(&mut self, bytecode: Bytecode) -> Result { - bytecode::Machine::new(bytecode).run(self) + let vm = self.vm.clone(); + let res = vm.borrow_mut().run(self, bytecode); + drop(vm); + res } } From b4a5db814c3429e58cd5b3919c1259d0176455fa Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 17 Aug 2024 23:13:44 +0200 Subject: [PATCH 136/142] Move value setting functions to a separate module --- src/bytecode/compiler/forms/mod.rs | 10 +- .../compiler/forms/other_functions.rs | 109 +---------------- src/bytecode/compiler/forms/setting.rs | 112 ++++++++++++++++++ 3 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 src/bytecode/compiler/forms/setting.rs diff --git a/src/bytecode/compiler/forms/mod.rs b/src/bytecode/compiler/forms/mod.rs index 96856b12..db2f3478 100644 --- a/src/bytecode/compiler/forms/mod.rs +++ b/src/bytecode/compiler/forms/mod.rs @@ -13,6 +13,7 @@ mod conditionals; mod list_elements; mod other_functions; mod plist; +mod setting; type FnCallCompiler = fn(&mut TulispContext, &TulispObject, &TulispObject) -> Result, Error>; @@ -53,12 +54,13 @@ impl VMCompilers { ("load", other_functions::compile_fn_load_file), ("print", other_functions::compile_fn_print), ("quote", other_functions::compile_fn_quote), - ("setq", other_functions::compile_fn_setq), - ("set", other_functions::compile_fn_set), ("defun", other_functions::compile_fn_defun), ("progn", other_functions::compile_fn_progn), - ("let", other_functions::compile_fn_let_star), - ("let*", other_functions::compile_fn_let_star), + // setting + ("let", setting::compile_fn_let_star), + ("let*", setting::compile_fn_let_star), + ("setq", setting::compile_fn_setq), + ("set", setting::compile_fn_set), // lists ("cons", other_functions::compile_fn_cons), ("list", other_functions::compile_fn_list), diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index d5db9d68..bfa2ab52 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -11,7 +11,7 @@ use crate::{ }, Instruction, Pos, }, - destruct_bind, list, + list, parse::mark_tail_calls, Error, ErrorKind, TulispContext, TulispObject, }; @@ -47,39 +47,6 @@ pub(super) fn compile_fn_quote( }) } -pub(super) fn compile_fn_setq( - ctx: &mut TulispContext, - name: &TulispObject, - args: &TulispObject, -) -> Result, Error> { - ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { - let mut result = compile_expr_keep_result(ctx, arg2)?; - if ctx.compiler.as_ref().unwrap().keep_result { - result.push(Instruction::Store(arg1.clone())); - } else { - result.push(Instruction::StorePop(arg1.clone())); - } - Ok(result) - }) -} - -pub(super) fn compile_fn_set( - ctx: &mut TulispContext, - name: &TulispObject, - args: &TulispObject, -) -> Result, Error> { - ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { - let mut result = compile_expr_keep_result(ctx, arg2)?; - result.append(&mut compile_expr_keep_result(ctx, arg1)?); - if ctx.compiler.as_ref().unwrap().keep_result { - result.push(Instruction::Set); - } else { - result.push(Instruction::SetPop); - } - Ok(result) - }) -} - pub(super) fn compile_fn_cons( ctx: &mut TulispContext, name: &TulispObject, @@ -229,7 +196,7 @@ pub(super) fn compile_fn_defun( rest: None, }; let mut fn_name = TulispObject::nil(); - let mut res = ctx.compile_2_arg_call(defun_kw, args, true, |ctx, defun_name, args, body| { + let res = ctx.compile_2_arg_call(defun_kw, args, true, |ctx, defun_name, args, body| { fn_name = defun_name.clone(); let compiler = ctx.compiler.as_mut().unwrap(); compiler @@ -302,78 +269,6 @@ pub(super) fn compile_fn_defun( Ok(vec![]) } -pub(super) fn compile_fn_let_star( - ctx: &mut TulispContext, - name: &TulispObject, - args: &TulispObject, -) -> Result, Error> { - ctx.compile_1_arg_call(name, args, true, |ctx, varlist, body| { - let mut result = vec![]; - let mut params = vec![]; - let mut symbols = vec![]; - for varitem in varlist.base_iter() { - if varitem.symbolp() { - let param = varitem.clone(); - params.push(param.clone()); - result.append(&mut vec![ - Instruction::Push(false.into()), - Instruction::BeginScope(param), - ]); - - symbols.push(varitem); - } else if varitem.consp() { - let varitem_clone = varitem.clone(); - destruct_bind!((&optional name value &rest rest) = varitem_clone); - if name.null() { - return Err(Error::new( - ErrorKind::Undefined, - "let varitem requires name".to_string(), - ) - .with_trace(varitem)); - } - if !name.symbolp() { - return Err(Error::new( - ErrorKind::TypeMismatch, - format!("Expected Symbol: Can't assign to {}", name), - ) - .with_trace(name)); - } - if !rest.null() { - return Err(Error::new( - ErrorKind::Undefined, - "let varitem has too many values".to_string(), - ) - .with_trace(varitem)); - } - let param = name.clone(); - params.push(param.clone()); - result.append( - &mut compile_expr_keep_result(ctx, &value).map_err(|e| e.with_trace(value))?, - ); - result.push(Instruction::BeginScope(param)); - } else { - return Err(Error::new( - ErrorKind::SyntaxError, - format!( - "varitems inside a let-varlist should be a var or a binding: {}", - varitem - ), - ) - .with_trace(varitem)); - } - } - let mut body = compile_progn(ctx, body)?; - if body.is_empty() { - return Ok(vec![]); - } - result.append(&mut body); - for param in params { - result.push(Instruction::EndScope(param)); - } - Ok(result) - }) -} - pub(super) fn compile_fn_progn( ctx: &mut TulispContext, _name: &TulispObject, diff --git a/src/bytecode/compiler/forms/setting.rs b/src/bytecode/compiler/forms/setting.rs new file mode 100644 index 00000000..8fed4586 --- /dev/null +++ b/src/bytecode/compiler/forms/setting.rs @@ -0,0 +1,112 @@ +use crate::{ + bytecode::{ + compiler::compiler::{compile_expr_keep_result, compile_progn}, + Instruction, + }, + destruct_bind, Error, ErrorKind, TulispContext, TulispObject, +}; + +pub(super) fn compile_fn_setq( + ctx: &mut TulispContext, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr_keep_result(ctx, arg2)?; + if ctx.compiler.as_ref().unwrap().keep_result { + result.push(Instruction::Store(arg1.clone())); + } else { + result.push(Instruction::StorePop(arg1.clone())); + } + Ok(result) + }) +} + +pub(super) fn compile_fn_set( + ctx: &mut TulispContext, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + ctx.compile_2_arg_call(name, args, false, |ctx, arg1, arg2, _| { + let mut result = compile_expr_keep_result(ctx, arg2)?; + result.append(&mut compile_expr_keep_result(ctx, arg1)?); + if ctx.compiler.as_ref().unwrap().keep_result { + result.push(Instruction::Set); + } else { + result.push(Instruction::SetPop); + } + Ok(result) + }) +} + +pub(super) fn compile_fn_let_star( + ctx: &mut TulispContext, + name: &TulispObject, + args: &TulispObject, +) -> Result, Error> { + ctx.compile_1_arg_call(name, args, true, |ctx, varlist, body| { + let mut result = vec![]; + let mut params = vec![]; + let mut symbols = vec![]; + for varitem in varlist.base_iter() { + if varitem.symbolp() { + let param = varitem.clone(); + params.push(param.clone()); + result.append(&mut vec![ + Instruction::Push(false.into()), + Instruction::BeginScope(param), + ]); + + symbols.push(varitem); + } else if varitem.consp() { + let varitem_clone = varitem.clone(); + destruct_bind!((&optional name value &rest rest) = varitem_clone); + if name.null() { + return Err(Error::new( + ErrorKind::Undefined, + "let varitem requires name".to_string(), + ) + .with_trace(varitem)); + } + if !name.symbolp() { + return Err(Error::new( + ErrorKind::TypeMismatch, + format!("Expected Symbol: Can't assign to {}", name), + ) + .with_trace(name)); + } + if !rest.null() { + return Err(Error::new( + ErrorKind::Undefined, + "let varitem has too many values".to_string(), + ) + .with_trace(varitem)); + } + let param = name.clone(); + params.push(param.clone()); + result.append( + &mut compile_expr_keep_result(ctx, &value).map_err(|e| e.with_trace(value))?, + ); + result.push(Instruction::BeginScope(param)); + } else { + return Err(Error::new( + ErrorKind::SyntaxError, + format!( + "varitems inside a let-varlist should be a var or a binding: {}", + varitem + ), + ) + .with_trace(varitem)); + } + } + let mut body = compile_progn(ctx, body)?; + if body.is_empty() { + return Ok(vec![]); + } + result.append(&mut body); + for param in params { + result.push(Instruction::EndScope(param)); + } + Ok(result) + }) +} From c751869362b607150e7315b65f03e546f4f3756a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 18 Aug 2024 12:09:21 +0200 Subject: [PATCH 137/142] Support storing `CompiledDefun` as values --- src/bytecode/compiler/compiler.rs | 15 ++++++++------- src/bytecode/mod.rs | 2 +- src/eval.rs | 1 + src/value.rs | 6 ++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index 2ebd43f3..f9ebc321 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -228,19 +228,20 @@ pub(crate) fn compile_expr( return Ok(vec![]); } } - TulispValue::String { .. } - | TulispValue::Lambda { .. } - | TulispValue::Func(_) - | TulispValue::Macro(_) - | TulispValue::Defmacro { .. } - | TulispValue::Any(_) - | TulispValue::Bounce { .. } => { + TulispValue::String { .. } | TulispValue::Any(_) => { if compiler.keep_result { return Ok(vec![Instruction::Push(expr.clone().into())]); } else { return Ok(vec![]); } } + TulispValue::Lambda { .. } + | TulispValue::Func(_) + | TulispValue::CompiledDefun { .. } + | TulispValue::Macro(_) + | TulispValue::Defmacro { .. } + | TulispValue::Bounce { .. } => return Ok(vec![]), + TulispValue::Backquote { value } => compile_back_quote(ctx, value), TulispValue::Quote { value } | TulispValue::Sharpquote { value } => { if compiler.keep_result { diff --git a/src/bytecode/mod.rs b/src/bytecode/mod.rs index 04291e56..9753eb79 100644 --- a/src/bytecode/mod.rs +++ b/src/bytecode/mod.rs @@ -1,5 +1,5 @@ mod bytecode; -pub(crate) use bytecode::Bytecode; +pub(crate) use bytecode::{Bytecode, CompiledDefun}; pub(crate) mod instruction; pub(crate) use instruction::{Instruction, Pos}; diff --git a/src/eval.rs b/src/eval.rs index 3c51b701..7a67a448 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -274,6 +274,7 @@ pub(crate) fn eval_basic<'a>( | TulispValue::Func(_) | TulispValue::Macro(_) | TulispValue::Defmacro { .. } + | TulispValue::CompiledDefun { .. } | TulispValue::Any(_) | TulispValue::Bounce { .. } | TulispValue::Nil diff --git a/src/value.rs b/src/value.rs index d6560656..1a125f5f 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,5 @@ use crate::{ + bytecode::CompiledDefun, cons::{self, Cons}, context::Scope, error::{Error, ErrorKind}, @@ -243,6 +244,9 @@ pub enum TulispValue { params: DefunParams, body: TulispObject, }, + CompiledDefun { + value: CompiledDefun, + }, Bounce { value: TulispObject, }, @@ -291,6 +295,7 @@ impl std::fmt::Debug for TulispValue { .field("params", params) .field("body", body) .finish(), + Self::CompiledDefun { .. } => f.debug_struct("CompiledDefun").finish(), Self::Bounce { value } => f.debug_struct("Bounce").field("value", value).finish(), } } @@ -386,6 +391,7 @@ impl std::fmt::Display for TulispValue { TulispValue::Macro(_) => f.write_str("Macro"), TulispValue::Defmacro { .. } => f.write_str("Defmacro"), TulispValue::Lambda { .. } => f.write_str("Defun"), + TulispValue::CompiledDefun { .. } => f.write_str("CompiledDefun"), } } } From 8cf55eccd3a1facaa5e8398af8e9caecfc344221 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 18 Aug 2024 12:10:26 +0200 Subject: [PATCH 138/142] Check parameter count in function calls --- src/bytecode/interpreter.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index 2f5d5fb7..d36805f5 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -437,6 +437,20 @@ impl Machine { }; let func = func.clone(); + if *args_count < func.params.required.len() { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + "Too few arguments".to_string(), + )); + } + if func.params.rest.is_none() + && *args_count > func.params.required.len() + func.params.optional.len() + { + return Err(Error::new( + crate::ErrorKind::TypeMismatch, // TODO: change to ArityMismatch + "Too many arguments".to_string(), + )); + } let left_args = *args_count - func.params.required.len(); if left_args > func.params.optional.len() { *rest_count = left_args - func.params.optional.len(); From 153bb8583f8577185a5a5395efdf0525aeb47292 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Fri, 23 Aug 2024 23:45:50 +0200 Subject: [PATCH 139/142] Fix tail-call compilation --- src/bytecode/compiler/forms/other_functions.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bytecode/compiler/forms/other_functions.rs b/src/bytecode/compiler/forms/other_functions.rs index bfa2ab52..6fed3954 100644 --- a/src/bytecode/compiler/forms/other_functions.rs +++ b/src/bytecode/compiler/forms/other_functions.rs @@ -241,6 +241,12 @@ pub(super) fn compile_fn_defun( } } + // This is required at this point, before the body is compiled, in case + // of tail calls. + compiler + .defun_args + .insert(defun_name.addr_as_usize(), defun_params.clone()); + // TODO: replace with `is_string` let body = if body.car()?.as_string().is_ok() { body.cdr()? From 1608b443ea6b6afa50d72b3c22355a0690a0814a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 24 Aug 2024 00:23:01 +0200 Subject: [PATCH 140/142] Fix backquote compilation edge-cases --- src/bytecode/compiler/compiler.rs | 31 ++++++++++++++++++++++++------- src/bytecode/instruction.rs | 3 +++ src/bytecode/interpreter.rs | 7 ++++++- tests/tests.rs | 8 ++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/bytecode/compiler/compiler.rs b/src/bytecode/compiler/compiler.rs index f9ebc321..e855bbba 100644 --- a/src/bytecode/compiler/compiler.rs +++ b/src/bytecode/compiler/compiler.rs @@ -110,8 +110,24 @@ fn compile_back_quote( if !compiler.keep_result { return Ok(vec![]); } - if !value.consp() { - return Ok(vec![Instruction::Push(value.clone().into())]); + match &*value.inner_ref() { + TulispValue::Quote { value } => { + return compile_back_quote(ctx, value).map(|mut v| { + v.push(Instruction::Quote); + v + }) + } + TulispValue::Unquote { value } => { + return compile_expr(ctx, value).map_err(|e| e.with_trace(value.clone())); + } + TulispValue::Splice { .. } => { + return Err(Error::new( + crate::ErrorKind::SyntaxError, + "Splice must be within a backquoted list.".to_string(), + )); + } + TulispValue::List { .. } => {} + _ => return Ok(vec![Instruction::Push(value.clone().into())]), } let mut result = vec![]; @@ -124,7 +140,9 @@ fn compile_back_quote( let first_inner = &*first.inner_ref(); if let TulispValue::Unquote { value } = first_inner { items += 1; - result.append(&mut compile_expr(ctx, &value)?); + result.append( + &mut compile_expr(ctx, &value).map_err(|e| e.with_trace(first.clone()))?, + ); } else if let TulispValue::Splice { value } = first_inner { let mut splice_result = compile_expr(ctx, &value)?; let list_inst = splice_result.pop().unwrap(); @@ -266,10 +284,9 @@ pub(crate) fn compile_expr( } TulispValue::Unquote { .. } | TulispValue::Splice { .. } => { return Err(Error::new( - crate::ErrorKind::SyntaxError, - "Unquote/Splice must be within a backquoted list.".to_string(), - ) - .with_trace(expr.clone())); + crate::ErrorKind::TypeMismatch, // TODO: ErrorKind::SyntaxError + "Unquote without backquote".to_string(), + )); } } } diff --git a/src/bytecode/instruction.rs b/src/bytecode/instruction.rs index 2560e7a3..dfa9dd13 100644 --- a/src/bytecode/instruction.rs +++ b/src/bytecode/instruction.rs @@ -124,6 +124,8 @@ pub(crate) enum Instruction { Append(usize), Cxr(Cxr), PlistGet, + // values + Quote, } impl std::fmt::Display for Instruction { @@ -204,6 +206,7 @@ impl std::fmt::Display for Instruction { Cxr::Cddddr => write!(f, " cddddr"), }, Instruction::PlistGet => write!(f, " plist_get"), + Instruction::Quote => write!(f, " quote"), } } } diff --git a/src/bytecode/interpreter.rs b/src/bytecode/interpreter.rs index d36805f5..fcca9833 100644 --- a/src/bytecode/interpreter.rs +++ b/src/bytecode/interpreter.rs @@ -1,5 +1,5 @@ use super::{bytecode::Bytecode, compile, compiler::VMDefunParams, Instruction}; -use crate::{bytecode::Pos, lists, Error, TulispContext, TulispObject}; +use crate::{bytecode::Pos, lists, Error, TulispContext, TulispObject, TulispValue}; use std::{cell::RefCell, collections::HashMap, rc::Rc}; macro_rules! binary_ops { @@ -557,6 +557,11 @@ impl Machine { let a = self.stack.last().unwrap().null(); *self.stack.last_mut().unwrap() = a.into(); } + Instruction::Quote => { + let a = self.stack.pop().unwrap(); + self.stack + .push(TulispValue::Quote { value: a.clone() }.into_ref(None)); + } } pc += 1; } diff --git a/tests/tests.rs b/tests/tests.rs index 8b94f7e2..2cf05706 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -706,6 +706,14 @@ fn test_backquotes() -> Result<(), Error> { result: r#"t"#, } + tulisp_assert! { + program: r#" + (let ((a 10)) + (cdr `(a . ,a))) + "#, + result: r#"10"#, + } + tulisp_assert! { program: r#"`(1 2 '(+ 10 20) ',(+ 10 20) (quote ,(+ 20 20)))"#, result: r#"'(1 2 '(+ 10 20) '30 (quote 40))"#, From 6a074abef29f8b8fc6b57102e176d1c61d257139 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sat, 24 Aug 2024 00:26:29 +0200 Subject: [PATCH 141/142] Update `load` test to not have non-functions in loaded files Because the VM doesn't support that. --- tests/good-load.lisp | 2 +- tests/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/good-load.lisp b/tests/good-load.lisp index 8ead51f7..b44ff4d8 100644 --- a/tests/good-load.lisp +++ b/tests/good-load.lisp @@ -1 +1 @@ -'(1 2 3) +(defun test () '(1 2 3)) diff --git a/tests/tests.rs b/tests/tests.rs index 2cf05706..b6e56562 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1244,7 +1244,7 @@ fn test_load() -> Result<(), Error> { tulisp_assert! { ctx: ctx, - program: r#"(load "tests/good-load.lisp")"#, + program: r#"(load "tests/good-load.lisp") (test)"#, result: "'(1 2 3)", } From 2d6742b025fb399f758d43607ebd180c18b39d53 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Sun, 7 Dec 2025 01:35:21 +0100 Subject: [PATCH 142/142] Run tests against VM also --- tests/tests.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index e7d18ee2..facc8c7f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -19,6 +19,23 @@ macro_rules! tulisp_assert { ); }; + (@impl_vm $ctx: expr, program:$input:expr, result:$result:expr $(,)?) => { + let output = $ctx.vm_eval_string($input).map_err(|err| { + panic!("{}:{}: execution failed: {}", file!(), line!(),err.format(&$ctx)); + + })?; + let expected = $ctx.vm_eval_string($result)?; + assert!( + output.equal(&expected), + "\n{}:{}: program: {}\n vm output: {},\n expected: {}\n", + file!(), + line!(), + $input, + output, + expected + ); + }; + (@impl $ctx: expr, program:$input:expr, result_str:$result:expr $(,)?) => { let output = $ctx.eval_string($input).map_err(|err| { println!("{}:{}: execution failed: {}", file!(), line!(),err.format(&$ctx)); @@ -35,19 +52,50 @@ macro_rules! tulisp_assert { ); }; + (@impl_vm $ctx: expr, program:$input:expr, result_str:$result:expr $(,)?) => { + let output = $ctx.vm_eval_string($input).map_err(|err| { + println!("{}:{}: execution failed: {}", file!(), line!(),err.format(&$ctx)); + err + })?; + let expected = $ctx.vm_eval_string($result)?; + assert_eq!(output.to_string(), expected.to_string(), + "\n{}:{}: program: {}\n vm output: {},\n expected: {}\n", + file!(), + line!(), + $input, + output, + expected + ); + }; + (@impl $ctx: expr, program:$input:expr, error:$desc:expr $(,)?) => { let output = $ctx.eval_string($input); assert!(output.is_err()); assert_eq!(output.unwrap_err().format(&$ctx), $desc); }; + (@impl_vm $ctx: expr, program:$input:expr, error:$desc:expr $(,)?) => { + let output = $ctx.vm_eval_string($input); + assert!(output.is_err()); + let output = output.unwrap_err().format(&$ctx); + assert!( + $desc.starts_with(&output), + " vm output: {},\n expected: {}\n", + &output, + $desc + ); + }; + (ctx: $ctx: expr, program: $($tail:tt)+) => { - tulisp_assert!(@impl $ctx, program: $($tail)+) + tulisp_assert!(@impl $ctx, program: $($tail)+); + tulisp_assert!(@impl_vm $ctx, program: $($tail)+); }; (program: $($tail:tt)+) => { let mut ctx = TulispContext::new(); - tulisp_assert!(ctx: ctx, program: $($tail)+) + tulisp_assert!(@impl ctx, program: $($tail)+); + let mut ctx = TulispContext::new(); + tulisp_assert!(@impl_vm ctx, program: $($tail)+); }; }