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:
@@ -1,4 +1,4 @@
|
|||||||
[ "Hell world" . ]
|
[ "Hell world" println! ]
|
||||||
:say
|
:say
|
||||||
|
|
||||||
say!
|
say!
|
||||||
@@ -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.
|
||||||
|
|||||||
20
src/main.rs
20
src/main.rs
@@ -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(())
|
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)]
|
#![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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user