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:
@@ -82,12 +82,6 @@ impl<'s> Compile<'s> {
|
||||
Inst::Store(word).into()
|
||||
}
|
||||
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,
|
||||
// then create a local variable. The local variable *should* be
|
||||
// defined already, but sometimes things happen.
|
||||
|
||||
22
src/main.rs
22
src/main.rs
@@ -8,7 +8,7 @@ use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
use syn::parser::Parser;
|
||||
use vm::machine::MachineBuilder;
|
||||
use vm::{error::RuntimeSpanError, machine::MachineBuilder};
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
struct Opt {
|
||||
@@ -46,7 +46,21 @@ fn main() -> Result {
|
||||
.max_arena_objects(opt.max_arena_objects)
|
||||
.finish();
|
||||
|
||||
machine.eval(stmts)?;
|
||||
|
||||
Ok(())
|
||||
if let Err(e) = machine.eval(stmts) {
|
||||
// print error message
|
||||
print_error(&e);
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_error(error: &RuntimeSpanError) {
|
||||
match error {
|
||||
RuntimeSpanError::Span(span, error) => {
|
||||
println!("at {}", span);
|
||||
print_error(error)
|
||||
}
|
||||
RuntimeSpanError::Error(error) => println!("{}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||
use std::fmt::{self, Debug};
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[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)]
|
||||
pub struct Spanned<T> {
|
||||
span: Span,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::syn::span::Span;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Clone)]
|
||||
@@ -15,7 +16,44 @@ pub enum RuntimeError {
|
||||
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
|
||||
// Error building idea:
|
||||
|
||||
@@ -16,9 +16,6 @@ pub enum Inst {
|
||||
//Dup,
|
||||
/// Applies the top stack value, which should be a macro.
|
||||
Call,
|
||||
|
||||
/// Pops and prints the top stack value.
|
||||
Print,
|
||||
}
|
||||
|
||||
// TODO - do we want separate function definition syntax? We would be able to
|
||||
|
||||
@@ -93,7 +93,7 @@ impl Machine {
|
||||
pub fn stack_push(&mut self, value: Value) -> Result<()> {
|
||||
if let Some(max) = self.max_stack_size() {
|
||||
if self.stack().len() >= max {
|
||||
return Err(RuntimeError::StackOverflow);
|
||||
return Err(RuntimeError::StackOverflow.into());
|
||||
}
|
||||
}
|
||||
self.stack_mut().push(value);
|
||||
@@ -103,7 +103,7 @@ impl Machine {
|
||||
pub fn stack_pop(&mut self) -> Result<Value> {
|
||||
self.stack_mut()
|
||||
.pop()
|
||||
.ok_or_else(|| RuntimeError::StackUnderflow)
|
||||
.ok_or_else(|| RuntimeError::StackUnderflow.into())
|
||||
}
|
||||
|
||||
pub fn lookup_word(&self, word: &Word) -> Option<&Value> {
|
||||
@@ -267,12 +267,8 @@ impl Machine {
|
||||
Value::BuiltinFn(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
|
||||
@@ -333,9 +329,22 @@ impl MachineBuilder {
|
||||
} else {
|
||||
return Err(RuntimeError::CannotCall(
|
||||
"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(
|
||||
|
||||
Reference in New Issue
Block a user