heraclitus_compiler/compiling/failing/
logger.rs1use colored::{Colorize, Color};
4use pad::PadStr;
5use crate::compiling::failing::position_info::PositionInfo;
6use crate::compiling::failing::message::MessageType;
7use crate::prelude::Position;
8
9#[cfg(feature = "serde")]
10use serde::{Serialize, Deserialize};
11
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct Logger {
17 kind: MessageType,
18 trace: Vec<PositionInfo>
19}
20
21impl Logger {
22 pub fn new(kind: MessageType, trace: &[PositionInfo]) -> Self {
24 Logger {
25 kind,
26 trace: trace.to_vec()
27 }
28 }
29
30 fn kind_to_color(&self) -> Color {
31 match self.kind {
32 MessageType::Error => Color::Red,
33 MessageType::Warning => Color::Yellow,
34 MessageType::Info => Color::Blue
35 }
36 }
37
38 pub fn header(self, kind: MessageType) -> Self {
40 let name = match kind {
41 MessageType::Error => " ERROR ".to_string(),
42 MessageType::Warning => " WARN ".to_string(),
43 MessageType::Info => " INFO ".to_string()
44 };
45 let formatted = name
46 .black()
47 .bold()
48 .on_color(self.kind_to_color());
49 eprint!("{formatted} ");
50 self
51 }
52
53 pub fn text(self, text: Option<String>) -> Self {
55 if let Some(text) = text {
56 eprint!("{}", text.color(self.kind_to_color()));
57 }
58 self
59 }
60
61 pub fn line(self, text: Option<String>) -> Self {
63 if let Some(text) = text {
64 eprintln!("{}", text.color(self.kind_to_color()));
65 }
66 self
67 }
68
69 pub fn padded_line(self, text: Option<String>) -> Self {
71 if let Some(text) = text {
72 eprintln!("\n{}", text.color(self.kind_to_color()));
73 }
74 self
75 }
76
77 pub fn path(self) -> Self {
79 let get_row_col = |pos: &PositionInfo| match pos.position {
80 Position::Pos(row, col) => format!("{}:{}", row, col),
81 Position::EOF => " end of file".to_string()
82 };
83 let path = match self.trace.first() {
84 Some(pos) => {
85 [
86 format!("at {}:{}", pos.get_path(), get_row_col(pos)),
87 self.trace.iter()
88 .skip(1)
89 .map(|pos| format!("in {}:{}", pos.get_path(), get_row_col(pos)))
90 .collect::<Vec<String>>()
91 .join("\n")
92 ].join("\n")
93 },
94 None => {
95 "at [unknown]:0:0".to_string()
96 }
97 }.trim_end().to_string();
98 eprintln!("{}", path.color(self.kind_to_color()).dimmed());
99 self
100 }
101
102 fn get_row_col_len(&self) -> Option<(usize, usize, usize)> {
104 match self.trace.first() {
105 Some(pos) => match pos.position {
106 Position::Pos(row, col) => Some((row, col, pos.len)),
107 Position::EOF => None
108 },
109 None => None
110 }
111 }
112
113 fn get_max_pad_size(&self, length: usize) -> Option<usize> {
115 let (row, _, _) = self.get_row_col_len()?;
116 if row < length - 1 {
117 Some(format!("{}", row + 1).len())
118 }
119 else {
120 Some(format!("{}", row).len())
121 }
122 }
123
124 fn get_highlighted_part(&self, line: &str) -> Option<[String;3]> {
127 let (_row, col, len) = self.get_row_col_len()?;
128 let begin = col - 1;
129 let end = begin + len;
130 let mut results: [String; 3] = Default::default();
131 for (index, letter) in line.chars().enumerate() {
132 if index < begin {
133 results[0].push(letter);
134 }
135 else if index >= end {
136 results[2].push(letter);
137 }
138 else {
139 results[1].push(letter);
140 }
141 }
142 Some(results)
143 }
144
145 fn get_snippet_row(&self, code: &Vec<String>, index: usize, offset: i8, overflow: &mut usize) -> Option<String> {
147 let (row, col, len) = self.get_row_col_len()?;
148 let max_pad = self.get_max_pad_size(code.len())?;
149 let index = index as i32 + offset as i32;
150 let row = row as i32 + offset as i32;
151 let code = code.get(index as usize)?.clone();
152 let line = format!("{row}").pad_to_width(max_pad);
153 if offset == 0 {
155 let slices = self.get_highlighted_part(&code)?;
156 let formatted = format!("{}{}{}", slices[0], slices[1].color(self.kind_to_color()), slices[2]);
157 let end = col.checked_add(len).unwrap_or(len);
158 if end - 1 > code.chars().count() {
160 *overflow = (end - 2).checked_sub(code.chars().count()).unwrap_or(0);
163 }
164 Some(format!("{line}| {formatted}"))
165 }
166 else {
168 if *overflow > 0 {
170 if *overflow > code.chars().count() {
172 Some(format!("{line}| {}", code.color(self.kind_to_color())).dimmed().to_string())
173 }
174 else {
176 let err = code.get(0..*overflow).unwrap().to_string().color(self.kind_to_color());
177 let rest = code.get(*overflow..).unwrap().to_string();
178 Some(format!("{line}| {err}{rest}").dimmed().to_string())
179 }
180 }
181 else {
183 Some(format!("{line}| {code}").dimmed().to_string())
184 }
185 }
186 }
187
188 pub fn snippet<T: AsRef<str>>(self, code: Option<T>) -> Self {
190 if let Some(pos) = self.trace.first() {
191 if let Ok(code) = std::fs::read_to_string(pos.get_path()) {
192 self.snippet_from_code(code);
193 return self;
194 }
195 }
196 if let Some(code) = code {
197 self.snippet_from_code(code.as_ref().to_string());
198 }
199 self
200 }
201
202 fn snippet_from_code(&self, code: String) -> Option<()> {
204 let (row, _, _) = self.get_row_col_len()?;
205 let mut overflow = 0;
206 let index = row - 1;
207 let code = code.split('\n')
208 .map(|item| item.trim_end().to_string())
209 .collect::<Vec<String>>();
210 eprintln!();
211 if let Some(line) = self.get_snippet_row(&code, index, -1, &mut overflow) {
213 eprintln!("{}", line);
214 }
215 eprintln!("{}", self.get_snippet_row(&code, index, 0, &mut overflow)?);
217 eprintln!("{}", self.get_snippet_row(&code, index, 1, &mut overflow)?);
219 Some(())
220 }
221}
222
223#[cfg(test)]
224mod test {
225 #![allow(unused_imports)]
226 use std::time::Duration;
227 use std::thread::sleep;
228
229 use crate::prelude::{DefaultMetadata, Metadata, MessageType, PositionInfo, Token};
230 #[allow(unused_variables)]
231
232 #[test]
233 fn test_displayer() {
234 let code = vec![
235 "let a = 12",
236 "value = 'this",
237 "is mutltiline",
238 "code'"
239 ].join("\n");
240 sleep(Duration::from_secs(1));
242 let trace = [
243 PositionInfo::at_pos(Some("/path/to/bar".to_string()), (3, 4), 10),
244 PositionInfo::at_pos(Some("/path/to/foo".to_string()), (2, 9), 24),
245 ];
246 super::Logger::new(MessageType::Error, &trace)
247 .header(MessageType::Error)
248 .line(Some(format!("Cannot call function \"foobar\" on a number")))
249 .path()
250 .snippet(Some(code));
251 }
252
253 #[test]
254 fn test_end_of_line_displayer() {
255 let code = vec![
256 "hello"
257 ].join("\n");
258 sleep(Duration::from_secs(1));
260 let trace = [
261 PositionInfo::at_pos(Some("/path/to/foo".to_string()), (2, 6), 1)
262 ];
263 super::Logger::new(MessageType::Error, &trace)
264 .header(MessageType::Error)
265 .line(Some(format!("Cannot call function \"foobar\" on a number")))
266 .path()
267 .snippet(Some(code));
268 }
269
270 #[test]
271 fn test_between_tokens() {
272 let code = vec![
273 "foo(12 + 24)"
274 ].join("\n");
275 sleep(Duration::from_secs(1));
277 let begin = Token { word: "12".to_string(), pos: (1, 5), start: 4 };
278 let end = Token { word: ")".to_string(), pos: (1, 12), start: 11 };
279 let mut meta = DefaultMetadata::new(vec![], Some("/path/to/foo".to_string()), Some(code.clone()));
280 let trace = [
281 PositionInfo::from_between_tokens(&mut meta, Some(begin), Some(end))
282 ];
283 super::Logger::new(MessageType::Error, &trace)
284 .header(MessageType::Error)
285 .line(Some(format!("Cannot call function \"foobar\" on a number")))
286 .path()
287 .snippet(Some(code));
288 }
289}