openbim_step/
diagnostic.rs1use std::error::Error;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
8pub struct Span {
9 pub start: usize,
11 pub end: usize,
13}
14
15impl Span {
16 #[must_use]
18 pub const fn new(start: usize, end: usize) -> Self {
19 Self { start, end }
20 }
21
22 #[must_use]
24 pub const fn len(self) -> usize {
25 self.end.saturating_sub(self.start)
26 }
27
28 #[must_use]
30 pub const fn is_empty(self) -> bool {
31 self.start >= self.end
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct Spanned<T> {
38 pub value: T,
40 pub span: Span,
42}
43
44impl<T> Spanned<T> {
45 #[must_use]
47 pub const fn new(value: T, span: Span) -> Self {
48 Self { value, span }
49 }
50}
51
52#[derive(Debug, Clone, Copy)]
54pub struct Source<'a> {
55 name: &'a str,
56 bytes: &'a [u8],
57}
58
59impl<'a> Source<'a> {
60 #[must_use]
62 pub const fn new(name: &'a str, bytes: &'a [u8]) -> Self {
63 Self { name, bytes }
64 }
65
66 #[must_use]
68 pub fn location(self, span: Span) -> SourceLocation<'a> {
69 let offset = span.start.min(self.bytes.len());
70 let prefix = &self.bytes[..offset];
71 let line = prefix
72 .iter()
73 .fold(1, |line, byte| line + usize::from(*byte == b'\n'));
74 let line_start = prefix
75 .iter()
76 .rposition(|byte| *byte == b'\n')
77 .map_or(0, |position| position + 1);
78 let line_end = self.bytes[line_start..]
79 .iter()
80 .position(|byte| *byte == b'\n')
81 .map_or(self.bytes.len(), |position| line_start + position);
82 let column = String::from_utf8_lossy(&self.bytes[line_start..offset])
83 .chars()
84 .count()
85 + 1;
86 SourceLocation {
87 source_name: self.name,
88 line,
89 column,
90 line_text: String::from_utf8_lossy(&self.bytes[line_start..line_end]).into_owned(),
91 span,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct SourceLocation<'a> {
99 pub source_name: &'a str,
101 pub line: usize,
103 pub column: usize,
105 pub line_text: String,
107 pub span: Span,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct StepError {
114 span: Span,
115 detail: String,
116 kind: ErrorKind,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120enum ErrorKind {
121 NotStep,
122 Syntax,
123 RealWithoutPoint,
124 InvalidArgument,
125}
126
127impl StepError {
128 pub(crate) fn not_step(detail: impl Into<String>) -> Self {
129 Self {
130 span: Span::new(0, 0),
131 detail: detail.into(),
132 kind: ErrorKind::NotStep,
133 }
134 }
135
136 pub(crate) fn syntax(span: Span, detail: impl Into<String>) -> Self {
137 Self {
138 span,
139 detail: detail.into(),
140 kind: ErrorKind::Syntax,
141 }
142 }
143
144 pub(crate) fn real_without_point(span: Span, token: &str) -> Self {
145 Self {
146 span,
147 detail: format!("real {token} has no decimal point before its exponent"),
148 kind: ErrorKind::RealWithoutPoint,
149 }
150 }
151
152 pub(crate) fn invalid_argument(detail: impl Into<String>) -> Self {
153 Self {
154 span: Span::new(0, 0),
155 detail: detail.into(),
156 kind: ErrorKind::InvalidArgument,
157 }
158 }
159
160 #[must_use]
162 pub const fn is_not_step(&self) -> bool {
163 matches!(self.kind, ErrorKind::NotStep)
164 }
165
166 #[must_use]
173 pub const fn is_real_without_point(&self) -> bool {
174 matches!(self.kind, ErrorKind::RealWithoutPoint)
175 }
176
177 #[must_use]
179 pub const fn span(&self) -> Span {
180 self.span
181 }
182
183 #[must_use]
185 pub fn detail(&self) -> &str {
186 &self.detail
187 }
188}
189
190impl fmt::Display for StepError {
191 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192 match self.kind {
193 ErrorKind::NotStep => write!(formatter, "not a STEP physical file: {}", self.detail),
194 ErrorKind::Syntax | ErrorKind::RealWithoutPoint => write!(
195 formatter,
196 "STEP syntax error at bytes {}..{}: {}",
197 self.span.start, self.span.end, self.detail
198 ),
199 ErrorKind::InvalidArgument => write!(formatter, "invalid argument: {}", self.detail),
200 }
201 }
202}
203
204impl Error for StepError {}