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 failure = error
361 .span()
362 .end
363 .max(self.lexer.offset())
364 .max(start.saturating_add(1));
365 let resume = self.resync_from(failure);
366 self.lookahead = None;
367 self.lexer.resume_at(resume);
368 self.last_end = resume;
369 self.diagnostics.push(Diagnostic::skipped_record(
370 Span::new(start, resume),
371 format!("skipped malformed data record: {}", error.detail()),
372 ));
373 Ok(())
374 }
375
376 fn resync_from(&self, from: usize) -> usize {
384 let mut position = from.min(self.input.len());
385 let mut in_string = false;
386 while position < self.input.len() {
387 let byte = self.input[position];
388 if in_string {
389 if byte == b'\'' {
390 if self.input.get(position + 1) == Some(&b'\'') {
391 position += 2;
392 continue;
393 }
394 in_string = false;
395 }
396 position += 1;
397 continue;
398 }
399 match byte {
400 b'\'' => {
401 in_string = true;
402 position += 1;
403 }
404 b'/' if self.input.get(position + 1) == Some(&b'*') => {
405 position = self.input[position + 2..]
406 .windows(2)
407 .position(|window| window == b"*/")
408 .map_or(self.input.len(), |offset| position + 2 + offset + 2);
409 }
410 b';' => return position + 1,
411 _ if self.section_end_at(position) => return position,
412 _ => position += 1,
413 }
414 }
415 self.input.len()
416 }
417
418 fn section_end_at(&self, position: usize) -> bool {
419 let preceded_by_word = position
420 .checked_sub(1)
421 .and_then(|previous| self.input.get(previous))
422 .is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_');
423 !preceded_by_word
424 && self
425 .input
426 .get(position..position + 6)
427 .is_some_and(|bytes| bytes.eq_ignore_ascii_case(b"ENDSEC"))
428 }
429
430 fn validate_header_record(&mut self, name: &[u8], span: Span) -> Result<(), StepError> {
431 const REQUIRED: [&[u8]; 3] = [b"FILE_DESCRIPTION", b"FILE_NAME", b"FILE_SCHEMA"];
432 if let Some(expected) = REQUIRED.get(self.header_records_seen) {
433 if !name.eq_ignore_ascii_case(expected) {
434 return Err(StepError::syntax(
435 span,
436 format!(
437 "expected mandatory {} header record",
438 String::from_utf8_lossy(expected)
439 ),
440 ));
441 }
442 } else if REQUIRED
443 .iter()
444 .any(|required| name.eq_ignore_ascii_case(required))
445 {
446 return Err(StepError::syntax(span, "duplicate mandatory header record"));
447 }
448 self.header_records_seen += 1;
449 Ok(())
450 }
451
452 fn parse_named_record(&mut self, name: &[u8]) -> Result<Record, StepError> {
453 Ok(Record {
454 name: upper(name),
455 parameters: self.parse_arguments()?,
456 })
457 }
458
459 fn parse_arguments(&mut self) -> Result<Vec<Parameter>, StepError> {
460 let token = self
461 .next()?
462 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '(' after record name"))?;
463 if token.value != Token::OpenParen {
464 return Err(StepError::syntax(
465 token.span,
466 "expected '(' after record name",
467 ));
468 }
469 self.parse_parameter_list(0)
470 }
471
472 fn parse_parameter_list(&mut self, depth: usize) -> Result<Vec<Parameter>, StepError> {
473 if depth > crate::MAX_PARAMETER_NESTING {
474 let span = match self.peek()? {
475 Some(token) => token.span,
476 None => self.eof_span(),
477 };
478 return Err(StepError::syntax(span, "parameter nesting limit exceeded"));
479 }
480 let mut parameters = Vec::new();
481 if self
482 .peek()?
483 .is_some_and(|token| token.value == Token::CloseParen)
484 {
485 let _ = self.next()?;
486 return Ok(parameters);
487 }
488 loop {
489 parameters.push(self.parse_parameter(depth)?);
490 let separator = self
491 .next()?
492 .ok_or_else(|| StepError::syntax(self.eof_span(), "unterminated parameter list"))?;
493 match separator.value {
494 Token::Comma => {}
495 Token::CloseParen => return Ok(parameters),
496 _ => {
497 return Err(StepError::syntax(
498 separator.span,
499 "expected ',' or ')' after parameter",
500 ));
501 }
502 }
503 }
504 }
505
506 fn parse_parameter(&mut self, depth: usize) -> Result<Parameter, StepError> {
507 let token = self
508 .next()?
509 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected parameter"))?;
510 match token.value {
511 Token::Dollar => Ok(Parameter::Null),
512 Token::Star => Ok(Parameter::Derived),
513 Token::Id(id) => Ok(Parameter::Ref(
514 InstanceId::new(std::str::from_utf8(&id).expect("instance digits are ASCII"))
515 .expect("lexer validates instance ids"),
516 )),
517 Token::Integer(value) => Ok(Parameter::Integer(
518 String::from_utf8_lossy(&value).into_owned(),
519 )),
520 Token::Real(value) => Ok(Parameter::Real(
521 String::from_utf8_lossy(&value).into_owned(),
522 )),
523 Token::Text(raw) => Ok(Parameter::Text(escape::decode(&raw))),
524 Token::Binary(raw) => Ok(Parameter::Binary(
525 String::from_utf8_lossy(&raw).into_owned(),
526 )),
527 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"T") => {
528 Ok(Parameter::Bool(true))
529 }
530 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"F") => {
531 Ok(Parameter::Bool(false))
532 }
533 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"U") => {
534 Ok(Parameter::LogicalUnknown)
535 }
536 Token::Keyword(keyword) => Ok(Parameter::Enum(upper(&keyword))),
537 Token::OpenParen => Ok(Parameter::List(self.parse_parameter_list(depth + 1)?)),
538 Token::Name(name) => {
539 let Some(next) = self.peek()? else {
540 return Err(StepError::syntax(
541 self.eof_span(),
542 "expected '(' after typed parameter name",
543 ));
544 };
545 if next.value != Token::OpenParen {
546 return Err(StepError::syntax(
547 next.span,
548 "expected '(' after typed parameter name",
549 ));
550 }
551 let _ = self.next()?;
552 let mut parameters = self.parse_parameter_list(depth + 1)?;
553 let value = if parameters.len() == 1 {
554 Box::new(parameters.remove(0))
555 } else {
556 Box::new(Parameter::List(parameters))
557 };
558 Ok(Parameter::Typed {
559 type_name: upper(&name),
560 value,
561 })
562 }
563 value => Err(StepError::syntax(
564 token.span,
565 format!("unexpected parameter token {value:?}"),
566 )),
567 }
568 }
569
570 fn expect_equals(&mut self) -> Result<(), StepError> {
571 let token = self
572 .next()?
573 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '=' after instance id"))?;
574 if token.value == Token::Equals {
575 Ok(())
576 } else {
577 Err(StepError::syntax(
578 token.span,
579 "expected '=' after instance id",
580 ))
581 }
582 }
583
584 fn expect_semicolon(&mut self, context: &str) -> Result<(), StepError> {
585 let token = self
586 .next()?
587 .ok_or_else(|| StepError::syntax(self.eof_span(), format!("expected ';' {context}")))?;
588 if token.value == Token::Semicolon {
589 Ok(())
590 } else {
591 Err(StepError::syntax(
592 token.span,
593 format!("expected ';' {context}"),
594 ))
595 }
596 }
597
598 fn next(&mut self) -> Result<Option<Spanned<Token<'a>>>, StepError> {
599 let token = match self.lookahead.take() {
600 Some(token) => Some(token),
601 None => self.lexer.next_spanned()?,
602 };
603 if let Some(token) = &token {
604 self.last_end = token.span.end;
605 }
606 Ok(token)
607 }
608
609 fn peek(&mut self) -> Result<Option<&Spanned<Token<'a>>>, StepError> {
610 if self.lookahead.is_none() {
611 self.lookahead = self.lexer.next_spanned()?;
612 }
613 Ok(self.lookahead.as_ref())
614 }
615
616 fn eof_span(&self) -> Span {
617 let offset = self.last_end.max(self.lexer.offset());
618 Span::new(offset, offset)
619 }
620}
621
622fn upper(bytes: &[u8]) -> String {
623 String::from_utf8_lossy(bytes).to_ascii_uppercase()
624}