use ifc_model::Value;
use ifc_schema::Schema;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FixedWidth(pub usize);
impl FixedWidth {
#[must_use]
pub fn from_resolved(resolved: &str) -> Option<Self> {
let upper = resolved.to_ascii_uppercase();
if !upper.contains("FIXED") {
return None;
}
let start = upper.find("STRING")?;
let open = upper[start..].find('(')? + start;
let close = upper[open..].find(')')? + open;
upper[open + 1..close].trim().parse().ok().map(Self)
}
#[must_use]
pub fn accepts(self, text: &str) -> bool {
text.chars().count() == self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Primitive {
Real,
Integer,
Text,
Boolean,
Logical,
Binary,
}
impl Primitive {
#[must_use]
pub fn from_resolved(resolved: &str) -> Option<Self> {
resolved
.split(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.filter_map(|token| match token.to_ascii_uppercase().as_str() {
"REAL" | "NUMBER" => Some(Self::Real),
"INTEGER" => Some(Self::Integer),
"STRING" => Some(Self::Text),
"BOOLEAN" => Some(Self::Boolean),
"LOGICAL" => Some(Self::Logical),
"BINARY" => Some(Self::Binary),
_ => None,
})
.next_back()
}
#[must_use]
pub fn accepts(self, value: &Value) -> bool {
match self {
Self::Real => matches!(value, Value::Real(_) | Value::Integer(_)),
Self::Integer => matches!(value, Value::Integer(_)),
Self::Text => matches!(value, Value::Text(_)),
Self::Boolean => matches!(value, Value::Bool(_)),
Self::Logical => matches!(value, Value::Bool(_) | Value::LogicalUnknown),
Self::Binary => matches!(value, Value::Binary(_)),
}
}
#[must_use]
pub const fn describe(self) -> &'static str {
match self {
Self::Real => "a number",
Self::Integer => "an integer",
Self::Text => "a string",
Self::Boolean => "a boolean",
Self::Logical => "a logical",
Self::Binary => "binary data",
}
}
}
#[must_use]
pub fn describe_value(value: &Value) -> &'static str {
match value {
Value::Null => "unset",
Value::Derived => "derived",
Value::Bool(_) => "a boolean",
Value::LogicalUnknown => "unknown",
Value::Integer(_) => "an integer",
Value::Real(_) => "a number",
Value::Text(_) => "a string",
Value::Binary(_) => "binary data",
Value::Enum(_) => "an enumeration constant",
Value::Ref(_) => "a reference",
Value::List(_) => "an aggregate",
Value::Typed { .. } => "a typed value",
}
}
#[must_use]
pub fn primitive_of(schema: &Schema, type_name: &str) -> Option<Primitive> {
Primitive::from_resolved(&schema.resolve_defined(type_name))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bounded_or_fixed_string_is_still_a_string() {
assert_eq!(
Primitive::from_resolved("STRING(255)"),
Some(Primitive::Text)
);
assert_eq!(
Primitive::from_resolved("STRING(22) FIXED"),
Some(Primitive::Text)
);
assert_eq!(
Primitive::from_resolved("LIST [1:?] OF REAL"),
Some(Primitive::Real)
);
}
#[test]
fn a_non_primitive_is_not_guessed() {
assert_eq!(Primitive::from_resolved("IfcLabel"), None);
assert_eq!(Primitive::from_resolved("LIST [1:?] OF IfcReal"), None);
}
#[test]
fn ifc4_labels_are_checked_as_strings() {
let schema = ifc_schema::ifc4();
assert_eq!(primitive_of(schema, "IfcLabel"), Some(Primitive::Text));
assert_eq!(primitive_of(schema, "IfcIdentifier"), Some(Primitive::Text));
}
}