1use nom::{
10 branch::alt,
11 bytes::complete::{take_while, take_while1},
12 character::complete::{char, digit1, one_of},
13 combinator::{map, map_res, opt, recognize},
14 multi::separated_list0,
15 sequence::{delimited, pair, preceded, tuple},
16 IResult,
17};
18
19use crate::error::{Error, Result};
20use crate::generated::IfcType;
21
22#[derive(Debug, Clone, PartialEq)]
28pub enum Token<'a> {
29 EntityRef(u32),
31 String(&'a [u8]),
33 Integer(i64),
35 Float(f64),
37 Enum(&'a [u8]),
39 List(Vec<Token<'a>>),
41 TypedValue(&'a [u8], Vec<Token<'a>>),
43 Null,
45 Derived,
47}
48
49fn entity_ref(input: &[u8]) -> IResult<&[u8], Token<'_>> {
51 map(
52 preceded(char('#'), map_res(digit1, lexical_core::parse::<u32>)),
53 Token::EntityRef,
54 )(input)
55}
56
57fn string_literal(input: &[u8]) -> IResult<&[u8], Token<'_>> {
61 #[inline]
63 fn parse_string_content(input: &[u8], quote_byte: u8) -> IResult<&[u8], &[u8]> {
64 let bytes = input;
65 let mut pos = 0;
66
67 while let Some(found) = memchr::memchr(quote_byte, &bytes[pos..]) {
69 let idx = pos + found;
70 if idx + 1 < bytes.len() && bytes[idx + 1] == quote_byte {
72 pos = idx + 2; continue;
74 }
75 return Ok((&input[idx..], &input[..idx]));
77 }
78
79 Err(nom::Err::Error(nom::error::Error::new(
81 input,
82 nom::error::ErrorKind::Char,
83 )))
84 }
85
86 alt((
87 map(
88 delimited(char('\''), |i| parse_string_content(i, b'\''), char('\'')),
89 Token::String,
90 ),
91 map(
92 delimited(char('"'), |i| parse_string_content(i, b'"'), char('"')),
93 Token::String,
94 ),
95 ))(input)
96}
97
98#[inline]
101fn integer(input: &[u8]) -> IResult<&[u8], Token<'_>> {
102 map_res(recognize(tuple((opt(char('-')), digit1))), |s: &[u8]| {
103 lexical_core::parse::<i64>(s)
104 .map(Token::Integer)
105 .map_err(|_| "parse error")
106 })(input)
107}
108
109#[inline]
113fn float(input: &[u8]) -> IResult<&[u8], Token<'_>> {
114 map_res(
115 recognize(tuple((
116 opt(char('-')),
117 digit1,
118 char('.'),
119 opt(digit1), opt(tuple((one_of("eE"), opt(one_of("+-")), digit1))),
121 ))),
122 |s: &[u8]| {
123 lexical_core::parse::<f64>(s)
124 .map(Token::Float)
125 .map_err(|_| "parse error")
126 },
127 )(input)
128}
129
130fn enum_value(input: &[u8]) -> IResult<&[u8], Token<'_>> {
132 map(
133 delimited(
134 char('.'),
135 take_while1(|c: u8| c.is_ascii_alphanumeric() || c == b'_'),
136 char('.'),
137 ),
138 Token::Enum,
139 )(input)
140}
141
142fn null(input: &[u8]) -> IResult<&[u8], Token<'_>> {
144 map(char('$'), |_| Token::Null)(input)
145}
146
147fn derived(input: &[u8]) -> IResult<&[u8], Token<'_>> {
149 map(char('*'), |_| Token::Derived)(input)
150}
151
152const MAX_NESTING_DEPTH: u32 = 256;
158
159fn typed_value_at_depth(input: &[u8], depth: u32) -> IResult<&[u8], Token<'_>> {
161 map(
162 pair(
163 take_while1(|c: u8| c.is_ascii_alphanumeric() || c == b'_'),
165 delimited(
167 char('('),
168 separated_list0(delimited(ws, char(','), ws), move |i| {
169 token_at_depth(i, depth)
170 }),
171 char(')'),
172 ),
173 ),
174 |(type_name, args)| Token::TypedValue(type_name, args),
175 )(input)
176}
177
178fn ws(input: &[u8]) -> IResult<&[u8], ()> {
180 map(take_while(|c: u8| c.is_ascii_whitespace()), |_| ())(input)
181}
182
183fn token(input: &[u8]) -> IResult<&[u8], Token<'_>> {
186 token_at_depth(input, 0)
187}
188
189fn token_at_depth(input: &[u8], depth: u32) -> IResult<&[u8], Token<'_>> {
190 if depth > MAX_NESTING_DEPTH {
191 return Err(nom::Err::Failure(nom::error::Error::new(
192 input,
193 nom::error::ErrorKind::TooLarge,
194 )));
195 }
196 delimited(
197 ws,
198 alt((
199 null, derived, entity_ref, enum_value, string_literal, move |i| list_at_depth(i, depth + 1), float,
209 integer,
210 move |i| typed_value_at_depth(i, depth + 1),
212 )),
213 ws,
214 )(input)
215}
216
217#[cfg(test)]
220fn list(input: &[u8]) -> IResult<&[u8], Token<'_>> {
221 list_at_depth(input, 0)
222}
223
224fn list_at_depth(input: &[u8], depth: u32) -> IResult<&[u8], Token<'_>> {
225 map(
226 delimited(
227 char('('),
228 separated_list0(delimited(ws, char(','), ws), move |i| {
229 token_at_depth(i, depth)
230 }),
231 char(')'),
232 ),
233 Token::List,
234 )(input)
235}
236
237#[allow(clippy::type_complexity)]
242pub fn parse_entity<'a, T>(input: &'a T) -> Result<(u32, IfcType, Vec<Token<'a>>)>
243where
244 T: AsRef<[u8]> + ?Sized,
245{
246 let input = input.as_ref();
247 let result: IResult<&[u8], (u32, &[u8], Vec<Token>)> = tuple((
248 delimited(
250 ws,
251 preceded(char('#'), map_res(digit1, lexical_core::parse::<u32>)),
252 ws,
253 ),
254 preceded(
256 char('='),
257 delimited(
259 ws,
260 take_while1(|c: u8| c.is_ascii_alphanumeric() || c == b'_'),
261 ws,
262 ),
263 ),
264 delimited(
266 char('('),
267 separated_list0(delimited(ws, char(','), ws), token),
268 tuple((char(')'), ws, char(';'))),
269 ),
270 ))(input);
271
272 match result {
273 Ok((_, (id, type_str, args))) => {
274 let type_str = std::str::from_utf8(type_str)
275 .map_err(|_| Error::parse(0, "Entity type is not ASCII/UTF-8"))?;
276 let ifc_type = IfcType::from_str(type_str);
277 Ok((id, ifc_type, args))
278 }
279 Err(e) => Err(Error::parse(0, format!("Failed to parse entity: {}", e))),
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
289 #[allow(clippy::approx_constant)]
290 fn test_basic_tokens() {
291 type Parser = for<'a> fn(&'a [u8]) -> IResult<&'a [u8], Token<'a>>;
292 let cases: &[(Parser, &[u8], Token)] = &[
293 (entity_ref, b"#123", Token::EntityRef(123)),
294 (entity_ref, b"#0", Token::EntityRef(0)),
295 (string_literal, b"'hello'", Token::String(b"hello")),
296 (
297 string_literal,
298 b"'with spaces'",
299 Token::String(b"with spaces"),
300 ),
301 (integer, b"42", Token::Integer(42)),
302 (integer, b"-42", Token::Integer(-42)),
303 (integer, b"0", Token::Integer(0)),
304 (float, b"3.14", Token::Float(3.14)),
305 (float, b"-3.14", Token::Float(-3.14)),
306 (float, b"1.5E-10", Token::Float(1.5e-10)),
307 (enum_value, b".TRUE.", Token::Enum(b"TRUE")),
308 (enum_value, b".FALSE.", Token::Enum(b"FALSE")),
309 (enum_value, b".ELEMENT.", Token::Enum(b"ELEMENT")),
310 ];
311 for (parse, input, expected) in cases {
312 assert_eq!(
313 parse(input),
314 Ok((&b""[..], expected.clone())),
315 "tokenizing {input:?}"
316 );
317 }
318 }
319
320 #[test]
321 fn test_list() {
322 let result = list(b"(1,2,3)");
323 assert!(result.is_ok());
324 let (_, token) = result.unwrap();
325 match token {
326 Token::List(items) => {
327 assert_eq!(items.len(), 3);
328 assert_eq!(items[0], Token::Integer(1));
329 assert_eq!(items[1], Token::Integer(2));
330 assert_eq!(items[2], Token::Integer(3));
331 }
332 _ => panic!("Expected List token"),
333 }
334 }
335
336 #[test]
337 fn test_nested_list() {
338 let result = list(b"(1,(2,3),4)");
339 assert!(result.is_ok());
340 let (_, token) = result.unwrap();
341 match token {
342 Token::List(items) => {
343 assert_eq!(items.len(), 3);
344 assert_eq!(items[0], Token::Integer(1));
345 match &items[1] {
346 Token::List(inner) => {
347 assert_eq!(inner.len(), 2);
348 assert_eq!(inner[0], Token::Integer(2));
349 assert_eq!(inner[1], Token::Integer(3));
350 }
351 _ => panic!("Expected nested List"),
352 }
353 assert_eq!(items[2], Token::Integer(4));
354 }
355 _ => panic!("Expected List token"),
356 }
357 }
358
359 #[test]
360 fn test_parse_entity() {
361 let input = "#123=IFCWALL('guid','owner',$,$,'name',$,$,$);";
362 let result = parse_entity(input);
363 assert!(result.is_ok());
364 let (id, ifc_type, args) = result.unwrap();
365 assert_eq!(id, 123);
366 assert_eq!(ifc_type, IfcType::IfcWall);
367 assert_eq!(args.len(), 8);
368 }
369
370 #[test]
371 fn test_parse_entity_with_nested_list() {
372 let simple = "(0.,0.,1.)";
374 println!("Testing simple list: {}", simple);
375 let simple_result = list(simple.as_bytes());
376 println!("Simple list result: {:?}", simple_result);
377
378 let input = "#9=IFCDIRECTION((0.,0.,1.));";
380 println!("\nTesting full entity: {}", input);
381 let result = parse_entity(input);
382
383 if let Err(ref e) = result {
384 println!("Parse error: {:?}", e);
385
386 println!("\nTrying to parse just arguments: ((0.,0.,1.))");
388 let args_input = "((0.,0.,1.))";
389 let args_result = list(args_input.as_bytes());
390 println!("Args list result: {:?}", args_result);
391 }
392
393 assert!(result.is_ok(), "Failed to parse: {:?}", result);
394 let (id, _ifc_type, args) = result.unwrap();
395 assert_eq!(id, 9);
396 assert_eq!(args.len(), 1);
397 if let Token::List(inner) = &args[0] {
399 assert_eq!(inner.len(), 3);
400 } else {
401 panic!("Expected Token::List, got {:?}", args[0]);
402 }
403 }
404
405 #[test]
408 fn test_parse_entity_rejects_excessive_nesting() {
409 let n = (MAX_NESTING_DEPTH as usize) + 64;
410 let mut s = String::from("#1=IFCWALL(");
411 for _ in 0..n {
412 s.push('(');
413 }
414 s.push('1');
415 for _ in 0..n {
416 s.push(')');
417 }
418 s.push_str(");");
419 assert!(parse_entity(&s).is_err());
421 }
422
423 #[test]
425 fn test_parse_entity_accepts_moderate_nesting() {
426 let n = 32;
427 let mut s = String::from("#1=IFCWALL(");
428 for _ in 0..n {
429 s.push('(');
430 }
431 s.push('1');
432 for _ in 0..n {
433 s.push(')');
434 }
435 s.push_str(");");
436 assert!(parse_entity(&s).is_ok());
437 }
438
439 fn nested(n: usize) -> String {
440 let mut s = String::from("#1=IFCWALL(");
441 for _ in 0..n {
442 s.push('(');
443 }
444 s.push('1');
445 for _ in 0..n {
446 s.push(')');
447 }
448 s.push_str(");");
449 s
450 }
451
452 #[test]
454 fn test_parse_entity_accepts_exactly_max_nesting() {
455 assert!(parse_entity(&nested(MAX_NESTING_DEPTH as usize)).is_ok());
456 }
457
458 #[test]
460 fn test_parse_entity_rejects_one_over_max_nesting() {
461 assert!(parse_entity(&nested(MAX_NESTING_DEPTH as usize + 1)).is_err());
462 }
463}