From 2d007c977c43cc7255244f82f0a7918755ac3aee Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Sun, 7 Jan 2024 14:26:42 +0100 Subject: [PATCH] add error reporting stuffs --- Cargo.lock | 17 ++++++ Cargo.toml | 1 + example.hswt | 1 + src/error.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 15 ++++- src/parse.rs | 52 ++++++++++------- 6 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 666fe5f..b1b09ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,6 +171,16 @@ dependencies = [ "cc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1112,6 +1122,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "utf8parse" version = "0.2.1" @@ -1145,6 +1161,7 @@ name = "webthing" version = "0.1.0" dependencies = [ "clap", + "codespan-reporting", "cosmic-text", "env_logger", "font-kit", diff --git a/Cargo.toml b/Cargo.toml index d3cdad9..90dcd97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ font-kit = { version = "0.11", features = [ "loader-freetype" ]} simple_logger = "4.3.3" logos = "0.13.0" cosmic-text = "0.10.0" +codespan-reporting = "0.11.1" diff --git a/example.hswt b/example.hswt index c8ae07d..ad3cf2f 100644 --- a/example.hswt +++ b/example.hswt @@ -15,3 +15,4 @@ more text (heading level=2 "heading level 2 too") (heading level=3 "heading level 3 too") + diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1e4dff0 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,159 @@ +use std::{collections::HashMap, ops::Range, process}; + +use codespan_reporting::{ + diagnostic::{Diagnostic, Label, Severity}, + files::SimpleFiles, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; + +pub type Span = Range; + +type FileId = usize; + +#[derive(PartialEq, Eq, Hash, Copy, Clone)] +pub struct FileIdent<'a> { + id: FileId, + name: &'a str, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DiagnosticKind { + InvalidToken, +} + +pub enum DiagCriticality { + /// Bugs in this program, unreachable code being reached, etc. + Bug, + + // TODO: Better names + /// Critical errors that cannot be recovered from, prompt immediate exit + CriticalImmediate, + /// Critical, this program must exit after this processing step, but can continue and may find other diagnostics + CriticalFinishStep, + + /// A warning, about to be deprecated syntax etc. + Warn, + + /// Information/advice + Info, + Help, +} + +impl DiagCriticality { + fn mk_diag_base(&self) -> Diagnostic { + Diagnostic::new(match self { + DiagCriticality::Bug => Severity::Bug, + DiagCriticality::CriticalImmediate => Severity::Error, + DiagCriticality::CriticalFinishStep => Severity::Error, + DiagCriticality::Warn => Severity::Warning, + DiagCriticality::Info => Severity::Note, + DiagCriticality::Help => Severity::Help, + }) + } +} + +impl DiagnosticKind { + pub fn criticality(&self) -> DiagCriticality { + match self { + DiagnosticKind::InvalidToken => DiagCriticality::CriticalFinishStep, + } + } + + fn into_diag(self, locs: &[Span], file_id: &FileIdent) -> Diagnostic { + self.criticality() + .mk_diag_base() + .with_labels( + locs.iter() + .map(|span| Label::primary(file_id.id, span.clone())) + .collect(), + ) + .with_message(match self { + DiagnosticKind::InvalidToken => { + if locs.len() > 1 { + "invalid tokens" + } else { + "invalid token" + } + } + }) + } +} + +pub struct DiagnosticReporter<'a> { + files: SimpleFiles<&'a str, &'a str>, + errs: HashMap, HashMap>>, +} + +impl<'a> DiagnosticReporter<'a> { + pub fn new() -> Self { + Self { + files: SimpleFiles::new(), + errs: HashMap::new(), + } + } + + pub fn register_file(&mut self, name: &'a str, content: &'a str) -> FileIdent<'a> { + FileIdent { + id: self.files.add(name, content), + name, + } + } + + pub fn report_diagnostic(&mut self, file_id: FileIdent<'a>, loc: Span, kind: DiagnosticKind) { + self.errs + .entry(file_id) + .or_insert_with(|| HashMap::with_capacity(1)); + + if !self.errs[&file_id].contains_key(&kind) { + self.errs.get_mut(&file_id).unwrap().insert(kind, vec![loc]); + } else { + self.errs + .get_mut(&file_id) + .unwrap() + .get_mut(&kind) + .unwrap() + .push(loc) + } + + match kind.criticality() { + DiagCriticality::Bug | DiagCriticality::CriticalImmediate => self.finish_and_exit(), + DiagCriticality::CriticalFinishStep + | DiagCriticality::Warn + | DiagCriticality::Info + | DiagCriticality::Help => {} + } + } + + pub fn fail_step_if_failed(&self) { + for (_, diags) in self.errs.iter() { + for kind in diags.keys() { + if matches!( + kind.criticality(), + DiagCriticality::Bug + | DiagCriticality::CriticalImmediate + | DiagCriticality::CriticalFinishStep + ) { + self.finish_and_exit() + } + } + } + } + + fn finish_and_exit(&self) -> ! { + let writer = StandardStream::stderr(ColorChoice::Always); + let config = term::Config::default(); + + for (file_id, diags) in self.errs.iter() { + for (kind, locs) in diags { + let writer = &mut writer.lock(); + term::emit(writer, &config, &self.files, &kind.into_diag(locs, file_id)) + .expect("can't even report errors :c"); + } + } + + process::exit(1); + } +} diff --git a/src/main.rs b/src/main.rs index 937854d..02d83ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,9 @@ use parse::parse_the_shit_out_of_this_but_manually; use render::render_text; +use crate::error::DiagnosticReporter; + +mod error; mod parse; mod render; @@ -34,9 +37,17 @@ fn main() { args.x, args.y ); - let txt = fs::read_to_string(args.file).unwrap(); + let mut reporter = DiagnosticReporter::new(); - dbg!(parse_the_shit_out_of_this_but_manually(&txt)); + let txt = fs::read_to_string(args.file.clone()).unwrap(); + + let fid = reporter.register_file(args.file.file_name().unwrap().to_str().unwrap(), &txt); + + dbg!(parse_the_shit_out_of_this_but_manually( + &txt, + &mut reporter, + &fid + )); let mut dt = DrawTarget::new(args.x.into(), args.y.into()); diff --git a/src/parse.rs b/src/parse.rs index 7b3182d..18eedb0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2,9 +2,11 @@ use std::collections::HashMap; use logos::{Lexer, Logos}; +use crate::error::{DiagnosticKind, DiagnosticReporter, FileIdent}; + #[derive(Logos, Debug, PartialEq)] pub enum Token { - #[regex("[a-zA-Z\\d\\w]+", |lex| lex.slice().to_owned())] + #[regex("[a-zA-Z\\d\\s]+", |lex| lex.slice().to_owned())] Text(String), #[token("(")] ParenOpen, @@ -40,35 +42,45 @@ pub enum LanguageStructureThingy { }, } -pub fn parse_the_shit_out_of_this_but_manually(text: &str) -> Vec { - let mut lex = Token::lexer(text); +pub fn parse_the_shit_out_of_this_but_manually<'a>( + text: &str, + reporter: &mut DiagnosticReporter<'a>, + file_id: &'a FileIdent, +) -> Vec { + let mut lex = Token::lexer(text).spanned(); let mut r = Vec::new(); - loop { - match lex.next() { - Some(Ok(Token::Text(s))) => r.push(LanguageStructureThingy::Text(s)), - Some(Ok(Token::Newline)) => r.push(LanguageStructureThingy::Text("\n".to_owned())), - Some(Ok(Token::ParenOpen)) => hehe_sexpression_funy(&mut r, &mut lex), - Some(Ok(Token::ParenClose)) => todo!(), - Some(Ok(Token::String(_))) => todo!(), - Some(Ok(Token::Equals)) => todo!(), - Some(Ok(Token::Asterisk)) => todo!(), - Some(Ok(Token::Underscore)) => todo!(), - Some(Ok(Token::Backslash)) => todo!(), - Some(Ok(Token::WavyThing)) => todo!(), - Some(Ok(Token::Sparkles)) => todo!(), - Some(Ok(Token::HeadingLevelIndicator)) => todo!(), - Some(Err(e)) => panic!("mauuu~ :(, e: {e:?}"), - None => break, + while let Some((res, loc)) = lex.next() { + match res { + Ok(Token::Text(s)) => r.push(LanguageStructureThingy::Text(s)), + Ok(Token::Newline) => r.push(LanguageStructureThingy::Text("\n".to_owned())), + Ok(Token::ParenOpen) => hehe_sexpression_funy(&mut r, &mut lex), + Ok(Token::String(_)) => todo!(), + Ok(Token::Equals) => todo!(), + Ok(Token::Asterisk) => todo!(), + Ok(Token::Underscore) => todo!(), + Ok(Token::Backslash) => todo!(), + Ok(Token::WavyThing) => todo!(), + Ok(Token::Sparkles) => todo!(), + Ok(Token::HeadingLevelIndicator) => todo!(), + Err(()) | Ok(Token::ParenClose) => { + reporter.report_diagnostic(*file_id, loc, DiagnosticKind::InvalidToken) + } } } + reporter.fail_step_if_failed(); + // match lex.next() { + // Some(Err(e)) => panic!("mauuu~ :(, e: {e:?}"), + // None => break, + // } + r } fn hehe_sexpression_funy(r: &mut Vec, lex: &mut Lexer<'_, Token>) { if let Some(Ok(Token::Text(s))) = lex.next() { - let strs = s.trim_start().split_whitespace().collect::>(); + let strs = s.split_whitespace().collect::>(); let name = strs[0].to_owned(); let mut attrs = HashMap::new();