Skip to content

Commit 92a94cc

Browse files
committed
change error system
Signed-off-by: squeakbug <[email protected]>
1 parent 4de7e74 commit 92a94cc

File tree

12 files changed

+387
-34
lines changed

12 files changed

+387
-34
lines changed

Cargo.lock

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ regex = { path = "crates/regex" }
1818
rpds = { version = "1.1.0" }
1919
thiserror = { version = "2.0.11" }
2020
clap = { version = "4.5.31", features = ["derive"] }
21+
termcolor = { version = "1.4.1" }
22+
codespan-reporting = { version = "0.9.0" }
23+
camino = { version = "1.1.9" }
2124

2225
[profile.release]
2326
lto = true

crates/core/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ edition = "2021"
66

77
[dependencies]
88
thiserror = { workspace = true }
9-
lazy_static = { workspace = true }
9+
lazy_static = { workspace = true }
10+
termcolor = { workspace = true }
11+
codespan-reporting = { workspace = true }
12+
camino = { workspace = true }

crates/core/src/diagnostic.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use std::collections::HashMap;
2+
3+
use camino::Utf8PathBuf;
4+
5+
pub use codespan_reporting::diagnostic::{LabelStyle, Severity};
6+
use codespan_reporting::{diagnostic::Label as CodespanLabel, files::SimpleFiles};
7+
use termcolor::Buffer;
8+
9+
use crate::lexer::SrcSpan;
10+
11+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12+
pub enum Level {
13+
Error,
14+
Warning,
15+
}
16+
17+
#[derive(Debug, Clone)]
18+
pub struct Label {
19+
pub text: Option<String>,
20+
pub span: SrcSpan,
21+
}
22+
23+
impl Label {
24+
fn to_codespan_label(&self, fileid: usize) -> CodespanLabel<usize> {
25+
let label = CodespanLabel::new(
26+
LabelStyle::Primary,
27+
fileid,
28+
(self.span.start as usize)..(self.span.end as usize),
29+
);
30+
match &self.text {
31+
None => label,
32+
Some(text) => label.with_message(text.clone()),
33+
}
34+
}
35+
}
36+
37+
#[derive(Debug, Clone)]
38+
pub struct ExtraLabel {
39+
pub src_info: Option<(String, Utf8PathBuf)>,
40+
pub label: Label,
41+
}
42+
43+
#[derive(Debug, Clone)]
44+
pub struct Location {
45+
pub src: String,
46+
pub path: Utf8PathBuf,
47+
pub label: Label,
48+
pub extra_labels: Vec<ExtraLabel>,
49+
}
50+
51+
// TODO: split this into locationed diagnostics and locationless diagnostics
52+
#[derive(Debug, Clone)]
53+
pub struct Diagnostic {
54+
pub title: String,
55+
pub text: String,
56+
pub level: Level,
57+
pub location: Option<Location>,
58+
pub hint: Option<String>,
59+
}
60+
61+
impl Diagnostic {
62+
pub fn write(&self, buffer: &mut Buffer) {
63+
use std::io::Write;
64+
match &self.location {
65+
Some(location) => self.write_span(location, buffer),
66+
None => self.write_title(buffer),
67+
};
68+
69+
if !self.text.is_empty() {
70+
writeln!(buffer, "{}", self.text).expect("write text");
71+
}
72+
73+
if let Some(hint) = &self.hint {
74+
writeln!(buffer, "Hint: {hint}").expect("write hint");
75+
}
76+
}
77+
78+
fn write_span(&self, location: &Location, buffer: &mut Buffer) {
79+
let mut file_map = HashMap::new();
80+
let mut files = SimpleFiles::new();
81+
82+
let main_location_path = location.path.as_str();
83+
let main_location_src = location.src.as_str();
84+
let main_file_id = files.add(main_location_path, main_location_src);
85+
let _ = file_map.insert(main_location_path, main_file_id);
86+
87+
let mut labels = vec![location.label.to_codespan_label(main_file_id)];
88+
89+
location
90+
.extra_labels
91+
.iter()
92+
.map(|l| {
93+
let (location_src, location_path) = match &l.src_info {
94+
Some(info) => (info.0.as_str(), info.1.as_str()),
95+
_ => (main_location_src, main_location_path),
96+
};
97+
match file_map.get(location_path) {
98+
None => {
99+
let file_id = files.add(location_path, location_src);
100+
let _ = file_map.insert(location_path, file_id);
101+
l.label.to_codespan_label(file_id)
102+
}
103+
Some(i) => l.label.to_codespan_label(*i),
104+
}
105+
})
106+
.for_each(|l| labels.push(l));
107+
108+
let severity = match self.level {
109+
Level::Error => Severity::Error,
110+
Level::Warning => Severity::Warning,
111+
};
112+
113+
let diagnostic = codespan_reporting::diagnostic::Diagnostic::new(severity)
114+
.with_message(&self.title)
115+
.with_labels(labels);
116+
let config = codespan_reporting::term::Config::default();
117+
codespan_reporting::term::emit(buffer, &config, &files, &diagnostic)
118+
.expect("write_diagnostic");
119+
}
120+
121+
fn write_title(&self, buffer: &mut Buffer) {
122+
use std::io::Write;
123+
use termcolor::{Color, ColorSpec, WriteColor};
124+
let (kind, colour) = match self.level {
125+
Level::Error => ("error", Color::Red),
126+
Level::Warning => ("warning", Color::Yellow),
127+
};
128+
buffer
129+
.set_color(ColorSpec::new().set_bold(true).set_fg(Some(colour)))
130+
.expect("write_title_color1");
131+
write!(buffer, "{kind}").expect("write_title_kind");
132+
buffer
133+
.set_color(ColorSpec::new().set_bold(true))
134+
.expect("write_title_color2");
135+
write!(buffer, ": {}\n\n", self.title).expect("write_title_title");
136+
buffer
137+
.set_color(&ColorSpec::new())
138+
.expect("write_title_reset");
139+
}
140+
}

