#![allow(missing_docs)]
use openbim_step::express::{parse, Attribute, EntityDef, ParsedSchema, TypeDef, TypeKind};
#[test]
fn schema_model_builders_preserve_the_ifc_schema_surface() {
let attribute = Attribute::new("Items", "IfcLabel").optional().aggregate();
assert!(attribute.optional);
assert!(attribute.aggregate);
let entity = EntityDef::new("IfcExample")
.with_supertype("IfcRoot")
.with_attribute(attribute);
assert_eq!(entity.supertype(), Some("IfcRoot"));
assert_eq!(entity.attributes.len(), 1);
let defined = TypeDef {
name: "IfcLabel".into(),
kind: TypeKind::Defined("STRING".into()),
};
assert!(defined.is_defined());
}
const SCHEMA: &str = r"
SCHEMA DEMO;
(* ENTITY Fake; value : TEXT; END_ENTITY; *)
TYPE Distance = REAL;
END_TYPE;
TYPE Shade = ENUMERATION OF (RED, GREEN, BLUE);
END_TYPE;
TYPE AnyValue = SELECT (Distance, Shade);
END_TYPE;
ENTITY Root ABSTRACT SUPERTYPE OF (ONEOF(Item));
Label : OPTIONAL STRING;
END_ENTITY;
ENTITY Item SUBTYPE OF (Root);
Size : Distance;
Points : LIST [1:?] OF Distance;
DERIVE
Doubled : Distance := Size * 2;
WHERE
Positive : Size > 0;
END_ENTITY;
END_SCHEMA;
";
#[test]
fn structural_partial_express_parser_extracts_supported_declarations() {
let ParsedSchema {
name,
entities,
types,
} = parse(SCHEMA);
assert_eq!(name, "DEMO");
assert_eq!(entities.len(), 2);
assert_eq!(types.len(), 3);
let root = &entities[0];
assert_eq!(
root,
&EntityDef {
name: "Root".into(),
supertypes: Vec::new(),
abstract_: true,
attributes: vec![Attribute {
name: "Label".into(),
type_name: "STRING".into(),
optional: true,
aggregate: false
}],
derived: Vec::new(),
redeclared: Vec::new(),
where_rules: Vec::new(),
}
);
let item = &entities[1];
assert_eq!(item.supertype(), Some("Root"));
assert_eq!(
item.attributes.len(),
2,
"DERIVE and WHERE are not explicit slots"
);
assert!(item.attributes[1].aggregate);
assert_eq!(
types[0],
TypeDef {
name: "Distance".into(),
kind: TypeKind::Defined("REAL".into())
}
);
assert_eq!(
types[1].kind,
TypeKind::Enumeration(vec!["RED".into(), "GREEN".into(), "BLUE".into()])
);
assert_eq!(
types[2].kind,
TypeKind::Select(vec!["Distance".into(), "Shade".into()])
);
}
#[test]
fn derive_blocks_report_redeclared_attribute_names() {
let source = "\
SCHEMA test;
ENTITY parent;
Precision : REAL;
Dimension : INTEGER;
END_ENTITY;
ENTITY child
SUBTYPE OF (parent);
ParentRef : parent;
DERIVE
SELF\\parent.Precision : REAL := NVL(ParentRef.Precision, 1.E-5);
SELF\\parent.Dimension : INTEGER := ParentRef.Dimension;
WHERE
NoSub : TRUE;
END_ENTITY;
END_SCHEMA;
";
let schema = parse(source);
let child = schema
.entities
.iter()
.find(|entity| entity.name == "child")
.expect("child entity");
assert_eq!(
child.derived,
vec!["Precision".to_owned(), "Dimension".to_owned()],
"the SELF\\Entity. prefix names the supertype, not the attribute"
);
assert!(
child.is_derived("precision"),
"matching is case-insensitive"
);
assert!(
!child.is_derived("ParentRef"),
"explicit attributes are not derived"
);
assert!(!child.is_derived("NoSub"));
let parent = schema
.entities
.iter()
.find(|entity| entity.name == "parent")
.expect("parent entity");
assert!(parent.derived.is_empty());
}
#[test]
fn unqualified_derived_attributes_are_reported() {
let source = "\
SCHEMA test;
ENTITY thing;
Length : REAL;
DERIVE
Area : REAL := Length * Length;
END_ENTITY;
END_SCHEMA;
";
let schema = parse(source);
let thing = &schema.entities[0];
assert_eq!(thing.derived, vec!["Area".to_owned()]);
assert_eq!(
thing.attributes.len(),
1,
"a derived attribute is not an explicit positional attribute"
);
}
#[test]
fn where_rule_labels_are_not_reported_as_derived() {
let source = "\
SCHEMA test;
ENTITY child;
ParentRef : INTEGER;
DERIVE
SELF\\parent.Precision : REAL := 1.0;
WHERE
FirstRule : TRUE;
SecondRule : TRUE;
ThirdRule : TRUE;
END_ENTITY;
END_SCHEMA;
";
let schema = parse(source);
let child = &schema.entities[0];
assert_eq!(
child.derived,
vec!["Precision".to_owned()],
"only the DERIVE statement, not the WHERE rule labels"
);
for rule in ["FirstRule", "SecondRule", "ThirdRule"] {
assert!(
!child.is_derived(rule),
"{rule} is a constraint, not an attribute"
);
}
}
#[test]
fn inverse_and_unique_clauses_do_not_leak_into_derived() {
let source = "\
SCHEMA test;
ENTITY child;
Ref : INTEGER;
DERIVE
Computed : REAL := 1.0;
INVERSE
FirstBack : SET OF other FOR Ref;
SecondBack : SET OF other FOR Ref;
UNIQUE
FirstKey : Ref;
SecondKey : Ref;
END_ENTITY;
END_SCHEMA;
";
let schema = parse(source);
let child = &schema.entities[0];
assert_eq!(child.derived, vec!["Computed".to_owned()]);
for name in ["FirstBack", "SecondBack", "FirstKey", "SecondKey"] {
assert!(!child.is_derived(name), "{name} must not be derived");
}
}
#[test]
fn a_unique_aggregate_does_not_truncate_the_attribute_list() {
let source = "\
ENTITY Holder
SUPERTYPE OF (ONEOF
(SubA
,SubB))
SUBTYPE OF (Base);
\tMaps : OPTIONAL LIST [1:?] OF UNIQUE Target;
\tTag : OPTIONAL Label;
INVERSE
\tUsedBy : SET [0:?] OF Other FOR Thing;
WHERE
\tRule : EXISTS(Tag);
END_ENTITY;
";
let schema = parse(source);
let holder = schema
.entities
.iter()
.find(|entity| entity.name == "Holder")
.expect("Holder parsed");
let names: Vec<&str> = holder
.attributes
.iter()
.map(|attribute| attribute.name.as_str())
.collect();
assert_eq!(
names,
["Maps", "Tag"],
"the attribute after the inline UNIQUE must survive"
);
assert_eq!(holder.supertype(), Some("Base"));
}
#[test]
fn a_statement_level_unique_block_still_ends_the_attributes() {
let source = "\
ENTITY Thing;
\tName : Label;
UNIQUE
\tOnlyOne : Name;
END_ENTITY;
";
let schema = parse(source);
let thing = schema
.entities
.iter()
.find(|entity| entity.name == "Thing")
.expect("Thing parsed");
let names: Vec<&str> = thing
.attributes
.iter()
.map(|attribute| attribute.name.as_str())
.collect();
assert_eq!(names, ["Name"], "the UNIQUE block is not an attribute");
}
#[test]
fn where_rules_are_captured_with_labels_and_expressions() {
let source = "\
ENTITY IfcSurfaceCurve
SUBTYPE OF (IfcCurve);
\tCurve3D : IfcCurve;
WHERE
\tCurveIs3D : Curve3D.Dim = 3;
\tCurveIsNotPcurve : NOT ('IFC4.IFCPCURVE' IN TYPEOF(Curve3D));
END_ENTITY;";
let schema = parse(source);
let entity = schema
.entities
.iter()
.find(|entity| entity.name.eq_ignore_ascii_case("IfcSurfaceCurve"))
.expect("entity");
let labels: Vec<_> = entity
.where_rules
.iter()
.map(|r| r.label.as_str())
.collect();
assert_eq!(labels, ["CurveIs3D", "CurveIsNotPcurve"]);
assert_eq!(entity.where_rules[0].expression, "Curve3D.Dim = 3");
assert_eq!(
entity.where_rules[1].expression,
"NOT ('IFC4.IFCPCURVE' IN TYPEOF(Curve3D))"
);
assert_eq!(entity.attributes.len(), 1);
}
#[test]
fn a_multi_line_query_rule_is_captured_whole() {
let source = "\
ENTITY Face;
\tBounds : SET OF Bound;
WHERE
\tFirstRule : SIZEOF(QUERY (b <* Bounds |
\t NOT ('SCHEMA.LOOP' IN TYPEOF(b)))) = 0;
\tSecondRule : SIZEOF(Bounds) > 0;
END_ENTITY;";
let schema = parse(source);
let entity = schema
.entities
.iter()
.find(|entity| entity.name.eq_ignore_ascii_case("Face"))
.expect("entity");
let labels: Vec<_> = entity
.where_rules
.iter()
.map(|r| r.label.as_str())
.collect();
assert_eq!(labels, ["FirstRule", "SecondRule"]);
assert_eq!(
entity.where_rules[0].expression,
"SIZEOF(QUERY (b <* Bounds | NOT ('SCHEMA.LOOP' IN TYPEOF(b)))) = 0"
);
assert_eq!(entity.attributes.len(), 1);
}
#[test]
fn a_where_token_inside_an_earlier_block_is_not_the_block_start() {
let source = "\
ENTITY Holder;
\tItems : SET OF Item;
DERIVE
\tPicked : Item := QUERY(i <* SELF.Items | i.Kind = 'WHERE')[1];
WHERE
\tRealRule : SIZEOF(Items) > 0;
END_ENTITY;";
let schema = parse(source);
let entity = schema
.entities
.iter()
.find(|entity| entity.name.eq_ignore_ascii_case("Holder"))
.expect("entity");
let labels: Vec<_> = entity
.where_rules
.iter()
.map(|r| r.label.as_str())
.collect();
assert_eq!(labels, ["RealRule"]);
assert_eq!(entity.derived, ["Picked"]);
}
const MULTI: &str = r"
SCHEMA M;
ENTITY top; t : INTEGER; END_ENTITY;
ENTITY a SUBTYPE OF (top); x : INTEGER; END_ENTITY;
ENTITY b SUBTYPE OF (top); y : INTEGER; END_ENTITY;
ENTITY c SUBTYPE OF (a, b); z : INTEGER; END_ENTITY;
END_SCHEMA;
";
#[test]
fn every_declared_supertype_is_recorded_in_order() {
let parsed = parse(MULTI);
let c = parsed.entities.iter().find(|e| e.name == "c").unwrap();
assert_eq!(c.supertypes, ["a", "b"]);
assert_eq!(c.supertype(), Some("a"));
let top = parsed.entities.iter().find(|e| e.name == "top").unwrap();
assert!(top.supertypes.is_empty());
assert_eq!(top.supertype(), None);
}
const REDECLARED: &str = r"
SCHEMA R;
ENTITY styled; name : STRING; target : thing; END_ENTITY;
ENTITY plane SUBTYPE OF (styled);
SELF\styled.target : plane_target;
extra : INTEGER;
END_ENTITY;
END_SCHEMA;
";
#[test]
fn explicit_redeclarations_are_not_new_attributes() {
let parsed = parse(REDECLARED);
let plane = parsed.entities.iter().find(|e| e.name == "plane").unwrap();
let names: Vec<_> = plane.attributes.iter().map(|a| a.name.as_str()).collect();
assert_eq!(names, ["extra"]);
assert_eq!(plane.redeclared.len(), 1);
let r = &plane.redeclared[0];
assert_eq!(
(r.supertype.as_str(), r.name.as_str(), r.type_name.as_str()),
("styled", "target", "plane_target")
);
assert!(plane.is_redeclared("TARGET"));
assert!(!plane.is_redeclared("extra"));
}