improve panic reports
This commit is contained in:
parent
273267f61d
commit
ac4bd0fb55
|
@ -10,4 +10,6 @@ fn dec (n as :number) -> base :sub (n, 1)
|
|||
|
||||
fn sub (x as :number, y as :number) -> base :sub (x, y)
|
||||
|
||||
#{add, dec, sub}
|
||||
fn panics! () -> add ("foo", "bar")
|
||||
|
||||
#{add, dec, sub, panics!}
|
||||
|
|
54
src/errors.rs
Normal file
54
src/errors.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use crate::process::{LErr, Trace};
|
||||
use crate::validator::VErr;
|
||||
use crate::value::Value;
|
||||
use ariadne::{sources, Color, Label, Report, ReportKind};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn report_panic(err: LErr) {
|
||||
let mut srcs = HashSet::new();
|
||||
let mut stack = vec![];
|
||||
let mut order = 1;
|
||||
for entry in err.trace.iter().rev() {
|
||||
let Trace {
|
||||
callee,
|
||||
caller,
|
||||
function,
|
||||
arguments,
|
||||
input,
|
||||
src,
|
||||
} = entry;
|
||||
let (_, first_span) = callee;
|
||||
let (_, second_span) = caller;
|
||||
let Value::Fn(f) = function else {
|
||||
unreachable!()
|
||||
};
|
||||
let fn_name = f.borrow().name.clone();
|
||||
let i = first_span.start;
|
||||
let j = second_span.end;
|
||||
let label = Label::new((entry.input, i..j))
|
||||
.with_message(format!("({order}) calling `{fn_name}` with `{arguments}`"));
|
||||
order += 1;
|
||||
stack.push(label);
|
||||
srcs.insert((*input, *src));
|
||||
}
|
||||
|
||||
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
.with_message(format!("Ludus panicked! {}", err.msg))
|
||||
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red))
|
||||
.with_labels(stack)
|
||||
.with_note(err.extra)
|
||||
.finish()
|
||||
.print(sources(srcs.iter().copied()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn report_invalidation(errs: Vec<VErr>) {
|
||||
for err in errs {
|
||||
Report::build(ReportKind::Error, (err.input, err.span.into_range()))
|
||||
.with_message(err.msg.to_string())
|
||||
.with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan))
|
||||
.finish()
|
||||
.print(sources(vec![(err.input, err.src)]))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
44
src/main.rs
44
src/main.rs
|
@ -96,15 +96,24 @@ pub fn prelude<'src>() -> (
|
|||
let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
|
||||
let base_pkg = base();
|
||||
|
||||
let base_names = base_pkg.iter().map(|binding| binding.0.clone()).collect();
|
||||
|
||||
let mut v6or = Validator::new(&prelude_parsed.0, prelude_parsed.1, &base_names);
|
||||
let mut v6or = Validator::new(
|
||||
&prelude_parsed.0,
|
||||
prelude_parsed.1,
|
||||
"prelude",
|
||||
prelude,
|
||||
&base_pkg,
|
||||
);
|
||||
v6or.validate();
|
||||
|
||||
// dbg!(&v6or);
|
||||
if !v6or.errors.is_empty() {
|
||||
report_invalidation(v6or.errors);
|
||||
panic!("interal Ludus error: invalid prelude")
|
||||
}
|
||||
|
||||
let mut base_ctx = Process::<'src> {
|
||||
locals: base_pkg,
|
||||
input: "prelude",
|
||||
src: prelude,
|
||||
locals: base_pkg.clone(),
|
||||
ast: &prelude_parsed.0,
|
||||
span: prelude_parsed.1,
|
||||
prelude: vec![],
|
||||
|
@ -154,20 +163,17 @@ pub fn run(src: &'static str) {
|
|||
|
||||
let parsed = parse_result.unwrap();
|
||||
|
||||
let dummy_prelude = vec![];
|
||||
let (prelude_ctx, mut prelude_fn_info) = prelude();
|
||||
|
||||
let mut v6or = Validator::new(&parsed.0, parsed.1, &dummy_prelude);
|
||||
let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, &prelude_ctx);
|
||||
|
||||
v6or.validate();
|
||||
|
||||
// dbg!(&v6or);
|
||||
// dbg!(&v6or.fn_info);
|
||||
|
||||
let (prelude_ctx, mut prelude_fn_info) = prelude();
|
||||
|
||||
prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
|
||||
|
||||
let mut proc = Process {
|
||||
input: "script",
|
||||
src,
|
||||
locals: vec![],
|
||||
prelude: prelude_ctx,
|
||||
ast: &parsed.0,
|
||||
|
@ -179,22 +185,14 @@ pub fn run(src: &'static str) {
|
|||
|
||||
match result {
|
||||
Ok(result) => println!("{}", result),
|
||||
Err(err) => report_panic(err, src),
|
||||
Err(err) => report_panic(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let src = "
|
||||
fn bar
|
||||
fn foo () -> {:foo}
|
||||
|
||||
let quux = foo
|
||||
let fuzz = :asdf
|
||||
|
||||
fn baz (...) -> bar ()
|
||||
|
||||
fn bar () -> quux ()
|
||||
baz (1, 2, 3)
|
||||
fn foo () -> panics! ()
|
||||
foo ()
|
||||
";
|
||||
run(src);
|
||||
// struct_scalpel::print_dissection_info::<value::Value>()
|
||||
|
|
|
@ -275,7 +275,7 @@ impl fmt::Display for Ast {
|
|||
StringPattern(s) => write!(f, "{}", s),
|
||||
KeywordPattern(k) => write!(f, ":{}", k),
|
||||
WordPattern(w) => write!(f, "{}", w),
|
||||
AsPattern(w, t) => write!(f, "{} as {}", w, t),
|
||||
AsPattern(w, t) => write!(f, "{} as :{}", w, t),
|
||||
Splattern(p) => write!(f, "...{}", p.0),
|
||||
PlaceholderPattern => write!(f, "_"),
|
||||
TuplePattern(t) => write!(
|
||||
|
|
103
src/process.rs
103
src/process.rs
|
@ -9,19 +9,40 @@ use imbl::Vector;
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LErr<'src> {
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
pub msg: String,
|
||||
pub span: SimpleSpan,
|
||||
pub trace: Vec<(Spanned<Ast>, Spanned<Ast>, Value<'src>, Value<'src>)>,
|
||||
pub trace: Vec<Trace<'src>>,
|
||||
pub extra: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Trace<'src> {
|
||||
pub callee: Spanned<Ast>,
|
||||
pub caller: Spanned<Ast>,
|
||||
pub function: Value<'src>,
|
||||
pub arguments: Value<'src>,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
}
|
||||
|
||||
impl<'src> LErr<'src> {
|
||||
pub fn new(msg: String, span: SimpleSpan) -> LErr<'src> {
|
||||
pub fn new(
|
||||
msg: String,
|
||||
span: SimpleSpan,
|
||||
input: &'static str,
|
||||
src: &'static str,
|
||||
) -> LErr<'src> {
|
||||
LErr {
|
||||
msg,
|
||||
span,
|
||||
input,
|
||||
src,
|
||||
trace: vec![],
|
||||
extra: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +51,8 @@ type LResult<'src> = Result<Value<'src>, LErr<'src>>;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Process<'src> {
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
pub locals: Vec<(String, Value<'src>)>,
|
||||
pub prelude: Vec<(String, Value<'src>)>,
|
||||
pub ast: &'src Ast,
|
||||
|
@ -47,14 +70,19 @@ impl<'src> Process<'src> {
|
|||
let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name);
|
||||
match resolved_prelude {
|
||||
Some((_, value)) => Ok(value.clone()),
|
||||
None => Err(LErr::new(format!("unbound name `{word}`"), self.span)),
|
||||
None => Err(LErr::new(
|
||||
format!("unbound name `{word}`"),
|
||||
self.span,
|
||||
self.input,
|
||||
self.src,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panic(&self, msg: String) -> LResult<'src> {
|
||||
Err(LErr::new(msg, self.span))
|
||||
Err(LErr::new(msg, self.span, self.input, self.src))
|
||||
}
|
||||
|
||||
pub fn bind(&mut self, word: String, value: &Value<'src>) {
|
||||
|
@ -207,8 +235,8 @@ impl<'src> Process<'src> {
|
|||
{
|
||||
let root = self.ast;
|
||||
let to = self.locals.len();
|
||||
let mut clauses = clauses.iter();
|
||||
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses.next() {
|
||||
let mut clauses_iter = clauses.iter();
|
||||
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses_iter.next() {
|
||||
if self.match_pattern(&patt.0, value).is_some() {
|
||||
let pass_guard = match guard.as_ref() {
|
||||
None => true,
|
||||
|
@ -224,7 +252,26 @@ impl<'src> Process<'src> {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
self.panic("no match".to_string())
|
||||
let patterns = clauses
|
||||
.iter()
|
||||
.map(|clause| {
|
||||
let (Ast::MatchClause(patt, ..), _) = clause else {
|
||||
unreachable!("internal Ludus error")
|
||||
};
|
||||
let patt = &patt.as_ref().0;
|
||||
patt.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
dbg!(&patterns);
|
||||
Err(LErr {
|
||||
input: self.input,
|
||||
src: self.src,
|
||||
msg: "no match".to_string(),
|
||||
span: self.span,
|
||||
trace: vec![],
|
||||
extra: format!("expected {value} to match one of\n{}", patterns),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +299,7 @@ impl<'src> Process<'src> {
|
|||
let mut f = f.borrow_mut();
|
||||
for i in 0..f.enclosing.len() {
|
||||
let (name, value) = f.enclosing[i].clone();
|
||||
if matches!(value, Value::FnDecl(_)) {
|
||||
if !f.has_run && matches!(value, Value::FnDecl(_)) {
|
||||
let defined = self.resolve(&name);
|
||||
match defined {
|
||||
Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)),
|
||||
|
@ -265,20 +312,23 @@ impl<'src> Process<'src> {
|
|||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
}
|
||||
self.locals.push(f.enclosing[i].clone());
|
||||
}
|
||||
let mut enclosing = f.enclosing.clone();
|
||||
self.locals.append(&mut enclosing);
|
||||
f.has_run = true;
|
||||
let input = self.input;
|
||||
let src = self.src;
|
||||
self.input = f.input;
|
||||
self.src = f.src;
|
||||
let result = self.match_clauses(&args, f.body);
|
||||
self.locals.truncate(to);
|
||||
self.input = input;
|
||||
self.src = src;
|
||||
result
|
||||
}
|
||||
// TODO: partially applied functions shnould work! In #15
|
||||
(Fn(_f), Args(_args)) => todo!(),
|
||||
(_, Keyword(_)) => Ok(Nil),
|
||||
(_, Args(_)) => Err(LErr::new(
|
||||
"you may only call a function".to_string(),
|
||||
self.span,
|
||||
)),
|
||||
(_, Args(_)) => self.panic("only functions and keywords may be called".to_string()),
|
||||
(Base(f), Tuple(args)) => match f {
|
||||
BaseFn::Nullary(f) => {
|
||||
let num_args = args.len();
|
||||
|
@ -440,8 +490,14 @@ impl<'src> Process<'src> {
|
|||
let first_val = self.visit(first)?;
|
||||
let mut result = self.apply(root_val.clone(), first_val.clone());
|
||||
if let Err(mut err) = result {
|
||||
err.trace
|
||||
.push((*root.clone(), *first.clone(), root_val, first_val));
|
||||
err.trace.push(Trace {
|
||||
callee: *root.clone(),
|
||||
caller: *first.clone(),
|
||||
function: root_val,
|
||||
arguments: first_val,
|
||||
input: self.input,
|
||||
src: self.src,
|
||||
});
|
||||
return Err(err);
|
||||
};
|
||||
let mut prev_node;
|
||||
|
@ -454,8 +510,14 @@ impl<'src> Process<'src> {
|
|||
result = self.apply(callee.clone(), caller.clone());
|
||||
|
||||
if let Err(mut err) = result {
|
||||
err.trace
|
||||
.push((prev_node.clone(), this_node.clone(), caller, callee));
|
||||
err.trace.push(Trace {
|
||||
callee: prev_node.clone(),
|
||||
caller: this_node.clone(),
|
||||
function: caller,
|
||||
arguments: callee,
|
||||
input: self.input,
|
||||
src: self.src,
|
||||
});
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
@ -493,6 +555,9 @@ impl<'src> Process<'src> {
|
|||
body: clauses,
|
||||
doc,
|
||||
enclosing,
|
||||
has_run: false,
|
||||
input: self.input,
|
||||
src: self.src,
|
||||
})));
|
||||
|
||||
let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name);
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
use crate::parser::*;
|
||||
use crate::spans::Span;
|
||||
use crate::value::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VErr {
|
||||
msg: String,
|
||||
span: Span,
|
||||
pub msg: String,
|
||||
pub span: Span,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
}
|
||||
|
||||
impl VErr {
|
||||
pub fn new(msg: String, span: Span) -> VErr {
|
||||
VErr { msg, span }
|
||||
pub fn new(msg: String, span: Span, input: &'static str, src: &'static str) -> VErr {
|
||||
VErr {
|
||||
msg,
|
||||
span,
|
||||
input,
|
||||
src,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,10 +53,11 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Validator<'a> {
|
||||
// TODO: add another term here: FnStatus. See Issue #18.
|
||||
pub struct Validator<'a, 'src> {
|
||||
pub locals: Vec<(String, Span, FnInfo)>,
|
||||
pub prelude: &'a Vec<String>,
|
||||
pub prelude: &'a Vec<(String, Value<'src>)>,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
pub ast: &'a Ast,
|
||||
pub span: Span,
|
||||
pub errors: Vec<VErr>,
|
||||
|
@ -56,9 +65,17 @@ pub struct Validator<'a> {
|
|||
status: VStatus,
|
||||
}
|
||||
|
||||
impl<'a> Validator<'a> {
|
||||
pub fn new(ast: &'a Ast, span: Span, prelude: &'a Vec<String>) -> Validator<'a> {
|
||||
impl<'a, 'src: 'a> Validator<'a, 'src> {
|
||||
pub fn new(
|
||||
ast: &'a Ast,
|
||||
span: Span,
|
||||
input: &'static str,
|
||||
src: &'static str,
|
||||
prelude: &'a Vec<(String, Value<'src>)>,
|
||||
) -> Validator<'a, 'src> {
|
||||
Validator {
|
||||
input,
|
||||
src,
|
||||
locals: vec![],
|
||||
prelude,
|
||||
ast,
|
||||
|
@ -92,7 +109,7 @@ impl<'a> Validator<'a> {
|
|||
|
||||
fn resolved(&self, name: &str) -> bool {
|
||||
self.locals.iter().any(|(bound, ..)| name == bound.as_str())
|
||||
|| self.prelude.iter().any(|bound| name == bound.as_str())
|
||||
|| self.prelude.iter().any(|(bound, _)| name == bound.as_str())
|
||||
}
|
||||
|
||||
fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> {
|
||||
|
@ -103,7 +120,8 @@ impl<'a> Validator<'a> {
|
|||
}
|
||||
|
||||
fn err(&mut self, msg: String) {
|
||||
self.errors.push(VErr::new(msg, self.span))
|
||||
self.errors
|
||||
.push(VErr::new(msg, self.span, self.input, self.src))
|
||||
}
|
||||
|
||||
fn use_name(&mut self, name: String) {
|
||||
|
|
|
@ -13,6 +13,9 @@ pub struct Fn<'src> {
|
|||
pub body: &'src Vec<Spanned<Ast>>,
|
||||
pub doc: Option<String>,
|
||||
pub enclosing: Vec<(String, Value<'src>)>,
|
||||
pub has_run: bool,
|
||||
pub input: &'static str,
|
||||
pub src: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Dissectible)]
|
||||
|
|
Loading…
Reference in New Issue
Block a user