diff --git a/src/bin/not.rs b/src/bin/not.rs index 81d21ad..a538966 100644 --- a/src/bin/not.rs +++ b/src/bin/not.rs @@ -1,4 +1,4 @@ -use not_python::{syn::ast, compile::Compile, vm::{Vm, signal::*}}; +use not_python::{syn::ast, compile::Compile, vm::Vm}; use std::{fs, path::PathBuf}; use structopt::StructOpt; @@ -57,8 +57,7 @@ fn main() -> Result<()> { if action == "run" { let mut vm = Vm::new(&const_pool); - vm.handle_signal(Signal::Call(main, vec![])); - vm.resume(); + vm.call(main, vec![])?; } Ok(()) diff --git a/src/compile/mod.rs b/src/compile/mod.rs index f5122b6..84b4a19 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -42,7 +42,7 @@ impl Compile { sym }) .collect(); - Ok((self.const_data.const_pool, UserFun::new_obj(main, globals))) + Ok((self.const_data.const_pool, UserFun::new_obj(main, globals, 0))) } /// Gets the constant data that is interned in this compile session. diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs index ad4c198..47b571b 100644 --- a/src/compile/thunk.rs +++ b/src/compile/thunk.rs @@ -407,7 +407,7 @@ impl Visit for CompileBody<'_> { }) .collect(); - let (hdl, _fun) = self.compile.push_const(UserFun::new_obj(code, locals)); + let (hdl, _fun) = self.compile.push_const(UserFun::new_obj(code, locals, expr.params.len())); // TODO(compile) : determine return value at the end of the body (preferably at parse-time) diff --git a/src/obj/builtin.rs b/src/obj/builtin.rs index 45852e9..c9fd651 100644 --- a/src/obj/builtin.rs +++ b/src/obj/builtin.rs @@ -1,45 +1,46 @@ -use crate::{obj::{prelude::*, reserved::*}, vm::signal::*}; +use crate::{obj::{prelude::*, reserved::*}, vm::{error::*, signal::*}}; use maplit::btreemap; use once_cell::sync::Lazy; use std::collections::BTreeMap; -pub static PRINTLN_BUILTIN_FUN: Lazy = Lazy::new(|| NativeFun::new_obj(|_, vm, args| { +pub static PRINTLN_BUILTIN_FUN: Lazy = Lazy::new(|| NativeFun::new_obj(1, |_, vm, args| { // TODO : use __get_attr__ when it gets added let to_string = { let obj_ref = &args[0]; read_obj!(let obj = obj_ref); obj.get_attr(STR_MEMBER_NAME.sym) - .or_else(|| obj.get_attr(REPR_MEMBER_NAME.sym)) - .expect("no __str__ or __repr__ member") + .ok_or(Error::MissingAttr { attr: STR_MEMBER_NAME.sym })? }; - let return_value = vm.call(to_string, vec![]); + let return_value = vm.call(to_string, vec![])?; { read_obj!(let str_obj = return_value); - let str_obj: &Str = str_obj.as_any().downcast_ref().unwrap(); + let str_obj: &Str = str_obj.as_any().downcast_ref() + .ok_or_else(|| Error::ValueError { error: "expected str value".to_string(), value: return_value.clone() })?; println!("{}", str_obj.value()); } vm.push(NIL_NAME.sym_ref()); - Signal::Return + Ok(Signal::Return) })); -pub static PRINT_BUILTIN_FUN: Lazy = Lazy::new(|| NativeFun::new_obj(|_, vm, args| { +pub static PRINT_BUILTIN_FUN: Lazy = Lazy::new(|| NativeFun::new_obj(1, |_, vm, args| { // TODO : use __get_attr__ when it gets added let to_string = { let obj_ref = &args[0]; read_obj!(let obj = obj_ref); obj.get_attr(STR_MEMBER_NAME.sym) - .or_else(|| obj.get_attr(REPR_MEMBER_NAME.sym)) - .expect("no __str__ or __repr__ member") + .ok_or(Error::MissingAttr { attr: STR_MEMBER_NAME.sym })? }; - let return_value = vm.call(to_string, vec![]); + let return_value = vm.call(to_string, vec![])?; { read_obj!(let str_obj = return_value); - let str_obj: &Str = str_obj.as_any().downcast_ref().unwrap(); + let str_obj: &Str = str_obj.as_any().downcast_ref() + .ok_or_else(|| Error::ValueError { error: "expected str value".to_string(), value: return_value.clone() })?; print!("{}", str_obj.value()); } - Signal::Return + vm.push(NIL_NAME.sym_ref()); + Ok(Signal::Return) })); pub static BUILTIN_OBJS: Lazy> = Lazy::new(|| btreemap! { diff --git a/src/obj/fun.rs b/src/obj/fun.rs index 2c365d4..7bd7bbf 100644 --- a/src/obj/fun.rs +++ b/src/obj/fun.rs @@ -1,4 +1,4 @@ -use crate::{obj::{reserved::*, prelude::*}, vm::{consts::ConstPool, frame::*, inst::Inst, signal::*, Vm}}; +use crate::{obj::{reserved::*, prelude::*}, vm::{consts::ConstPool, error::*, frame::*, inst::Inst, signal::*, Vm}}; use once_cell::sync::Lazy; use shredder::{GcSafeWrapper, Scan}; use std::{fmt::{Debug, Formatter, self}, io::{self, Write}, sync::Arc}; @@ -8,7 +8,7 @@ pub type FunLocals = Vec; /// A function object which can create a new stack frame. pub trait Fun { /// Creates a new `vm::Frame` from this function object. - fn create_frame(&self, callee: ObjRef, vm: &Vm, _args: Vec) -> Frame; + fn create_frame(&self, callee: ObjRef, vm: &Vm, _args: Vec) -> Result; } pub type MethodRef = ObjRef; @@ -55,7 +55,7 @@ impl Obj for Method { } impl Fun for Method { - fn create_frame(&self, _callee: ObjRef, vm: &Vm, mut args: Vec) -> Frame { + fn create_frame(&self, _callee: ObjRef, vm: &Vm, mut args: Vec) -> Result { // insert self argument args.insert(0, self.get_attr(SELF_MEMBER_NAME.sym).unwrap()); // get function and use its create_frame method with the updated args @@ -82,16 +82,18 @@ pub struct UserFun { // Safe because this is just an interner that points to symbols, which aren't GC'd #[shredder(unsafe_skip)] locals: FunLocals, + arity: usize, } impl UserFun { - pub fn new_obj(code: Vec, locals: FunLocals) -> UserFunRef { + pub fn new_obj(code: Vec, locals: FunLocals, arity: usize) -> UserFunRef { self_referring_obj! ( Self { vtable: Default::default(), // this is a placeholder for the real vtable attrs: Default::default(), code: Arc::new(code), locals, + arity, }, vtable: |obj_ref: ObjRef| vtable! { TY_MEMBER_NAME.sym => USER_FUN_TY.clone(), @@ -215,13 +217,19 @@ impl Obj for UserFun { } impl Fun for UserFun { - fn create_frame(&self, callee: ObjRef, vm: &Vm, args: Vec) -> Frame { + fn create_frame(&self, callee: ObjRef, vm: &Vm, args: Vec) -> Result { + if args.len() != self.arity { + return Err(Error::ArityError { + expected: self.arity, + got: args.len(), + }); + } let bindings: FrameBindings = args.into_iter() .enumerate() .collect(); - Frame::User( + Ok(Frame::User( UserFrame::new(callee, bindings, vm.pc(), Arc::clone(&self.code)) - ) + )) } } @@ -241,7 +249,7 @@ pub static USER_FUN_TY: Lazy> = Lazy::new(|| Ty::new_obj(USER_FUN_NAM // struct NativeFun // -pub type NativeFunPtr = fn(ObjRef, &mut Vm, Vec) -> Signal; +pub type NativeFunPtr = fn(ObjRef, &mut Vm, Vec) -> Result; pub type NativeFunRef = ObjRef; #[derive(Scan)] @@ -250,6 +258,7 @@ pub struct NativeFun { attrs: Attrs, #[shredder(skip)] fun: GcSafeWrapper, + arity: usize, } // @@ -257,12 +266,13 @@ pub struct NativeFun { // impl NativeFun { - pub fn new_obj(fun: NativeFunPtr) -> ObjRef { + pub fn new_obj(arity: usize, fun: NativeFunPtr) -> ObjRef { self_referring_obj! ( Self { vtable: Default::default(), attrs: Default::default(), fun: GcSafeWrapper::new(fun), + arity, }, vtable: |obj_ref: ObjRef| vtable! { TY_MEMBER_NAME.sym => NATIVE_FUN_TY.clone(), @@ -313,10 +323,16 @@ impl Obj for NativeFun { } impl Fun for NativeFun { - fn create_frame(&self, callee: ObjRef, _vm: &Vm, args: Vec) -> Frame { - Frame::Native( + fn create_frame(&self, callee: ObjRef, _vm: &Vm, args: Vec) -> Result { + if args.len() != self.arity { + return Err(Error::ArityError { + expected: self.arity, + got: args.len(), + }); + } + Ok(Frame::Native( NativeFrame::new(callee, *self.fun, args) - ) + )) } } diff --git a/src/obj/int.rs b/src/obj/int.rs index e1b47a3..405e1c8 100644 --- a/src/obj/int.rs +++ b/src/obj/int.rs @@ -44,13 +44,14 @@ impl Debug for Int { } static INT_STR_FUN: Lazy = Lazy::new(|| { - NativeFun::new_obj(|_callee, vm, args| { + NativeFun::new_obj(1, |_callee, vm, args| { read_obj!(let int_obj = &args[0]); if let Some(int_obj) = std::any::Any::downcast_ref::(int_obj.as_any()) { vm.push(Str::new_obj(int_obj.value.to_string())); } else { panic!("{:?} is not an int", int_obj) } - crate::vm::signal::Signal::Return + + Ok(crate::vm::signal::Signal::Return) }) }); diff --git a/src/obj/mod.rs b/src/obj/mod.rs index 737d83f..70f6307 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -20,6 +20,7 @@ pub mod prelude { use shredder::{Gc, Scan}; use std::{ + fmt::{self, Debug, Formatter}, marker::Unsize, ops::{CoerceUnsized, Deref, DerefMut}, sync::RwLock, @@ -42,7 +43,7 @@ macro_rules! obj_attr { } } -pub trait Obj: Scan + std::fmt::Debug { +pub trait Obj: Scan + Debug { fn vtable(&self) -> &Vtable; fn attrs(&self) -> &Attrs; fn attrs_mut(&mut self) -> Option<&mut Attrs>; @@ -82,7 +83,7 @@ pub trait Obj: Scan + std::fmt::Debug { // struct ObjRef // -#[derive(Debug, Scan)] +#[derive(Scan)] pub struct ObjRef where T: Obj + ?Sized + Send + Sync, @@ -135,6 +136,18 @@ where { } +// +// impl Debug for ObjRef +// +impl Debug for ObjRef + where T: Obj + ?Sized + Send + Sync + 'static, +{ + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + read_obj!(let obj: &T = self); + Debug::fmt(obj, fmt) + } +} + // // impl Deref for ObjRef // diff --git a/src/vm/error.rs b/src/vm/error.rs index 4105658..e9580b3 100644 --- a/src/vm/error.rs +++ b/src/vm/error.rs @@ -1,9 +1,24 @@ +use crate::obj::prelude::*; use snafu::Snafu; #[derive(Debug, Snafu)] pub enum Error { - #[snafu(display("attempted to pop an empty stack"))] - EmptyStack, + #[snafu(display("missing attribute: {}", global_sym_lookup(*attr).unwrap()))] + MissingAttr { + attr: Sym, + }, + + #[snafu(display("{}", error))] + ValueError { + error: String, + value: ObjRef, + }, + + #[snafu(display("incorrect function arity; expected {} but got {} instead", expected, got))] + ArityError { + expected: usize, + got: usize, + }, } pub type Result = std::result::Result; diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 21f96ec..1ad0c68 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -6,7 +6,7 @@ pub mod signal; use crate::{ obj::{builtin::BUILTIN_OBJS, reserved::*, prelude::*}, - vm::{consts::ConstPool, frame::*, inst::*, signal::*}, + vm::{consts::ConstPool, error::*, frame::*, inst::*, signal::*}, }; #[derive(Debug)] @@ -92,25 +92,26 @@ impl<'c> Vm<'c> { } /// Calls a function. - pub fn call(&mut self, fun: ObjRef, args: Vec) -> ObjRef { - self.handle_signal(Signal::Call(fun, args)); - self.resume_until_return(); - self.pop().expect("return value") + pub fn call(&mut self, fun: ObjRef, args: Vec) -> Result { + self.handle_signal(Signal::Call(fun, args))?; + self.resume_until_return()?; + Ok(self.pop().expect("return value")) } /// Resumes execution of the current program. - pub fn resume(&mut self) { + pub fn resume(&mut self) -> Result<()> { while !self.frames().is_empty() { - self.resume_until_return(); + self.resume_until_return()?; } + Ok(()) } /// Resume execution until the currently executing function returns. - pub fn resume_until_return(&mut self) { + pub fn resume_until_return(&mut self) -> Result<()> { // resume execution until control is returned to the originally calling function let start_frames = self.frames().len(); if start_frames == 0 { - return; + return Ok(()); } while self.frames().len() >= start_frames { @@ -119,14 +120,15 @@ impl<'c> Vm<'c> { Frame::Native(fun) => { let callee = fun.callee().clone(); let args = fun.args().clone(); - (*fun.fun_ptr())(callee, self, args) + (*fun.fun_ptr())(callee, self, args)? } Frame::User(_) => { self.resume_user_fun() } }; - self.handle_signal(signal); + self.handle_signal(signal)?; } + Ok(()) } fn resume_user_fun(&mut self) -> Signal { @@ -142,13 +144,13 @@ impl<'c> Vm<'c> { /// /// The signal may originate from the VM itself, or from an external location. Signals /// generally interrupt control flow of a program. - pub fn handle_signal(&mut self, signal: Signal) { + pub fn handle_signal(&mut self, signal: Signal) -> Result<()> { match signal { Signal::Call(callee, args) => { read_obj!(let callee_obj = callee); let frame = callee_obj.as_fun() - .expect("callable function") - .create_frame(callee.clone(), self, args); + .ok_or_else(|| Error::ValueError { error: "cannot call this object".to_string(), value: callee.clone() })? + .create_frame(callee.clone(), self, args)?; // Jump to the first address of the new function call if it's a user function if let Frame::User(_) = &frame { self.set_pc(0); @@ -171,6 +173,7 @@ impl<'c> Vm<'c> { self.push(ret_val); } } + Ok(()) } /// Gets the current instruction.