1use std::borrow::Cow;
8use std::collections::HashMap;
9
10use crate::escape;
11use crate::lexer::{Lexer, Token};
12use crate::recovery::{Diagnostic, OnMalformed, ParseOptions, ParseOutcome};
13use crate::references::ReferenceCheck;
14use crate::{
15 DataRecord, DataSection, Exchange, HeaderRecord, HeaderSection, Instance, InstanceId,
16 Parameter, Record, Span, Spanned, StepError, Str,
17};
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum Event<S = Str> {
22 StartHeader,
24 HeaderRecord(HeaderRecord<S>),
26 EndHeader,
28 StartData,
30 DataRecord(DataRecord<S>),
32 EndData,
34}
35
36pub trait EventSink<S = Str> {
38 fn event(&mut self, event: Event<S>);
40}
41
42impl<S, F> EventSink<S> for F
43where
44 F: FnMut(Event<S>),
45{
46 fn event(&mut self, event: Event<S>) {
47 self(event);
48 }
49}
50
51pub fn parse(input: &[u8]) -> Result<Exchange, StepError> {
57 parse_with(input, ParseOptions::strict()).map(|outcome| outcome.exchange)
58}
59
60pub fn parse_with(input: &[u8], options: ParseOptions) -> Result<ParseOutcome, StepError> {
73 #[derive(Default)]
74 struct Builder {
75 header: HeaderSection,
76 data: DataSection,
77 }
78
79 impl EventSink for Builder {
80 fn event(&mut self, event: Event) {
81 match event {
82 Event::HeaderRecord(record) => self.header.records.push(record),
83 Event::DataRecord(record) => self.data.records.push(record),
84 Event::StartHeader | Event::EndHeader | Event::StartData | Event::EndData => {}
85 }
86 }
87 }
88
89 let mut builder = Builder::default();
90 let diagnostics = parse_events_with(input, &mut builder, options)?;
91 builder.data.records.shrink_to_fit();
95 Ok(ParseOutcome {
96 exchange: Exchange {
97 header: builder.header,
98 data: builder.data,
99 },
100 diagnostics,
101 })
102}
103
104pub fn parse_events(input: &[u8], sink: &mut impl EventSink) -> Result<(), StepError> {
113 parse_events_with(input, sink, ParseOptions::strict()).map(|_| ())
114}
115
116pub fn parse_events_with(
123 input: &[u8],
124 sink: &mut impl EventSink,
125 options: ParseOptions,
126) -> Result<Vec<Diagnostic>, StepError> {
127 if !crate::is_step_file(input) {
128 return Err(StepError::not_step("missing ISO-10303-21 marker"));
129 }
130 let mut parser = Parser::new(input);
131 parser.options = options;
132 parser.references = options.check_references.then(ReferenceCheck::default);
133 parser.parse(sink)?;
134 Ok(parser.diagnostics)
135}
136
137pub fn parse_events_borrowed<'a>(
150 input: &'a [u8],
151 sink: &mut impl EventSink<Cow<'a, str>>,
152 options: ParseOptions,
153) -> Result<Vec<Diagnostic>, StepError> {
154 if !crate::is_step_file(input) {
155 return Err(StepError::not_step("missing ISO-10303-21 marker"));
156 }
157 let mut parser = Parser::new(input);
158 parser.options = options;
159 parser.references = options.check_references.then(ReferenceCheck::default);
160 parser.parse(sink)?;
161 Ok(parser.diagnostics)
162}
163
164trait Text<'a>: Sized {
171 type Names;
173 fn names() -> Self::Names;
175 fn record_names() -> Self::Names;
178 fn name(bytes: Cow<'a, [u8]>, names: &mut Self::Names) -> Self;
180 fn lexeme(bytes: Cow<'a, [u8]>) -> Self;
182 fn text(raw: Cow<'a, [u8]>) -> Self;
184}
185
186pub(crate) type NameTable = Option<HashMap<Box<[u8]>, Str>>;
192
193const INLINE_NAME: usize = 22;
196
197impl<'a> Text<'a> for Str {
198 type Names = NameTable;
199
200 fn names() -> Self::Names {
201 Some(HashMap::new())
202 }
203
204 fn record_names() -> Self::Names {
205 None
206 }
207
208 fn name(bytes: Cow<'a, [u8]>, names: &mut Self::Names) -> Self {
209 let Some(table) = names.as_mut().filter(|_| bytes.len() > INLINE_NAME) else {
210 return upper_str(&bytes);
211 };
212 if let Some(name) = table.get(&*bytes) {
213 return name.clone();
214 }
215 let name = upper_str(&bytes);
216 table.insert(bytes.into_owned().into_boxed_slice(), name.clone());
217 name
218 }
219
220 fn lexeme(bytes: Cow<'a, [u8]>) -> Self {
221 Str::from(&*borrowed_str(bytes))
222 }
223
224 fn text(raw: Cow<'a, [u8]>) -> Self {
225 if memchr::memchr2(b'\\', b'\'', &raw).is_none() {
228 return Str::from(&*borrowed_str(raw));
229 }
230 Str::from(escape::decode(&raw))
231 }
232}
233
234impl<'a> Text<'a> for Cow<'a, str> {
235 type Names = ();
236
237 fn names() -> Self::Names {}
238
239 fn record_names() -> Self::Names {}
240
241 fn name(bytes: Cow<'a, [u8]>, (): &mut Self::Names) -> Self {
242 borrowed_str(bytes)
243 }
244
245 fn lexeme(bytes: Cow<'a, [u8]>) -> Self {
246 borrowed_str(bytes)
247 }
248
249 fn text(raw: Cow<'a, [u8]>) -> Self {
250 match raw {
253 Cow::Borrowed(bytes) if memchr::memchr2(b'\\', b'\'', bytes).is_none() => {
254 borrowed_str(Cow::Borrowed(bytes))
255 }
256 raw => Cow::Owned(escape::decode(&raw)),
257 }
258 }
259}
260
261fn borrowed_str(bytes: Cow<'_, [u8]>) -> Cow<'_, str> {
264 match bytes {
265 Cow::Borrowed(bytes) => match std::str::from_utf8(bytes) {
270 Ok(text) => Cow::Borrowed(text),
271 Err(_) => String::from_utf8_lossy(bytes),
272 },
273 Cow::Owned(bytes) => Cow::Owned(lexeme_string(Cow::Owned(bytes))),
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278enum Phase {
279 BeforeStart,
280 BeforeHeader,
281 Header,
282 BeforeData,
283 Data,
284 BeforeEnd,
285 Done,
286}
287
288struct Parser<'a, S: Text<'a>> {
289 input: &'a [u8],
290 lexer: Lexer<'a>,
291 lookahead: Option<Spanned<Token<'a>>>,
292 last_end: usize,
293 phase: Phase,
294 header_records_seen: usize,
295 options: ParseOptions,
296 diagnostics: Vec<Diagnostic>,
297 references: Option<ReferenceCheck>,
299 stop: Stop,
301 stopped: Option<usize>,
303 record_spans: Option<Vec<Span>>,
305 scratch: Vec<Parameter<S>>,
310 names: S::Names,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub(crate) enum Stop {
318 Never,
320 AfterDataStart,
322 AtOffset(usize),
324}
325
326pub(crate) struct Chunk {
328 pub(crate) records: Vec<DataRecord>,
329 pub(crate) spans: Vec<Span>,
331 pub(crate) diagnostics: Vec<Diagnostic>,
332 pub(crate) aligned: bool,
335}
336
337pub(crate) fn parse_prefix(
343 input: &[u8],
344 options: ParseOptions,
345) -> Result<Option<(HeaderSection, usize)>, StepError> {
346 let mut header = HeaderSection::default();
347 let mut parser = Parser::new(input);
348 parser.options = options;
349 parser.stop = Stop::AfterDataStart;
350 parser.parse(&mut |event: Event| {
351 if let Event::HeaderRecord(record) = event {
352 header.records.push(record);
353 }
354 })?;
355 debug_assert!(parser.diagnostics.is_empty());
356 Ok(parser.stopped.map(|offset| (header, offset)))
357}
358
359pub(crate) fn parse_chunk(
364 input: &[u8],
365 options: ParseOptions,
366 start: usize,
367 end: Option<usize>,
368) -> Result<Chunk, StepError> {
369 let mut records = Vec::new();
370 let mut parser = Parser::new(input);
371 parser.options = options;
372 parser.phase = Phase::Data;
373 parser.enter_data();
374 parser.lexer.resume_at(start);
375 parser.last_end = start;
376 parser.record_spans = Some(Vec::new());
377 parser.stop = end.map_or(Stop::Never, Stop::AtOffset);
378 parser.parse(&mut |event: Event| {
379 if let Event::DataRecord(record) = event {
380 records.push(record);
381 }
382 })?;
383 Ok(Chunk {
384 records,
385 spans: parser.record_spans.take().unwrap_or_default(),
386 diagnostics: parser.diagnostics,
387 aligned: end.is_none() || parser.stopped.is_some(),
388 })
389}
390
391const RECORD_SCRATCH: usize = 32;
394
395fn parse_record_at<'a, S: Text<'a>>(
400 input: &'a [u8],
401 span: Span,
402 options: ParseOptions,
403) -> Result<(DataRecord<S>, Vec<Diagnostic>), StepError> {
404 if span.start > span.end || span.end > input.len() {
405 return Err(StepError::invalid_argument(format!(
406 "record span {}..{} is outside the {}-byte input",
407 span.start,
408 span.end,
409 input.len()
410 )));
411 }
412 let mut parser = Parser::new(input);
413 parser.scratch = Vec::with_capacity(RECORD_SCRATCH);
416 parser.names = S::record_names();
417 parser.options = options;
418 parser.phase = Phase::Data;
419 parser.enter_data();
420 parser.lexer.resume_at(span.start);
421 parser.last_end = span.start;
422 let token = parser
423 .next()?
424 .ok_or_else(|| StepError::syntax(span, "expected a data record"))?;
425 let Token::Id(id) = token.value else {
426 return Err(StepError::syntax(token.span, "expected a data record"));
427 };
428 let record = parser.parse_data_record(&id, token.span)?;
429 if parser.lexer.offset() != span.end {
430 return Err(StepError::syntax(
431 Span::new(span.start, parser.lexer.offset()),
432 "data record does not end at the end of its span",
433 ));
434 }
435 parser.take_reals_without_point();
436 Ok((record, parser.diagnostics))
437}
438
439pub(crate) fn decode_owned(
441 input: &[u8],
442 span: Span,
443 options: ParseOptions,
444) -> Result<(DataRecord, Vec<Diagnostic>), StepError> {
445 parse_record_at(input, span, options)
446}
447
448pub(crate) fn decode_borrowed(
451 input: &[u8],
452 span: Span,
453 options: ParseOptions,
454) -> Result<(DataRecord<Cow<'_, str>>, Vec<Diagnostic>), StepError> {
455 parse_record_at(input, span, options)
456}
457
458impl<'a, S: Text<'a>> Parser<'a, S> {
459 fn new(input: &'a [u8]) -> Self {
460 Self {
461 input,
462 lexer: Lexer::new(input),
463 lookahead: None,
464 last_end: 0,
465 phase: Phase::BeforeStart,
466 header_records_seen: 0,
467 options: ParseOptions::strict(),
468 diagnostics: Vec::new(),
469 references: None,
470 stop: Stop::Never,
471 stopped: None,
472 record_spans: None,
473 scratch: Vec::new(),
474 names: S::names(),
475 }
476 }
477
478 fn enter_data(&mut self) {
481 self.lexer
482 .accept_real_without_point(self.options.accept_real_without_point);
483 }
484
485 #[inline]
488 fn take_reals_without_point(&mut self) {
489 if self.lexer.has_reals_without_point() {
490 self.report_reals_without_point();
491 }
492 }
493
494 #[cold]
495 fn report_reals_without_point(&mut self) {
496 for span in self.lexer.take_reals_without_point() {
497 let token = String::from_utf8_lossy(&self.input[span.start..span.end]);
498 self.diagnostics
499 .push(Diagnostic::real_without_point(span, &token));
500 }
501 }
502
503 #[allow(clippy::too_many_lines)]
505 fn parse(&mut self, sink: &mut impl EventSink<S>) -> Result<(), StepError> {
506 loop {
507 if let Stop::AtOffset(end) = self.stop {
508 if self.lookahead.is_none() {
513 let offset = self.lexer.offset();
514 if offset == end {
515 self.stopped = Some(end);
516 return Ok(());
517 }
518 if offset > end {
519 return Ok(());
520 }
521 }
522 }
523 let token = match self.next() {
524 Ok(Some(token)) => token,
525 Ok(None) => break,
526 Err(error) => {
527 self.recover_or_fail(error, None)?;
530 continue;
531 }
532 };
533 if self.phase == Phase::Done {
534 return Err(StepError::syntax(
535 token.span,
536 "content after END-ISO-10303-21",
537 ));
538 }
539 match token.value {
540 Token::Name(name) if name.eq_ignore_ascii_case(b"ISO-10303-21") => {
541 if self.phase != Phase::BeforeStart {
542 return Err(StepError::syntax(
543 token.span,
544 "unexpected ISO-10303-21 marker",
545 ));
546 }
547 self.expect_semicolon("after ISO-10303-21")?;
548 self.phase = Phase::BeforeHeader;
549 }
550 Token::Name(name) if name.eq_ignore_ascii_case(b"HEADER") => {
551 if self.phase != Phase::BeforeHeader {
552 return Err(StepError::syntax(token.span, "unexpected HEADER section"));
553 }
554 self.expect_semicolon("after HEADER")?;
555 self.phase = Phase::Header;
556 sink.event(Event::StartHeader);
557 }
558 Token::Name(name) if name.eq_ignore_ascii_case(b"DATA") => {
559 if self.phase != Phase::BeforeData {
560 return Err(StepError::syntax(token.span, "unexpected DATA section"));
561 }
562 self.expect_semicolon("after DATA")?;
563 self.phase = Phase::Data;
564 self.enter_data();
565 sink.event(Event::StartData);
566 if self.stop == Stop::AfterDataStart {
567 self.stopped = Some(self.lexer.offset());
568 return Ok(());
569 }
570 }
571 Token::Name(name) if name.eq_ignore_ascii_case(b"ENDSEC") => {
572 self.expect_semicolon("after ENDSEC")?;
573 match self.phase {
574 Phase::Header => {
575 if self.header_records_seen < 3 {
576 return Err(StepError::syntax(
577 token.span,
578 "missing mandatory STEP header record",
579 ));
580 }
581 sink.event(Event::EndHeader);
582 self.phase = Phase::BeforeData;
583 }
584 Phase::Data => {
585 if let Some(check) = self.references.take() {
586 check.finish(&mut self.diagnostics);
587 }
588 sink.event(Event::EndData);
589 self.phase = Phase::BeforeEnd;
590 }
591 _ => {
592 return Err(StepError::syntax(token.span, "ENDSEC outside a section"));
593 }
594 }
595 }
596 Token::Name(name) if name.eq_ignore_ascii_case(b"END-ISO-10303-21") => {
597 if self.phase != Phase::BeforeEnd {
598 return Err(StepError::syntax(
599 token.span,
600 "unexpected END-ISO-10303-21 marker",
601 ));
602 }
603 self.expect_semicolon("after END-ISO-10303-21")?;
604 self.phase = Phase::Done;
605 }
606 Token::Name(name) if self.phase == Phase::Header => {
607 self.validate_header_record(&name, token.span)?;
608 let parameters = self.parse_arguments()?;
609 self.expect_semicolon("after header record")?;
610 sink.event(Event::HeaderRecord(HeaderRecord {
611 name: S::name(name, &mut self.names),
612 parameters,
613 }));
614 }
615 Token::Id(id) if self.phase == Phase::Data => {
616 let start = token.span.start;
617 match self.parse_data_record(&id, token.span) {
618 Ok(record) => {
619 self.take_reals_without_point();
620 if let Some(check) = &mut self.references {
621 let span = Span::new(start, self.last_end);
622 check.record(&record, span, &mut self.diagnostics);
623 }
624 if let Some(spans) = &mut self.record_spans {
625 spans.push(Span::new(start, self.last_end));
626 }
627 sink.event(Event::DataRecord(record));
628 }
629 Err(error) => self.recover_or_fail(error, Some(start))?,
630 }
631 }
632 _ => {
633 let error = StepError::syntax(
634 token.span,
635 format!("unexpected token {:?} in {:?}", token.value, self.phase),
636 );
637 self.recover_or_fail(error, Some(token.span.start))?;
638 }
639 }
640 }
641 if self.phase != Phase::Done {
642 let detail = if matches!(self.phase, Phase::Header | Phase::Data) {
643 "unterminated section (expected ENDSEC)"
644 } else {
645 "physical file requires start, HEADER, DATA, and end markers"
646 };
647 return Err(StepError::syntax(self.eof_span(), detail));
648 }
649 Ok(())
650 }
651
652 fn parse_data_record(&mut self, id: &[u8], id_span: Span) -> Result<DataRecord<S>, StepError> {
654 self.expect_equals()?;
655 let record_token = self.next()?.ok_or_else(|| {
656 StepError::syntax(Span::new(id_span.end, id_span.end), "missing record body")
657 })?;
658 let instance = match record_token.value {
659 Token::Name(name) => Instance::Simple(self.parse_named_record(name)?),
660 Token::OpenParen => {
661 let mut records = Vec::new();
662 loop {
663 if !self
664 .peek()?
665 .is_some_and(|next| next.value != Token::CloseParen)
666 {
667 break;
668 }
669 let component = self.next()?.ok_or_else(|| {
670 StepError::syntax(self.eof_span(), "missing complex record")
671 })?;
672 let Token::Name(name) = component.value else {
673 return Err(StepError::syntax(
674 component.span,
675 "expected complex record name",
676 ));
677 };
678 records.push(self.parse_named_record(name)?);
679 }
680 let close = self.next()?.ok_or_else(|| {
681 StepError::syntax(self.eof_span(), "unterminated complex instance")
682 })?;
683 if close.value != Token::CloseParen {
684 return Err(StepError::syntax(
685 close.span,
686 "expected ')' after complex instance",
687 ));
688 }
689 if records.is_empty() {
690 return Err(StepError::syntax(
691 record_token.span,
692 "complex instance must contain a record",
693 ));
694 }
695 Instance::Complex(records.into_boxed_slice())
696 }
697 _ => {
698 return Err(StepError::syntax(
699 record_token.span,
700 "expected record name or complex instance",
701 ));
702 }
703 };
704 self.expect_semicolon("after data record")?;
705 Ok(DataRecord {
706 id: InstanceId::from_ascii_digits(id).expect("lexer validates instance ids"),
707 instance,
708 })
709 }
710
711 fn recover_or_fail(
720 &mut self,
721 error: StepError,
722 record_start: Option<usize>,
723 ) -> Result<(), StepError> {
724 if self.options.on_malformed_record != OnMalformed::Skip || self.phase != Phase::Data {
725 return Err(error);
726 }
727 let start = record_start.unwrap_or_else(|| error.span().start);
728 self.lexer.discard_reals_without_point();
731 let resume = self.resync_from(start.saturating_add(1));
736 self.lookahead = None;
737 self.lexer.resume_at(resume);
738 self.last_end = resume;
739 self.diagnostics.push(Diagnostic::skipped_record(
740 Span::new(start, resume),
741 format!("skipped malformed data record: {}", error.detail()),
742 ));
743 Ok(())
744 }
745
746 fn resync_from(&self, from: usize) -> usize {
756 enum Literal {
759 None,
760 Text,
762 Binary,
764 }
765
766 let mut position = from.min(self.input.len());
767 let mut literal = Literal::None;
768 while position < self.input.len() {
769 let byte = self.input[position];
770 match literal {
771 Literal::Text => {
772 if self.input[position..].starts_with(br"\\") {
776 position += 2;
777 continue;
778 }
779 if self.input[position..].starts_with(br"\S\") {
780 position = (position + 4).min(self.input.len());
781 continue;
782 }
783 if byte == b'\'' {
784 if self.input.get(position + 1) == Some(&b'\'') {
785 position += 2;
786 continue;
787 }
788 literal = Literal::None;
789 }
790 position += 1;
791 }
792 Literal::Binary => {
793 if byte == b'"' {
794 literal = Literal::None;
795 }
796 position += 1;
797 }
798 Literal::None => match byte {
799 b'\'' => {
800 literal = Literal::Text;
801 position += 1;
802 }
803 b'"' => {
804 literal = Literal::Binary;
805 position += 1;
806 }
807 b'/' if self.input.get(position + 1) == Some(&b'*') => {
808 position = self.input[position + 2..]
809 .windows(2)
810 .position(|window| window == b"*/")
811 .map_or(self.input.len(), |offset| position + 2 + offset + 2);
812 }
813 b';' => return position + 1,
814 _ if self.section_end_at(position) => return position,
815 _ => position += 1,
816 },
817 }
818 }
819 self.input.len()
820 }
821
822 fn section_end_at(&self, position: usize) -> bool {
823 let preceded_by_word = position
824 .checked_sub(1)
825 .and_then(|previous| self.input.get(previous))
826 .is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_');
827 !preceded_by_word
828 && self
829 .input
830 .get(position..position + 6)
831 .is_some_and(|bytes| bytes.eq_ignore_ascii_case(b"ENDSEC"))
832 }
833
834 fn validate_header_record(&mut self, name: &[u8], span: Span) -> Result<(), StepError> {
835 const REQUIRED: [&[u8]; 3] = [b"FILE_DESCRIPTION", b"FILE_NAME", b"FILE_SCHEMA"];
836 if let Some(expected) = REQUIRED.get(self.header_records_seen) {
837 if !name.eq_ignore_ascii_case(expected) {
838 return Err(StepError::syntax(
839 span,
840 format!(
841 "expected mandatory {} header record",
842 String::from_utf8_lossy(expected)
843 ),
844 ));
845 }
846 } else if REQUIRED
847 .iter()
848 .any(|required| name.eq_ignore_ascii_case(required))
849 {
850 return Err(StepError::syntax(span, "duplicate mandatory header record"));
851 }
852 self.header_records_seen += 1;
853 Ok(())
854 }
855
856 fn parse_named_record(&mut self, name: Cow<'a, [u8]>) -> Result<Record<S>, StepError> {
857 Ok(Record {
858 name: S::name(name, &mut self.names),
859 parameters: self.parse_arguments()?,
860 })
861 }
862
863 fn parse_arguments(&mut self) -> Result<Box<[Parameter<S>]>, StepError> {
864 let token = self
865 .next()?
866 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '(' after record name"))?;
867 if token.value != Token::OpenParen {
868 return Err(StepError::syntax(
869 token.span,
870 "expected '(' after record name",
871 ));
872 }
873 self.parse_parameter_list(0)
874 }
875
876 fn parse_parameter_list(&mut self, depth: usize) -> Result<Box<[Parameter<S>]>, StepError> {
878 let base = self.parse_list_onto_scratch(depth)?;
879 Ok(self.scratch.drain(base..).collect())
882 }
883
884 fn parse_list_onto_scratch(&mut self, depth: usize) -> Result<usize, StepError> {
890 let base = self.scratch.len();
891 let result = self.parse_list_items(depth);
892 if result.is_err() {
893 self.scratch.truncate(base);
894 }
895 result.map(|()| base)
896 }
897
898 fn parse_list_items(&mut self, depth: usize) -> Result<(), StepError> {
899 if depth > crate::MAX_PARAMETER_NESTING {
900 let span = match self.peek()? {
901 Some(token) => token.span,
902 None => self.eof_span(),
903 };
904 return Err(StepError::syntax(span, "parameter nesting limit exceeded"));
905 }
906 if self
907 .peek()?
908 .is_some_and(|token| token.value == Token::CloseParen)
909 {
910 let _ = self.next()?;
911 return Ok(());
912 }
913 loop {
914 let parameter = self.parse_parameter(depth)?;
915 self.scratch.push(parameter);
916 let separator = self
917 .next()?
918 .ok_or_else(|| StepError::syntax(self.eof_span(), "unterminated parameter list"))?;
919 match separator.value {
920 Token::Comma => {}
921 Token::CloseParen => return Ok(()),
922 _ => {
923 return Err(StepError::syntax(
924 separator.span,
925 "expected ',' or ')' after parameter",
926 ));
927 }
928 }
929 }
930 }
931
932 fn parse_parameter(&mut self, depth: usize) -> Result<Parameter<S>, StepError> {
933 let token = self
934 .next()?
935 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected parameter"))?;
936 match token.value {
937 Token::Dollar => Ok(Parameter::Null),
938 Token::Star => Ok(Parameter::Derived),
939 Token::Id(id) => Ok(Parameter::Ref(
940 InstanceId::from_ascii_digits(&id).expect("lexer validates instance ids"),
941 )),
942 Token::Integer(value) => Ok(Parameter::Integer(S::lexeme(value))),
943 Token::Real(value) => Ok(Parameter::Real(S::lexeme(value))),
944 Token::Text(raw) => Ok(Parameter::Text(S::text(raw))),
945 Token::Binary(raw) => Ok(Parameter::Binary(S::lexeme(raw))),
946 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"T") => {
947 Ok(Parameter::Bool(true))
948 }
949 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"F") => {
950 Ok(Parameter::Bool(false))
951 }
952 Token::Keyword(keyword) if keyword.eq_ignore_ascii_case(b"U") => {
953 Ok(Parameter::LogicalUnknown)
954 }
955 Token::Keyword(keyword) => Ok(Parameter::Enum(S::name(keyword, &mut self.names))),
956 Token::OpenParen => Ok(Parameter::List(self.parse_parameter_list(depth + 1)?)),
957 Token::Name(name) => {
958 let Some(next) = self.peek()? else {
959 return Err(StepError::syntax(
960 self.eof_span(),
961 "expected '(' after typed parameter name",
962 ));
963 };
964 if next.value != Token::OpenParen {
965 return Err(StepError::syntax(
966 next.span,
967 "expected '(' after typed parameter name",
968 ));
969 }
970 let _ = self.next()?;
971 let base = self.parse_list_onto_scratch(depth + 1)?;
972 let value = if self.scratch.len() == base + 1 {
975 Box::new(self.scratch.pop().expect("the list holds one value"))
976 } else {
977 Box::new(Parameter::List(self.scratch.drain(base..).collect()))
978 };
979 Ok(Parameter::Typed {
980 type_name: S::name(name, &mut self.names),
981 value,
982 })
983 }
984 value => Err(StepError::syntax(
985 token.span,
986 format!("unexpected parameter token {value:?}"),
987 )),
988 }
989 }
990
991 fn expect_equals(&mut self) -> Result<(), StepError> {
992 let token = self
993 .next()?
994 .ok_or_else(|| StepError::syntax(self.eof_span(), "expected '=' after instance id"))?;
995 if token.value == Token::Equals {
996 Ok(())
997 } else {
998 Err(StepError::syntax(
999 token.span,
1000 "expected '=' after instance id",
1001 ))
1002 }
1003 }
1004
1005 fn expect_semicolon(&mut self, context: &str) -> Result<(), StepError> {
1006 let token = self
1007 .next()?
1008 .ok_or_else(|| StepError::syntax(self.eof_span(), format!("expected ';' {context}")))?;
1009 if token.value == Token::Semicolon {
1010 Ok(())
1011 } else {
1012 Err(StepError::syntax(
1013 token.span,
1014 format!("expected ';' {context}"),
1015 ))
1016 }
1017 }
1018
1019 fn next(&mut self) -> Result<Option<Spanned<Token<'a>>>, StepError> {
1020 let token = match self.lookahead.take() {
1021 Some(token) => Some(token),
1022 None => self.lexer.next_spanned()?,
1023 };
1024 if let Some(token) = &token {
1025 self.last_end = token.span.end;
1026 }
1027 Ok(token)
1028 }
1029
1030 fn peek(&mut self) -> Result<Option<&Spanned<Token<'a>>>, StepError> {
1031 if self.lookahead.is_none() {
1032 self.lookahead = self.lexer.next_spanned()?;
1033 }
1034 Ok(self.lookahead.as_ref())
1035 }
1036
1037 fn eof_span(&self) -> Span {
1038 let offset = self.last_end.max(self.lexer.offset());
1039 Span::new(offset, offset)
1040 }
1041}
1042
1043fn upper_str(bytes: &[u8]) -> Str {
1047 let mut buffer = [0u8; 128];
1048 if let Some(stack) = buffer.get_mut(..bytes.len()) {
1049 stack.copy_from_slice(bytes);
1050 stack.make_ascii_uppercase();
1051 if let Ok(text) = std::str::from_utf8(stack) {
1052 return Str::from(text);
1053 }
1054 }
1055 Str::from(String::from_utf8_lossy(bytes).to_ascii_uppercase())
1056}
1057
1058fn lexeme_string(bytes: Cow<'_, [u8]>) -> String {
1061 match bytes {
1062 Cow::Borrowed(bytes) => match std::str::from_utf8(bytes) {
1063 Ok(text) => text.to_owned(),
1064 Err(_) => String::from_utf8_lossy(bytes).into_owned(),
1065 },
1066 Cow::Owned(bytes) => String::from_utf8(bytes)
1067 .unwrap_or_else(|error| String::from_utf8_lossy(error.as_bytes()).into_owned()),
1068 }
1069}