1use std::iter;
6
7use crate::{
8 reporter::Annotation as ReportedAnnotation,
9 span::{Position, Span},
10};
11
12#[derive(Clone, Debug, PartialEq)]
38pub struct AnnotatedError {
39 pub(crate) span: Span,
40 pub(crate) msg: String,
41 annotations: Vec<Annotation>,
42}
43
44impl AnnotatedError {
45 pub fn new<Msg>(span: Span, msg: Msg) -> AnnotatedError
51 where
52 Msg: ToString,
53 {
54 let msg = msg.to_string();
55 AnnotatedError {
56 annotations: Vec::new(),
57 span,
58 msg,
59 }
60 }
61
62 pub fn with_annotation<Msg>(mut self, span: Span, msg: Msg) -> AnnotatedError
64 where
65 Msg: ToString,
66 {
67 let content = msg.to_string();
68 let ann = Annotation { span, content };
69 self.annotations.push(ann);
70 self
71 }
72
73 pub fn span(&self) -> Span {
75 self.span
76 }
77
78 fn all_spans<'a>(&'a self) -> impl Iterator<Item = Span> + 'a {
79 self.annotations
80 .iter()
81 .map(|a| a.span)
82 .chain(iter::once(self.span))
83 }
84
85 pub(crate) fn bounds(&self) -> (Position, Position) {
86 let min = self.all_spans().map(Span::start).min().unwrap();
89 let max = self.all_spans().map(Span::end).max().unwrap();
90
91 (min, max)
92 }
93
94 pub(crate) fn error_matrix<'a>(&'a self) -> Vec<Vec<ReportedAnnotation<'a>>> {
95 let (start_pos, end_pos) = self.bounds();
96
97 let (first_line_number, last_line_number) =
98 (start_pos.line() as usize, end_pos.line() as usize);
99 let total_line_number = last_line_number - first_line_number + 1;
100
101 let mut matrix = (0..total_line_number)
102 .map(|_| Vec::new())
103 .collect::<Vec<_>>();
104
105 for annotation in self.annotations.iter() {
106 let line_idx = annotation.span.start().line() as usize - first_line_number;
107
108 assert_eq!(
109 annotation.span.start().line(),
110 annotation.span.end().line(),
111 "Multiline spans are not supported",
112 );
113
114 let col_number = annotation.span.start().col() as usize;
115 let length = annotation.span.end().col() as usize - col_number;
116 let text = annotation.content.as_str();
117
118 let ann = ReportedAnnotation {
119 col_number,
120 length,
121 text,
122 };
123 matrix[line_idx].push(ann);
124 }
125
126 matrix
127 .iter_mut()
128 .for_each(|anns| anns.sort_by_key(|a| a.col_number));
129
130 matrix
131 }
132}
133
134#[derive(Clone, Debug, PartialEq)]
135struct Annotation {
136 span: Span,
137 content: String,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 mod annotated_error {
145 use super::*;
146
147 use crate::span::SpannedStr;
148
149 #[test]
150 fn bounds_double() {
151 let input = SpannedStr::input_file("ab");
152 let (a, b) = input.split_at(1);
153
154 let report = AnnotatedError::new(a.span(), "Some generic message")
155 .with_annotation(a.span(), "ann1")
156 .with_annotation(b.span(), "ann2");
157
158 let (start, end) = report.bounds();
159
160 assert_eq!(start.offset(), 0);
161 assert_eq!(end.offset(), 2);
162
163 assert_eq!(start.col(), 0);
164 assert_eq!(end.col(), 2);
165
166 assert_eq!(start.line(), 0);
167 assert_eq!(end.line(), 0);
168 }
169
170 #[test]
171 fn bounds_single() {
172 let input = SpannedStr::input_file("ab");
173
174 let report = AnnotatedError::new(input.span(), "Some generic message");
175
176 let (start, end) = report.bounds();
177
178 assert_eq!(start.offset(), 0);
179 assert_eq!(end.offset(), 2);
180
181 assert_eq!(start.col(), 0);
182 assert_eq!(end.col(), 2);
183
184 assert_eq!(start.line(), 0);
185 assert_eq!(end.line(), 0);
186 }
187
188 #[test]
189 fn error_matrix_for() {
190 let input_file = SpannedStr::input_file("line 1\nline 2\nline3");
195
196 let l1 = input_file.split_at(6).0;
197 assert_eq!(l1.content(), "line 1");
198 let l2 = input_file.split_at(7).1.split_at(6).0;
199 assert_eq!(l2.content(), "line 2");
200 let num = input_file.split_at(12).1.split_at(1).0;
201 assert_eq!(num.content(), "2");
202
203 let report = AnnotatedError::new(l1.span(), "<insert general message here>")
204 .with_annotation(l1.span(), "first line")
205 .with_annotation(l2.span(), "second line")
206 .with_annotation(num.span(), "second line, but better");
207
208 let matrix = report.error_matrix();
209
210 assert_eq!(matrix.len(), 2);
211 assert_eq!(matrix[0].len(), 1);
212 assert_eq!(matrix[1].len(), 2);
213
214 assert!(matrix[1][0].col_number < matrix[1][1].col_number);
215 }
216 }
217}