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 InvalidArgument,
124}
125
126impl StepError {
127 pub(crate) fn not_step(detail: impl Into<String>) -> Self {
128 Self {
129 span: Span::new(0, 0),
130 detail: detail.into(),
131 kind: ErrorKind::NotStep,
132 }
133 }
134
135 pub(crate) fn syntax(span: Span, detail: impl Into<String>) -> Self {
136 Self {
137 span,
138 detail: detail.into(),
139 kind: ErrorKind::Syntax,
140 }
141 }
142
143 pub(crate) fn invalid_argument(detail: impl Into<String>) -> Self {
144 Self {
145 span: Span::new(0, 0),
146 detail: detail.into(),
147 kind: ErrorKind::InvalidArgument,
148 }
149 }
150
151 #[must_use]
153 pub const fn is_not_step(&self) -> bool {
154 matches!(self.kind, ErrorKind::NotStep)
155 }
156
157 #[must_use]
159 pub const fn span(&self) -> Span {
160 self.span
161 }
162
163 #[must_use]
165 pub fn detail(&self) -> &str {
166 &self.detail
167 }
168}
169
170impl fmt::Display for StepError {
171 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self.kind {
173 ErrorKind::NotStep => write!(formatter, "not a STEP physical file: {}", self.detail),
174 ErrorKind::Syntax => write!(
175 formatter,
176 "STEP syntax error at bytes {}..{}: {}",
177 self.span.start, self.span.end, self.detail
178 ),
179 ErrorKind::InvalidArgument => write!(formatter, "invalid argument: {}", self.detail),
180 }
181 }
182}
183
184impl Error for StepError {}