svugy_parse/
diagnostic.rs1use std::error::Error;
4use std::fmt;
5use std::marker::PhantomData;
6use std::ops::Range;
7
8use miette::{Diagnostic, LabeledSpan, Severity, SourceSpan};
9use svugy_model::model::Model;
10use svugy_model::problem::Suggestion;
11use svugy_model::validate::{ModelAdmission, ModelReport};
12
13pub trait SourceLanguage: fmt::Debug + Clone + PartialEq + Eq + Sized {
15 const ERROR_CODE: &'static str;
17 type Warning: Diagnostic + Clone + PartialEq;
19
20 fn import(source_text: &str) -> Import<Self>;
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct SourceError<L> {
27 pub message: String,
28 pub span: SourceSpan,
29 language: PhantomData<L>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum NoWarning {}
35
36#[derive(Debug)]
38pub(crate) struct Diagnostics<L: SourceLanguage> {
39 pub(crate) errors: Vec<SourceError<L>>,
40 pub(crate) warnings: Vec<L::Warning>,
41}
42
43#[derive(Debug)]
45pub struct Import<L: SourceLanguage> {
46 pub admission: ModelAdmission,
47 pub errors: Vec<SourceError<L>>,
48 pub warnings: Vec<L::Warning>,
49}
50
51#[derive(Debug)]
53pub struct ImportParts<L: SourceLanguage> {
54 pub model: Option<Model>,
55 pub errors: Vec<SourceError<L>>,
56 pub warnings: Vec<L::Warning>,
57 pub report: ModelReport,
58}
59
60pub trait LabelledDiagnostic {
62 fn first_label_offset(&self) -> Option<usize>;
64}
65
66impl<L> SourceError<L> {
67 pub(crate) fn new(message: String, span: Range<usize>) -> Self {
69 let span = SourceSpan::from(span);
70 Self {
71 message,
72 span,
73 language: PhantomData,
74 }
75 }
76}
77
78impl<L> fmt::Display for SourceError<L> {
79 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80 formatter.write_str(&self.message)
81 }
82}
83
84impl<L: fmt::Debug> Error for SourceError<L> {}
85
86impl<L: SourceLanguage> Diagnostic for SourceError<L> {
87 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
88 let code = Box::new(L::ERROR_CODE);
89 Some(code)
90 }
91
92 fn severity(&self) -> Option<Severity> {
93 Some(Severity::Error)
94 }
95
96 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
97 let label_text = Some("here".to_owned());
98 let label = LabeledSpan::new_with_span(label_text, self.span);
99 let labels = Box::new(std::iter::once(label));
100 Some(labels)
101 }
102}
103
104impl fmt::Display for NoWarning {
105 fn fmt(&self, _formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match *self {}
107 }
108}
109
110impl Error for NoWarning {}
111
112impl Diagnostic for NoWarning {}
113
114impl<L: SourceLanguage> Default for Diagnostics<L> {
115 fn default() -> Self {
116 Self::from_parts(Vec::new(), Vec::new())
117 }
118}
119
120impl<L: SourceLanguage> Diagnostics<L> {
121 pub(crate) fn from_parts(errors: Vec<SourceError<L>>, warnings: Vec<L::Warning>) -> Self {
123 Self { errors, warnings }
124 }
125
126 pub(crate) fn record_error(
128 &mut self,
129 message: impl Into<String>,
130 span: impl Into<Range<usize>>,
131 ) {
132 let message = message.into();
133 let byte_range = span.into();
134 let error = SourceError::new(message, byte_range);
135 self.errors.push(error);
136 }
137
138 pub(crate) fn record_unknown<T: fmt::Display>(
140 &mut self,
141 prefix: &str,
142 unknown: &str,
143 candidates: impl IntoIterator<Item = T>,
144 span: impl Into<Range<usize>>,
145 ) {
146 let suggestion = Suggestion::closest_to(unknown, candidates);
147 let message = match suggestion {
148 Some(suggestion) => format!("{prefix} `{unknown}`; {suggestion}"),
149 None => format!("{prefix} `{unknown}`"),
150 };
151 self.record_error(message, span);
152 }
153
154 pub(crate) fn into_sorted_parts(self) -> (Vec<SourceError<L>>, Vec<L::Warning>) {
156 let Self {
157 mut errors,
158 mut warnings,
159 } = self;
160 errors.sort_by_key(|error| error.span.offset());
161 warnings.sort_by_key(|warning| {
162 let first_label_offset = warning.first_label_offset();
163 first_label_offset.unwrap_or(usize::MAX)
164 });
165 (errors, warnings)
166 }
167}
168
169impl<L: SourceLanguage> Import<L> {
170 pub fn diagnostics(&self) -> Vec<&dyn Diagnostic> {
172 let errors = self.errors.iter().map(|error| error as &dyn Diagnostic);
173 let warnings = self
174 .warnings
175 .iter()
176 .map(|warning| warning as &dyn Diagnostic);
177 let problems = self
178 .admission
179 .report
180 .problems()
181 .iter()
182 .map(|problem| problem as &dyn Diagnostic);
183 let mut diagnostics: Vec<&dyn Diagnostic> =
184 errors.chain(warnings).chain(problems).collect();
185 diagnostics.sort_by_key(|diagnostic| {
186 let first_label_offset = diagnostic.first_label_offset();
187 first_label_offset.unwrap_or(usize::MAX)
188 });
189 diagnostics
190 }
191
192 pub fn admitted_model(&self) -> Option<&Model> {
194 let has_errors = self.has_errors();
195 if has_errors {
196 return None;
197 }
198 self.admission.model.as_ref()
199 }
200
201 pub fn into_admitted_parts(self) -> ImportParts<L> {
203 let has_errors = self.has_errors();
204 let Self {
205 admission,
206 errors,
207 warnings,
208 } = self;
209 let ModelAdmission { model, report } = admission;
210 let admitted_model = if has_errors { None } else { model };
211 ImportParts {
212 model: admitted_model,
213 errors,
214 warnings,
215 report,
216 }
217 }
218
219 fn has_errors(&self) -> bool {
221 let has_language_errors = !self.errors.is_empty();
222 let has_model_errors = self.admission.report.has_errors();
223 has_language_errors || has_model_errors
224 }
225}
226
227impl<D: Diagnostic + ?Sized> LabelledDiagnostic for D {
228 fn first_label_offset(&self) -> Option<usize> {
229 let mut labels = self.labels().into_iter().flatten();
230 let first_label = labels.next();
231 first_label.map(|label| label.offset())
232 }
233}