1use std::sync::Arc;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Span {
17 pub start: usize,
19 pub end: usize,
21}
22
23impl Span {
24 pub fn clamped(start: usize, end: usize, len: usize) -> Self {
27 let start = start.min(len);
28 Self {
29 start,
30 end: end.clamp(start, len),
31 }
32 }
33
34 pub fn len(&self) -> usize {
36 self.end - self.start
37 }
38
39 pub fn is_empty(&self) -> bool {
41 self.start == self.end
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Severity {
48 Error,
50 Warning,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Diag {
57 pub code: &'static str,
59 pub severity: Severity,
61 pub message: String,
63 pub source_name: Option<String>,
65 pub source_text: Option<Arc<str>>,
67 pub span: Option<Span>,
69 pub help: Option<String>,
71}
72
73impl Diag {
74 pub fn error(code: &'static str, message: impl Into<String>) -> Self {
76 Self {
77 code,
78 severity: Severity::Error,
79 message: message.into(),
80 source_name: None,
81 source_text: None,
82 span: None,
83 help: None,
84 }
85 }
86
87 pub fn warning(code: &'static str, message: impl Into<String>) -> Self {
89 Self {
90 severity: Severity::Warning,
91 ..Self::error(code, message)
92 }
93 }
94
95 #[must_use]
97 pub fn with_source(mut self, name: impl Into<String>, text: Arc<str>) -> Self {
98 self.source_name = Some(name.into());
99 self.source_text = Some(text);
100 self
101 }
102
103 #[must_use]
105 pub fn with_span(mut self, span: Span) -> Self {
106 self.span = Some(span);
107 self
108 }
109
110 #[must_use]
112 pub fn with_help(mut self, help: impl Into<String>) -> Self {
113 self.help = Some(help.into());
114 self
115 }
116}
117
118#[derive(Debug, thiserror::Error)]
121pub enum FrontError {
122 #[error("{} diagnostic(s)", .0.len())]
124 Diagnostics(Vec<Diag>),
125 #[error(transparent)]
127 Core(#[from] crate::error::CoreError),
128}
129
130impl FrontError {
131 pub fn exit_code(&self) -> crate::error::ExitCode {
133 match self {
134 Self::Diagnostics(_) => crate::error::ExitCode::UserError,
135 Self::Core(err) => err.exit_code(),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn spans_clamp_into_the_source() {
146 let span = Span::clamped(5, 12, 8);
147 assert_eq!((span.start, span.end), (5, 8));
148 let span = Span::clamped(10, 12, 8);
149 assert!(span.is_empty());
150 }
151
152 #[test]
153 fn front_error_maps_to_user_error() {
154 let err = FrontError::Diagnostics(vec![Diag::error("proef::test::x", "boom")]);
155 assert_eq!(err.exit_code().code(), 2);
156 }
157}