use bimifc_model::{AttributeValue, DecodedEntity, EntityId, IfcType};
use nom::{
branch::alt,
bytes::complete::{take_while, take_while1},
character::complete::{char, multispace0},
combinator::{opt, recognize},
multi::separated_list0,
sequence::{delimited, pair},
IResult, Parser,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Token<'a> {
EntityRef(u32),
String(&'a str),
Integer(i64),
Float(f64),
Enum(&'a str),
List(Vec<Token<'a>>),
TypedValue(&'a str, Vec<Token<'a>>),
Null,
Derived,
}
impl<'a> Token<'a> {
pub fn to_attribute_value(&self) -> AttributeValue {
match self {
Token::EntityRef(id) => AttributeValue::EntityRef(EntityId(*id)),
Token::String(s) => AttributeValue::String((*s).to_string()),
Token::Integer(i) => AttributeValue::Integer(*i),
Token::Float(f) => AttributeValue::Float(*f),
Token::Enum(s) => AttributeValue::Enum((*s).to_string()),
Token::List(items) => {
AttributeValue::List(items.iter().map(|t| t.to_attribute_value()).collect())
}
Token::TypedValue(name, args) => AttributeValue::TypedValue(
(*name).to_string(),
args.iter().map(|t| t.to_attribute_value()).collect(),
),
Token::Null => AttributeValue::Null,
Token::Derived => AttributeValue::Derived,
}
}
}
fn ws(input: &str) -> IResult<&str, ()> {
let (input, _) = multispace0(input)?;
Ok((input, ()))
}
fn entity_ref(input: &str) -> IResult<&str, Token<'_>> {
let (input, _) = char('#')(input)?;
let (input, digits) = take_while1(|c: char| c.is_ascii_digit())(input)?;
let id = digits.parse::<u32>().unwrap_or(0);
Ok((input, Token::EntityRef(id)))
}
fn step_string(input: &str) -> IResult<&str, Token<'_>> {
let (input, _) = char('\'')(input)?;
let mut end = 0;
let bytes = input.as_bytes();
while end < bytes.len() {
if bytes[end] == b'\'' {
if end + 1 < bytes.len() && bytes[end + 1] == b'\'' {
end += 2;
continue;
}
break;
}
end += 1;
}
let content = &input[..end];
let remaining = &input[end + 1..];
Ok((remaining, Token::String(content)))
}
fn number(input: &str) -> IResult<&str, Token<'_>> {
let (input, num_str) = recognize((
opt(char('-')),
take_while1(|c: char| c.is_ascii_digit()),
opt(pair(char('.'), take_while(|c: char| c.is_ascii_digit()))),
opt((
alt((char('e'), char('E'))),
opt(alt((char('+'), char('-')))),
take_while1(|c: char| c.is_ascii_digit()),
)),
))
.parse(input)?;
if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
let f: f64 = lexical_core::parse(num_str.as_bytes()).unwrap_or(0.0);
Ok((input, Token::Float(f)))
} else {
let i: i64 = lexical_core::parse(num_str.as_bytes()).unwrap_or(0);
Ok((input, Token::Integer(i)))
}
}
fn enumeration(input: &str) -> IResult<&str, Token<'_>> {
let (input, _) = char('.')(input)?;
let (input, name) = take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)?;
let (input, _) = char('.')(input)?;
Ok((input, Token::Enum(name)))
}
fn null_value(input: &str) -> IResult<&str, Token<'_>> {
let (input, _) = char('$')(input)?;
Ok((input, Token::Null))
}
fn derived_value(input: &str) -> IResult<&str, Token<'_>> {
let (input, _) = char('*')(input)?;
Ok((input, Token::Derived))
}
fn list(input: &str) -> IResult<&str, Token<'_>> {
let (input, items) = delimited(
pair(char('('), ws),
separated_list0((ws, char(','), ws), token),
pair(ws, char(')')),
)
.parse(input)?;
Ok((input, Token::List(items)))
}
fn typed_value(input: &str) -> IResult<&str, Token<'_>> {
let (input, type_name) = take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)?;
let (input, _) = ws(input)?;
let (input, args) = delimited(
pair(char('('), ws),
separated_list0((ws, char(','), ws), token),
pair(ws, char(')')),
)
.parse(input)?;
Ok((input, Token::TypedValue(type_name, args)))
}
fn token(input: &str) -> IResult<&str, Token<'_>> {
alt((
entity_ref,
step_string,
null_value,
derived_value,
enumeration,
number,
list,
typed_value,
))
.parse(input)
}
fn attribute_list(input: &str) -> IResult<&str, Vec<Token<'_>>> {
delimited(
pair(char('('), ws),
separated_list0((ws, char(','), ws), token),
pair(ws, char(')')),
)
.parse(input)
}
pub fn parse_entity(input: &str) -> Result<DecodedEntity, String> {
let input = input.trim_start();
let (input, _) = char::<&str, nom::error::Error<&str>>('#')
.parse(input)
.map_err(|_| "Expected # at start of entity")?;
let (input, id_str) =
take_while1::<_, &str, nom::error::Error<&str>>(|c: char| c.is_ascii_digit())
.parse(input)
.map_err(|_| "Expected entity ID")?;
let id: u32 = id_str.parse().map_err(|_| "Invalid entity ID")?;
let (input, _) = (ws, char('='), ws)
.parse(input)
.map_err(|_: nom::Err<nom::error::Error<&str>>| "Expected = after entity ID")?;
let (input, type_name) =
take_while1::<_, &str, nom::error::Error<&str>>(|c: char| c.is_alphanumeric() || c == '_')
.parse(input)
.map_err(|_| "Expected type name")?;
let (input, _) = ws(input).unwrap_or((input, ()));
let (_, tokens) =
attribute_list(input).map_err(|e| format!("Failed to parse attributes: {:?}", e))?;
let attributes: Vec<AttributeValue> = tokens.iter().map(|t| t.to_attribute_value()).collect();
Ok(DecodedEntity {
id: EntityId(id),
ifc_type: IfcType::parse(type_name),
attributes,
})
}
pub fn parse_entity_at(content: &str, start: usize, end: usize) -> Result<DecodedEntity, String> {
let slice = &content[start..end];
parse_entity(slice)
}
#[allow(dead_code)]
pub fn parse_coordinate_list_3d_fast(content: &str) -> Option<Vec<f64>> {
let start = content.find("((")?;
let end = content.rfind("))")?;
let list_content = &content[start + 1..end + 1];
let mut coords = Vec::new();
let mut current = list_content;
while let Some(paren_start) = current.find('(') {
let paren_end = current[paren_start..].find(')')? + paren_start;
let point_str = ¤t[paren_start + 1..paren_end];
for num_str in point_str.split(',') {
let num_str = num_str.trim();
if !num_str.is_empty() {
let val: f64 = lexical_core::parse(num_str.as_bytes()).ok()?;
coords.push(val);
}
}
current = ¤t[paren_end + 1..];
}
if coords.is_empty() {
None
} else {
Some(coords)
}
}
#[allow(dead_code)]
pub fn parse_index_list_fast(content: &str) -> Option<Vec<u32>> {
let start = content.find("((")?;
let end = content.rfind("))")?;
let list_content = &content[start + 1..end + 1];
let mut indices = Vec::new();
let mut current = list_content;
while let Some(paren_start) = current.find('(') {
let paren_end = current[paren_start..].find(')')? + paren_start;
let index_str = ¤t[paren_start + 1..paren_end];
for num_str in index_str.split(',') {
let num_str = num_str.trim();
if !num_str.is_empty() {
let val: u32 = lexical_core::parse(num_str.as_bytes()).ok()?;
indices.push(val.saturating_sub(1));
}
}
current = ¤t[paren_end + 1..];
}
if indices.is_empty() {
None
} else {
Some(indices)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_entity_ref() {
let (remaining, token) = entity_ref("#123").unwrap();
assert_eq!(remaining, "");
assert_eq!(token, Token::EntityRef(123));
}
#[test]
fn test_parse_string() {
let (remaining, token) = step_string("'hello world'").unwrap();
assert_eq!(remaining, "");
assert_eq!(token, Token::String("hello world"));
}
#[test]
fn test_parse_string_with_escaped_quote() {
let (remaining, token) = step_string("'it''s a test'").unwrap();
assert_eq!(remaining, "");
assert_eq!(token, Token::String("it''s a test"));
}
#[test]
fn test_parse_number_integer() {
let (remaining, token) = number("42").unwrap();
assert_eq!(remaining, "");
assert_eq!(token, Token::Integer(42));
}
#[test]
fn test_parse_number_float() {
let (remaining, token) = number("3.14159").unwrap();
assert_eq!(remaining, "");
if let Token::Float(f) = token {
assert!((f - std::f64::consts::PI).abs() < 1e-5);
} else {
panic!("Expected float");
}
}
#[test]
fn test_parse_number_scientific() {
let (remaining, token) = number("1.5E-3").unwrap();
assert_eq!(remaining, "");
if let Token::Float(f) = token {
assert!((f - 0.0015).abs() < 1e-10);
} else {
panic!("Expected float");
}
}
#[test]
fn test_parse_enum() {
let (remaining, token) = enumeration(".TRUE.").unwrap();
assert_eq!(remaining, "");
assert_eq!(token, Token::Enum("TRUE"));
}
#[test]
fn test_parse_list() {
let (remaining, token) = list("(1, 2, 3)").unwrap();
assert_eq!(remaining, "");
if let Token::List(items) = token {
assert_eq!(items.len(), 3);
} else {
panic!("Expected list");
}
}
#[test]
fn test_parse_entity() {
let entity = parse_entity("#1=IFCWALL('abc',$,#2);").unwrap();
assert_eq!(entity.id, EntityId(1));
assert_eq!(entity.ifc_type, IfcType::IfcWall);
assert_eq!(entity.attributes.len(), 3);
}
}