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) 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 prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
let base_pkg = base(); let base_pkg = base();
let base_names = base_pkg.iter().map(|binding| binding.0.clone()).collect(); let mut v6or = Validator::new(
&prelude_parsed.0,
let mut v6or = Validator::new(&prelude_parsed.0, prelude_parsed.1, &base_names); prelude_parsed.1,
"prelude",
prelude,
&base_pkg,
);
v6or.validate(); 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> { let mut base_ctx = Process::<'src> {
locals: base_pkg, input: "prelude",
src: prelude,
locals: base_pkg.clone(),
ast: &prelude_parsed.0, ast: &prelude_parsed.0,
span: prelude_parsed.1, span: prelude_parsed.1,
prelude: vec![], prelude: vec![],
@ -154,20 +163,17 @@ pub fn run(src: &'static str) {
let parsed = parse_result.unwrap(); 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(); 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()); prelude_fn_info.extend(&mut v6or.fn_info.into_iter());
let mut proc = Process { let mut proc = Process {
input: "script",
src,
locals: vec![], locals: vec![],
prelude: prelude_ctx, prelude: prelude_ctx,
ast: &parsed.0, ast: &parsed.0,
@ -179,22 +185,14 @@ pub fn run(src: &'static str) {
match result { match result {
Ok(result) => println!("{}", result), Ok(result) => println!("{}", result),
Err(err) => report_panic(err, src), Err(err) => report_panic(err),
} }
} }
pub fn main() { pub fn main() {
let src = " let src = "
fn bar fn foo () -> panics! ()
fn foo () -> {:foo} foo ()
let quux = foo
let fuzz = :asdf
fn baz (...) -> bar ()
fn bar () -> quux ()
baz (1, 2, 3)
"; ";
run(src); run(src);
// struct_scalpel::print_dissection_info::<value::Value>() // struct_scalpel::print_dissection_info::<value::Value>()

View File

@ -275,7 +275,7 @@ impl fmt::Display for Ast {
StringPattern(s) => write!(f, "{}", s), StringPattern(s) => write!(f, "{}", s),
KeywordPattern(k) => write!(f, ":{}", k), KeywordPattern(k) => write!(f, ":{}", k),
WordPattern(w) => write!(f, "{}", w), 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), Splattern(p) => write!(f, "...{}", p.0),
PlaceholderPattern => write!(f, "_"), PlaceholderPattern => write!(f, "_"),
TuplePattern(t) => write!( TuplePattern(t) => write!(

View File

@ -9,19 +9,40 @@ use imbl::Vector;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
pub struct LErr<'src> { pub struct LErr<'src> {
pub input: &'static str,
pub src: &'static str,
pub msg: String, pub msg: String,
pub span: SimpleSpan, 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> { 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 { LErr {
msg, msg,
span, span,
input,
src,
trace: vec![], trace: vec![],
extra: "".to_string(),
} }
} }
} }
@ -30,6 +51,8 @@ type LResult<'src> = Result<Value<'src>, LErr<'src>>;
#[derive(Debug)] #[derive(Debug)]
pub struct Process<'src> { pub struct Process<'src> {
pub input: &'static str,
pub src: &'static str,
pub locals: Vec<(String, Value<'src>)>, pub locals: Vec<(String, Value<'src>)>,
pub prelude: Vec<(String, Value<'src>)>, pub prelude: Vec<(String, Value<'src>)>,
pub ast: &'src Ast, pub ast: &'src Ast,
@ -47,14 +70,19 @@ impl<'src> Process<'src> {
let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name); let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name);
match resolved_prelude { match resolved_prelude {
Some((_, value)) => Ok(value.clone()), 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> { 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>) { pub fn bind(&mut self, word: String, value: &Value<'src>) {
@ -207,8 +235,8 @@ impl<'src> Process<'src> {
{ {
let root = self.ast; let root = self.ast;
let to = self.locals.len(); let to = self.locals.len();
let mut clauses = clauses.iter(); let mut clauses_iter = clauses.iter();
while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses.next() { while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses_iter.next() {
if self.match_pattern(&patt.0, value).is_some() { if self.match_pattern(&patt.0, value).is_some() {
let pass_guard = match guard.as_ref() { let pass_guard = match guard.as_ref() {
None => true, None => true,
@ -224,7 +252,26 @@ impl<'src> Process<'src> {
return result; 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(); let mut f = f.borrow_mut();
for i in 0..f.enclosing.len() { for i in 0..f.enclosing.len() {
let (name, value) = f.enclosing[i].clone(); 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); let defined = self.resolve(&name);
match defined { match defined {
Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)), Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)),
@ -265,20 +312,23 @@ impl<'src> Process<'src> {
_ => unreachable!("internal Ludus error"), _ => unreachable!("internal Ludus error"),
} }
} }
self.locals.push(f.enclosing[i].clone());
} }
let mut enclosing = f.enclosing.clone(); f.has_run = true;
self.locals.append(&mut enclosing); let input = self.input;
let src = self.src;
self.input = f.input;
self.src = f.src;
let result = self.match_clauses(&args, f.body); let result = self.match_clauses(&args, f.body);
self.locals.truncate(to); self.locals.truncate(to);
self.input = input;
self.src = src;
result result
} }
// TODO: partially applied functions shnould work! In #15 // TODO: partially applied functions shnould work! In #15
(Fn(_f), Args(_args)) => todo!(), (Fn(_f), Args(_args)) => todo!(),
(_, Keyword(_)) => Ok(Nil), (_, Keyword(_)) => Ok(Nil),
(_, Args(_)) => Err(LErr::new( (_, Args(_)) => self.panic("only functions and keywords may be called".to_string()),
"you may only call a function".to_string(),
self.span,
)),
(Base(f), Tuple(args)) => match f { (Base(f), Tuple(args)) => match f {
BaseFn::Nullary(f) => { BaseFn::Nullary(f) => {
let num_args = args.len(); let num_args = args.len();
@ -440,8 +490,14 @@ impl<'src> Process<'src> {
let first_val = self.visit(first)?; let first_val = self.visit(first)?;
let mut result = self.apply(root_val.clone(), first_val.clone()); let mut result = self.apply(root_val.clone(), first_val.clone());
if let Err(mut err) = result { if let Err(mut err) = result {
err.trace err.trace.push(Trace {
.push((*root.clone(), *first.clone(), root_val, first_val)); callee: *root.clone(),
caller: *first.clone(),
function: root_val,
arguments: first_val,
input: self.input,
src: self.src,
});
return Err(err); return Err(err);
}; };
let mut prev_node; let mut prev_node;
@ -454,8 +510,14 @@ impl<'src> Process<'src> {
result = self.apply(callee.clone(), caller.clone()); result = self.apply(callee.clone(), caller.clone());
if let Err(mut err) = result { if let Err(mut err) = result {
err.trace err.trace.push(Trace {
.push((prev_node.clone(), this_node.clone(), caller, callee)); callee: prev_node.clone(),
caller: this_node.clone(),
function: caller,
arguments: callee,
input: self.input,
src: self.src,
});
return Err(err); return Err(err);
} }
} }
@ -493,6 +555,9 @@ impl<'src> Process<'src> {
body: clauses, body: clauses,
doc, doc,
enclosing, enclosing,
has_run: false,
input: self.input,
src: self.src,
}))); })));
let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name); let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name);

View File

@ -1,16 +1,24 @@
use crate::parser::*; use crate::parser::*;
use crate::spans::Span; use crate::spans::Span;
use crate::value::Value;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct VErr { pub struct VErr {
msg: String, pub msg: String,
span: Span, pub span: Span,
pub input: &'static str,
pub src: &'static str,
} }
impl VErr { impl VErr {
pub fn new(msg: String, span: Span) -> VErr { pub fn new(msg: String, span: Span, input: &'static str, src: &'static str) -> VErr {
VErr { msg, span } VErr {
msg,
span,
input,
src,
}
} }
} }
@ -45,10 +53,11 @@ fn match_arities(arities: &HashSet<Arity>, num_args: u8) -> bool {
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Validator<'a> { pub struct Validator<'a, 'src> {
// TODO: add another term here: FnStatus. See Issue #18.
pub locals: Vec<(String, Span, FnInfo)>, 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 ast: &'a Ast,
pub span: Span, pub span: Span,
pub errors: Vec<VErr>, pub errors: Vec<VErr>,
@ -56,9 +65,17 @@ pub struct Validator<'a> {
status: VStatus, status: VStatus,
} }
impl<'a> Validator<'a> { impl<'a, 'src: 'a> Validator<'a, 'src> {
pub fn new(ast: &'a Ast, span: Span, prelude: &'a Vec<String>) -> Validator<'a> { pub fn new(
ast: &'a Ast,
span: Span,
input: &'static str,
src: &'static str,
prelude: &'a Vec<(String, Value<'src>)>,
) -> Validator<'a, 'src> {
Validator { Validator {
input,
src,
locals: vec![], locals: vec![],
prelude, prelude,
ast, ast,
@ -92,7 +109,7 @@ impl<'a> Validator<'a> {
fn resolved(&self, name: &str) -> bool { fn resolved(&self, name: &str) -> bool {
self.locals.iter().any(|(bound, ..)| name == bound.as_str()) 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)> { fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> {
@ -103,7 +120,8 @@ impl<'a> Validator<'a> {
} }
fn err(&mut self, msg: String) { 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) { fn use_name(&mut self, name: String) {

View File

@ -13,6 +13,9 @@ pub struct Fn<'src> {
pub body: &'src Vec<Spanned<Ast>>, pub body: &'src Vec<Spanned<Ast>>,
pub doc: Option<String>, pub doc: Option<String>,
pub enclosing: Vec<(String, Value<'src>)>, pub enclosing: Vec<(String, Value<'src>)>,
pub has_run: bool,
pub input: &'static str,
pub src: &'static str,
} }
#[derive(Debug, Dissectible)] #[derive(Debug, Dissectible)]