use ifc_schema::{Schema, TypeKind};
const MAX_HOPS: usize = 16;
const MAX_VISITED: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Form {
Typed,
Bare,
Unresolved,
}
pub(crate) fn form_of(schema: &Schema, type_name: &str) -> Form {
let mut current = type_name.trim().to_owned();
for _ in 0..MAX_HOPS {
if schema.entity(¤t).is_some() {
return Form::Bare;
}
match schema.type_def(¤t).map(|definition| &definition.kind) {
Some(TypeKind::Select(_)) => return Form::Typed,
Some(TypeKind::Enumeration(_)) => return Form::Bare,
Some(TypeKind::Defined(target)) => current = target.trim().to_owned(),
None if is_builtin(¤t) => return Form::Bare,
None => return Form::Unresolved,
}
}
Form::Unresolved
}
fn is_builtin(text: &str) -> bool {
let head = text
.split(|c: char| !c.is_ascii_alphabetic())
.next()
.unwrap_or_default()
.to_ascii_uppercase();
matches!(
head.as_str(),
"REAL"
| "INTEGER"
| "NUMBER"
| "STRING"
| "BOOLEAN"
| "LOGICAL"
| "BINARY"
| "LIST"
| "SET"
| "ARRAY"
| "BAG"
)
}
pub(crate) fn select_lists(schema: &Schema, type_name: &str, keyword: &str) -> Option<bool> {
let mut start = type_name.trim().to_owned();
for _ in 0..MAX_HOPS {
match &schema.type_def(&start)?.kind {
TypeKind::Select(_) => break,
TypeKind::Defined(target) => start = target.trim().to_owned(),
TypeKind::Enumeration(_) => return None,
}
}
let mut frontier = vec![start];
let mut seen = std::collections::BTreeSet::new();
let mut unknown = false;
while let Some(current) = frontier.pop() {
if !seen.insert(current.to_ascii_uppercase()) {
continue;
}
if seen.len() > MAX_VISITED {
return None;
}
let Some(TypeKind::Select(members)) =
schema.type_def(¤t).map(|definition| &definition.kind)
else {
continue;
};
for member in members {
if member.eq_ignore_ascii_case(keyword) {
return Some(true);
}
if schema.type_def(member).is_none() && schema.entity(member).is_none() {
unknown = true;
}
frontier.push(member.clone());
}
}
(!unknown).then_some(false)
}
pub(crate) fn aliases_to(schema: &Schema, wrapper: &str, declared: &str) -> bool {
let mut current = wrapper.trim().to_owned();
for _ in 0..MAX_HOPS {
if current.eq_ignore_ascii_case(declared) {
return true;
}
let Some(TypeKind::Defined(target)) =
schema.type_def(¤t).map(|definition| &definition.kind)
else {
return false;
};
current = target.trim().to_owned();
}
false
}