Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libhaystack"
version = "2.0.1"
version = "2.0.2"
description = "Rust implementation of the Haystack 4 data types, defs, filter, units, and encodings"
authors = ["J2 Innovations", "Radu Racariu <radur@j2inn.com>"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src/c_api/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub unsafe extern "C" fn haystack_filter_parse(val: *const c_char) -> Option<Box
}
},
Err(err) => {
new_error(&format!("Invalid C string. {}", err));
new_error(&format!("Invalid C string. {err}"));
None
}
}
Expand Down
135 changes: 72 additions & 63 deletions src/haystack/defs/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,89 +831,98 @@ impl<'a> Namespace<'a> {
ref_target: &Option<Ref>,
resolve: &F,
) -> bool {
if let Some(relationship) = self.get(rel_name) {
if !self
.inheritance(rel_name)
.iter()
.any(|def| def.def_name() == "relationship")
{
return false;
}
// Def must be registered.
let Some(relationship) = self.get(rel_name) else {
return false;
};

let transitive = relationship.has_marker("transitive");
// Def must be a relationship.
if !self
.inheritance(rel_name)
.iter()
.any(|def| def.def_name() == "relationship")
{
return false;
}

// https://project-haystack.dev/doc/docHaystack/Relationships#reciprocalOf
let reciprocal_of = relationship.get_symbol("reciprocalOf");
let is_transitive = relationship.has_marker("transitive");

let mut queried_refs = HashSet::<Ref>::new();
let mut ref_tag: Option<Ref> = ref_target.as_ref().cloned();
let mut cur_subject: Dict = subject.clone();
// https://project-haystack.dev/doc/docHaystack/Relationships#reciprocalOf
let reciprocal_of = relationship.get_symbol("reciprocalOf");

'search: loop {
let id = cur_subject.get_ref("id");
for (subject_key, subject_val) in cur_subject.iter() {
let subject_def = self.get_by_name(subject_key);
let mut rel_val = subject_def.and_then(|def| def.get(&rel_name.value));
let mut queried_refs = HashSet::new();
let mut ref_tag = ref_target.as_ref().cloned();
let mut subjects = vec![subject.clone()];

// Handle a reciprocal relationship. A reciprocal relationship can only
// be inverted when a ref is specified.
if rel_val.is_none() && ref_tag.as_ref() == id && subject_val.is_ref() {
if let Some(reciprocal_of) = reciprocal_of {
rel_val = subject_def.and_then(|def| def.get(&reciprocal_of.value));
'search: loop {
let Some(mut cur_subject) = subjects.pop() else {
break;
};

if rel_val.is_some() {
if let Value::Ref(val) = subject_val {
ref_tag = Some(val.clone())
}
let id = cur_subject.get_ref("id").cloned();
while let Some((subject_key, subject_val)) = cur_subject.pop_first() {
let subject_def = self.get_by_name(&subject_key);
let mut rel_val = subject_def.and_then(|def| def.get(&rel_name.value));

// Handle a reciprocal relationship. A reciprocal relationship can only
// be inverted when a ref is specified.
if rel_val.is_none() && ref_tag.as_ref() == id.as_ref() && subject_val.is_ref() {
if let Some(reciprocal_of) = reciprocal_of {
rel_val = subject_def.and_then(|def| def.get(&reciprocal_of.value));

if rel_val.is_some() {
if let Value::Ref(ref val) = subject_val {
ref_tag = Some(val.clone())
}
}
}
}

// Test to see if the relationship exists on any of the
// reflected defs for an entry in subject.
if let Some(Value::Symbol(rel_val)) = rel_val {
// If we're testing against a relationship value then
// ensure the target is also a symbol so we can see if it fits.
let mut has_match = if let Some(rel_term) = rel_term.as_ref() {
self.fits(rel_val, rel_term)
} else {
true
};

// Test to see if the value matches.
if has_match && ref_tag.is_some() {
has_match = false;

if matches!(subject_val, Value::Ref(val) if Some(val) == ref_tag.as_ref())
{
has_match = true;
} else if transitive {
if let Value::Ref(subject_val) = subject_val {
if !queried_refs.contains(subject_val) {
queried_refs.insert(subject_val.clone());

// If the value doesn't match but the relationship is transitive
// then follow the refs until we find a match or not.
// https://project-haystack.dev/doc/docHaystack/Relationships#transitive
if let Some(new_subject) = resolve(subject_val) {
if !new_subject.is_empty() {
cur_subject = new_subject;
continue 'search;
}
// Test to see if the relationship exists on any of the
// reflected defs for an entry in subject.
if let Some(Value::Symbol(rel_val)) = rel_val {
// If we're testing against a relationship value then
// ensure the target is also a symbol so we can see if it fits.
let mut has_match = if let Some(rel_term) = rel_term.as_ref() {
self.fits(rel_val, rel_term)
} else {
true
};

// Test to see if the value matches.
if has_match && ref_tag.is_some() {
has_match = false;

if matches!(subject_val, Value::Ref(ref val) if Some(val) == ref_tag.as_ref())
{
has_match = true;
} else if is_transitive {
if let Value::Ref(subject_val) = subject_val {
if !queried_refs.contains(&subject_val) {
queried_refs.insert(subject_val.clone());

// If the value doesn't match but the relationship is transitive
// then follow the refs until we find a match or not.
// https://project-haystack.dev/doc/docHaystack/Relationships#transitive
if let Some(new_subject) = resolve(&subject_val) {
if !new_subject.is_empty() {
subjects.push(cur_subject);
subjects.push(new_subject);
continue 'search;
}
}
}
}
}
}

if has_match {
return true;
}
if has_match {
return true;
}
}
break;
}
}

false
}
}
6 changes: 3 additions & 3 deletions src/haystack/encoding/zinc/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ impl ToZinc for Number {
} else {
""
};
writer.write_fmt(format_args!("{}INF", sign))?
writer.write_fmt(format_args!("{sign}INF"))?
} else if let Some(unit) = &self.unit {
writer.write_fmt(format_args!("{}{}", self.value, unit))?
writer.write_fmt(format_args!("{value}{unit}", value = self.value))?
} else {
writer.write_fmt(format_args!("{}", self.value))?
}
Expand Down Expand Up @@ -203,7 +203,7 @@ impl ToZinc for Str {
writer.write_all(br"\$")?
} else {
let chunk = c.encode_utf8(&mut buf);
writer.write_fmt(format_args!("{}", chunk))?
writer.write_fmt(format_args!("{chunk}"))?
}
}
writer.write_all(b"\"")?;
Expand Down
4 changes: 2 additions & 2 deletions src/haystack/filter/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,11 @@ impl Display for Relation {
write!(f, "{}?", self.rel.value)?;

if let Some(rel_term) = &self.rel_term {
write!(f, " {}", rel_term)?;
write!(f, " {rel_term}")?;
}

if let Some(ref_value) = &self.ref_value {
write!(f, " {}", ref_value)
write!(f, " {ref_value}")
} else {
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion src/haystack/filter/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl<'a, R: Read> Parser<Lexer<Scanner<'a, R>>> {
// If there is something left over then we have an invalid haystack filter.
self.parse_or().and_then(|or| match &self.lexer.cur.value {
None => Ok(or),
Some(value) => self.make_generic_err(&format!("Unexpected token: {:?}.", value)),
Some(value) => self.make_generic_err(&format!("Unexpected token: {value:?}")),
})
}

Expand Down
20 changes: 20 additions & 0 deletions tests/defs/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,12 @@ fn test_namespace_relationship() {
fn test_namespace_transitive_relationship() {
let mut map = HashMap::<&str, &Dict>::new();

let site = dict! {
"id" => Value::make_ref("site"),
"site" => Value::Marker,
};
map.insert("site", &site);

let ahu = dict! {
"id" => Value::make_ref("ahu"),
"ahu" => Value::Marker,
Expand All @@ -1171,6 +1177,8 @@ fn test_namespace_transitive_relationship() {
"discharge" => Value::Marker,
"fan" => Value::Marker,
"equip" => Value::Marker,
// Not typically done but used for indirectly multiple transitive relationships.
"siteRef" => Value::make_ref("site"),
"equipRef" => Value::make_ref("ahu"),
};
map.insert("fan", &fan);
Expand Down Expand Up @@ -1235,6 +1243,18 @@ fn test_namespace_transitive_relationship() {
&resolve,
);
assert!(!has);

// true for a point that indirectly references a site
// The fan has a siteRef that also needs to be traversed as well as the equipRef. This
// tests multiple transitive relationships on the same dict.
let has = DEFS_NS.has_relationship(
&status,
&Symbol::from("containedBy"),
&None,
&Some(Ref::from("site")),
&resolve,
);
assert!(has);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion unit-gen/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub(super) fn parse_unit<'a>() -> Parser<'a, u8, Unit> {
"A" => cur.a = *scale,
"mol" => cur.mol = *scale,
"cd" => cur.cd = *scale,
_ => panic!("Invalid dimension {}", dim),
_ => panic!("Invalid dimension {dim}"),
};
cur
})
Expand Down
Loading