1use colored::*;
4
5use crate::{
6 source::Source,
7 span::{Span, location_to_line_col},
8};
9
10#[derive(Debug)]
12pub struct Error<'a> {
13 message: String,
15 source: &'a Source<'a>,
17 span: Span,
19 context: Vec<String>,
21 notes: Vec<String>,
23}
24
25impl<'a> Error<'a> {
26 pub fn new(message: String, source: &'a Source<'a>, span: Span) -> Self {
28 Error {
29 message,
30 source,
31 span,
32 context: Vec::new(),
33 notes: Vec::new(),
34 }
35 }
36
37 pub fn with_context(mut self, context: String) -> Self {
39 self.context.push(context);
40 self
41 }
42
43 pub fn with_note(mut self, note: String) -> Self {
45 self.notes.push(note);
46 self
47 }
48
49 pub fn display(&self) {
51 let (start_line, start_col) = location_to_line_col(&self.source.code, self.span.start);
52 let (end_line, mut end_col) = location_to_line_col(&self.source.code, self.span.end);
53 end_col -= 1;
54
55 let number_of_spaces = start_line.max(end_line).to_string().len();
56
57 eprintln!(
58 "{}{} {}",
59 "error".red().bold(),
60 ":".bold(),
61 self.message.bold()
62 );
63
64 if start_line == end_line {
65 if start_col == end_col {
66 eprintln!(
67 "{}{} {}:{}:{}",
68 " ".repeat(number_of_spaces),
69 "-->".cyan().bold(),
70 self.source.filename,
71 start_line,
72 start_col
73 );
74 } else {
75 eprintln!(
76 "{}{} {}:{}:{}-{}",
77 " ".repeat(number_of_spaces),
78 "-->".cyan().bold(),
79 self.source.filename,
80 start_line,
81 start_col,
82 end_col
83 );
84 }
85 } else {
86 eprintln!(
87 "{}{} {}:{}:{}-{}:{}",
88 " ".repeat(number_of_spaces),
89 "-->".cyan().bold(),
90 self.source.filename,
91 start_line,
92 start_col,
93 end_line,
94 end_col
95 );
96 }
97
98 let lines = self.source.code.lines().collect::<Vec<&str>>();
99 let lines = lines
100 .iter()
101 .skip(start_line - 1)
102 .take(end_line - start_line + 1);
103
104 eprintln!("{} {}", " ".repeat(number_of_spaces), "|".cyan().bold());
105
106 for (line_index, line) in lines.enumerate() {
107 let line_number = start_line + line_index;
108
109 if line_number == start_line && line_number == end_line {
110 eprintln!(
111 "{}{} {} {}",
112 line_number.to_string().cyan().bold(),
113 " ".repeat(number_of_spaces - line_number.to_string().len()),
114 "|".cyan().bold(),
115 line
116 );
117 eprintln!(
118 "{} {} {}{}",
119 " ".repeat(number_of_spaces),
120 "|".cyan().bold(),
121 " ".repeat(start_col - 1),
122 "^".repeat(end_col - start_col + 1).red().bold()
123 );
124 continue;
125 }
126
127 eprintln!(
128 "{}{} {} {}",
129 line_number.to_string().cyan().bold(),
130 " ".repeat(number_of_spaces - line_number.to_string().len()),
131 "|".cyan().bold(),
132 line
133 );
134
135 if line_number == start_line {
136 eprintln!(
137 "{} {} {}{}",
138 " ".repeat(number_of_spaces),
139 "|".cyan().bold(),
140 " ".repeat(start_col - 1),
141 "^".repeat(line.len() - start_col + 1).red().bold()
142 );
143 } else if line_number == end_line {
144 eprintln!(
145 "{} {} {}",
146 " ".repeat(number_of_spaces),
147 "|".cyan().bold(),
148 "^".repeat(end_col + 1).red().bold()
149 );
150 } else {
151 eprintln!(
152 "{} {} {}",
153 " ".repeat(number_of_spaces),
154 "|".cyan().bold(),
155 "^".repeat(line.len()).red().bold()
156 );
157 }
158 }
159
160 if !self.context.is_empty() || !self.notes.is_empty() {
161 eprintln!("{} {}", " ".repeat(number_of_spaces), "|".cyan().bold());
162 }
163
164 for context in self.context.iter() {
165 eprintln!(
166 "{} {} {}",
167 " ".repeat(number_of_spaces),
168 "=".cyan().bold(),
169 context
170 );
171 }
172
173 for note in self.notes.iter() {
174 eprintln!(
175 "{} {} {} {}",
176 " ".repeat(number_of_spaces),
177 "=".cyan().bold(),
178 "note:".bold(),
179 note
180 );
181 }
182 }
183}
184
185pub fn basic_error(message: String) {
195 eprintln!("{}: {}", "error".red().bold(), message.bold());
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
203 use crate::span::Span;
204
205 #[test]
206 fn test_error_display_single_line() {
207 let source = Source::from_str(
208 "test.rs",
209 "fn main() {\n println!(\"Hello, world!\");\n}",
210 );
211 let span = Span { start: 12, end: 25 };
212 let error = Error::new("Syntax error".to_string(), &source, span)
213 .with_context("In function main".to_string())
214 .with_note("Check the syntax".to_string());
215 error.display();
216 }
217
218 #[test]
219 fn test_error_display_multi_line() {
220 let source = Source::from_str(
221 "test.rs",
222 "fn main() {\n println!(\"Hello, world!\");\n}",
223 );
224 let span = Span { start: 12, end: 40 };
225 let error = Error::new("Syntax error".to_string(), &source, span)
226 .with_context("In function main".to_string())
227 .with_note("Check the syntax".to_string());
228 error.display();
229 }
230
231 #[test]
232 fn test_basic_error() {
233 let message = "An error occurred".to_string();
234 basic_error(message);
235 }
236}