1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub struct SourcePosition {
6 pub byte_offset: usize,
8 pub line: usize,
10 pub column: usize,
12}
13
14impl SourcePosition {
15 pub const fn start() -> Self {
17 Self {
18 byte_offset: 0,
19 line: 1,
20 column: 1,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub struct Span {
28 pub start: SourcePosition,
30 pub end: SourcePosition,
32}
33
34impl Span {
35 pub const fn point(position: SourcePosition) -> Self {
37 Self {
38 start: position,
39 end: position,
40 }
41 }
42
43 pub fn covering(start: Self, end: Self) -> Self {
45 Self {
46 start: start.start,
47 end: end.end,
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Spanned<T> {
55 pub value: T,
57 pub span: Span,
59}
60
61impl<T> Spanned<T> {
62 pub const fn new(value: T, span: Span) -> Self {
64 Self { value, span }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum Severity {
71 Error,
73 Warning,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct RelatedInformation {
80 pub message: String,
82 pub span: Span,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Diagnostic {
89 pub code: &'static str,
91 pub severity: Severity,
93 pub message: String,
95 pub span: Span,
97 pub expected: Vec<String>,
99 pub help: Option<String>,
101 pub related: Vec<RelatedInformation>,
103}
104
105impl Diagnostic {
106 pub(crate) fn error(code: &'static str, message: impl Into<String>, span: Span) -> Self {
107 Self {
108 code,
109 severity: Severity::Error,
110 message: message.into(),
111 span,
112 expected: Vec::new(),
113 help: None,
114 related: Vec::new(),
115 }
116 }
117
118 pub(crate) fn warning(code: &'static str, message: impl Into<String>, span: Span) -> Self {
119 Self {
120 code,
121 severity: Severity::Warning,
122 message: message.into(),
123 span,
124 expected: Vec::new(),
125 help: None,
126 related: Vec::new(),
127 }
128 }
129
130 pub(crate) fn with_help(mut self, help: impl Into<String>) -> Self {
131 self.help = Some(help.into());
132 self
133 }
134
135 pub(crate) fn with_expected<I, S>(mut self, expected: I) -> Self
136 where
137 I: IntoIterator<Item = S>,
138 S: Into<String>,
139 {
140 self.expected = expected.into_iter().map(Into::into).collect();
141 self
142 }
143
144 pub(crate) fn with_related(mut self, message: impl Into<String>, span: Span) -> Self {
145 self.related.push(RelatedInformation {
146 message: message.into(),
147 span,
148 });
149 self
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::{Diagnostic, SourcePosition, Span};
156
157 #[test]
158 fn diagnostics_default_to_no_expected_values() {
159 let diagnostic = Diagnostic::error(
160 "STK2002",
161 "Unexpected value.",
162 Span::point(SourcePosition::start()),
163 );
164
165 assert!(diagnostic.expected.is_empty());
166 }
167
168 #[test]
169 fn diagnostics_preserve_expected_value_order() {
170 let diagnostic = Diagnostic::error(
171 "STK2002",
172 "Unknown direction.",
173 Span::point(SourcePosition::start()),
174 )
175 .with_expected(["right", "down"]);
176
177 assert_eq!(diagnostic.expected, ["right", "down"]);
178 }
179}