crates/core/src/error.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::io::Write;
2+
3+
use camino::Utf8PathBuf;
4+
use termcolor::Buffer;
5+
use thiserror::Error;
6+
7+
use crate::{diagnostic::{Diagnostic, Label, Level, Location}, lexer::{self, SrcSpan}, parser::{ParserError, ParserErrorType}, sema::SemanticError};
8+
9+
pub type Result<Ok, Err = Error> = std::result::Result<Ok, Err>;
10+
11+
#[derive(Debug, Clone, Error)]
12+
pub enum Error {
13+
#[error("failed to scan source code")]
14+
Lexer{
15+
path: Utf8PathBuf,
16+
src: String,
17+
error: lexer::LexerError,
18+
},
19+
20+
#[error("failed to parse source code")]
21+
Parser {
22+
path: Utf8PathBuf,
23+
src: String,
24+
error: ParserError,
25+
},
26+
27+
#[error("failed to resolve source code")]
28+
Semantic {
29+
path: Utf8PathBuf,
30+
src: String,
31+
error: SemanticError,
32+
},
33+
}
34+
35+
impl Error {
36+
pub fn pretty_string(&self) -> String {
37+
let mut nocolor = Buffer::no_color();
38+
self.pretty(&mut nocolor);
39+
String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8")
40+
}
41+
42+
pub fn pretty(&self, buffer: &mut Buffer) {
43+
for diagnostic in self.to_diagnostics() {
44+
diagnostic.write(buffer);
45+
writeln!(buffer).expect("write new line after diagnostic");
46+
}
47+
}
48+
49+
pub fn to_diagnostics(&self) -> Vec<Diagnostic> {
50+
match &self {
51+
Error::Lexer { path, src, error } => {
52+
let (label, extra) = error.details();
53+
let text = extra.join("\n");
54+
55+
vec![Diagnostic {
56+
title: "Scanning error".into(),
57+
text,
58+
hint: None,
59+
level: Level::Error,
60+
location: Some(Location {
61+
label: Label {
62+
text: Some(label.to_string()),
63+
span: error.location.clone(),
64+
},
65+
path: path.clone(),
66+
src: src.clone(),
67+
extra_labels: vec![],
68+
}),
69+
}]
70+
},
71+
Error::Parser { path, src, error } => {
72+
let (label, extra) = error.details();
73+
let text = extra.join("\n");
74+
75+
let adjusted_location = if error.kind == ParserErrorType::UnexpectedEof {
76+
SrcSpan {
77+
start: src.len() - 1,
78+
end: src.len() - 1,
79+
}
80+
} else {
81+
error.location.clone()
82+
};
83+
84+
vec![Diagnostic {
85+
title: "Parsing error".into(),
86+
text,
87+
hint: None,
88+
level: Level::Error,
89+
location: Some(Location {
90+
label: Label {
91+
text: Some(label.to_string()),
92+
span: adjusted_location,
93+
},
94+
path: path.clone(),
95+
src: src.clone(),
96+
extra_labels: vec![],
97+
}),
98+
}]
99+
},
100+
Error::Semantic { path, src, error } => {
101+
let (label, extra) = error.details();
102+
let text = extra.join("\n");
103+
104+
vec![Diagnostic {
105+
title: "Semantic error".into(),
106+
text,
107+
hint: None,
108+
level: Level::Error,
109+
location: Some(Location {
110+
label: Label {
111+
text: Some(label.to_string()),
112+
span: error.location.clone(),
113+
},
114+
path: path.clone(),
115+
src: src.clone(),
116+
extra_labels: vec![],
117+
}),
118+
}]
119+
},
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)