1use std::error::Error;
3use std::fmt::Debug;
4use std::iter::{self, FromIterator};
5use std::ops::Range;
6use std::path::Path;
7use std::sync::RwLock;
8
9use codespan_reporting::diagnostic::{Diagnostic as RawDiagnostic, Label, Severity};
10use codespan_reporting::files::SimpleFile;
11use codespan_reporting::term;
12use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
13use codespan_reporting::term::Config;
14use serde::{Deserialize, Serialize};
15
16#[derive(
18 Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default, PartialOrd, Ord,
19)]
20pub enum Span {
21 Direct {
23 start: usize,
25 end: usize,
27 },
28 Indirect {
30 start: usize,
32 end: usize,
34 },
35 #[default]
37 Unknown,
38}
39impl<'a> From<pest::Span<'a>> for Span {
40 fn from(span: pest::Span<'a>) -> Self {
41 Span::Direct {
42 start: span.start(),
43 end: span.end(),
44 }
45 }
46}
47
48impl From<Span> for Range<usize> {
49 fn from(s: Span) -> Range<usize> {
50 let (s, e) = s.get_bounds();
51 Range { start: s, end: e }
52 }
53}
54
55impl Span {
56 pub fn is_indirect(&self) -> bool {
58 match self {
59 Span::Direct { .. } => false,
60 Span::Indirect { .. } => true,
61 Span::Unknown => false,
62 }
63 }
64
65 pub fn is_unknown(&self) -> bool {
67 match self {
68 Span::Direct { .. } => false,
69 Span::Indirect { .. } => false,
70 Span::Unknown => true,
71 }
72 }
73
74 pub fn get_bounds(&self) -> (usize, usize) {
77 match self {
78 Span::Indirect { start, end } | Span::Direct { start, end } => (*start, *end),
79 Span::Unknown => (usize::MIN, usize::MAX),
80 }
81 }
82
83 pub fn union(&self, other: &Self) -> Self {
85 if self.is_unknown() {
86 return *other;
87 }
88 if other.is_unknown() {
89 return *self;
90 }
91 let (start1, end1) = self.get_bounds();
92 let (start2, end2) = other.get_bounds();
93 if self.is_indirect() || other.is_indirect() {
94 Span::Indirect {
95 start: start1.min(start2),
96 end: end1.max(end2),
97 }
98 } else {
99 Span::Direct {
100 start: start1.min(start2),
101 end: end1.max(end2),
102 }
103 }
104 }
105
106 pub fn to_indirect(self) -> Self {
108 match self {
109 Span::Direct { start, end } => Span::Indirect { start, end },
110 Span::Indirect { .. } => self,
111 Span::Unknown => self,
112 }
113 }
114}
115
116pub struct Handler<'a> {
118 error_count: RwLock<usize>,
120 warning_count: RwLock<usize>,
122 input: SimpleFile<&'a str, &'a str>,
124 output: RwLock<Box<dyn WriteColor>>,
126 config: Config,
128}
129impl Debug for Handler<'_> {
130 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
131 f.debug_struct("Handler")
132 .field("error_count", &self.error_count)
133 .field("warning_count", &self.warning_count)
134 .field("input", &self.input)
135 .field("config", &self.config)
136 .finish()
137 }
138}
139
140impl<'a> Handler<'a> {
141 pub fn new(input_path: &'a Path, input_content: &'a str) -> Self {
145 Handler {
146 error_count: RwLock::new(0),
147 warning_count: RwLock::new(0),
148 input: SimpleFile::new(input_path.to_str().unwrap_or("unknown file"), input_content),
149 output: RwLock::new(Box::new(StandardStream::stderr(ColorChoice::Always))),
150 config: Config::default(),
151 }
152 }
153
154 pub fn without_file(input_content: &'a str) -> Self {
156 Handler {
157 error_count: RwLock::new(0),
158 warning_count: RwLock::new(0),
159 input: SimpleFile::new("unknown file", input_content),
160 output: RwLock::new(Box::new(StandardStream::stderr(ColorChoice::Always))),
161 config: Config::default(),
162 }
163 }
164
165 pub fn emit(&self, diag: &Diagnostic) {
167 let mut diag = diag.clone();
168 if diag.has_indirect_span {
169 diag.inner
170 .notes
171 .push("Warning was caused indirectly by transformations.".into());
172 }
173 self.emit_raw(diag.inner);
174 }
175
176 pub fn emit_error(&self, err: &RtLolaError) {
178 err.iter().for_each(|diag| self.emit(diag));
179 }
180
181 fn emit_raw(&self, diag: RawDiagnostic<()>) {
182 match diag.severity {
183 Severity::Error => *self.error_count.write().unwrap() += 1,
184 Severity::Warning => *self.warning_count.write().unwrap() += 1,
185 _ => {}
186 }
187 term::emit(
188 (*self.output.write().unwrap()).as_mut(),
189 &self.config,
190 &self.input,
191 &diag,
192 )
193 .expect("Could not write diagnostic.");
194 }
195
196 pub fn contains_error(&self) -> bool {
198 self.emitted_errors() > 0
199 }
200
201 pub fn emitted_errors(&self) -> usize {
203 *self.error_count.read().unwrap()
204 }
205
206 pub fn emitted_warnings(&self) -> usize {
208 *self.warning_count.read().unwrap()
209 }
210
211 pub fn warn(&self, message: &str) {
213 self.emit_raw(RawDiagnostic::warning().with_message(message))
214 }
215
216 pub fn warn_with_span(&self, message: &str, span: Span, span_label: Option<&str>) {
219 let mut diag = RawDiagnostic::warning().with_message(message);
220 if !span.is_unknown() {
221 let mut label = Label::primary((), span);
222 if let Some(l) = span_label {
223 label.message = l.into();
224 }
225 diag.labels = vec![label];
226 }
227 if span.is_indirect() {
228 diag.notes = vec!["Warning was caused indirectly by transformations.".into()];
229 }
230 self.emit_raw(diag)
231 }
232
233 pub fn error(&self, message: &str) {
235 self.emit_raw(RawDiagnostic::error().with_message(message))
236 }
237
238 pub fn error_with_span(&self, message: &str, span: Span, span_label: Option<&str>) {
241 let mut diag = RawDiagnostic::error().with_message(message);
242 if !span.is_unknown() {
243 let mut label = Label::primary((), span);
244 if let Some(l) = span_label {
245 label.message = l.into();
246 }
247 diag.labels = vec![label];
248 }
249 if span.is_indirect() {
250 diag.notes = vec!["Error was caused indirectly by transformations.".into()];
251 }
252 self.emit_raw(diag)
253 }
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct Diagnostic {
259 pub(crate) inner: RawDiagnostic<()>,
261 pub(crate) has_indirect_span: bool,
263}
264
265impl Diagnostic {
266 pub fn warning(message: &str) -> Self {
268 Diagnostic {
269 inner: RawDiagnostic::warning().with_message(message),
270 has_indirect_span: false,
271 }
272 }
273
274 pub fn error(message: &str) -> Self {
276 Diagnostic {
277 inner: RawDiagnostic::error().with_message(message),
278 has_indirect_span: false,
279 }
280 }
281
282 pub fn add_span_with_label(mut self, span: Span, label: Option<&str>, primary: bool) -> Self {
286 if span.is_unknown() {
287 return self;
288 }
289 self.has_indirect_span |= span.is_indirect();
290 let mut rep_label = if primary {
291 Label::primary((), span)
292 } else {
293 Label::secondary((), span)
294 };
295 if let Some(l) = label {
296 rep_label.message = l.into();
297 }
298 self.inner.labels.push(rep_label);
299 self
300 }
301
302 pub fn maybe_add_span_with_label(
306 mut self,
307 span: Option<Span>,
308 label: Option<&str>,
309 primary: bool,
310 ) -> Self {
311 let span = match span {
312 None | Some(Span::Unknown) => return self,
313 Some(s) => s,
314 };
315 self.has_indirect_span |= span.is_indirect();
316 let mut rep_label = if primary {
317 Label::primary((), span)
318 } else {
319 Label::secondary((), span)
320 };
321 if let Some(l) = label {
322 rep_label.message = l.into();
323 }
324 self.inner.labels.push(rep_label);
325 self
326 }
327
328 pub fn add_note(mut self, note: &str) -> Self {
330 self.inner.notes.push(note.into());
331 self
332 }
333}
334
335impl From<Diagnostic> for RawDiagnostic<()> {
336 fn from(diag: Diagnostic) -> Self {
337 diag.inner
338 }
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
342pub struct RtLolaError {
344 errors: Vec<Diagnostic>,
345}
346
347impl Error for RtLolaError {}
348
349impl std::fmt::Display for RtLolaError {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 writeln!(
352 f,
353 "RTLola Error: {} errors, {} warnings:",
354 self.num_errors(),
355 self.num_warnings()
356 )?;
357 for msg in self.iter() {
358 let severity = match msg.inner.severity {
359 Severity::Warning => "[WARNING]",
360 Severity::Error => "[ERROR]",
361 _ => unreachable!(),
362 };
363 writeln!(f, "- {severity} {}", msg.inner.message)?;
364 }
365 Ok(())
366 }
367}
368
369impl RtLolaError {
370 pub fn new() -> Self {
372 RtLolaError { errors: vec![] }
373 }
374
375 pub fn add(&mut self, diag: Diagnostic) {
377 self.errors.push(diag)
378 }
379
380 pub fn as_slice(&self) -> &[Diagnostic] {
382 self.errors.as_slice()
383 }
384
385 pub fn num_errors(&self) -> usize {
387 self.errors
388 .iter()
389 .filter(|e| matches!(e.inner.severity, Severity::Error))
390 .count()
391 }
392
393 pub fn num_warnings(&self) -> usize {
395 self.errors
396 .iter()
397 .filter(|e| matches!(e.inner.severity, Severity::Warning))
398 .count()
399 }
400
401 pub fn join(&mut self, mut other: RtLolaError) {
403 self.errors.append(&mut other.errors)
404 }
405
406 pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
408 self.errors.iter()
409 }
410
411 pub fn combine<L, R, U, F: FnOnce(L, R) -> U>(
416 left: Result<L, RtLolaError>,
417 right: Result<R, RtLolaError>,
418 op: F,
419 ) -> Result<U, RtLolaError> {
420 match (left, right) {
421 (Ok(l), Ok(r)) => Ok(op(l, r)),
422 (Ok(_), Err(e)) | (Err(e), Ok(_)) => Err(e),
423 (Err(mut l), Err(r)) => {
424 l.join(r);
425 Err(l)
426 }
427 }
428 }
429}
430
431impl Default for RtLolaError {
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437impl IntoIterator for RtLolaError {
438 type IntoIter = std::vec::IntoIter<Self::Item>;
439 type Item = Diagnostic;
440
441 fn into_iter(self) -> Self::IntoIter {
442 self.errors.into_iter()
443 }
444}
445
446impl FromIterator<Diagnostic> for RtLolaError {
447 fn from_iter<T: IntoIterator<Item = Diagnostic>>(iter: T) -> Self {
448 RtLolaError {
449 errors: iter.into_iter().collect(),
450 }
451 }
452}
453
454impl From<Diagnostic> for RtLolaError {
455 fn from(diag: Diagnostic) -> Self {
456 RtLolaError { errors: vec![diag] }
457 }
458}
459
460impl From<Result<(), RtLolaError>> for RtLolaError {
461 fn from(res: Result<(), RtLolaError>) -> Self {
462 match res {
463 Ok(()) => RtLolaError::new(),
464 Err(e) => e,
465 }
466 }
467}
468
469impl RtLolaError {
470 #[allow(clippy::manual_try_fold)]
473 pub fn collect<T, Q: FromIterator<T> + Extend<T>>(
474 iter: impl IntoIterator<Item = Result<T, Self>>,
475 ) -> Result<Q, RtLolaError> {
476 iter.into_iter()
477 .fold(Ok(Q::from_iter(iter::empty())), |e, item| match (e, item) {
478 (Ok(mut e), Ok(item)) => {
479 e.extend(iter::once(item));
480 Ok(e)
481 }
482 (Err(e), Ok(_)) | (Ok(_), Err(e)) => Err(e),
483 (Err(mut e1), Err(e2)) => {
484 e1.join(e2);
485 Err(e1)
486 }
487 })
488 }
489}
490
491impl From<RtLolaError> for Result<(), RtLolaError> {
492 fn from(e: RtLolaError) -> Self {
493 if e.errors
494 .iter()
495 .filter(|e| matches!(e.inner.severity, Severity::Error))
496 .count()
497 == 0
498 {
499 Ok(())
500 } else {
501 Err(e)
502 }
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use std::path::PathBuf;
509
510 use super::*;
511
512 #[test]
513 fn error_span() {
514 let path = PathBuf::from("stdin");
515 let content = "input i: Int\noutput x = 5";
516 let handler = Handler::new(&path, &content);
517 let span = Span::Direct { start: 9, end: 12 };
518 handler.error_with_span("Unknown Type", span, Some("here".into()));
519 assert_eq!(handler.emitted_errors(), 1);
520 }
521
522 #[test]
523 fn warning_span() {
524 let path = PathBuf::from("stdin");
525 let content = "input i: Int\noutput x = 5";
526 let handler = Handler::new(&path, &content);
527 let span = Span::Direct { start: 9, end: 12 };
528 handler.warn_with_span("Unknown Type", span, Some("here".into()));
529 assert_eq!(handler.emitted_warnings(), 1);
530 }
531
532 #[test]
533 fn error() {
534 let path = PathBuf::from("stdin");
535 let content = "input i: Int\noutput x = 5";
536 let handler = Handler::new(&path, &content);
537 handler.error("Unknown Type");
538 assert_eq!(handler.emitted_errors(), 1);
539 }
540
541 #[test]
542 fn warning() {
543 let path = PathBuf::from("stdin");
544 let content = "input i: Int\noutput x = 5";
545 let handler = Handler::new(&path, &content);
546 handler.warn("Unknown Type");
547 assert_eq!(handler.emitted_warnings(), 1);
548 }
549
550 #[test]
551 fn error_span_no_label() {
552 let path = PathBuf::from("stdin");
553 let content = "input i: Int\noutput x = 5";
554 let handler = Handler::new(&path, &content);
555 let span = Span::Direct { start: 9, end: 12 };
556 handler.error_with_span("Unknown Type", span, None);
557 assert_eq!(handler.emitted_errors(), 1);
558 }
559
560 #[test]
561 fn custom() {
562 let path = PathBuf::from("stdin");
563 let content = "input i: Int\noutput x = 5";
564 let handler = Handler::new(&path, &content);
565 let span1 = Span::Direct { start: 9, end: 12 };
566 let span2 = Span::Indirect { start: 20, end: 21 };
567 let span3 = Span::Direct { start: 24, end: 25 };
568 handler.emit(
569 &Diagnostic::error("Failed with love")
570 .add_span_with_label(span1, Some("here"), true)
571 .add_span_with_label(span2, Some("and here"), false)
572 .maybe_add_span_with_label(None, Some("Maybe there is no span"), false)
573 .maybe_add_span_with_label(Some(span3), None, false)
574 .add_note("This is a note"),
575 );
576 assert_eq!(handler.emitted_errors(), 1);
577 }
578}