use crate::js::*; use crate::lexer::Token; use crate::panic::{Panic, PanicMsg}; use crate::validator::VErr; use crate::vm::CallFrame; use chumsky::error::RichPattern; use chumsky::prelude::*; const SEPARATOR: &str = "\n\n"; fn line_number(src: &'static str, span: SimpleSpan) -> usize { src.chars().take(span.start).filter(|c| *c == '\n').count() } fn get_line(src: &'static str, line: usize) -> String { src.split("\n").nth(line).unwrap().to_string() } pub fn lexing(errs: Vec>, src: &'static str, input: &'static str) -> String { let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { let mut msg = vec![]; let line_number = line_number(src, *err.span()); let line = get_line(src, line_number); let char = src.chars().nth(err.span().start).unwrap(); msg.push(format!("Syntax error: unexpected {char}")); msg.push(format!(" on line {} in {}", line_number + 1, input)); msg.push(format!(" >>> {line}")); msgs.push(msg.join("\n")); } msgs.join(SEPARATOR) } pub fn validation(errs: Vec) -> String { let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { let mut msg = vec![]; let line_number = line_number(err.src, *err.span); let line = get_line(err.src, line_number); msg.push(format!("Validation error: {}", err.msg)); msg.push(format!(" on line {} in {}", line_number + 1, err.input)); msg.push(format!(" >>> {line}")); msgs.push(msg.join("\n")); } msgs.join(SEPARATOR) } pub fn parsing(errs: Vec>, src: &'static str, input: &'static str) -> String { let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { let mut msg = vec![]; let line_number = line_number(src, *err.span()); let line = get_line(src, line_number); let details = parsing_message(err); msg.push(format!("Syntax error: {}", details)); msg.push(format!(" on line {} in {}", line_number + 1, input)); msg.push(format!(" >>> {line}")); msgs.push(msg.join("\n")) } msgs.join(SEPARATOR) } fn parsing_message(err: Rich<'static, Token>) -> String { let found = match err.found() { Some(token) => token.show(), None => "end of input".to_string(), }; let expected = err.expected(); let mut expecteds = vec![]; for pattern in expected { let shown = match pattern { RichPattern::Token(t) => t.show(), RichPattern::Label(s) => s.to_string(), RichPattern::Identifier(s) => s.clone(), RichPattern::Any => "any".to_string(), RichPattern::SomethingElse => "something else".to_string(), RichPattern::EndOfInput => "eof".to_string(), }; expecteds.push(shown); } let expecteds = if expecteds.iter().any(|e| e == &"else".to_string()) { vec!["else".to_string()] } else { expecteds }; let expecteds = if expecteds.iter().any(|e| e == &"then".to_string()) { vec!["then".to_string()] } else { expecteds }; let expecteds = expecteds.join(" | "); format!("Ludus did not expect to see: {found}\n expected: {expecteds}") } pub fn panic(panic: Panic) -> String { // console_log!("Ludus panicked!: {panic}"); // panic.call_stack.last().unwrap().chunk().dissasemble(); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); let mut msgs = vec!["Ludus panicked!".to_string()]; let msg = match panic.msg { PanicMsg::Generic(ref s) => s, _ => &"no match".to_string(), }; msgs.push(msg.clone()); msgs.push(traceback(&panic)); msgs.join("\n") } fn traceback(panic: &Panic) -> String { let mut traceback = vec![]; for frame in panic.call_stack.iter().rev() { traceback.push(frame_info(frame)); } traceback.join("\n") } fn frame_info(frame: &CallFrame) -> String { let span = frame.chunk().spans[if frame.ip == 0 { frame.ip } else { frame.ip - 1 }]; let line_number = line_number(frame.chunk().src, span); let line = get_line(frame.chunk().src, line_number); let line = line.trim_start(); let name = frame.function.as_fn().name(); let input = frame.chunk().input; format!( " in {name} on line {} in {input}\n >>> {line}", line_number + 1 ) } /////// Some thoughts // We're putting the information we need on the function and the chunk. // In the compiler, on functions, build up a vec of strings that are the patterns the function can match against // The pattern asts have a `show` method. // And with the additional members on Chunk, we should have everything we need for a pretty fn no match message // Let no match is no problem, either. We should have no concerns pulling the line with the span start and string // We don't need to reproduce the pattern, since it will be right there in the code // As for match forms, we'll just use "no match" and print the value