diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 8421ee9..c55b350 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -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!} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..4077e7a --- /dev/null +++ b/src/errors.rs @@ -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) { + 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(); + } +} diff --git a/src/main.rs b/src/main.rs index 0784892..66d8c40 100644 --- a/src/main.rs +++ b/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::() diff --git a/src/parser.rs b/src/parser.rs index 3cf1258..80eecfa 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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!( diff --git a/src/process.rs b/src/process.rs index 177a9a8..55d0fa0 100644 --- a/src/process.rs +++ b/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, Spanned, Value<'src>, Value<'src>)>, + pub trace: Vec>, + pub extra: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Trace<'src> { + pub callee: Spanned, + pub caller: Spanned, + 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, 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::>() + .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); diff --git a/src/validator.rs b/src/validator.rs index cb433ae..95c1275 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -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, 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, + 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, @@ -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) -> 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) { diff --git a/src/value.rs b/src/value.rs index 5490547..5b4aec1 100644 --- a/src/value.rs +++ b/src/value.rs @@ -13,6 +13,9 @@ pub struct Fn<'src> { pub body: &'src Vec>, pub doc: Option, pub enclosing: Vec<(String, Value<'src>)>, + pub has_run: bool, + pub input: &'static str, + pub src: &'static str, } #[derive(Debug, Dissectible)]