mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
348 lines
11 KiB
Rust
348 lines
11 KiB
Rust
use crate::model::*;
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Executor {
|
|
pub stack: Vec<Value>,
|
|
pub env: Rc<RefCell<Env>>,
|
|
pub outer_env: Option<Rc<RefCell<Env>>>,
|
|
pub instrs: Vec<Instr>,
|
|
pub ip: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Error(String, usize);
|
|
|
|
impl Error {
|
|
pub fn make<S: Into<String>>(msg: S, ip: usize) -> Self {
|
|
Self(msg.into(), ip)
|
|
}
|
|
}
|
|
|
|
impl Executor {
|
|
pub fn new(instrs: Vec<Instr>) -> Self {
|
|
Self {
|
|
stack: Vec::new(),
|
|
env: Rc::new(RefCell::new(Env::new())),
|
|
outer_env: None,
|
|
instrs,
|
|
ip: 0,
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self) -> Result<(), Error> {
|
|
while self.ip < self.instrs.len() {
|
|
self.step()?;
|
|
self.ip += 1;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn run_with<F: Fn(&mut Self) -> Result<(), Error>>(&mut self, f: F) -> Result<(), Error> {
|
|
while self.ip < self.instrs.len() {
|
|
self.step()?;
|
|
self.ip += 1;
|
|
f(self)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn err(&self, msg: &str) -> Error {
|
|
Error::make(msg, self.ip)
|
|
}
|
|
|
|
fn push(&mut self, v: Value) -> Result<(), Error> {
|
|
self.stack.push(v);
|
|
Ok(())
|
|
}
|
|
|
|
fn pop(&mut self) -> Result<Value, Error> {
|
|
self.stack.pop().ok_or_else(|| self.err("stack underflow"))
|
|
}
|
|
|
|
fn get(&self, name: &str) -> Result<Value, Error> {
|
|
// Get from the current environment first
|
|
self.env
|
|
.borrow()
|
|
.binds
|
|
.get(name)
|
|
.cloned()
|
|
// If it doesn't exist then try the outer environment
|
|
.or_else(|| {
|
|
self.outer_env
|
|
.as_ref()
|
|
.and_then(|env| env.borrow().binds.get(name).cloned())
|
|
.or(None)
|
|
})
|
|
.ok_or_else(|| self.err(format!("undefined variable {}", name).as_str()))
|
|
}
|
|
|
|
fn set(&mut self, name: &str, v: Value) -> Result<(), Error> {
|
|
// Set the variable in the current environment if it is defined
|
|
if self.env.borrow().binds.contains_key(name) {
|
|
self.env.borrow_mut().binds.insert(name.to_string(), v);
|
|
// If it is not defined in the current environment then try the outer environment
|
|
} else if let Some(env) = &self.outer_env {
|
|
if env.borrow().binds.contains_key(name) {
|
|
env.borrow_mut().binds.insert(name.to_string(), v);
|
|
} else {
|
|
// If not then define it in the current environment
|
|
self.env.borrow_mut().binds.insert(name.to_string(), v);
|
|
}
|
|
} else {
|
|
self.env.borrow_mut().binds.insert(name.to_string(), v);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn step(&mut self) -> Result<(), Error> {
|
|
let instr = self.instrs.clone(); // TODO: maybe don't clone here
|
|
let instr = instr
|
|
.get(self.ip)
|
|
.ok_or_else(|| self.err("invalid instruction pointer"))?;
|
|
|
|
macro_rules! impl_binop {
|
|
($op:tt, $inp:ident, $ret:ident) => {
|
|
match (self.pop()?, self.pop()?) {
|
|
(Value::$inp(a), Value::$inp(b)) => {
|
|
self.stack.push(Value::$ret(a $op b));
|
|
}
|
|
_ => return Err(Error::make(format!("can't apply operator to non-{}", stringify!($inp)).as_str(), self.ip)),
|
|
}
|
|
};
|
|
}
|
|
|
|
match instr {
|
|
Instr::NumPush(x) => {
|
|
self.push(Value::Num(*x))?;
|
|
}
|
|
Instr::NumAdd => impl_binop!(+, Num, Num),
|
|
Instr::NumSub => impl_binop!(-, Num, Num),
|
|
Instr::NumMul => impl_binop!(*, Num, Num),
|
|
Instr::NumDiv => impl_binop!(/, Num, Num),
|
|
Instr::NumMod => impl_binop!(%, Num, Num),
|
|
Instr::NumEq => impl_binop!(==, Num, Bool),
|
|
|
|
Instr::BoolPush(x) => {
|
|
self.push(Value::Bool(*x))?;
|
|
}
|
|
Instr::BoolAnd => impl_binop!(&&, Bool, Bool),
|
|
Instr::BoolOr => impl_binop!(||, Bool, Bool),
|
|
Instr::BoolNot => {
|
|
if let Value::Bool(b) = self.pop()? {
|
|
self.push(Value::Bool(!b))?;
|
|
} else {
|
|
return Err(Error::make("can't apply `not` to non-boolean", self.ip));
|
|
}
|
|
}
|
|
|
|
Instr::StrPush(x) => {
|
|
self.push(Value::Str(x.clone()))?;
|
|
}
|
|
Instr::StrConcat => {
|
|
if let (Value::Str(a), Value::Str(b)) = (self.pop()?, self.pop()?) {
|
|
self.push(Value::Str(a + &b))?;
|
|
} else {
|
|
return Err(Error::make("can't concatenate non-strings", self.ip));
|
|
}
|
|
}
|
|
Instr::Pop => {
|
|
self.pop()?;
|
|
}
|
|
Instr::Dup => {
|
|
let v = self.pop()?;
|
|
self.push(v.clone())?;
|
|
self.push(v)?;
|
|
}
|
|
|
|
Instr::ListMake(len) => {
|
|
let mut list = Vec::new();
|
|
for _ in 0..*len {
|
|
list.push(
|
|
self.pop()
|
|
.map_err(|_| self.err("not enough arguments to make List"))?,
|
|
);
|
|
}
|
|
list.reverse();
|
|
self.push(Value::List(list))?;
|
|
}
|
|
Instr::ListGet(index) => {
|
|
if let Value::List(list) = self.pop()? {
|
|
let v = list
|
|
.get(*index)
|
|
.cloned()
|
|
.ok_or_else(|| self.err("index out of bounds"))?;
|
|
self.push(v)?;
|
|
} else {
|
|
return Err(Error::make("can't get from non-List", self.ip));
|
|
}
|
|
}
|
|
Instr::ListSet(index) => {
|
|
if let Value::List(mut list) = self.pop()? {
|
|
let v = self.pop()?;
|
|
list.get_mut(*index)
|
|
.ok_or_else(|| self.err("index out of bounds"))?
|
|
.clone_from(&v);
|
|
self.push(Value::List(list))?;
|
|
} else {
|
|
return Err(Error::make("can't set in non-List", self.ip));
|
|
}
|
|
}
|
|
Instr::ListLen => {
|
|
if let Value::List(list) = self.pop()? {
|
|
self.push(Value::Num(list.len() as i64))?;
|
|
} else {
|
|
return Err(Error::make("can't get length of non-List", self.ip));
|
|
}
|
|
}
|
|
Instr::ListJoin => {
|
|
if let (Value::List(mut list1), Value::List(list2)) = (self.pop()?, self.pop()?) {
|
|
list1.extend(list2);
|
|
self.push(Value::List(list1))?;
|
|
} else {
|
|
return Err(Error::make("can't join non-Lists", self.ip));
|
|
}
|
|
}
|
|
|
|
Instr::FuncMake(args, instrs) => {
|
|
let closure = Func::new(args.to_vec(), Rc::clone(&self.env), instrs.clone());
|
|
self.push(Value::Func(closure))?;
|
|
}
|
|
Instr::FuncApply => {
|
|
let v = self.pop()?;
|
|
if let Value::Func(closure) = v {
|
|
// Pop the arguments
|
|
let mut args = Vec::new();
|
|
for _ in 0..closure.args.len() {
|
|
args.push(
|
|
self.pop()
|
|
.map_err(|_| self.err("not enough arguments to apply Function"))?,
|
|
);
|
|
}
|
|
args.reverse();
|
|
|
|
self.stack.append(&mut closure.run(args)?);
|
|
} else {
|
|
return Err(Error::make(
|
|
format!("can't apply non-Function, got {:?}", v),
|
|
self.ip,
|
|
));
|
|
}
|
|
}
|
|
Instr::FuncCall(name) => {
|
|
if let Value::Func(closure) = self.get(name)? {
|
|
let mut args = Vec::new();
|
|
for _ in 0..closure.args.len() {
|
|
args.push(
|
|
self.pop()
|
|
.map_err(|_| self.err("not enough arguments to call Function"))?,
|
|
);
|
|
}
|
|
args.reverse();
|
|
|
|
self.stack.append(&mut closure.run(args)?);
|
|
} else {
|
|
return Err(Error::make("can't call non-Function", self.ip));
|
|
}
|
|
}
|
|
|
|
Instr::Get(name) => {
|
|
let v = self.get(name)?;
|
|
self.push(v)?;
|
|
}
|
|
Instr::Set(name) => {
|
|
let v = self.pop()?;
|
|
self.set(name, v)?;
|
|
}
|
|
|
|
Instr::Jump(n) => {
|
|
self.ip += n;
|
|
}
|
|
Instr::JumpIfFalse(n) => {
|
|
if let Value::Bool(b) = self.pop()? {
|
|
if !b {
|
|
self.ip += n;
|
|
}
|
|
} else {
|
|
return Err(Error::make("can't apply `if` to non-boolean", self.ip));
|
|
}
|
|
}
|
|
|
|
Instr::Print => {
|
|
let v = self.pop()?;
|
|
print!("{}", v);
|
|
}
|
|
Instr::PrintLn => {
|
|
let v = self.pop()?;
|
|
println!("{}", v);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn exec_expect(executor: &mut Executor, expected: Vec<Value>) {
|
|
match executor.run() {
|
|
Ok(_) => {
|
|
assert_eq!(executor.stack, expected);
|
|
}
|
|
Err(e) => panic!("{:?}", e),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_sanity() {
|
|
let mut executor = Executor::new(vec![Instr::NumPush(1), Instr::NumPush(2), Instr::NumAdd]);
|
|
exec_expect(&mut executor, vec![Value::Num(3)]);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_pop_underflow() {
|
|
let mut executor = Executor::new(vec![Instr::NumAdd]);
|
|
executor.run().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_closure() {
|
|
let mut executor = Executor::new(vec![
|
|
Instr::FuncMake(
|
|
vec![],
|
|
vec![
|
|
Instr::NumPush(0),
|
|
Instr::Set("total".to_string()),
|
|
Instr::FuncMake(
|
|
vec![],
|
|
vec![
|
|
Instr::Get("total".to_string()),
|
|
Instr::NumPush(1),
|
|
Instr::NumAdd,
|
|
Instr::Set("total".to_string()),
|
|
Instr::Get("total".to_string()),
|
|
],
|
|
),
|
|
Instr::Set("counter".to_string()),
|
|
Instr::Get("counter".to_string()),
|
|
],
|
|
),
|
|
Instr::FuncApply,
|
|
Instr::Set("tally".to_string()),
|
|
Instr::Get("tally".to_string()),
|
|
Instr::FuncApply,
|
|
Instr::Get("tally".to_string()),
|
|
Instr::FuncApply,
|
|
Instr::Get("tally".to_string()),
|
|
Instr::FuncApply,
|
|
]);
|
|
exec_expect(
|
|
&mut executor,
|
|
vec![Value::Num(1), Value::Num(2), Value::Num(3)],
|
|
);
|
|
}
|
|
}
|