use std::error::Error;
use std::fmt;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
#[must_use]
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
#[must_use]
pub const fn len(self) -> usize {
self.end.saturating_sub(self.start)
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.start >= self.end
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
}
impl<T> Spanned<T> {
#[must_use]
pub const fn new(value: T, span: Span) -> Self {
Self { value, span }
}
}
#[derive(Debug, Clone, Copy)]
pub struct Source<'a> {
name: &'a str,
bytes: &'a [u8],
}
impl<'a> Source<'a> {
#[must_use]
pub const fn new(name: &'a str, bytes: &'a [u8]) -> Self {
Self { name, bytes }
}
#[must_use]
pub fn location(self, span: Span) -> SourceLocation<'a> {
let offset = span.start.min(self.bytes.len());
let prefix = &self.bytes[..offset];
let line = prefix
.iter()
.fold(1, |line, byte| line + usize::from(*byte == b'\n'));
let line_start = prefix
.iter()
.rposition(|byte| *byte == b'\n')
.map_or(0, |position| position + 1);
let line_end = self.bytes[line_start..]
.iter()
.position(|byte| *byte == b'\n')
.map_or(self.bytes.len(), |position| line_start + position);
let column = String::from_utf8_lossy(&self.bytes[line_start..offset])
.chars()
.count()
+ 1;
SourceLocation {
source_name: self.name,
line,
column,
line_text: String::from_utf8_lossy(&self.bytes[line_start..line_end]).into_owned(),
span,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceLocation<'a> {
pub source_name: &'a str,
pub line: usize,
pub column: usize,
pub line_text: String,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StepError {
span: Span,
detail: String,
kind: ErrorKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ErrorKind {
NotStep,
Syntax,
InvalidArgument,
}
impl StepError {
pub(crate) fn not_step(detail: impl Into<String>) -> Self {
Self {
span: Span::new(0, 0),
detail: detail.into(),
kind: ErrorKind::NotStep,
}
}
pub(crate) fn syntax(span: Span, detail: impl Into<String>) -> Self {
Self {
span,
detail: detail.into(),
kind: ErrorKind::Syntax,
}
}
pub(crate) fn invalid_argument(detail: impl Into<String>) -> Self {
Self {
span: Span::new(0, 0),
detail: detail.into(),
kind: ErrorKind::InvalidArgument,
}
}
#[must_use]
pub const fn is_not_step(&self) -> bool {
matches!(self.kind, ErrorKind::NotStep)
}
#[must_use]
pub const fn span(&self) -> Span {
self.span
}
#[must_use]
pub fn detail(&self) -> &str {
&self.detail
}
}
impl fmt::Display for StepError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
ErrorKind::NotStep => write!(formatter, "not a STEP physical file: {}", self.detail),
ErrorKind::Syntax => write!(
formatter,
"STEP syntax error at bytes {}..{}: {}",
self.span.start, self.span.end, self.detail
),
ErrorKind::InvalidArgument => write!(formatter, "invalid argument: {}", self.detail),
}
}
}
impl Error for StepError {}