Add internal error handling to VM, plus function arity

* VM is able to handle basic runtime errors although there is no way to
  catch this in executing code currently
* If a function is called with the wrong number of arguments (arity) it
  will invoke a runtime error.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-10-13 14:07:22 -07:00
parent c738c52455
commit 902da3f2f3
9 changed files with 98 additions and 50 deletions

View File

@@ -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<T, E = Error> = std::result::Result<T, E>;

View File

@@ -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>) -> 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<ObjRef>) -> Result<ObjRef> {
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.