improve panic reports

This commit is contained in:
Scott Richmond 2024-12-12 19:01:51 -05:00
parent 273267f61d
commit ac4bd0fb55
7 changed files with 195 additions and 55 deletions

View File

@ -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
View 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();
}
}

View File

@ -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>()

View File

@ -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!(

View File

@ -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);

View File

@ -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) {

View File

@ -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)]