1use std::{cell::Cell, fmt, rc::Rc};
5use tilewright_jsonc::ErrorCode;
6
7pub(crate) trait CodeName: Copy {
9 fn name(self) -> &'static str;
10}
11
12impl CodeName for Code {
13 fn name(self) -> &'static str {
14 self.as_str()
15 }
16}
17
18impl CodeName for ErrorCode {
19 fn name(self) -> &'static str {
20 self.as_str()
21 }
22}
23
24#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
26pub enum Code {
27 Io,
29 Encoding,
31 Budget,
33 Symlink,
35 UnexpectedFile,
37 MissingFile,
39 Path,
41 NameMismatch,
43 Format,
45 UnknownKey,
47 MissingKey,
49 Type,
51 Range,
53 Identifier,
55 Duplicate,
57 Reference,
59 Cycle,
61 Grid,
63 StairwellStep,
67 StairwellOverlap,
69}
70
71impl Code {
72 #[must_use]
73 pub const fn as_str(self) -> &'static str {
74 match self {
75 Self::Io => "source.io",
76 Self::Encoding => "source.encoding",
77 Self::Budget => "source.budget",
78 Self::Symlink => "source.symlink",
79 Self::UnexpectedFile => "source.unexpected_file",
80 Self::MissingFile => "source.missing_file",
81 Self::Path => "source.path",
82 Self::NameMismatch => "source.name_mismatch",
83 Self::Format => "shape.format",
84 Self::UnknownKey => "shape.unknown_key",
85 Self::MissingKey => "shape.missing_key",
86 Self::Type => "shape.type",
87 Self::Range => "shape.range",
88 Self::Identifier => "shape.identifier",
89 Self::Duplicate => "ref.duplicate",
90 Self::Reference => "ref.unresolved",
91 Self::Cycle => "ref.cycle",
92 Self::Grid => "grid.invalid",
93 Self::StairwellStep => "stairwell.step",
94 Self::StairwellOverlap => "stairwell.overlap",
95 }
96 }
97}
98
99#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub struct Diagnostic {
106 pub path: String,
107 pub start: usize,
108 pub end: usize,
109 pub line: usize,
110 pub column: usize,
111 pub code: &'static str,
112 pub message: String,
113}
114
115impl Diagnostic {
116 pub(crate) fn file(path: &str, code: impl CodeName, message: impl Into<String>) -> Self {
118 Self {
119 path: path.to_owned(),
120 start: 0,
121 end: 0,
122 line: 0,
123 column: 0,
124 code: code.name(),
125 message: message.into(),
126 }
127 }
128
129 pub(crate) fn at(
131 path: &str,
132 positions: &Positions<'_>,
133 start: usize,
134 end: usize,
135 code: impl CodeName,
136 message: impl Into<String>,
137 ) -> Self {
138 let (line, column) = positions.locate(start);
139 Self {
140 path: path.to_owned(),
141 start,
142 end,
143 line,
144 column,
145 code: code.name(),
146 message: message.into(),
147 }
148 }
149}
150
151const STRIDE: usize = 256;
153
154const fn is_continuation(byte: u8) -> bool {
156 byte & 0xC0 == 0x80
157}
158
159pub(crate) struct Positions<'s> {
163 source: &'s str,
164 lines: Vec<usize>,
166 characters: Vec<usize>,
169}
170
171impl<'s> Positions<'s> {
172 pub(crate) fn new(source: &'s str) -> Self {
173 let mut lines = vec![0];
174 let mut characters = Vec::with_capacity(source.len() / STRIDE + 1);
175 let mut count = 0;
176 for (index, byte) in source.bytes().enumerate() {
177 if index.is_multiple_of(STRIDE) {
178 characters.push(count);
179 }
180 if byte == b'\n' {
181 lines.push(index + 1);
182 }
183 if !is_continuation(byte) {
184 count += 1;
185 }
186 }
187 if source.len().is_multiple_of(STRIDE) {
188 characters.push(count);
189 }
190 Self {
191 source,
192 lines,
193 characters,
194 }
195 }
196
197 fn characters_before(&self, offset: usize) -> usize {
199 let block = offset / STRIDE;
200 self.characters[block]
201 + self.source.as_bytes()[block * STRIDE..offset]
202 .iter()
203 .filter(|byte| !is_continuation(**byte))
204 .count()
205 }
206
207 pub(crate) fn locate(&self, offset: usize) -> (usize, usize) {
210 let mut boundary = offset.min(self.source.len());
211 while !self.source.is_char_boundary(boundary) {
212 boundary -= 1;
213 }
214 let line = self.lines.partition_point(|start| *start <= boundary);
215 let start = self.lines[line - 1];
216 (
217 line,
218 self.characters_before(boundary) - self.characters_before(start) + 1,
219 )
220 }
221}
222
223pub(crate) struct Diagnostics {
227 items: Vec<Diagnostic>,
228 maximum: usize,
229 dropped: bool,
230 skipped: bool,
233 total: Rc<Cell<usize>>,
237}
238
239impl Diagnostics {
240 pub(crate) fn new(maximum: usize) -> Self {
241 Self::sharing(maximum, Rc::new(Cell::new(0)))
242 }
243
244 pub(crate) const fn sharing(maximum: usize, total: Rc<Cell<usize>>) -> Self {
246 Self {
247 items: Vec::new(),
248 maximum,
249 dropped: false,
250 skipped: false,
251 total,
252 }
253 }
254
255 pub(crate) fn total(&self) -> Rc<Cell<usize>> {
257 Rc::clone(&self.total)
258 }
259
260 pub(crate) fn is_full(&self) -> bool {
262 self.total.get() >= self.maximum
263 }
264
265 pub(crate) fn push(&mut self, diagnostic: Diagnostic) {
266 if self.is_full() {
267 self.dropped = true;
268 } else {
269 self.items.push(diagnostic);
270 self.total.set(self.total.get() + 1);
271 }
272 }
273
274 pub(crate) const fn is_empty(&self) -> bool {
275 self.items.is_empty() && !self.dropped && !self.skipped
276 }
277
278 pub(crate) const fn skip(&mut self) {
280 self.skipped = true;
281 }
282
283 pub(crate) fn append(&mut self, other: Self) {
286 self.items.extend(other.items);
287 self.dropped |= other.dropped;
288 self.skipped |= other.skipped;
289 }
290
291 pub(crate) fn finish(self) -> Vec<Diagnostic> {
293 let mut items = self.items;
294 if self.dropped {
295 items.push(Diagnostic::file(
296 ".",
297 Code::Budget,
298 format!(
299 "more than {} problems; the rest are not reported",
300 self.maximum
301 ),
302 ));
303 } else if self.skipped {
304 items.push(Diagnostic::file(
305 ".",
306 Code::Budget,
307 format!(
308 "{} problems, as many as are reported; reading stopped there, so there may be more",
309 self.maximum
310 ),
311 ));
312 }
313 items.sort();
314 items.dedup();
315 items
316 }
317}
318
319impl fmt::Display for Diagnostic {
320 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
321 if self.line == 0 {
322 write!(formatter, "{}: {}: {}", self.path, self.code, self.message)
323 } else {
324 write!(
325 formatter,
326 "{}:{}:{}: {}: {}",
327 self.path, self.line, self.column, self.code, self.message
328 )
329 }
330 }
331}