use super::*;
#[test]
#[allow(clippy::approx_constant)]
fn test_basic_tokens() {
type Parser = for<'a> fn(&'a [u8]) -> IResult<&'a [u8], Token<'a>>;
let cases: &[(Parser, &[u8], Token)] = &[
(entity_ref, b"#123", Token::EntityRef(123)),
(entity_ref, b"#0", Token::EntityRef(0)),
(string_literal, b"'hello'", Token::String(b"hello")),
(
string_literal,
b"'with spaces'",
Token::String(b"with spaces"),
),
(integer, b"42", Token::Integer(42)),
(integer, b"-42", Token::Integer(-42)),
(integer, b"0", Token::Integer(0)),
(float, b"3.14", Token::Float(3.14)),
(float, b"-3.14", Token::Float(-3.14)),
(float, b"1.5E-10", Token::Float(1.5e-10)),
(enum_value, b".TRUE.", Token::Enum(b"TRUE")),
(enum_value, b".FALSE.", Token::Enum(b"FALSE")),
(enum_value, b".ELEMENT.", Token::Enum(b"ELEMENT")),
];
for (parse, input, expected) in cases {
assert_eq!(
parse(input),
Ok((&b""[..], expected.clone())),
"tokenizing {input:?}"
);
}
}
#[test]
fn test_list() {
let result = list(b"(1,2,3)");
assert!(result.is_ok());
let (_, token) = result.unwrap();
match token {
Token::List(items) => {
assert_eq!(items.len(), 3);
assert_eq!(items[0], Token::Integer(1));
assert_eq!(items[1], Token::Integer(2));
assert_eq!(items[2], Token::Integer(3));
}
_ => panic!("Expected List token"),
}
}
#[test]
fn test_nested_list() {
let result = list(b"(1,(2,3),4)");
assert!(result.is_ok());
let (_, token) = result.unwrap();
match token {
Token::List(items) => {
assert_eq!(items.len(), 3);
assert_eq!(items[0], Token::Integer(1));
match &items[1] {
Token::List(inner) => {
assert_eq!(inner.len(), 2);
assert_eq!(inner[0], Token::Integer(2));
assert_eq!(inner[1], Token::Integer(3));
}
_ => panic!("Expected nested List"),
}
assert_eq!(items[2], Token::Integer(4));
}
_ => panic!("Expected List token"),
}
}
#[test]
fn test_parse_entity() {
let input = "#123=IFCWALL('guid','owner',$,$,'name',$,$,$);";
let result = parse_entity(input);
assert!(result.is_ok());
let (id, ifc_type, args) = result.unwrap();
assert_eq!(id, 123);
assert_eq!(ifc_type, IfcType::IfcWall);
assert_eq!(args.len(), 8);
}
#[test]
fn test_parse_entity_with_nested_list() {
let simple = "(0.,0.,1.)";
println!("Testing simple list: {}", simple);
let simple_result = list(simple.as_bytes());
println!("Simple list result: {:?}", simple_result);
let input = "#9=IFCDIRECTION((0.,0.,1.));";
println!("\nTesting full entity: {}", input);
let result = parse_entity(input);
if let Err(ref e) = result {
println!("Parse error: {:?}", e);
println!("\nTrying to parse just arguments: ((0.,0.,1.))");
let args_input = "((0.,0.,1.))";
let args_result = list(args_input.as_bytes());
println!("Args list result: {:?}", args_result);
}
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (id, _ifc_type, args) = result.unwrap();
assert_eq!(id, 9);
assert_eq!(args.len(), 1);
if let Token::List(inner) = &args[0] {
assert_eq!(inner.len(), 3);
} else {
panic!("Expected Token::List, got {:?}", args[0]);
}
}
#[test]
fn test_parse_entity_rejects_excessive_nesting() {
let n = (MAX_NESTING_DEPTH as usize) + 64;
let mut s = String::from("#1=IFCWALL(");
for _ in 0..n {
s.push('(');
}
s.push('1');
for _ in 0..n {
s.push(')');
}
s.push_str(");");
assert!(parse_entity(&s).is_err());
}
#[test]
fn test_parse_entity_accepts_moderate_nesting() {
let n = 32;
let mut s = String::from("#1=IFCWALL(");
for _ in 0..n {
s.push('(');
}
s.push('1');
for _ in 0..n {
s.push(')');
}
s.push_str(");");
assert!(parse_entity(&s).is_ok());
}
fn nested(n: usize) -> String {
let mut s = String::from("#1=IFCWALL(");
for _ in 0..n {
s.push('(');
}
s.push('1');
for _ in 0..n {
s.push(')');
}
s.push_str(");");
s
}
#[test]
fn test_parse_entity_accepts_exactly_max_nesting() {
assert!(parse_entity(&nested(MAX_NESTING_DEPTH as usize)).is_ok());
}
#[test]
fn test_parse_entity_rejects_one_over_max_nesting() {
assert!(parse_entity(&nested(MAX_NESTING_DEPTH as usize + 1)).is_err());
}
#[test]
fn comment_before_a_value_is_not_part_of_the_value() {
let input = "#1=IFCWALL('a', /* rev; b */ $);";
let (_, _, args) = parse_entity(input).expect("comment is trivia, not a parse failure");
assert_eq!(args, vec![Token::String(b"a"), Token::Null]);
}
#[test]
fn comment_before_dollar_still_decodes_as_null() {
let input = "#1=IFCWALL(/* c1 */ $);";
let (_, _, args) = parse_entity(input).unwrap();
assert_eq!(args, vec![Token::Null]);
}
#[test]
fn comma_inside_a_comment_does_not_separate_attributes() {
let input = "#1=IFCWALL(/* x, y */ 5);";
let (_, _, args) = parse_entity(input).unwrap();
assert_eq!(args, vec![Token::Integer(5)]);
}
#[test]
fn slash_star_inside_a_string_is_unchanged() {
let input = "#1=IFCWALL('has /* not a comment */ text');";
let (_, _, args) = parse_entity(input).unwrap();
assert_eq!(args, vec![Token::String(b"has /* not a comment */ text")]);
}
#[test]
fn comment_free_entity_decodes_unchanged() {
let input = "#123=IFCWALL('guid','owner',$,$,'name',$,$,$);";
let (id, ifc_type, args) = parse_entity(input).unwrap();
assert_eq!(id, 123);
assert_eq!(ifc_type, IfcType::IfcWall);
assert_eq!(args.len(), 8);
}
#[test]
fn test_typed_value_tolerates_crlf_before_paren() {
let result = typed_value_at_depth(b"IFCPOSITIVELENGTHMEASURE\r\n(1.);", 0);
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (rest, token) = result.unwrap();
assert_eq!(rest, b";");
match token {
Token::TypedValue(type_name, args) => {
assert_eq!(type_name, b"IFCPOSITIVELENGTHMEASURE");
assert_eq!(args, vec![Token::Float(1.0)]);
}
_ => panic!("Expected TypedValue token"),
}
}
#[test]
fn test_parse_entity_typed_value_wrapped_with_crlf() {
let input = "#42=IFCSURFACESTYLERENDERING($,IFCPOSITIVELENGTHMEASURE\r\n(1.),$,$,$,$,$,$,.NOTDEFINED.);";
let result = parse_entity(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (id, ifc_type, args) = result.unwrap();
assert_eq!(id, 42);
assert_eq!(ifc_type, IfcType::IfcSurfaceStyleRendering);
assert_eq!(args.len(), 9);
match &args[1] {
Token::TypedValue(type_name, inner) => {
assert_eq!(*type_name, b"IFCPOSITIVELENGTHMEASURE");
assert_eq!(*inner, vec![Token::Float(1.0)]);
}
other => panic!("Expected TypedValue token, got {:?}", other),
}
}
#[test]
fn test_parse_entity_empty_args_with_whitespace() {
let input = "#1=IFCX(\r\n);";
let result = parse_entity(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (id, _ifc_type, args) = result.unwrap();
assert_eq!(id, 1);
assert_eq!(args.len(), 0);
}
#[test]
fn test_parse_entity_nested_empty_list_with_whitespace() {
let input = "#1=IFCTABLE('a',( ));";
let result = parse_entity(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (id, _ifc_type, args) = result.unwrap();
assert_eq!(id, 1);
assert_eq!(args.len(), 2);
match &args[1] {
Token::List(items) => assert_eq!(items.len(), 0),
other => panic!("Expected empty List token, got {:?}", other),
}
}
#[test]
fn test_parse_entity_nested_typed_value_empty_args_with_whitespace() {
let input = "#1=IFCX(IFCLABEL(\r\n));";
let result = parse_entity(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (id, _ifc_type, args) = result.unwrap();
assert_eq!(id, 1);
assert_eq!(args.len(), 1);
match &args[0] {
Token::TypedValue(type_name, inner) => {
assert_eq!(*type_name, b"IFCLABEL");
assert_eq!(inner.len(), 0);
}
other => panic!("Expected TypedValue token, got {:?}", other),
}
}
#[test]
fn test_list_empty_with_comment_only() {
let result = list(b"(/* empty */)");
assert!(result.is_ok(), "Failed to parse: {:?}", result);
let (rest, token) = result.unwrap();
assert_eq!(rest, b"");
match token {
Token::List(items) => assert_eq!(items.len(), 0),
other => panic!("Expected empty List token, got {:?}", other),
}
}
#[test]
fn test_list_rejects_junk_where_only_whitespace_is_allowed() {
assert!(
list(b"(x)").is_err(),
"`(x)` must be rejected: `x` is neither STEP trivia nor a token"
);
}