1use crate::ast::Span;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Severity {
8 Warning,
9 Error,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Position {
17 pub line: u32,
18 pub column: u32,
19 pub current_offset: u32,
20 pub end_offset: u32,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Issue {
25 pub file_name: Option<String>,
27 pub position: Option<Position>,
29 pub description: String,
30 pub severity: Severity,
31}
32
33impl Issue {
34 pub fn line(&self) -> String {
36 let (line, column) = self.position.map_or((0, 0), |p| (p.line, p.column));
37 format!("L{line}:{column} {}", self.description)
38 }
39}
40
41#[derive(Debug, Clone)]
43pub(crate) struct Diag {
44 pub span: Option<Span>,
45 pub message: String,
46 pub severity: Severity,
47}
48
49impl Diag {
50 pub fn error(span: Span, message: impl Into<String>) -> Self {
51 Self {
52 span: Some(span),
53 message: message.into(),
54 severity: Severity::Error,
55 }
56 }
57
58 pub fn warning(span: Span, message: impl Into<String>) -> Self {
59 Self {
60 span: Some(span),
61 message: message.into(),
62 severity: Severity::Warning,
63 }
64 }
65
66 pub fn into_issue(self, offsets: &Offsets, file_name: Option<&str>) -> Issue {
67 Issue {
68 file_name: file_name.map(str::to_owned),
69 position: self.span.map(|span| Position {
70 line: span.line,
71 column: span.column,
72 current_offset: offsets.at(span.start),
73 end_offset: offsets.at(span.end).max(offsets.at(span.start) + 1) - 1,
75 }),
76 description: self.message,
77 severity: self.severity,
78 }
79 }
80}
81
82pub(crate) struct Offsets {
84 len: usize,
85 counts: Option<Vec<u32>>,
86}
87
88impl Offsets {
89 pub fn new(src: &str) -> Self {
90 let counts = (!src.is_ascii()).then(|| {
91 let mut counts = Vec::with_capacity(src.len() + 1);
92 for (count, c) in src.chars().enumerate() {
93 let count = u32::try_from(count).unwrap_or(u32::MAX);
94 counts.extend(std::iter::repeat_n(count, c.len_utf8()));
95 }
96 counts.push(u32::try_from(src.chars().count()).unwrap_or(u32::MAX));
97 counts
98 });
99 Self {
100 len: src.len(),
101 counts,
102 }
103 }
104
105 fn at(&self, offset: usize) -> u32 {
106 let offset = offset.min(self.len);
107 match &self.counts {
108 Some(counts) => counts[offset],
109 None => u32::try_from(offset).unwrap_or(u32::MAX),
110 }
111 }
112}
113
114pub(crate) fn sort(diags: &mut [Diag]) {
116 diags.sort_by(|a, b| {
117 let key = |d: &Diag| d.span.map_or((0, 0), |s| (s.line, s.column));
118 key(a).cmp(&key(b)).then_with(|| a.message.cmp(&b.message))
119 });
120}