use crate::lexer::{Lexer, Token};
use crate::parser::{decode_borrowed, decode_owned, parse_prefix};
use crate::{DataRecord, HeaderSection, InstanceId, ParseOptions, Span, StepError};
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct Scan<'a> {
input: &'a [u8],
header: HeaderSection,
data_start: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScannedRecord<'a> {
pub id: InstanceId,
pub name: Option<Cow<'a, str>>,
pub span: Span,
}
pub fn scan(input: &[u8]) -> Result<Scan<'_>, StepError> {
if !crate::is_step_file(input) {
return Err(StepError::not_step("missing ISO-10303-21 marker"));
}
match parse_prefix(input, ParseOptions::strict())? {
Some((header, data_start)) => Ok(Scan {
input,
header,
data_start,
}),
None => Err(crate::parse(input).err().unwrap_or_else(|| {
StepError::syntax(Span::new(input.len(), input.len()), "missing DATA section")
})),
}
}
impl<'a> Scan<'a> {
#[must_use]
pub const fn header(&self) -> &HeaderSection {
&self.header
}
#[must_use]
pub const fn input(&self) -> &'a [u8] {
self.input
}
#[must_use]
pub fn records(&self) -> Records<'a> {
let mut lexer = Lexer::new(self.input);
lexer.resume_at(self.data_start);
Records {
input: self.input,
lexer,
done: false,
}
}
pub fn decode(&self, record: &ScannedRecord<'_>) -> Result<DataRecord, StepError> {
decode_record(self.input, record.span)
}
}
pub fn decode_record(input: &[u8], span: Span) -> Result<DataRecord, StepError> {
decode_owned(input, span)
}
pub fn decode_record_borrowed(
input: &[u8],
span: Span,
) -> Result<DataRecord<Cow<'_, str>>, StepError> {
decode_borrowed(input, span)
}
#[derive(Debug, Clone)]
pub struct Records<'a> {
input: &'a [u8],
lexer: Lexer<'a>,
done: bool,
}
impl<'a> Iterator for Records<'a> {
type Item = Result<ScannedRecord<'a>, StepError>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let step = self.step();
if !matches!(step, Ok(Some(_))) {
self.done = true;
}
step.transpose()
}
}
impl<'a> Records<'a> {
fn step(&mut self) -> Result<Option<ScannedRecord<'a>>, StepError> {
if let Some(head) = self.lexer.record_head() {
let end = self.record_end(head.start)?;
return Ok(Some(ScannedRecord {
id: InstanceId::from_digits(head.id),
name: head.name.map(ascii_borrowed),
span: Span::new(head.start, end),
}));
}
let token = self.token("unterminated section (expected ENDSEC)")?;
match token.value {
Token::Id(id) => {
let start = token.span.start;
let equals = self.token("expected '=' after instance id")?;
if equals.value != Token::Equals {
return Err(StepError::syntax(
equals.span,
"expected '=' after instance id",
));
}
let body = self.token("missing record body")?;
let name = match body.value {
Token::Name(name) => Some(ascii(name)),
Token::OpenParen => None,
_ => {
return Err(StepError::syntax(
body.span,
"expected record name or complex instance",
));
}
};
let end = self.record_end(start)?;
Ok(Some(ScannedRecord {
id: InstanceId::from_ascii_digits(&id).expect("lexer validates instance ids"),
name,
span: Span::new(start, end),
}))
}
Token::Name(name) if name.eq_ignore_ascii_case(b"ENDSEC") => {
self.expect(&Token::Semicolon, "expected ';' after ENDSEC")?;
let marker =
self.token("physical file requires start, HEADER, DATA, and end markers")?;
if !matches!(&marker.value, Token::Name(name) if name.eq_ignore_ascii_case(b"END-ISO-10303-21"))
{
return Err(StepError::syntax(
marker.span,
format!("unexpected token {:?} in BeforeEnd", marker.value),
));
}
self.expect(&Token::Semicolon, "expected ';' after END-ISO-10303-21")?;
if let Some(extra) = self.lexer.next_spanned()? {
return Err(StepError::syntax(
extra.span,
"content after END-ISO-10303-21",
));
}
Ok(None)
}
value => Err(StepError::syntax(
token.span,
format!("unexpected token {value:?} in Data"),
)),
}
}
fn token(&mut self, at_end: &str) -> Result<crate::Spanned<Token<'a>>, StepError> {
self.lexer.next_spanned()?.ok_or_else(|| {
let end = self.input.len();
StepError::syntax(Span::new(end, end), at_end)
})
}
fn expect(&mut self, expected: &Token<'_>, message: &str) -> Result<(), StepError> {
let token = self.token(message)?;
if &token.value == expected {
Ok(())
} else {
Err(StepError::syntax(token.span, message))
}
}
fn record_end(&mut self, start: usize) -> Result<usize, StepError> {
let input = self.input;
let mut position = self.lexer.offset();
loop {
let Some(offset) = memchr::memchr3(b';', b'\'', b'/', &input[position..]) else {
return Err(StepError::syntax(
Span::new(start, input.len()),
"unterminated data record",
));
};
position += offset;
match input[position] {
b';' => {
position += 1;
self.lexer.resume_at(position);
return Ok(position);
}
b'\'' => {
self.lexer.skip_text(position)?;
position = self.lexer.offset();
}
_ => {
self.lexer.resume_at(position);
self.lexer.skip_trivia()?;
position = self.lexer.offset().max(position + 1);
}
}
}
}
}
fn ascii(bytes: Cow<'_, [u8]>) -> Cow<'_, str> {
match bytes {
Cow::Borrowed(bytes) => ascii_borrowed(bytes),
Cow::Owned(bytes) => Cow::Owned(String::from_utf8_lossy(&bytes).into_owned()),
}
}
fn ascii_borrowed(bytes: &[u8]) -> Cow<'_, str> {
std::str::from_utf8(bytes).map_or_else(|_| String::from_utf8_lossy(bytes), Cow::Borrowed)
}