use std::fmt::{self, Display, Write}; use crate::{ compiler::CompilerError, parser::{Location, ParseError}, }; pub fn print_error(err: E, src: Option<&str>) { let mut buf = String::new(); match src { Some(src) => { err.fmt_with_src(&mut buf, &src).unwrap(); } None => err.fmt(&mut buf).unwrap(), } print!("{buf}"); } pub trait FormattedLocatedError { fn fmt(&self, w: &mut W) -> fmt::Result; fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result; } impl Display for Location { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Location::Char { line, col } => write!(f, "{line}:{col}"), Location::String { lines, cols } => write!(f, "{}:{}", lines.start(), cols.start()), Location::All => write!(f, "Entire file"), } } } impl FormattedLocatedError for ParseError { fn fmt(&self, w: &mut W) -> fmt::Result { match self { ParseError::UnexpectedParenClose(location) => { writeln!(w, "Unexpected closing paren at {location}")? } ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, } Ok(()) } fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { match self { ParseError::UnexpectedParenClose(location) => { writeln!(w, "Unexpected closing paren at {location}")?; here(location, src, Some("Unexpected ')'".to_string()), w)?; } ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, } Ok(()) } } impl FormattedLocatedError for CompilerError { fn fmt(&self, w: &mut W) -> fmt::Result { match self { CompilerError::ExpectedSymbol(location) => { writeln!( w, "Expected a symbol but got a list or nothing at {location}" ) } CompilerError::ExpectedList(location) => { writeln!( w, "Expected a list but got a symbol or nothing at {location}" ) } CompilerError::UnknownKeyword(k, location) => { writeln!(w, "Got unexpected keyword {k} at {location}")?; writeln!(w, "Expected one of: module, import, bind, dec, entry, fun") } CompilerError::ExpectedOneOf(vec, k, location) => { writeln!(w, "Got unexpected token {k} at {location}")?; writeln!(w, "Expected one of: {}", vec.join(", ")) } CompilerError::TrailingTokens(location) => { writeln!(w, "Got unexpected trailing tokens at {location}")?; writeln!(w, "Expected end of list") } CompilerError::UnknownBind(b, location) => { writeln!(w, "Got unexpected keyword {b} at {location}")?; writeln!(w, "Expected one of: BuiltIn, Location") } CompilerError::ExpectedType(location) => { writeln!(w, "Expected name:type but got name at {location}") } } } fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { match self { CompilerError::ExpectedSymbol(location) => { writeln!( w, "Expected a symbol but got a list or nothing at {location}" )?; here(location, src, Some("Expected symbol".to_string()), w) } CompilerError::ExpectedList(location) => { writeln!( w, "Expected a list but got a symbol or nothing at {location}" )?; here(location, src, Some("Expected List".to_string()), w) } CompilerError::UnknownKeyword(k, location) => { writeln!(w, "Got unexpected keyword {k} at {location}")?; writeln!(w, "Expected one of: module, import, bind, dec, entry, fun")?; here(location, src, Some("Unexpected keyword".to_string()), w) } CompilerError::ExpectedOneOf(vec, k, location) => { writeln!(w, "Got unexpected token {k} at {location}")?; writeln!(w, "Expected one of: {}", vec.join(", "))?; here(location, src, Some("Unexpected token".to_string()), w) } CompilerError::TrailingTokens(location) => { writeln!(w, "Got unexpected trailing tokens at {location}")?; writeln!(w, "Expected end of list")?; here(location, src, Some("Unexpected token".to_string()), w) } CompilerError::UnknownBind(b, location) => { writeln!(w, "Got unexpected keyword {b} at {location}")?; writeln!(w, "Expected one of: BuiltIn, Location")?; here(location, src, Some("Unexpected keyword".to_string()), w) } CompilerError::ExpectedType(location) => { writeln!(w, "Expected name:type but got name at {location}")?; here(location, src, None, w) } } } } fn here( loc: &Location, src: &str, comment: Option, w: &mut W, ) -> fmt::Result { writeln!(w, "Here:")?; writeln!( w, "{}", annotate_src(&loc, src, comment.unwrap_or_default()) )?; Ok(()) } fn annotate_src(loc: &Location, src: &str, comment: String) -> String { let src_lines = src.split('\n').collect::>(); let mut buf = String::new(); match loc { Location::Char { line, col } => { writeln!(buf, "{}|{}", line_num(*line - 1), src_lines[*line - 2]).unwrap(); writeln!(buf, "{}|{}", line_num(*line), src_lines[*line - 1]).unwrap(); writeln!(buf, "{}|{}^ {}", " ", " ".repeat(col - 1), comment).unwrap(); writeln!(buf, "{}|{}", line_num(*line + 1), src_lines[*line]).unwrap(); } Location::String { lines, cols } => { writeln!( buf, "{}|{}", line_num(*lines.start() - 1), src_lines[*lines.start() - 2] ) .unwrap(); writeln!( buf, "{}", src_lines[(lines.start() - 1)..*lines.end()] .iter() .zip(lines.clone()) .map(|(l, n)| format!("{}|{}", line_num(n), l)) .collect::>() .join("\n") ) .unwrap(); writeln!( buf, "{}|{}{} {}", " ", " ".repeat(cols.start() - 1), "^".repeat(cols.end() - cols.start() + 1), comment ) .unwrap(); writeln!( buf, "{}|{}", line_num(*lines.end() + 1), src_lines[*lines.end()] ) .unwrap(); } Location::All => todo!(), } buf } fn line_num(num: usize) -> String { let num = format!("{num}"); format!("{num}{}", " ".repeat(6 - num.len())) }