1use std::{
2 borrow::Cow,
3 fmt::{self, Display, Formatter},
4};
5
6use rush_parser::{Error, Span};
7
8#[derive(PartialEq, Eq, Debug, Clone)]
9pub struct Diagnostic<'src> {
10 pub level: DiagnosticLevel,
11 pub message: Cow<'static, str>,
12 pub notes: Vec<Cow<'static, str>>,
13 pub span: Span<'src>,
14 pub source: &'src str,
15}
16
17impl<'src> From<Error<'src>> for Diagnostic<'src> {
18 fn from(err: Error<'src>) -> Self {
19 Self::new(
20 DiagnosticLevel::Error(ErrorKind::Syntax),
21 err.message,
22 vec![],
23 err.span,
24 err.source,
25 )
26 }
27}
28
29impl<'src> From<Box<Error<'src>>> for Diagnostic<'src> {
30 fn from(err: Box<Error<'src>>) -> Self {
31 Self::from(*err)
32 }
33}
34
35impl<'src> Diagnostic<'src> {
36 pub fn new(
37 level: DiagnosticLevel,
38 message: impl Into<Cow<'static, str>>,
39 notes: Vec<Cow<'static, str>>,
40 span: Span<'src>,
41 source: &'src str,
42 ) -> Self {
43 Self {
44 level,
45 message: message.into(),
46 notes,
47 span,
48 source,
49 }
50 }
51}
52
53impl Display for Diagnostic<'_> {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 let ansi_col = |col: u8, bold: bool| -> Cow<'static, str> {
56 match (f.alternate(), bold) {
57 (true, true) => format!("\x1b[1;{col}m").into(),
58 (true, false) => format!("\x1b[{col}m").into(),
59 (false, _) => "".into(),
60 }
61 };
62 let ansi_reset = match f.alternate() {
63 true => "\x1b[0m",
64 false => "",
65 };
66
67 let lines: Vec<_> = self.source.split('\n').collect();
68
69 let (raw_marker, raw_marker_single, color) = match self.level {
70 DiagnosticLevel::Hint => ("~", "^", 5), DiagnosticLevel::Info => ("~", "^", 4), DiagnosticLevel::Warning => ("~", "^", 3), DiagnosticLevel::Error(_) => ("^", "^", 1), };
75
76 let notes: String = self
77 .notes
78 .iter()
79 .map(|note| {
80 format!(
81 "\n {color}note:{ansi_reset} {note}",
82 color = ansi_col(36, true),
83 )
84 })
85 .collect();
86
87 if self.source.is_empty() || self.span.is_empty() {
89 return writeln!(
90 f,
91 " {color}{lvl}{reset_col} in {path}{ansi_reset} \n {msg}{notes}",
92 color = ansi_col(color + 30, true),
93 lvl = self.level,
94 reset_col = ansi_col(39, false),
95 path = self.span.start.path,
96 msg = self.message,
97 );
98 }
99
100 let line1 = match self.span.start.line > 1 {
101 true => format!(
102 "\n {}{: >3} | {ansi_reset}{}",
103 ansi_col(90, false),
104 self.span.start.line - 1,
105 lines[self.span.start.line - 2],
106 ),
107 false => String::new(),
108 };
109
110 let line2 = format!(
111 " {}{: >3} | {ansi_reset}{}",
112 ansi_col(90, false),
113 self.span.start.line,
114 lines[self.span.start.line - 1]
115 );
116
117 let line3 = match self.span.start.line < lines.len() {
118 true => format!(
119 "\n {}{: >3} | {ansi_reset}{}",
120 ansi_col(90, false),
121 self.span.start.line + 1,
122 lines[self.span.start.line]
123 ),
124 false => String::new(),
125 };
126
127 let markers = match (
128 self.span.start.line == self.span.end.line,
129 self.span.start.column + 1 == self.span.end.column,
130 ) {
131 (true, false) => raw_marker.repeat(self.span.end.column - self.span.start.column),
133 (true, true) => raw_marker_single.to_string(),
135 (_, _) => {
137 format!(
138 "{marker} ...\n{space}{color}+ {line_count} more line{s}{ansi_reset}",
139 marker = raw_marker
140 .repeat(lines[self.span.start.line - 1].len() - self.span.start.column + 1),
141 space = " ".repeat(self.span.start.column + 6),
142 color = ansi_col(32, true),
143 line_count = self.span.end.line - self.span.start.line,
144 s = match self.span.end.line - self.span.start.line == 1 {
145 true => "",
146 false => "s",
147 },
148 )
149 }
150 };
151
152 let marker = format!(
153 "{space}{color}{markers}{ansi_reset}",
154 color = ansi_col(color + 30, true),
155 space = " ".repeat(self.span.start.column + 6),
156 );
157
158 writeln!(
159 f,
160 " {color}{lvl}{reset_col} at {path}:{line}:{col}{ansi_reset}\n{line1}\n{line2}\n{marker}{line3}\n\n {color}{msg}{ansi_reset}{notes}",
161 color = ansi_col(color + 30, true),
162 lvl = self.level,
163 reset_col = ansi_col(39, false),
164 path = self.span.start.path,
165 line = self.span.start.line,
166 col = self.span.start.column,
167 msg = self.message,
168 )
169 }
170}
171
172#[derive(PartialEq, Eq, Debug, Clone)]
173pub enum DiagnosticLevel {
174 Hint,
175 Info,
176 Warning,
177 Error(ErrorKind),
178}
179
180impl Display for DiagnosticLevel {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 match self {
183 Self::Hint | Self::Info | Self::Warning => write!(f, "{self:?}"),
184 Self::Error(kind) => write!(f, "{kind}"),
185 }
186 }
187}
188
189#[derive(PartialEq, Eq, Debug, Clone)]
190pub enum ErrorKind {
191 Syntax,
192 Type,
193 Semantic,
194 Reference,
195}
196
197impl Display for ErrorKind {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 write!(f, "{self:?}Error")
200 }
201}