Testing stuff for an upcoming project
at main 223 lines 6.6 kB view raw
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}