Add call stack error chain

This allows us to write out errors that follow the call stack and give
locations to where errors originated.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-01-16 15:25:58 -08:00
parent 2752bdf6d3
commit 9704e07c45
7 changed files with 90 additions and 24 deletions

View File

@@ -1,4 +1,4 @@
[ "Hell world" . ] [ "Hell world" println! ]
:say :say
say! say!

View File

@@ -82,12 +82,6 @@ impl<'s> Compile<'s> {
Inst::Store(word).into() Inst::Store(word).into()
} }
Atom::Word(text) => { Atom::Word(text) => {
// XXX : probably something better than this
// Check builtins
if text == "." {
return Inst::Print.into();
}
// Look for locally defined symbols first. One can't be found, // Look for locally defined symbols first. One can't be found,
// then create a local variable. The local variable *should* be // then create a local variable. The local variable *should* be
// defined already, but sometimes things happen. // defined already, but sometimes things happen.

View File

@@ -8,7 +8,7 @@ use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
use syn::parser::Parser; use syn::parser::Parser;
use vm::machine::MachineBuilder; use vm::{error::RuntimeSpanError, machine::MachineBuilder};
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
struct Opt { struct Opt {
@@ -46,7 +46,21 @@ fn main() -> Result {
.max_arena_objects(opt.max_arena_objects) .max_arena_objects(opt.max_arena_objects)
.finish(); .finish();
machine.eval(stmts)?; if let Err(e) = machine.eval(stmts) {
// print error message
print_error(&e);
Ok(())
} else {
Ok(()) Ok(())
} }
}
fn print_error(error: &RuntimeSpanError) {
match error {
RuntimeSpanError::Span(span, error) => {
println!("at {}", span);
print_error(error)
}
RuntimeSpanError::Error(error) => println!("{}", error),
}
}

View File

@@ -4,7 +4,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::cmp::{Ord, Ordering, PartialOrd}; use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug, Display};
use std::rc::Rc; use std::rc::Rc;
#[cfg_attr(not(test), derive(PartialEq))] #[cfg_attr(not(test), derive(PartialEq))]
@@ -96,6 +96,20 @@ impl Span {
} }
} }
impl Display for Span {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if self.start.line == self.end.line {
write!(fmt, "line {} in {}", self.start.line, self.source)
} else {
write!(
fmt,
"lines {}-{} in {}",
self.start.line, self.end.line, self.source
)
}
}
}
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Spanned<T> { pub struct Spanned<T> {
span: Span, span: Span,

View File

@@ -1,3 +1,4 @@
use crate::syn::span::Span;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
@@ -15,7 +16,44 @@ pub enum RuntimeError {
CannotCall(String), CannotCall(String),
} }
pub type Result<T, E = RuntimeError> = std::result::Result<T, E>; pub type Result<T, E = RuntimeSpanError> = std::result::Result<T, E>;
pub enum RuntimeSpanError {
Span(Span, Box<RuntimeSpanError>),
Error(RuntimeError),
}
/// Add a location to the implemented value.
pub trait WithLocation {
type Out;
fn with_location(self, span: Span) -> Self::Out;
}
impl<T> WithLocation for Result<T, RuntimeSpanError> {
type Out = Result<T, RuntimeSpanError>;
fn with_location(self, span: Span) -> Self::Out {
if let Err(e) = self {
Err(RuntimeSpanError::Span(span, Box::new(e)))
} else {
self
}
}
}
impl WithLocation for RuntimeError {
type Out = RuntimeSpanError;
fn with_location(self, span: Span) -> Self::Out {
RuntimeSpanError::Span(span, Box::new(self.into()))
}
}
impl From<RuntimeError> for RuntimeSpanError {
fn from(other: RuntimeError) -> Self {
RuntimeSpanError::Error(other)
}
}
// TODO // TODO
// Error building idea: // Error building idea:

View File

@@ -16,9 +16,6 @@ pub enum Inst {
//Dup, //Dup,
/// Applies the top stack value, which should be a macro. /// Applies the top stack value, which should be a macro.
Call, Call,
/// Pops and prints the top stack value.
Print,
} }
// TODO - do we want separate function definition syntax? We would be able to // TODO - do we want separate function definition syntax? We would be able to

View File

@@ -93,7 +93,7 @@ impl Machine {
pub fn stack_push(&mut self, value: Value) -> Result<()> { pub fn stack_push(&mut self, value: Value) -> Result<()> {
if let Some(max) = self.max_stack_size() { if let Some(max) = self.max_stack_size() {
if self.stack().len() >= max { if self.stack().len() >= max {
return Err(RuntimeError::StackOverflow); return Err(RuntimeError::StackOverflow.into());
} }
} }
self.stack_mut().push(value); self.stack_mut().push(value);
@@ -103,7 +103,7 @@ impl Machine {
pub fn stack_pop(&mut self) -> Result<Value> { pub fn stack_pop(&mut self) -> Result<Value> {
self.stack_mut() self.stack_mut()
.pop() .pop()
.ok_or_else(|| RuntimeError::StackUnderflow) .ok_or_else(|| RuntimeError::StackUnderflow.into())
} }
pub fn lookup_word(&self, word: &Word) -> Option<&Value> { pub fn lookup_word(&self, word: &Word) -> Option<&Value> {
@@ -267,12 +267,8 @@ impl Machine {
Value::BuiltinFn(builtin) => { Value::BuiltinFn(builtin) => {
self.call_native(builtin); self.call_native(builtin);
} }
value => return Err(RuntimeError::CannotCall(value.name().to_string())), value => return Err(RuntimeError::CannotCall(value.name().to_string()).into()),
}, },
Inst::Print => {
let value = self.stack_pop()?;
println!("{}", value);
}
}; };
// Update this frame's PC // Update this frame's PC
@@ -333,9 +329,22 @@ impl MachineBuilder {
} else { } else {
return Err(RuntimeError::CannotCall( return Err(RuntimeError::CannotCall(
"if statement requires quote value".to_string(), "if statement requires quote value".to_string(),
)); )
.into());
} }
}); });
self.register_builtin_fun("print", |machine, _| {
let value = machine.stack_pop()?;
print!("{}", value);
Ok(BuiltinExit::Return)
});
self.register_builtin_fun("println", |machine, _| {
let value = machine.stack_pop()?;
println!("{}", value);
Ok(BuiltinExit::Return)
});
} }
fn register_builtin_fun( fn register_builtin_fun(