1use std::collections::BTreeMap;
2
3use super::entity::{Attribute, Error, Graph, ParseWarning, RawEntity, RawEntityPart};
4use super::lexer::{Lexer, Span, Token, TokenKind};
5use super::schema::SchemaId;
6
7pub fn parse(source: &str) -> Result<Graph, Error> {
13 Parser::new(source).parse()
14}
15
16pub fn parse_bytes(bytes: &[u8]) -> Result<Graph, Error> {
28 if let Ok(s) = std::str::from_utf8(bytes) {
29 parse(s)
30 } else {
31 let s: String = bytes.iter().map(|&b| b as char).collect();
32 parse(&s)
33 }
34}
35
36pub const MAX_NESTING_DEPTH: usize = 64;
48
49pub struct Parser<'src> {
50 lexer: Lexer<'src>,
51 warnings: Vec<ParseWarning>,
52 depth: usize,
54}
55
56impl<'src> Parser<'src> {
57 #[must_use]
58 pub fn new(source: &'src str) -> Self {
59 Self {
60 lexer: Lexer::new(source),
61 warnings: Vec::new(),
62 depth: 0,
63 }
64 }
65
66 pub fn parse(self) -> Result<Graph, Error> {
72 let mut this = self;
73 this.parse_file()
74 }
75
76 fn parse_file(&mut self) -> Result<Graph, Error> {
81 self.expect_token_kind(&TokenKind::IsoStart, "ISO-10303-21")?;
83 self.expect_semicolon()?;
84
85 self.expect_token_kind(&TokenKind::Header, "HEADER")?;
87 self.expect_semicolon()?;
88 let (header, schema) = self.parse_header_section()?;
89 self.expect_token_kind(&TokenKind::EndSec, "ENDSEC")?;
90 self.expect_semicolon()?;
91
92 let mut external_references: BTreeMap<u64, String> = BTreeMap::new();
97 let mut anchors: Vec<(String, u64)> = Vec::new();
98 while self.peek_is_ed3_section()? {
99 self.parse_or_skip_ed3_section(&mut external_references, &mut anchors)?;
100 }
101
102 self.expect_token_kind(&TokenKind::Data, "DATA")?;
104 self.expect_semicolon()?;
105 let entities = self.parse_data_section()?;
106 self.expect_token_kind(&TokenKind::EndSec, "ENDSEC")?;
107 self.expect_semicolon()?;
108
109 self.expect_token_kind(&TokenKind::IsoEnd, "END-ISO-10303-21")?;
111 self.expect_semicolon()?;
112
113 if let Some(result) = self.lexer.next() {
115 let tok = result?;
116 return Err(Error::UnexpectedToken {
117 expected: "end of file",
118 found: tok.kind,
119 span: tok.span,
120 });
121 }
122
123 Ok(Graph {
124 schema,
125 header,
126 entities,
127 external_references,
128 anchors,
129 warnings: std::mem::take(&mut self.warnings),
130 })
131 }
132
133 fn peek_is_ed3_section(&mut self) -> Result<bool, Error> {
138 Ok(matches!(
139 self.peek_kind()?,
140 TokenKind::Keyword(k)
141 if matches!(k.to_uppercase().as_str(),
142 "ANCHOR" | "REFERENCE" | "SIGNATURE")
143 ))
144 }
145
146 fn parse_or_skip_ed3_section(
150 &mut self,
151 external_references: &mut BTreeMap<u64, String>,
152 anchors: &mut Vec<(String, u64)>,
153 ) -> Result<(), Error> {
154 let tok = self.next_token()?;
155 let section = match &tok.kind {
156 TokenKind::Keyword(k) => k.to_uppercase(),
157 _ => unreachable!("peek_is_ed3_section guarantees a Keyword"),
158 };
159 let span = tok.span;
160 self.expect_semicolon()?;
161
162 let mut discarded = false;
163 while !matches!(self.peek_kind()?, TokenKind::EndSec) {
164 let lhs = self.next_token()?;
169 if !matches!(self.peek_kind()?, TokenKind::Equals) {
170 discarded = true;
171 continue;
172 }
173 self.next_token()?; let rhs = self.next_token()?;
175 match (section.as_str(), lhs.kind, rhs.kind) {
176 ("REFERENCE", TokenKind::EntityRef(id), TokenKind::AnchorRef(s)) => {
177 external_references.insert(id, s);
178 }
179 ("ANCHOR", TokenKind::AnchorRef(name), TokenKind::EntityRef(id)) => {
180 anchors.push((name, id));
181 }
182 _ => discarded = true,
183 }
184 if matches!(self.peek_kind()?, TokenKind::Semicolon) {
186 self.next_token()?;
187 }
188 }
189 self.expect_token_kind(&TokenKind::EndSec, "ENDSEC")?;
190 self.expect_semicolon()?;
191 if discarded || section == "SIGNATURE" {
192 self.warnings
193 .push(ParseWarning::Ed3SectionDiscarded { section, span });
194 }
195 Ok(())
196 }
197
198 fn parse_header_section(&mut self) -> Result<(Vec<RawEntity>, SchemaId), Error> {
208 let mut header = Vec::new();
209 let mut schema_raw = Vec::new();
210
211 let mut pseudo_id = 0u64;
214 while !matches!(self.peek_kind()?, TokenKind::EndSec) {
215 let tok = self.next_token()?;
216 let name = match tok.kind {
217 TokenKind::Keyword(name) => name.to_uppercase(),
218 other => {
219 return Err(Error::UnexpectedToken {
220 expected: "HEADER entity name",
221 found: other,
222 span: tok.span,
223 });
224 }
225 };
226
227 let attributes = self.parse_parameter_list()?;
228
229 if name == "FILE_SCHEMA" {
231 schema_raw = Self::extract_file_schema_strings(&attributes, tok.span)?;
232 }
233
234 match self.peek_kind()? {
239 TokenKind::Semicolon => {
240 self.next_token()?;
241 }
242 TokenKind::Keyword(_) | TokenKind::EndSec => {
243 self.warnings.push(ParseWarning::MissingHeaderSemicolon {
244 entity_name: name.clone(),
245 span: tok.span,
246 });
247 }
248 _ => {
249 self.expect_semicolon()?;
250 }
251 }
252
253 pseudo_id += 1;
254 header.push(RawEntity::Simple {
255 id: pseudo_id,
256 name,
257 attributes,
258 span: tok.span,
259 });
260 }
261
262 let names: Vec<&str> = header
264 .iter()
265 .filter_map(|e| match e {
266 RawEntity::Simple { name, .. } => Some(name.as_str()),
267 RawEntity::Complex { .. } => None,
268 })
269 .collect();
270
271 if !names.contains(&"FILE_DESCRIPTION") {
272 return Err(Error::MissingHeaderEntity {
273 name: "FILE_DESCRIPTION",
274 });
275 }
276 if !names.contains(&"FILE_NAME") {
277 return Err(Error::MissingHeaderEntity { name: "FILE_NAME" });
278 }
279 if !names.contains(&"FILE_SCHEMA") {
280 return Err(Error::MissingHeaderEntity {
281 name: "FILE_SCHEMA",
282 });
283 }
284
285 let schema = super::schema::identify_schema(&schema_raw);
286
287 Ok((header, schema))
288 }
289
290 fn extract_file_schema_strings(
293 attributes: &[Attribute],
294 span: Span,
295 ) -> Result<Vec<String>, Error> {
296 let list = attributes
298 .first()
299 .ok_or(Error::MalformedFileSchema { span })?;
300 match list {
301 Attribute::List(items) => {
302 let mut result = Vec::with_capacity(items.len());
303 for item in items {
304 match item {
305 Attribute::String(s) => result.push(s.clone()),
306 _ => return Err(Error::MalformedFileSchema { span }),
307 }
308 }
309 Ok(result)
310 }
311 _ => Err(Error::MalformedFileSchema { span }),
312 }
313 }
314
315 fn parse_data_section(&mut self) -> Result<BTreeMap<u64, RawEntity>, Error> {
321 let mut entities = BTreeMap::new();
322
323 while !matches!(self.peek_kind()?, TokenKind::EndSec) {
324 let entity = self.parse_entity_instance()?;
325 let id = entity.id();
326 let span = entity.span();
327 if entities.insert(id, entity).is_some() {
328 return Err(Error::DuplicateEntityId { id, span });
329 }
330 }
331
332 Ok(entities)
333 }
334
335 fn parse_entity_instance(&mut self) -> Result<RawEntity, Error> {
341 let id_tok = self.expect(
343 |k| matches!(k, TokenKind::EntityRef(_)),
344 "entity reference #N",
345 )?;
346 let TokenKind::EntityRef(id) = id_tok.kind else {
347 unreachable!()
348 };
349 let span = id_tok.span;
350
351 self.expect_equals()?;
353
354 let kind = self.peek_kind()?.clone();
356 match kind {
357 TokenKind::Keyword(name) => {
358 self.next_token()?; self.parse_simple_body(id, &name, span)
360 }
361 TokenKind::LParen => {
362 self.next_token()?; self.parse_complex_body(id, span)
364 }
365 _ => {
366 let tok = self.next_token()?;
367 Err(Error::UnexpectedToken {
368 expected: "entity type name or '('",
369 found: tok.kind,
370 span: tok.span,
371 })
372 }
373 }
374 }
375
376 fn parse_simple_body(&mut self, id: u64, name: &str, span: Span) -> Result<RawEntity, Error> {
377 let attributes = self.parse_parameter_list()?;
378 self.expect_semicolon()?;
379 Ok(RawEntity::Simple {
380 id,
381 name: name.to_uppercase(),
382 attributes,
383 span,
384 })
385 }
386
387 fn parse_complex_body(&mut self, id: u64, span: Span) -> Result<RawEntity, Error> {
388 let mut parts = Vec::new();
389
390 if matches!(self.peek_kind()?, TokenKind::RParen) {
392 let tok = self.next_token()?;
393 return Err(Error::UnexpectedToken {
394 expected: "entity type name",
395 found: tok.kind,
396 span: tok.span,
397 });
398 }
399
400 loop {
401 let kind = self.peek_kind()?.clone();
402 match kind {
403 TokenKind::Keyword(name) => {
404 self.next_token()?; let attributes = self.parse_parameter_list()?;
406 parts.push(RawEntityPart {
407 name: name.to_uppercase(),
408 attributes,
409 });
410 }
411 TokenKind::RParen => {
412 self.next_token()?; break;
414 }
415 _ => {
416 let tok = self.next_token()?;
417 return Err(Error::UnexpectedToken {
418 expected: "entity type name or ')'",
419 found: tok.kind,
420 span: tok.span,
421 });
422 }
423 }
424 }
425
426 self.expect_semicolon()?;
427 Ok(RawEntity::Complex { id, parts, span })
428 }
429
430 fn parse_parameter_list(&mut self) -> Result<Vec<Attribute>, Error> {
438 self.expect_lparen()?;
439 let attrs = self.parse_list_value()?;
440 self.expect_rparen()?;
441 Ok(attrs)
442 }
443
444 fn parse_list_value(&mut self) -> Result<Vec<Attribute>, Error> {
447 let mut items = Vec::new();
448 if matches!(self.peek_kind()?, TokenKind::RParen) {
450 return Ok(items);
451 }
452 items.push(self.parse_parameter_or_unset_if_empty()?);
453 while matches!(self.peek_kind()?, TokenKind::Comma) {
454 self.next_token()?;
456 items.push(self.parse_parameter_or_unset_if_empty()?);
457 }
458 Ok(items)
459 }
460
461 fn parse_parameter_or_unset_if_empty(&mut self) -> Result<Attribute, Error> {
467 if matches!(self.peek_kind()?, TokenKind::Comma | TokenKind::RParen) {
468 let span = self.peek_span().unwrap_or(Span {
469 start: 0,
470 end: 0,
471 line: 0,
472 column: 0,
473 });
474 self.warnings.push(ParseWarning::EmptyAttribute { span });
475 return Ok(Attribute::Unset);
476 }
477 self.parse_parameter()
478 }
479
480 fn peek_span(&mut self) -> Option<Span> {
484 match self.lexer.peek() {
485 Some(Ok(tok)) => Some(tok.span),
486 _ => None,
487 }
488 }
489
490 fn parse_parameter(&mut self) -> Result<Attribute, Error> {
496 self.depth += 1;
497 if self.depth > MAX_NESTING_DEPTH {
498 self.depth -= 1;
499 let span = self.peek_span().unwrap_or(Span {
500 start: 0,
501 end: 0,
502 line: 0,
503 column: 0,
504 });
505 return Err(Error::NestingTooDeep { span });
506 }
507 let r = self.parse_parameter_inner();
508 self.depth -= 1;
509 r
510 }
511
512 fn parse_parameter_inner(&mut self) -> Result<Attribute, Error> {
513 let kind = self.peek_kind()?.clone();
514 match kind {
515 TokenKind::Integer(v) => {
516 self.next_token()?;
517 Ok(Attribute::Integer(v))
518 }
519 TokenKind::Real(v) => {
520 self.next_token()?;
521 Ok(Attribute::Real(v))
522 }
523 TokenKind::String(ref s) => {
524 let s = s.clone();
525 self.next_token()?;
526 Ok(Attribute::String(s))
527 }
528 TokenKind::Enum(ref s) => {
529 let s = s.clone();
530 self.next_token()?;
531 Ok(Attribute::Enum(s))
532 }
533 TokenKind::Binary(ref s) => {
534 let s = s.clone();
535 self.next_token()?;
536 Ok(Attribute::Binary(s))
537 }
538 TokenKind::EntityRef(id) => {
539 self.next_token()?;
540 Ok(Attribute::EntityRef(id))
541 }
542 TokenKind::Dollar => {
543 self.next_token()?;
544 Ok(Attribute::Unset)
545 }
546 TokenKind::Asterisk => {
547 self.next_token()?;
548 Ok(Attribute::Derived)
549 }
550 TokenKind::LParen => {
551 self.next_token()?; let items = self.parse_list_value()?;
554 self.expect_rparen()?;
555 Ok(Attribute::List(items))
556 }
557 TokenKind::Keyword(ref name) => {
558 let type_name = name.to_uppercase();
560 self.next_token()?; self.expect_lparen()?;
562 let value = self.parse_parameter()?;
563 self.expect_rparen()?;
564 Ok(Attribute::Typed {
565 type_name,
566 value: Box::new(value),
567 })
568 }
569 _ => {
570 let tok = self.next_token()?;
571 Err(Error::InvalidAttributePosition {
572 span: tok.span,
573 detail: "unexpected token in attribute position",
574 })
575 }
576 }
577 }
578
579 fn next_token(&mut self) -> Result<Token, Error> {
586 match self.lexer.next() {
587 Some(result) => Ok(result?),
588 None => Err(Error::UnexpectedEof {
589 expected: "any token",
590 }),
591 }
592 }
593
594 fn peek_kind(&mut self) -> Result<&TokenKind, Error> {
596 match self.lexer.peek() {
597 Some(Ok(tok)) => Ok(&tok.kind),
598 Some(Err(err)) => Err(Error::Lex(err.clone())),
599 None => Err(Error::UnexpectedEof {
600 expected: "any token",
601 }),
602 }
603 }
604
605 fn expect<F>(&mut self, pred: F, expected: &'static str) -> Result<Token, Error>
607 where
608 F: Fn(&TokenKind) -> bool,
609 {
610 let tok = self.next_token()?;
611 if pred(&tok.kind) {
612 Ok(tok)
613 } else {
614 Err(Error::UnexpectedToken {
615 expected,
616 found: tok.kind,
617 span: tok.span,
618 })
619 }
620 }
621
622 fn expect_token_kind(
625 &mut self,
626 kind: &TokenKind,
627 expected: &'static str,
628 ) -> Result<Token, Error> {
629 self.expect(
630 |k| std::mem::discriminant(k) == std::mem::discriminant(kind),
631 expected,
632 )
633 }
634
635 fn expect_semicolon(&mut self) -> Result<Span, Error> {
637 Ok(self
638 .expect(|k| matches!(k, TokenKind::Semicolon), ";")?
639 .span)
640 }
641
642 fn expect_lparen(&mut self) -> Result<Span, Error> {
644 Ok(self.expect(|k| matches!(k, TokenKind::LParen), "(")?.span)
645 }
646
647 fn expect_rparen(&mut self) -> Result<Span, Error> {
649 Ok(self.expect(|k| matches!(k, TokenKind::RParen), ")")?.span)
650 }
651
652 fn expect_equals(&mut self) -> Result<Span, Error> {
654 Ok(self.expect(|k| matches!(k, TokenKind::Equals), "=")?.span)
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn parse_empty_input_errors() {
664 let err = parse("").unwrap_err();
665 assert!(matches!(err, Error::UnexpectedEof { .. }));
666 }
667
668 #[test]
669 fn parser_next_token_returns_eof_on_empty() {
670 let mut parser = Parser::new("");
671 assert!(matches!(
672 parser.next_token(),
673 Err(Error::UnexpectedEof { .. })
674 ));
675 }
676
677 #[test]
678 fn parser_peek_kind_returns_eof_on_empty() {
679 let mut parser = Parser::new("");
680 assert!(matches!(
681 parser.peek_kind(),
682 Err(Error::UnexpectedEof { .. })
683 ));
684 }
685
686 #[test]
687 fn parser_expect_semicolon_ok() {
688 let mut parser = Parser::new(";");
689 assert!(parser.expect_semicolon().is_ok());
690 }
691
692 #[test]
693 fn parser_expect_semicolon_wrong_token() {
694 let mut parser = Parser::new("(");
695 let err = parser.expect_semicolon().unwrap_err();
696 assert!(matches!(err, Error::UnexpectedToken { expected: ";", .. }));
697 }
698
699 fn parse_single_attr(param_text: &str) -> Attribute {
703 let mut parser = Parser::new(param_text);
704 parser
705 .parse_parameter()
706 .unwrap_or_else(|e| panic!("parse_parameter failed on {param_text:?}: {e}"))
707 }
708
709 #[test]
712 fn parse_param_integer() {
713 assert_eq!(parse_single_attr("42"), Attribute::Integer(42));
714 }
715
716 #[test]
717 fn parse_param_real() {
718 assert_eq!(parse_single_attr("1.5"), Attribute::Real(1.5));
719 }
720
721 #[test]
722 fn parse_param_real_exponent() {
723 assert_eq!(parse_single_attr("1.E-07"), Attribute::Real(1e-7));
724 }
725
726 #[test]
727 fn parse_param_string() {
728 assert_eq!(
729 parse_single_attr("'hello'"),
730 Attribute::String("hello".into())
731 );
732 }
733
734 #[test]
735 fn parse_param_enum() {
736 assert_eq!(
737 parse_single_attr(".MILLI."),
738 Attribute::Enum("MILLI".into())
739 );
740 }
741
742 #[test]
743 fn parse_param_binary() {
744 assert_eq!(
745 parse_single_attr("\"3FFA\""),
746 Attribute::Binary("3FFA".into())
747 );
748 }
749
750 #[test]
751 fn parse_param_entity_ref() {
752 assert_eq!(parse_single_attr("#42"), Attribute::EntityRef(42));
753 }
754
755 #[test]
756 fn parse_param_unset() {
757 assert_eq!(parse_single_attr("$"), Attribute::Unset);
758 }
759
760 #[test]
761 fn parse_param_derived() {
762 assert_eq!(parse_single_attr("*"), Attribute::Derived);
763 }
764
765 #[test]
768 fn parse_param_empty_list() {
769 assert_eq!(parse_single_attr("()"), Attribute::List(vec![]));
770 }
771
772 #[test]
773 fn parse_param_flat_list() {
774 assert_eq!(
775 parse_single_attr("(1,2,3)"),
776 Attribute::List(vec![
777 Attribute::Integer(1),
778 Attribute::Integer(2),
779 Attribute::Integer(3),
780 ])
781 );
782 }
783
784 #[test]
785 fn parse_param_nested_list() {
786 assert_eq!(
787 parse_single_attr("((#1),(#2,#3))"),
788 Attribute::List(vec![
789 Attribute::List(vec![Attribute::EntityRef(1)]),
790 Attribute::List(vec![Attribute::EntityRef(2), Attribute::EntityRef(3)]),
791 ])
792 );
793 }
794
795 #[test]
796 fn parse_param_enum_list() {
797 assert_eq!(
798 parse_single_attr("(.T.,.F.)"),
799 Attribute::List(vec![
800 Attribute::Enum("T".into()),
801 Attribute::Enum("F".into()),
802 ])
803 );
804 }
805
806 #[test]
809 fn parse_param_typed_real() {
810 assert_eq!(
811 parse_single_attr("LENGTH_MEASURE(1.E-07)"),
812 Attribute::Typed {
813 type_name: "LENGTH_MEASURE".into(),
814 value: Box::new(Attribute::Real(1e-7)),
815 }
816 );
817 }
818
819 #[test]
820 fn parse_param_typed_positive_length() {
821 assert_eq!(
822 parse_single_attr("POSITIVE_LENGTH_MEASURE(0.1)"),
823 Attribute::Typed {
824 type_name: "POSITIVE_LENGTH_MEASURE".into(),
825 value: Box::new(Attribute::Real(0.1)),
826 }
827 );
828 }
829
830 #[test]
831 fn parse_param_typed_of_list() {
832 assert_eq!(
833 parse_single_attr("BOUNDED_CURVE((#1,#2))"),
834 Attribute::Typed {
835 type_name: "BOUNDED_CURVE".into(),
836 value: Box::new(Attribute::List(vec![
837 Attribute::EntityRef(1),
838 Attribute::EntityRef(2),
839 ])),
840 }
841 );
842 }
843
844 #[test]
845 fn parse_param_typed_nested() {
846 assert_eq!(
848 parse_single_attr("TYPE1(TYPE2(42))"),
849 Attribute::Typed {
850 type_name: "TYPE1".into(),
851 value: Box::new(Attribute::Typed {
852 type_name: "TYPE2".into(),
853 value: Box::new(Attribute::Integer(42)),
854 }),
855 }
856 );
857 }
858
859 fn parse_entity(src: &str) -> RawEntity {
862 let mut parser = Parser::new(src);
863 parser
864 .parse_entity_instance()
865 .unwrap_or_else(|e| panic!("parse_entity_instance failed: {e}"))
866 }
867
868 #[test]
869 fn parse_simple_entity() {
870 let e = parse_entity("#1 = LINE('', #2, #3);");
871 match e {
872 RawEntity::Simple {
873 id,
874 name,
875 attributes,
876 ..
877 } => {
878 assert_eq!(id, 1);
879 assert_eq!(name, "LINE");
880 assert_eq!(attributes.len(), 3);
881 assert_eq!(attributes[0], Attribute::String(String::new()));
882 assert_eq!(attributes[1], Attribute::EntityRef(2));
883 assert_eq!(attributes[2], Attribute::EntityRef(3));
884 }
885 RawEntity::Complex { .. } => panic!("expected Simple"),
886 }
887 }
888
889 #[test]
890 fn parse_simple_entity_with_derived_and_unset() {
891 let e = parse_entity("#20 = ORIENTED_EDGE('',*,*,#21,.T.);");
892 match e {
893 RawEntity::Simple { id, attributes, .. } => {
894 assert_eq!(id, 20);
895 assert_eq!(attributes[0], Attribute::String(String::new()));
896 assert_eq!(attributes[1], Attribute::Derived);
897 assert_eq!(attributes[2], Attribute::Derived);
898 assert_eq!(attributes[3], Attribute::EntityRef(21));
899 assert_eq!(attributes[4], Attribute::Enum("T".into()));
900 }
901 RawEntity::Complex { .. } => panic!("expected Simple"),
902 }
903 }
904
905 #[test]
906 fn parse_simple_entity_name_is_uppercased() {
907 let e = parse_entity("#1 = cartesian_point('', (0., 0., 0.));");
908 match e {
909 RawEntity::Simple { name, .. } => {
910 assert_eq!(name, "CARTESIAN_POINT");
911 }
912 RawEntity::Complex { .. } => panic!("expected Simple"),
913 }
914 }
915
916 #[test]
917 fn parse_complex_entity_four_parts() {
918 let src = "#165 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) \
919 GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#169)) \
920 GLOBAL_UNIT_ASSIGNED_CONTEXT((#166,#167,#168)) \
921 REPRESENTATION_CONTEXT('a','b') );";
922 let e = parse_entity(src);
923 match e {
924 RawEntity::Complex { id, parts, .. } => {
925 assert_eq!(id, 165);
926 assert_eq!(parts.len(), 4);
927 assert_eq!(parts[0].name, "GEOMETRIC_REPRESENTATION_CONTEXT");
928 assert_eq!(parts[1].name, "GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT");
929 assert_eq!(parts[2].name, "GLOBAL_UNIT_ASSIGNED_CONTEXT");
930 assert_eq!(parts[3].name, "REPRESENTATION_CONTEXT");
931 assert_eq!(parts[0].attributes, vec![Attribute::Integer(3)]);
932 assert_eq!(parts[3].attributes.len(), 2);
933 }
934 RawEntity::Simple { .. } => panic!("expected Complex"),
935 }
936 }
937
938 #[test]
939 fn parse_complex_entity_three_parts_mixed_attrs() {
940 let src = "#166 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );";
941 let e = parse_entity(src);
942 match e {
943 RawEntity::Complex { id, parts, .. } => {
944 assert_eq!(id, 166);
945 assert_eq!(parts.len(), 3);
946 assert_eq!(parts[0].name, "LENGTH_UNIT");
947 assert_eq!(parts[0].attributes, vec![]);
948 assert_eq!(parts[1].name, "NAMED_UNIT");
949 assert_eq!(parts[1].attributes, vec![Attribute::Derived]);
950 assert_eq!(parts[2].name, "SI_UNIT");
951 assert_eq!(parts[2].attributes.len(), 2);
952 }
953 RawEntity::Simple { .. } => panic!("expected Complex"),
954 }
955 }
956
957 #[test]
958 fn parse_complex_entity_name_uppercased() {
959 let src = "#1 = ( length_unit() named_unit(*) );";
960 let e = parse_entity(src);
961 match e {
962 RawEntity::Complex { parts, .. } => {
963 assert_eq!(parts[0].name, "LENGTH_UNIT");
964 assert_eq!(parts[1].name, "NAMED_UNIT");
965 }
966 RawEntity::Simple { .. } => panic!("expected Complex"),
967 }
968 }
969
970 #[test]
971 fn parse_empty_complex_entity_errors() {
972 let mut parser = Parser::new("#1 = ( );");
973 let err = parser.parse_entity_instance().unwrap_err();
974 assert!(matches!(
975 err,
976 Error::UnexpectedToken {
977 expected: "entity type name",
978 ..
979 }
980 ));
981 }
982
983 #[test]
984 fn parse_entity_missing_semicolon_errors() {
985 let mut parser = Parser::new("#1 = LINE('', #2)");
986 let err = parser.parse_entity_instance().unwrap_err();
987 assert!(matches!(err, Error::UnexpectedEof { .. }));
988 }
989
990 fn minimal_step_with_string_attr(s_bytes: &[u8]) -> Vec<u8> {
993 let prefix = b"ISO-10303-21;\n\
994 HEADER;\n\
995 FILE_DESCRIPTION((' ',";
996 let mid = b"),'2;1');\n\
997 FILE_NAME('n','t',(' '),(' '),'p','o','a');\n\
998 FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\n\
999 ENDSEC;\n\
1000 DATA;\n\
1001 ENDSEC;\n\
1002 END-ISO-10303-21;\n";
1003 let mut buf = Vec::with_capacity(prefix.len() + s_bytes.len() + mid.len() + 2);
1004 buf.extend_from_slice(prefix);
1005 buf.push(b'\'');
1006 buf.extend_from_slice(s_bytes);
1007 buf.push(b'\'');
1008 buf.extend_from_slice(mid);
1009 buf
1010 }
1011
1012 #[test]
1013 fn parse_bytes_accepts_latin1_byte_via_fallback() {
1014 let buf = minimal_step_with_string_attr(&[0xC0]);
1017 assert!(
1018 std::str::from_utf8(&buf).is_err(),
1019 "fixture must be invalid UTF-8 to exercise the fallback"
1020 );
1021 let graph = parse_bytes(&buf).expect("parse_bytes must accept Latin-1 fallback");
1022 let RawEntity::Simple {
1026 name, attributes, ..
1027 } = &graph.header[0]
1028 else {
1029 panic!("expected Simple HEADER entity");
1030 };
1031 assert_eq!(name, "FILE_DESCRIPTION");
1032 let Attribute::List(items) = &attributes[0] else {
1033 panic!("FILE_DESCRIPTION attr[0] must be a list");
1034 };
1035 let Attribute::String(s) = &items[1] else {
1036 panic!("expected String attribute");
1037 };
1038 assert_eq!(s.chars().count(), 1);
1039 assert_eq!(s.chars().next().unwrap(), '\u{00C0}');
1040 }
1041
1042 #[test]
1043 fn parse_bytes_passes_through_valid_utf8() {
1044 let buf = minimal_step_with_string_attr("À".as_bytes());
1047 assert!(std::str::from_utf8(&buf).is_ok());
1048 let graph = parse_bytes(&buf).expect("valid UTF-8 must parse");
1049 let RawEntity::Simple { attributes, .. } = &graph.header[0] else {
1050 panic!();
1051 };
1052 let Attribute::List(items) = &attributes[0] else {
1053 panic!();
1054 };
1055 let Attribute::String(s) = &items[1] else {
1056 panic!();
1057 };
1058 assert_eq!(s, "\u{00C0}");
1059 }
1060
1061 #[test]
1064 fn parse_records_ed3_anchor_and_reference_sections() {
1065 let src = "ISO-10303-21;\n\
1066 HEADER;\n\
1067 FILE_DESCRIPTION((''),'2;1');\n\
1068 FILE_NAME('n','t',(''),(''),'p','o','a');\n\
1069 FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\n\
1070 ENDSEC;\n\
1071 ANCHOR;\n\
1072 <ParentAnchor>=#123;\n\
1073 ENDSEC;\n\
1074 REFERENCE;\n\
1075 #123=<testAnchorAndData.stp#TestAnchor>;\n\
1076 ENDSEC;\n\
1077 DATA;\n\
1078 #1=CIRCULAR_AREA('testarea',#123,2.);\n\
1079 ENDSEC;\n\
1080 END-ISO-10303-21;\n";
1081 let graph = parse(src).expect("ed.3 ANCHOR/REFERENCE must be tolerated");
1082 assert_eq!(graph.entities.len(), 1);
1083 assert_eq!(
1085 graph.external_references.get(&123).map(String::as_str),
1086 Some("<testAnchorAndData.stp#TestAnchor>")
1087 );
1088 assert_eq!(graph.anchors, vec![("<ParentAnchor>".to_string(), 123)]);
1090 assert!(
1092 !graph
1093 .warnings
1094 .iter()
1095 .any(|w| matches!(w, ParseWarning::Ed3SectionDiscarded { .. }))
1096 );
1097 }
1098
1099 #[test]
1100 fn parse_discards_unrecognised_ed3_signature_section() {
1101 let src = "ISO-10303-21;\n\
1102 HEADER;\n\
1103 FILE_DESCRIPTION((''),'2;1');\n\
1104 FILE_NAME('n','t',(''),(''),'p','o','a');\n\
1105 FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\n\
1106 ENDSEC;\n\
1107 SIGNATURE;\n\
1108 some opaque signature payload;\n\
1109 ENDSEC;\n\
1110 DATA;\n\
1111 #1 = CARTESIAN_POINT('',(0.,0.,0.));\n\
1112 ENDSEC;\n\
1113 END-ISO-10303-21;\n";
1114 let graph = parse(src).expect("ed.3 SIGNATURE must be tolerated");
1115 assert_eq!(graph.entities.len(), 1);
1116 assert!(
1117 graph
1118 .warnings
1119 .iter()
1120 .any(|w| matches!(w, ParseWarning::Ed3SectionDiscarded { section, .. } if section == "SIGNATURE"))
1121 );
1122 }
1123
1124 #[test]
1125 fn parse_warns_on_missing_header_semicolon() {
1126 let src = "ISO-10303-21;\n\
1128 HEADER;\n\
1129 FILE_DESCRIPTION((''),'2;1')\n\
1130 FILE_NAME('n','t',(''),(''),'p','o','a');\n\
1131 FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\n\
1132 ENDSEC;\n\
1133 DATA;\n\
1134 ENDSEC;\n\
1135 END-ISO-10303-21;\n";
1136 let graph = parse(src).expect("missing `;` must be tolerated");
1137 assert!(graph.warnings.iter().any(|w| matches!(
1138 w,
1139 ParseWarning::MissingHeaderSemicolon { entity_name, .. }
1140 if entity_name == "FILE_DESCRIPTION"
1141 )));
1142 }
1143
1144 #[test]
1145 fn parse_normalises_empty_attribute_to_unset_with_warning() {
1146 let src = "ISO-10303-21;\n\
1148 HEADER;\n\
1149 FILE_DESCRIPTION((''),'2;1');\n\
1150 FILE_NAME('n','t',(''),(''),'p','o','a');\n\
1151 FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\n\
1152 ENDSEC;\n\
1153 DATA;\n\
1154 #1 = EDGE_CURVE('', #2, #3, #0, );\n\
1155 #2 = VERTEX_POINT('', #4);\n\
1156 #3 = VERTEX_POINT('', #4);\n\
1157 #4 = CARTESIAN_POINT('',(0.,0.,0.));\n\
1158 ENDSEC;\n\
1159 END-ISO-10303-21;\n";
1160 let graph = parse(src).expect("empty attribute slot must be tolerated");
1161 let RawEntity::Simple { attributes, .. } = &graph.entities[&1] else {
1163 panic!("expected Simple entity");
1164 };
1165 assert_eq!(attributes.len(), 5);
1166 assert!(matches!(attributes[4], Attribute::Unset));
1167 assert!(
1168 graph
1169 .warnings
1170 .iter()
1171 .any(|w| matches!(w, ParseWarning::EmptyAttribute { .. }))
1172 );
1173 }
1174}