diff --git a/src/compile.rs b/src/compile.rs index b1575ba..70e204d 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1,4 +1,4 @@ -use crate::object::{QuoteTable, Value}; +use crate::object::*; use crate::scope::*; use crate::syn::ast::*; use crate::vm::inst::*; @@ -66,7 +66,7 @@ impl<'s> Compile<'s> { let quote = self.quote_table .insert(expr.span().clone(), locals, stmts.clone(), compiled); - let inst = Inst::PushValue(Value::Quote(quote)); + let inst = Inst::PushValue(QuoteObj::new(quote).into_gc()); vec![SpInst::new(expr.span().clone(), inst)] } } @@ -74,9 +74,9 @@ impl<'s> Compile<'s> { fn compile_atom(&mut self, atom: &SpAtom) -> Vec { let inst = match atom.inner() { - Atom::Float(f) => Inst::PushValue(Value::Float(*f)), - Atom::Int(i) => Inst::PushValue(Value::Int(*i)), - Atom::Str(s) => Inst::PushValue(Value::Str(s.clone())), + Atom::Float(f) => Inst::PushValue(FloatObj::new(*f).into_gc()), + Atom::Int(i) => Inst::PushValue(IntObj::new(*i).into_gc()), + Atom::Str(s) => Inst::PushValue(StrObj::new(s.clone()).into_gc()), Atom::Assign(text) => { let word = self.scope_stack.insert_local(text); Inst::Store(word) diff --git a/src/object.rs b/src/object.rs index 3886693..7f695ff 100644 --- a/src/object.rs +++ b/src/object.rs @@ -7,11 +7,12 @@ use crate::syn::ast::SpStmt; use crate::vm::{error::*, machine::Machine}; use crate::{scope::Scope, syn::span::Span, vm::inst::SpInst}; use gc::{unsafe_empty_trace, Finalize, Gc, Trace}; +use std::any::Any; use std::collections::HashMap; -use std::fmt::{self, Debug, Display}; +use std::fmt::{self, Debug}; use std::rc::Rc; -pub type ObjPtr = Gc; +pub type ObjPtr = Gc; pub type Str = String; pub type Int = i64; pub type Float = f64; @@ -63,64 +64,6 @@ impl QuoteTable { } } -// ///////////////////////////////////////////////////////////////////////////// -// Value -// ///////////////////////////////////////////////////////////////////////////// - -#[derive(Debug, Clone, PartialEq)] -pub enum Value { - Array(Vec), - Float(Float), - Int(Int), - Str(Str), - Quote(Quote), - //ObjPtr(ObjPtr), - BuiltinFn(BuiltinFn), -} - -impl Value { - pub fn name(&self) -> &str { - use Value::*; - match self { - Array(_) => "array", - Float(_) => "float", - Int(_) => "int", - Str(_) => "str", - Quote(_) => "quote", - //ObjPtr(_) => "object", - BuiltinFn(_) => "builtin function", - } - } - - pub fn is_truthy(&self) -> bool { - use Value::*; - match self { - Array(a) => a.len() > 0, - Float(f) => *f != 0.0, - Int(i) => *i != 0, - Str(s) => s.len() > 0, - Quote(_) => true, - //ObjPtr(_) => true, - BuiltinFn(_) => true, - } - } -} - -impl Display for Value { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - use Value::*; - match self { - Array(_) => write!(fmt, "[array]"), - Float(f) => write!(fmt, "{}", f), - Int(i) => write!(fmt, "{}", i), - Str(s) => write!(fmt, "{}", s), - Quote(q) => write!(fmt, "[quoted value #{}]", q.index()), - //ObjPtr(o) => write!(fmt, "[object #{}]", o.slot()), - BuiltinFn(b) => write!(fmt, "[{:?}]", b), - } - } -} - /// The intent of a builtin function once the function proper returns. /// /// When a builtin function itself returns, it may want to remain on the call @@ -169,7 +112,10 @@ impl BuiltinFn { } } -//pub type BuiltinFnPtr = dyn Fn(&mut Machine, usize) -> Result; +/// BuiltinFn types must be function pointers, and not closures. +/// +/// This has to do with how the garbage collector works, and it apparently does +/// not play nicely with closures. pub type BuiltinFnPtr = fn(&mut Machine, usize) -> Result; impl Debug for BuiltinFn { @@ -183,7 +129,7 @@ impl Debug for BuiltinFn { } /// An object that has a vtable and common functions. -pub trait Obj: Trace + Finalize { +pub trait Obj: Trace + Finalize + Debug { /// Gets the vtable for this object. fn vtable(&self) -> &VTable; @@ -191,7 +137,7 @@ pub trait Obj: Trace + Finalize { fn vtable_mut(&mut self) -> &mut VTable; /// Calls this object. - fn call(&self, machine: &mut Machine) -> Result<()>; + fn call(&self, call_site: Option, machine: &mut Machine) -> Result<()>; /// Gets a value in the vtable. /// @@ -208,6 +154,20 @@ pub trait Obj: Trace + Finalize { fn set(&mut self, key: String, value: ObjPtr) -> Option { self.vtable_mut().insert(key, value) } + + /// Allocate a new GC pointer and convert it to a `dyn Obj` pointer. + fn into_gc(self) -> Gc + where + Self: Sized + 'static, + { + let gc = Gc::new(self); + unsafe { + let ptr = Gc::into_raw(gc) as *const dyn Obj; + Gc::from_raw(ptr) + } + } + + fn as_any(&self) -> &(dyn Any + 'static); } macro_rules! vtable { @@ -222,12 +182,45 @@ macro_rules! vtable { }}; } -#[derive(Trace, Finalize)] +macro_rules! builtin_fn { + ($name:expr, $fun:expr) => {{ + BuiltinFnObj::new(BuiltinFn::new($name.to_string(), Rc::new($fun))).into_gc() + }}; +} + +#[derive(Debug, Trace, Finalize)] pub struct FloatObj { value: Float, vtable: VTable, } +impl FloatObj { + pub fn new(value: Float) -> Self { + let float_str: ObjPtr = builtin_fn!("__str__", |machine, _| { + let obj_ptr: ObjPtr = machine.stack_pop()?; + let obj: &(dyn Obj + 'static) = &*obj_ptr; + if let Some(float) = obj.as_any().downcast_ref::() { + let string = StrObj::new(float.value.to_string()).into_gc(); + machine.stack_push(string)?; + Ok(BuiltinExit::Return) + } else { + Err(RuntimeError::WrongValue("float".to_string())) + } + // Create the builtin float string + }); + FloatObj { + value, + vtable: vtable! { + "__str__" => float_str, + }, + } + } + + pub fn value(&self) -> Float { + self.value + } +} + impl Obj for FloatObj { fn vtable(&self) -> &VTable { &self.vtable @@ -237,12 +230,16 @@ impl Obj for FloatObj { &mut self.vtable } - fn call(&self, _: &mut Machine) -> Result<()> { - Err(RuntimeError::CannotCall("float value".to_string())) + fn call(&self, call_site: Option, _: &mut Machine) -> Result<()> { + Err(RuntimeError::CannotCall("float value".to_string()).with_location(call_site)) + } + + fn as_any(&self) -> &(dyn Any + 'static) { + self } } -#[derive(Trace, Finalize)] +#[derive(Debug, Trace, Finalize)] pub struct IntObj { value: Int, vtable: VTable, @@ -255,6 +252,10 @@ impl IntObj { vtable: vtable! {}, } } + + pub fn value(&self) -> Int { + self.value + } } impl Obj for IntObj { @@ -266,12 +267,16 @@ impl Obj for IntObj { &mut self.vtable } - fn call(&self, _: &mut Machine) -> Result<()> { - Err(RuntimeError::CannotCall("int value".to_string())) + fn call(&self, call_site: Option, _: &mut Machine) -> Result<()> { + Err(RuntimeError::CannotCall("int value".to_string()).with_location(call_site)) + } + + fn as_any(&self) -> &(dyn Any + 'static) { + self } } -#[derive(Trace, Finalize)] +#[derive(Debug, Trace, Finalize)] pub struct StrObj { value: String, vtable: VTable, @@ -279,11 +284,27 @@ pub struct StrObj { impl StrObj { pub fn new(value: String) -> Self { + let str_str: ObjPtr = builtin_fn!("__str__", |machine, _| { + let obj_ptr: ObjPtr = machine.stack_pop()?; + let obj: &(dyn Obj + 'static) = &*obj_ptr; + if obj.as_any().is::() { + machine.stack_push(obj_ptr)?; + Ok(BuiltinExit::Return) + } else { + Err(RuntimeError::WrongValue("float".to_string())) + } + }); StrObj { value, - vtable: vtable! {}, + vtable: vtable! { + "__str__" => str_str, + }, } } + + pub fn value(&self) -> &String { + &self.value + } } impl Obj for StrObj { @@ -295,12 +316,16 @@ impl Obj for StrObj { &mut self.vtable } - fn call(&self, _: &mut Machine) -> Result<()> { - Err(RuntimeError::CannotCall("string value".to_string())) + fn call(&self, call_site: Option, _: &mut Machine) -> Result<()> { + Err(RuntimeError::CannotCall("string value".to_string()).with_location(call_site)) + } + + fn as_any(&self) -> &(dyn Any + 'static) { + self } } -#[derive(Trace, Finalize)] +#[derive(Debug, Trace, Finalize)] pub struct QuoteObj { value: Quote, vtable: VTable, @@ -313,6 +338,10 @@ impl QuoteObj { vtable: vtable! {}, } } + + pub fn value(&self) -> Quote { + self.value + } } impl Obj for QuoteObj { @@ -324,12 +353,17 @@ impl Obj for QuoteObj { &mut self.vtable } - fn call(&self, _: &mut Machine) -> Result<()> { - Err(RuntimeError::CannotCall("quote value".to_string())) + fn call(&self, _call_site: Option, machine: &mut Machine) -> Result<()> { + machine.call_quote(self.value); + Ok(()) + } + + fn as_any(&self) -> &(dyn Any + 'static) { + self } } -#[derive(Trace, Finalize)] +#[derive(Debug, Trace, Finalize)] pub struct BuiltinFnObj { value: BuiltinFn, vtable: VTable, @@ -342,6 +376,10 @@ impl BuiltinFnObj { vtable: vtable! {}, } } + + pub fn value(&self) -> &BuiltinFn { + &self.value + } } impl Obj for BuiltinFnObj { @@ -353,7 +391,12 @@ impl Obj for BuiltinFnObj { &mut self.vtable } - fn call(&self, _: &mut Machine) -> Result<()> { - Err(RuntimeError::CannotCall("quote value".to_string())) + fn call(&self, call_site: Option, machine: &mut Machine) -> Result<()> { + machine.call_native(call_site, self.value.clone()); + Ok(()) + } + + fn as_any(&self) -> &(dyn Any + 'static) { + self } } diff --git a/src/vm/builtins.rs b/src/vm/builtins.rs index 037d9d6..c555b4d 100644 --- a/src/vm/builtins.rs +++ b/src/vm/builtins.rs @@ -1,4 +1,4 @@ -use crate::object::{BuiltinExit, Int, Value}; +use crate::object::{BuiltinExit, Obj, StrObj}; use crate::vm::{error::RuntimeError, machine::MachineBuilder}; impl MachineBuilder { @@ -19,6 +19,7 @@ impl MachineBuilder { panic!(); }); + /* // // if // @@ -26,6 +27,7 @@ impl MachineBuilder { let if_false = machine.stack_pop()?; let if_true = machine.stack_pop()?; let condition = machine.stack_pop()?; + let value = if condition.is_truthy() { if_true } else { @@ -41,25 +43,75 @@ impl MachineBuilder { .into()); } }); + */ // // print // - self.register_builtin_fun("print", |machine, _| { - let value = machine.stack_pop()?; - print!("{}", value); - Ok(BuiltinExit::Return) + self.register_builtin_fun("print", |machine, reentry| { + const CALL_STR: usize = 1; + if reentry == 0 { + let obj = machine + .stack_peek() + .ok_or_else(|| RuntimeError::StackUnderflow)?; + // Push the value's __str__ value, call it, and then print it. + let str_fun = obj + .get("__str__") + .ok_or_else(|| RuntimeError::UnsetWord("__str__".to_string()))?; + let call_site = machine.call_stack().last().unwrap().call_site().cloned(); + str_fun.call(call_site, machine)?; + Ok(BuiltinExit::Resume(CALL_STR)) + } else if reentry == CALL_STR { + let obj_ptr = machine.stack_pop()?; + let str_obj: &(dyn Obj + 'static) = &*obj_ptr; + if let Some(string) = str_obj.as_any().downcast_ref::() { + print!("{}", string.value()); + Ok(BuiltinExit::Return) + } else { + panic!( + "expected StrObj from __str__ function but got {:?} instead", + str_obj + ); + } + } else { + unreachable!() + } }); // // println // - self.register_builtin_fun("println", |machine, _| { - let value = machine.stack_pop()?; - println!("{}", value); - Ok(BuiltinExit::Return) + self.register_builtin_fun("println", |machine, reentry| { + const CALL_STR: usize = 1; + if reentry == 0 { + let obj = machine + .stack_peek() + .ok_or_else(|| RuntimeError::StackUnderflow)?; + // Push the value's __str__ value, call it, and then print it. + let str_fun = obj + .get("__str__") + .ok_or_else(|| RuntimeError::UnsetWord("__str__".to_string()))?; + let call_site = machine.call_stack().last().unwrap().call_site().cloned(); + str_fun.call(call_site, machine)?; + Ok(BuiltinExit::Resume(CALL_STR)) + } else if reentry == CALL_STR { + let obj_ptr = machine.stack_pop()?; + let str_obj: &(dyn Obj + 'static) = &*obj_ptr; + if let Some(string) = str_obj.as_any().downcast_ref::() { + println!("{}", string.value()); + Ok(BuiltinExit::Return) + } else { + panic!( + "expected StrObj from __str__ function but got {:?} instead", + str_obj + ); + } + } else { + unreachable!() + } }); + /* // // == // @@ -79,5 +131,6 @@ impl MachineBuilder { machine.stack_push(Value::Int((lhs != rhs) as Int))?; Ok(BuiltinExit::Return) }); + */ } } diff --git a/src/vm/error.rs b/src/vm/error.rs index 48ce611..6e3ef8a 100644 --- a/src/vm/error.rs +++ b/src/vm/error.rs @@ -12,9 +12,12 @@ pub enum RuntimeError { #[error("unset word '{0}'")] UnsetWord(String), - #[error("cannot call non-quote value '{0}'")] + #[error("cannot call value '{0}'")] CannotCall(String), + #[error("expected {0} value")] + WrongValue(String), + #[error("at {0}")] Span(Span, Box), } @@ -24,13 +27,13 @@ pub type Result = std::result::Result; /// Add a location to the implemented value. pub trait WithLocation { type Out; - fn with_location(self, span: Span) -> Self::Out; + fn with_location(self, span: Option) -> Self::Out; } impl WithLocation for Result { type Out = Result; - fn with_location(self, span: Span) -> Self::Out { + fn with_location(self, span: Option) -> Self::Out { if let Err(e) = self { Err(e.with_location(span)) } else { @@ -42,7 +45,11 @@ impl WithLocation for Result { impl WithLocation for RuntimeError { type Out = RuntimeError; - fn with_location(self, span: Span) -> Self::Out { - RuntimeError::Span(span, Box::new(self)) + fn with_location(self, span: Option) -> Self::Out { + if let Some(span) = span { + RuntimeError::Span(span, Box::new(self)) + } else { + self + } } } diff --git a/src/vm/inst.rs b/src/vm/inst.rs index dd989a4..9683b95 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -1,11 +1,11 @@ -use crate::object::Value; +use crate::object::ObjPtr; use crate::scope::Word; use crate::syn::span::Spanned; #[derive(Debug, Clone)] pub enum Inst { /// Push a constant value to the stack. - PushValue(Value), + PushValue(ObjPtr), /// Load a word's value onto the stack. Load(Word), diff --git a/src/vm/machine.rs b/src/vm/machine.rs index c905971..88bfdd3 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -16,7 +16,7 @@ impl Frame { pub fn call_site(&self) -> Option<&Span> { use Frame::*; match self { - Native(frame) => Some(&frame.call_site), + Native(frame) => frame.call_site.as_ref(), Quote(frame) => { if frame.pc >= frame.code.len() { frame.code.last().map(|inst| inst.span()) @@ -42,14 +42,14 @@ impl From for Frame { #[derive(Debug, Clone)] pub struct NativeFrame { - call_site: Span, + call_site: Option, fun: BuiltinFn, reentry: usize, } #[derive(Debug, Clone)] pub struct QuoteFrame { - locals: BTreeMap>, + locals: BTreeMap>, code: Rc>, pc: usize, } @@ -63,8 +63,8 @@ impl QuoteFrame { /// The current state of a VM. #[derive(Debug)] pub struct Machine { - globals: BTreeMap, - stack: Vec, + globals: BTreeMap, + stack: Vec, max_stack_size: Option, quote_table: QuoteTable, scope_stack: ScopeStack, @@ -73,7 +73,7 @@ pub struct Machine { impl Machine { pub fn new( - globals: BTreeMap, + globals: BTreeMap, max_stack_size: Option, scope_stack: ScopeStack, ) -> Self { @@ -95,15 +95,19 @@ impl Machine { self.max_stack_size } - pub fn stack(&self) -> &Vec { + pub fn call_stack(&self) -> &Vec { + &self.call_stack + } + + pub fn stack(&self) -> &Vec { &self.stack } - pub fn stack_mut(&mut self) -> &mut Vec { + pub fn stack_mut(&mut self) -> &mut Vec { &mut self.stack } - pub fn stack_push(&mut self, value: Value) -> Result<()> { + pub fn stack_push(&mut self, value: ObjPtr) -> Result<()> { if let Some(max) = self.max_stack_size() { if self.stack().len() >= max { return Err(RuntimeError::StackOverflow.into()); @@ -113,13 +117,17 @@ impl Machine { Ok(()) } - pub fn stack_pop(&mut self) -> Result { + pub fn stack_pop(&mut self) -> Result { self.stack_mut() .pop() .ok_or_else(|| RuntimeError::StackUnderflow.into()) } - pub fn lookup_word(&self, word: &Word) -> Option<&Value> { + pub fn stack_peek(&self) -> Option<&ObjPtr> { + self.stack().last() + } + + pub fn lookup_word(&self, word: &Word) -> Option<&ObjPtr> { self.call_stack .iter() .rev() @@ -135,7 +143,7 @@ impl Machine { .or_else(|| self.globals.get(word)) } - pub fn store_local(&mut self, word: Word, value: Value) { + pub fn store_local(&mut self, word: Word, value: ObjPtr) { let last = self .call_stack .iter_mut() @@ -156,7 +164,7 @@ impl Machine { self.call_stack.last() } - fn call_quote(&mut self, quote: Quote) { + pub(crate) fn call_quote(&mut self, quote: Quote) { let (_span, locals, _expr, code) = self.quote_table.get(quote); // create a new stack frame @@ -170,7 +178,7 @@ impl Machine { ); } - fn call_native(&mut self, call_site: Span, native: BuiltinFn) { + pub(crate) fn call_native(&mut self, call_site: Option, native: BuiltinFn) { self.call_stack.push(Frame::Native(NativeFrame { call_site, fun: native, @@ -193,9 +201,7 @@ impl Machine { if let Err(mut error) = self.eval_stmt_list(stmts) { // go through the call stack and make the error for frame in self.call_stack.iter() { - if let Some(call_site) = frame.call_site() { - error = error.with_location(call_site.clone()); - } + error = error.with_location(frame.call_site().cloned()); } Err(error) } else { @@ -203,22 +209,7 @@ impl Machine { } } - fn eval_stmt_list(&mut self, stmts: Vec) -> Result<()> { - // TODO - figure out the best way to figure out the call stack - // locations, and build an error out of that - self.scope_stack.push_scope(); - let mut compile = Compile::new(&mut self.scope_stack, &mut self.quote_table); - let code = Rc::new(compile.compile(stmts)); - let locals = self.scope_stack.pop_scope().unwrap(); - self.call_stack.push( - QuoteFrame { - locals: locals.keys().map(|w| (*w, None)).collect(), - code, - pc: 0, - } - .into(), - ); - + pub fn resume(&mut self) -> Result<()> { // Run instructions loop { // nothing left to execute @@ -243,7 +234,7 @@ impl Machine { let current_frame = self.call_stack.len() - 1; let fun = frame.fun.clone(); let reentry = frame.reentry; - let exit = self.do_call_native(fun, reentry)?; + let exit = fun.call(self, reentry)?; match exit { BuiltinExit::Call(quote) => { @@ -268,8 +259,22 @@ impl Machine { Ok(()) } - fn do_call_native(&mut self, native: BuiltinFn, reentry: usize) -> Result { - native.call(self, reentry) + fn eval_stmt_list(&mut self, stmts: Vec) -> Result<()> { + // TODO - figure out the best way to figure out the call stack + // locations, and build an error out of that + self.scope_stack.push_scope(); + let mut compile = Compile::new(&mut self.scope_stack, &mut self.quote_table); + let code = Rc::new(compile.compile(stmts)); + let locals = self.scope_stack.pop_scope().unwrap(); + self.call_stack.push( + QuoteFrame { + locals: locals.keys().map(|w| (*w, None)).collect(), + code, + pc: 0, + } + .into(), + ); + self.resume() } fn eval_inst(&mut self, inst: SpInst) -> Result<()> { @@ -290,15 +295,10 @@ impl Machine { let value = self.stack_pop()?; self.store_local(word.clone(), value); } - Inst::Call => match self.stack_pop()? { - Value::Quote(quote) => { - self.call_quote(quote); - } - Value::BuiltinFn(builtin) => { - self.call_native(inst.span().clone(), builtin); - } - value => return Err(RuntimeError::CannotCall(value.name().to_string()).into()), - }, + Inst::Call => { + let value = self.stack_pop()?; + value.call(Some(inst.span().clone()), self)?; + } }; // Update this frame's PC @@ -314,7 +314,7 @@ impl Machine { #[derive(Debug, Default)] pub struct MachineBuilder { - pub(super) globals: BTreeMap, + pub(super) globals: BTreeMap, pub(super) scope_stack: ScopeStack, pub(super) max_stack_size: Option, } @@ -332,11 +332,11 @@ impl MachineBuilder { ) { self.register_global( name, - Value::BuiltinFn(BuiltinFn::new(name.to_string(), Rc::new(fun))), + BuiltinFnObj::new(BuiltinFn::new(name.to_string(), Rc::new(fun))).into_gc(), ); } - pub(super) fn register_global(&mut self, name: &str, value: Value) { + pub(super) fn register_global(&mut self, name: &str, value: ObjPtr) { let word = self.scope_stack.insert_local(name); self.globals.insert(word, value); }