Testing stuff for an upcoming project
1use codespan_reporting::{
2 diagnostic::{Label, LabelStyle, Severity},
3 files::SimpleFiles,
4};
5use ecow::{EcoString, eco_format};
6use termcolor::Buffer;
7
8use crate::{lexer::Span, parser::Error as ParseError};
9
10pub enum Error {
11 Parse {
12 src: EcoString,
13 path: EcoString,
14 error: ParseError,
15 },
16}
17
18macro_rules! wrap_format {
19 ($($tts:tt)*) => {
20 wrap(&format!($($tts)+))
21 };
22}
23
24impl Error {
25 pub fn write_to(&self, buffer: &mut Buffer) {
26 self.to_diagnostic().write(buffer);
27 }
28
29 pub fn to_diagnostic(&self) -> Diagnostic {
30 match self {
31 Error::Parse { src, path, error } => match error {
32 ParseError::LexError(errors) => {
33 let (title, text, location) = errors[0].to_diagnostic_info();
34 Diagnostic {
35 level: Level::Error,
36 title,
37 text,
38 label: None,
39 location,
40 src: src.clone(),
41 path: path.clone(),
42 }
43 }
44 ParseError::UnexpectedEndOfFile { expected } => Diagnostic {
45 level: Level::Error,
46 title: "Unexpected end of file".into(),
47 text: wrap_format!("Unexpected end of file, {}", one_of(expected)),
48 label: None,
49 location: Span::new(src.len() - 1, src.len()),
50 src: src.clone(),
51 path: path.clone(),
52 },
53 ParseError::UnexpectedToken { token, expected } => Diagnostic {
54 level: Level::Error,
55 title: "Unexpected token".into(),
56 text: wrap_format!("Unexpected token, {}", one_of(expected)),
57 label: None,
58 location: token.location,
59 src: src.clone(),
60 path: path.clone(),
61 },
62 ParseError::InvalidEscapeSequence { location } => Diagnostic {
63 level: Level::Error,
64 title: "Invalid escape sequence".into(),
65 text: "".into(),
66 label: None,
67 location: *location,
68 src: src.clone(),
69 path: path.clone(),
70 },
71 },
72 }
73 }
74}
75
76fn one_of(expected: &[&str]) -> EcoString {
77 if expected.len() == 1 {
78 eco_format!("expected {}", expected[0])
79 } else {
80 eco_format!("expected one of:\n {}", expected.join(", "))
81 }
82}
83
84pub fn wrap(text: &str) -> String {
85 let mut result = String::with_capacity(text.len());
86
87 for (i, line) in wrap_text(text, 75).iter().enumerate() {
88 if i > 0 {
89 result.push('\n');
90 }
91 result.push_str(line);
92 }
93
94 result
95}
96
97fn wrap_text(text: &str, width: usize) -> Vec<EcoString> {
98 let mut lines = Vec::new();
99 for line in text.split('\n') {
100 // check if line needs to be broken
101 match line.len() > width {
102 false => lines.push(line.into()),
103 true => {
104 let mut new_lines = break_line(line, width);
105 lines.append(&mut new_lines);
106 }
107 };
108 }
109
110 lines
111}
112
113fn break_line(line: &str, width: usize) -> Vec<EcoString> {
114 let mut lines = Vec::new();
115 let mut newline = String::from("");
116
117 // split line by spaces
118 for (i, word) in line.split(' ').enumerate() {
119 let is_new_line = i < 1 || newline.is_empty();
120
121 let can_add_word = match is_new_line {
122 true => newline.len() + word.len() <= width,
123 // +1 accounts for space added before word
124 false => newline.len() + (word.len() + 1) <= width,
125 };
126
127 if can_add_word {
128 if !is_new_line {
129 newline.push(' ');
130 }
131 newline.push_str(word);
132 } else {
133 // word too big, save existing line if present
134 if !newline.is_empty() {
135 // save current line and reset it
136 lines.push(newline.as_str().into());
137 newline.clear();
138 }
139
140 // then save word to a new line or break it
141 match word.len() > width {
142 false => newline.push_str(word),
143 true => {
144 let (mut newlines, remainder) = break_word(word, width);
145 lines.append(&mut newlines);
146 newline.push_str(remainder);
147 }
148 }
149 }
150 }
151
152 // save last line after loop finishes
153 if !newline.is_empty() {
154 lines.push(newline.into());
155 }
156
157 lines
158}
159
160// breaks word into n lines based on width. Returns list of new lines and remainder
161fn break_word(word: &str, width: usize) -> (Vec<EcoString>, &str) {
162 let mut new_lines = Vec::new();
163 let (first, mut remainder) = word.split_at(width);
164 new_lines.push(first.into());
165
166 // split remainder until it's small enough
167 while remainder.len() > width {
168 let (first, second) = remainder.split_at(width);
169 new_lines.push(first.into());
170 remainder = second;
171 }
172
173 (new_lines, remainder)
174}
175
176pub enum Level {
177 Error,
178 Warning,
179}
180
181pub struct Diagnostic {
182 level: Level,
183 title: String,
184 text: String,
185 label: Option<String>,
186 location: Span,
187 src: EcoString,
188 path: EcoString,
189}
190
191impl Diagnostic {
192 pub fn write(&self, buffer: &mut Buffer) {
193 let mut files = SimpleFiles::new();
194 let main_file_id = files.add(&self.path, &self.src);
195
196 let mut label = Label::new(
197 LabelStyle::Primary,
198 main_file_id,
199 (self.location.start)..(self.location.end),
200 );
201 match &self.label {
202 None => {}
203 Some(text) => label = label.with_message(text.clone()),
204 }
205
206 let severity = match self.level {
207 Level::Error => Severity::Error,
208 Level::Warning => Severity::Warning,
209 };
210
211 let diagnostic = codespan_reporting::diagnostic::Diagnostic::new(severity)
212 .with_message(&self.title)
213 .with_labels(vec![label]);
214 let config = codespan_reporting::term::Config::default();
215 codespan_reporting::term::emit(buffer, &config, &files, &diagnostic)
216 .expect("write_diagnostic");
217
218 if !self.text.is_empty() {
219 use std::io::Write;
220 writeln!(buffer, "{}", self.text).expect("write text");
221 }
222 }
223}