1use crate::escape;
8use crate::lexer::{Lexer, Token};
9use crate::recovery::{Diagnostic, OnMalformed, ParseOptions, ParseOutcome};
10use crate::{
11 DataRecord, DataSection, Exchange, HeaderRecord, HeaderSection, InstanceId, Parameter, Record,
12 Span, Spanned, StepError,
13};
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum Event<S = String> {
18 StartHeader,
20 HeaderRecord(HeaderRecord<S>),
22 EndHeader,
24 StartData,
26 DataRecord(DataRecord<S>),
28 EndData,
30}
31
32pub trait EventSink<S = String> {
34 fn event(&mut self, event: Event<S>);
36}
37
38impl<S, F> EventSink<S> for F
39where
40 F: FnMut(Event<S>),
41{
42 fn event(&mut self, event: Event<S>) {
43 self(event);
44 }
45}
46
47pub fn parse(input: &[u8]) -> Result<Exchange, StepError> {
53 parse_with(input, ParseOptions::strict()).map(|outcome| outcome.exchange)
54}
55
56pub fn parse_with(input: &[u8], options: ParseOptions) -> Result<ParseOutcome, StepError> {
69 #[derive(Default)]
70 struct Builder {
71 header: HeaderSection,
72 data: DataSection,
73 }
74
75 impl EventSink for Builder {
76 fn event(&mut self, event: Event) {
77 match event {
78 Event::HeaderRecord(record) => self.header.records.push(record),
79 Event::DataRecord(record) => self.data.records.push(record),
80 Event::StartHeader | Event::EndHeader | Event::StartData | Event::EndData => {}
81 }
82 }
83 }
84
85 let mut builder = Builder::default();
86 let diagnostics = parse_events_with(input, &mut builder, options)?;
87 Ok(ParseOutcome {
88 exchange: Exchange {
89 header: builder.header,
90 data: builder.data,
91 },
92 diagnostics,
93 })
94}
95
96pub fn parse_events(input: &[u8], sink: &mut impl EventSink) -> Result<(), StepError> {
105 parse_events_with(input, sink, ParseOptions::strict()).map(|_| ())
106}
107
108pub fn parse_events_with(
115 input: &[u8],
116 sink: &mut impl EventSink,
117 options: ParseOptions,
118) -> Result<Vec<Diagnostic>, StepError> {
119 if !crate::is_step_file(input) {
120 return Err(StepError::not_step("missing ISO-10303-21 marker"));
121 }
122 let mut parser = Parser::new(input);
123 parser.options = options;
124 parser.parse(sink)?;
125 Ok(parser.diagnostics)
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129enum Phase {
130 BeforeStart,
131 BeforeHeader,
132 Header,
133 BeforeData,
134 Data,
135 BeforeEnd,
136 Done,
137}
138
139struct Parser<'a> {
140 input: &'a [u8],
141 lexer: Lexer<'a>,
142 lookahead: Option<Spanned<Token<'a>>>,
143 last_end: usize,
144 phase: Phase,
145 header_records_seen: usize,
146 options: ParseOptions,
147 diagnostics: Vec<Diagnostic>,
148}
149
150impl<'a> Parser<'a> {
151 fn new(input: &'a [u8]) -> Self {
152 Self {
153 input,
154 lexer: Lexer::new(input),
155 lookahead: None,
156 last_end: 0,
157 phase: Phase::BeforeStart,
158 header_records_seen: 0,
159 options: ParseOptions::strict(),
160 diagnostics: Vec::new(),
161 }
162 }
163
164 #[allow(clippy::too_many_lines)]
166 fn parse(&mut self, sink: &mut impl EventSink) -> Result<(), StepError> {
167 loop {
168 let token = match self.next() {
169 Ok(Some(token)) => token,
170 Ok(None) => break,
171 Err(error) => {
172 self.recover_or_fail(error, None)?;
175 continue;
176 }
177 };
178 if self.phase == Phase::Done {
179 return Err(StepError::syntax(
180 token.span,
181 "content after END-ISO-10303-21",
182 ));
183 }
184 match token.value {
185 Token::Name(name) if name.eq_ignore_ascii_case(b"ISO-10303-21") => {
186 if self.phase != Phase::BeforeStart {
187 return Err(StepError::syntax(
188 token.span,
189 "unexpected ISO-10303-21 marker",
190 ));
191 }
192 self.expect_semicolon("after ISO-10303-21")?;
193 self.phase = Phase::BeforeHeader;
194 }
195 Token::Name(name) if name.eq_ignore_ascii_case(b"HEADER") => {
196 if self.phase != Phase::BeforeHeader {
197 return Err(StepError::syntax(token.span, "unexpected HEADER section"));
198 }
199 self.expect_semicolon("after HEADER")?;
200 self.phase = Phase::Header;
201 sink.event(Event::StartHeader);
202 }
203 Token::Name(name) if name.eq_ignore_ascii_case(b"DATA") => {
204 if self.phase != Phase::BeforeData {
205 return Err(StepError::syntax(token.span, "unexpected DATA section"));
206 }
207 self.expect_semicolon("after DATA")?;
208 self.phase = Phase::Data;
209 sink.event(Event::StartData);
210 }
211 Token::Name(name) if name.eq_ignore_ascii_case(b"ENDSEC") => {
212 self.expect_semicolon("after ENDSEC")?;
213 match self.phase {
214 Phase::Header => {
215 if self.header_records_seen < 3 {
216 return Err(StepError::syntax(
217 token.span,
218 "missing mandatory STEP header record",
219 ));
220 }
221 sink.event(Event::EndHeader);
222 self.phase = Phase::BeforeData;
223 }
224 Phase::Data => {
225 sink.event(Event::EndData);
226 self.phase = Phase::BeforeEnd;
227 }
228 _ => {
229 return Err(StepError::syntax(token.span, "ENDSEC outside a section"));
230 }
231 }
232 }
233 Token::Name(name) if name.eq_ignore_ascii_case(b"END-ISO-10303-21") => {
234 if self.phase != Phase::BeforeEnd {
235 return Err(StepError::syntax(
236 token.span,
237 "unexpected END-ISO-10303-21 marker",
238 ));
239 }
240 self.expect_semicolon("after END-ISO-10303-21")?;
241 self.phase = Phase::Done;
242 }
243 Token::Name(name) if self.phase == Phase::Header => {
244 self.validate_header_record(&name, token.span)?;
245 let parameters = self.parse_arguments()?;
246 self.expect_semicolon("after header record")?;
247 sink.event(Event::HeaderRecord(HeaderRecord {
248 name: upper(&name),
249 parameters,
250 }));
251 }
252 Token::Id(id) if self.phase == Phase::Data => {
253 let start = token.span.start;
254 match self.parse_data_record(&id, token.span) {
255 Ok(record) => sink.event(Event::DataRecord(record)),
256 Err(error) => self.recover_or_fail(error, Some(start))?,
257 }
258 }
259 _ => {
260 let error = StepError::syntax(
261 token.span,
262 format!("unexpected token {:?} in {:?}", token.value, self.phase),
263 );
264 self.recover_or_fail(error, Some(token.span.start))?;
265 }
266 }
267 }
268 if self.phase != Phase::Done {
269 let detail = if matches!(self.phase, Phase::Header | Phase::Data) {
270 "unterminated section (expected ENDSEC)"
271 } else {
272 "physical file requires start, HEADER, DATA, and end markers"
273 };
274 return Err(StepError::syntax(self.eof_span(), detail));
275 }
276 Ok(())
277 }
278
279 fn parse_data_record(
281 &mut self,
282 id: &[u8],
283 id_span: Span,
284 ) -> Result<DataRecord<String>, StepError> {
285 self.expect_equals()?;
286 let record_token = self.next()?.ok_or_else(|| {
287 StepError::syntax(Span::new(id_span.end, id_span.end), "missing record body")
288 })?;
289 let records = match record_token.value {
290 Token::Name(name) => vec![self.parse_named_record(&name)?],
291 Token::OpenParen => {
292 let mut records = Vec::new();
293 loop {
294 if !self
295 .peek()?
296 .is_some_and(|next| next.value != Token::CloseParen)
297 {
298 break;
299 }
300 let component = self.next()?.ok_or_else(|| {
301 StepError::syntax(self.eof_span(), "missing complex record")
302 })?;
303 let Token::Name(name) = component.value else {
304 return Err(StepError::syntax(
305 component.span,
306 "expected complex record name",
307 ));
308 };
309 records.push(self.parse_named_record(&name)?);
310 }
311 let close = self.next()?.ok_or_else(|| {
312 StepError::syntax(self.eof_span(), "unterminated complex instance")
313 })?;
314 if close.value != Token::CloseParen {
315 return Err(StepError::syntax(
316 close.span,
317 "expected ')' after complex instance",
318 ));
319 }
320 if records.is_empty() {
321 return Err(StepError::syntax(
322 record_token.span,
323 "complex instance must contain a record",
324 ));
325 }
326 records
327 }
328 _ => {
329 return Err(StepError::syntax(
330 record_token.span,
331 "expected record name or complex instance",
332 ));
333 }
334 };
335 self.expect_semicolon("after data record")?;
336 Ok(DataRecord {
337 id: InstanceId::new(std::str::from_utf8(id).expect("instance digits are ASCII"))
338 .expect("lexer validates instance ids"),
339 records,
340 })
341 }
342
343 fn recover_or_fail(
352 &mut self,
353 error: StepError,
354 record_start: Option<usize>,
355 ) -> Result<(), StepError> {
356 if self.options.on_malformed_record != OnMalformed::Skip || self.phase != Phase::Data {
357 return Err(error);
358 }
359 let start = record_start.unwrap_or_else(|| error.span().start);
360 let resume = self.resync_from(start.saturating_add(1));
365 self.lookahead = None;
366 self.lexer.resume_at(resume);
367 self.last_end = resume;
368 self.diagnostics.push(Diagnostic::skipped_record(
369 Span::new(start, resume),
370 format!("skipped malformed data record: {}", error.detail()),
371 ));
372 Ok(())
373 }
374
375 fn resync_from(&self, from: usize) -> usize {
385 enum Literal {
388 None,
389 Text,
391 Binary,
393 }
394
395 let mut position = from.min(self.input.len());
396 let mut literal = Literal::None;
397 while position < self.input.len() {
398 let byte = self.input[position];
399 match literal {
400 Literal::Text => {
401 if byte == b'\'' {
402 if self.input.get(position + 1) == Some(&b'\'') {
403 position += 2;
404 continue;
405 }
406 literal = Literal::None;
407 }
408 position += 1;
409 }
410 Literal::Binary => {
411 if byte == b'"' {
412 literal = Literal::None;
413 }
414 position += 1;
415 }
416 Literal::None => match byte {
417 b'\'' => {
418 literal = Literal::Text;
419 position += 1;
420 }
421 b'"' => {
422 literal = Literal::Binary;
423 position += 1;
424 }
425 b'/' if self.input.get(position + 1) == Some(&b'*') => {
426 position = self.input[position + 2..]
427 .windows(2)
428 .position(|window| window == b"*/")
429 .map_or(self.input.len(), |offset| position + 2 + offset + 2);
430 }
431 b';' => return position + 1,
432 _ if self.section_end_at(position) => return position,
433 _ => position += 1,
434 },
435 }
436 }
437 self.input.len()
438 }
439
440 fn section_end_at(&self, position: usize) -> bool {
441 let preceded_by_word = position
442 .checked_sub(1)
443 .and_then(|previous| self.input.get(previous))
444 .is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_');
445 !preceded_by_word
446 && self
447 .input
448 .get(position..position + 6)
449 .is_some_and(|bytes| bytes.eq_ignore_ascii_case(b"ENDSEC"))
450 }
451
452 fn validate_header_record(&mut self, name: &[u8], span: Span) -> Result<(), StepError> {
453 const REQUIRED: [&[u8]; 3] = [b"FILE_DESCRIPTION", b"FILE_NAME", b"FILE_SCHEMA"];
454 if let Some(expected) = REQUIRED.get(self.header_records_seen) {
455 if !name.eq_ignore_ascii_case(expected) {
456 return Err(StepError::syntax(
457 span,
458 format!(
459 "expected mandatory {} header record",
460 String::from_utf8_lossy(expected)
461 ),
462 ));
463 }
464 } else if REQUIRED
465 .iter()
466 .any(|required| name.eq_ignore_ascii_case(required))
467 {
468 return Err(StepError::syntax(span, "duplicate mandatory header record"));
469 }
470 self.header_records_seen += 1;
471 Ok(())
472 }
473
474 fn parse_named_record(&mut self, name: &[u8]) -> Result<Record, StepError> {
475 Ok(Record {
476 name: upper(name),
477 parameters: self.parse_arguments()?,
478 })
479 }
480
481 fn parse_arguments(&mut self) -> Result<Vec<Parameter>, StepError> {
482 let token = self
483 .next()?
484 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '(' after record name"))?;
485 if token.value != Token::OpenParen {
486 return Err(StepError::syntax(
487 token.span,
488 "expected '(' after record name",
489 ));
490 }
491 self.parse_parameter_list(0)
492 }
493
494 fn parse_parameter_list(&mut self, depth: usize) -> Result<Vec<Parameter>, StepError> {
495 if depth > crate::MAX_PARAMETER_NESTING {
496 let span = match self.peek()? {
497 Some(token) => token.span,
498 None => self.eof_span(),
499 };
500 return Err(StepError::syntax(span, "parameter nesting limit exceeded"));
501 }
502 let mut parameters = Vec::new();
503 if self
504 .peek()?
505 .is_some_and(|token| token.value == Token::CloseParen)
506 {
507 let _ = self.next()?;
508 return Ok(parameters);
509 }
510 loop {
511 parameters.push(self.parse_parameter(depth)?);
512 let separator = self
513 .next()?
514 .ok_or_else(|| StepError::syntax(self.eof_span(), "unterminated parameter list"))?;
515 match separator.value {
516 Token::Comma => {}
517 Token::CloseParen => return Ok(parameters),
518 _ => {
519 return Err(StepError::syntax(
520 separator.span,
521 "expected ',' or ')' after parameter",
522 ));
523 }
524 }
525 }
526 }
527
528 fn parse_parameter(&mut self, depth: usize) -> Result<Parameter, StepError> {
529 let token = self
530 .next()?
531 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected parameter"))?;
532 match token.value {
533 Token::Dollar => Ok(Parameter::Null),
534 Token::Star => Ok(Parameter::Derived),
535 Token::Id(id) => Ok(Parameter::Ref(
536 InstanceId::new(std::str::from_utf8(&id).expect("instance digits are ASCII"))
537 .expect("lexer validates instance ids"),
538 )),
539 Token::Integer(value) => Ok(Parameter::Integer(
540 String::from_utf8_lossy(&value).into_owned(),
541 )),
542 Token::Real(value) => Ok(Parameter::Real(
543 String::from_utf8_lossy(&value).into_owned(),
544 )),
545 Token::Text(raw) => Ok(Parameter::Text(escape::decode(&raw))),
546 Token::Binary(raw) => Ok(Parameter::Binary(
547 String::from_utf8_lossy(&raw).into_owned(),
548 )),
549 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"T") => {
550 Ok(Parameter::Bool(true))
551 }
552 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"F") => {
553 Ok(Parameter::Bool(false))
554 }
555 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"U") => {
556 Ok(Parameter::LogicalUnknown)
557 }
558 Token::Keyword(keyword) => Ok(Parameter::Enum(upper(&keyword))),
559 Token::OpenParen => Ok(Parameter::List(self.parse_parameter_list(depth + 1)?)),
560 Token::Name(name) => {
561 let Some(next) = self.peek()? else {
562 return Err(StepError::syntax(
563 self.eof_span(),
564 "expected '(' after typed parameter name",
565 ));
566 };
567 if next.value != Token::OpenParen {
568 return Err(StepError::syntax(
569 next.span,
570 "expected '(' after typed parameter name",
571 ));
572 }
573 let _ = self.next()?;
574 let mut parameters = self.parse_parameter_list(depth + 1)?;
575 let value = if parameters.len() == 1 {
576 Box::new(parameters.remove(0))
577 } else {
578 Box::new(Parameter::List(parameters))
579 };
580 Ok(Parameter::Typed {
581 type_name: upper(&name),
582 value,
583 })
584 }
585 value => Err(StepError::syntax(
586 token.span,
587 format!("unexpected parameter token {value:?}"),
588 )),
589 }
590 }
591
592 fn expect_equals(&mut self) -> Result<(), StepError> {
593 let token = self
594 .next()?
595 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '=' after instance id"))?;
596 if token.value == Token::Equals {
597 Ok(())
598 } else {
599 Err(StepError::syntax(
600 token.span,
601 "expected '=' after instance id",
602 ))
603 }
604 }
605
606 fn expect_semicolon(&mut self, context: &str) -> Result<(), StepError> {
607 let token = self
608 .next()?
609 .ok_or_else(|| StepError::syntax(self.eof_span(), format!("expected ';' {context}")))?;
610 if token.value == Token::Semicolon {
611 Ok(())
612 } else {
613 Err(StepError::syntax(
614 token.span,
615 format!("expected ';' {context}"),
616 ))
617 }
618 }
619
620 fn next(&mut self) -> Result<Option<Spanned<Token<'a>>>, StepError> {
621 let token = match self.lookahead.take() {
622 Some(token) => Some(token),
623 None => self.lexer.next_spanned()?,
624 };
625 if let Some(token) = &token {
626 self.last_end = token.span.end;
627 }
628 Ok(token)
629 }
630
631 fn peek(&mut self) -> Result<Option<&Spanned<Token<'a>>>, StepError> {
632 if self.lookahead.is_none() {
633 self.lookahead = self.lexer.next_spanned()?;
634 }
635 Ok(self.lookahead.as_ref())
636 }
637
638 fn eof_span(&self) -> Span {
639 let offset = self.last_end.max(self.lexer.offset());
640 Span::new(offset, offset)
641 }
642}
643
644fn upper(bytes: &[u8]) -> String {
645 String::from_utf8_lossy(bytes).to_ascii_uppercase()
646}