parser.rs (17699B)
1 use crate::ast::*; 2 use crate::tokenizer::{tokenize, Token}; 3 use std::fmt; 4 5 #[derive(Debug)] 6 pub struct ParseError { 7 pub msg: String, 8 } 9 10 impl fmt::Display for ParseError { 11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 12 write!(f, "parse error: {}", self.msg) 13 } 14 } 15 16 impl std::error::Error for ParseError {} 17 18 /// Parse an s-expression string into a Space. 19 pub fn parse(input: &str) -> Result<Space, ParseError> { 20 let tokens = tokenize(input).map_err(|e| ParseError { 21 msg: format!("tokenization failed: {}", e), 22 })?; 23 24 let mut parser = Parser { 25 tokens, 26 pos: 0, 27 cells: Vec::new(), 28 attributes: Vec::new(), 29 child_ids: Vec::new(), 30 }; 31 32 let root = parser.parse_cell().ok_or_else(|| ParseError { 33 msg: "failed to parse root cell".into(), 34 })?; 35 36 Ok(Space { 37 cells: parser.cells, 38 attributes: parser.attributes, 39 child_ids: parser.child_ids, 40 root, 41 }) 42 } 43 44 struct Parser<'a> { 45 tokens: Vec<Token<'a>>, 46 pos: usize, 47 cells: Vec<Cell>, 48 attributes: Vec<Attribute>, 49 child_ids: Vec<CellId>, 50 } 51 52 #[derive(Clone)] 53 struct Checkpoint { 54 pos: usize, 55 cells_len: usize, 56 attrs_len: usize, 57 child_ids_len: usize, 58 } 59 60 impl<'a> Parser<'a> { 61 fn checkpoint(&self) -> Checkpoint { 62 Checkpoint { 63 pos: self.pos, 64 cells_len: self.cells.len(), 65 attrs_len: self.attributes.len(), 66 child_ids_len: self.child_ids.len(), 67 } 68 } 69 70 fn restore(&mut self, cp: Checkpoint) { 71 self.pos = cp.pos; 72 self.cells.truncate(cp.cells_len); 73 self.attributes.truncate(cp.attrs_len); 74 self.child_ids.truncate(cp.child_ids_len); 75 } 76 77 fn peek(&self) -> Option<&Token<'a>> { 78 self.tokens.get(self.pos) 79 } 80 81 fn eat_open(&mut self) -> bool { 82 if matches!(self.peek(), Some(Token::Open)) { 83 self.pos += 1; 84 true 85 } else { 86 false 87 } 88 } 89 90 fn eat_close(&mut self) -> bool { 91 if matches!(self.peek(), Some(Token::Close)) { 92 self.pos += 1; 93 true 94 } else { 95 false 96 } 97 } 98 99 fn eat_symbol_match(&mut self, expected: &str) -> bool { 100 if let Some(Token::Symbol(s)) = self.peek() { 101 if *s == expected { 102 self.pos += 1; 103 return true; 104 } 105 } 106 false 107 } 108 109 fn eat_symbol(&mut self) -> Option<&'a str> { 110 if let Some(Token::Symbol(s)) = self.peek() { 111 let s = *s; 112 self.pos += 1; 113 Some(s) 114 } else { 115 None 116 } 117 } 118 119 fn eat_string(&mut self) -> Option<&'a str> { 120 if let Some(Token::Str(s)) = self.peek() { 121 let s = *s; 122 self.pos += 1; 123 Some(s) 124 } else { 125 None 126 } 127 } 128 129 fn eat_number(&mut self) -> Option<f64> { 130 if let Some(Token::Number(s)) = self.peek() { 131 if let Ok(n) = s.parse::<f64>() { 132 self.pos += 1; 133 return Some(n); 134 } 135 } 136 None 137 } 138 139 fn push_cell(&mut self, cell: Cell) -> CellId { 140 let id = CellId(self.cells.len() as u32); 141 self.cells.push(cell); 142 id 143 } 144 145 // --- Attribute parsing --- 146 147 fn try_parse_attribute(&mut self) -> Option<Attribute> { 148 let cp = self.checkpoint(); 149 150 if !self.eat_open() { 151 return None; 152 } 153 154 let sym = match self.eat_symbol() { 155 Some(s) => s, 156 None => { 157 self.restore(cp); 158 return None; 159 } 160 }; 161 162 let result = match sym { 163 "shape" => self.eat_symbol().and_then(|s| { 164 let shape = match s { 165 "rectangle" => Shape::Rectangle, 166 "circle" => Shape::Circle, 167 "square" => Shape::Square, 168 _ => return None, 169 }; 170 Some(Attribute::Shape(shape)) 171 }), 172 "id" => self.eat_symbol().map(|s| Attribute::Id(s.to_string())), 173 "name" => self.eat_string().map(|s| Attribute::Name(s.to_string())), 174 "material" => self 175 .eat_string() 176 .map(|s| Attribute::Material(s.to_string())), 177 "condition" => self 178 .eat_string() 179 .map(|s| Attribute::Condition(s.to_string())), 180 "location" => self.eat_symbol().and_then(|s| { 181 let loc = match s { 182 "center" => Location::Center, 183 "floor" => Location::Floor, 184 "ceiling" => Location::Ceiling, 185 "top-of" => { 186 let id = self.eat_symbol()?; 187 Location::TopOf(id.to_string()) 188 } 189 "near" => { 190 let id = self.eat_symbol()?; 191 Location::Near(id.to_string()) 192 } 193 other => Location::Custom(other.to_string()), 194 }; 195 Some(Attribute::Location(loc)) 196 }), 197 "state" => self.eat_symbol().and_then(|s| { 198 let state = match s { 199 "on" => CellState::On, 200 "off" => CellState::Off, 201 "sleeping" => CellState::Sleeping, 202 _ => return None, 203 }; 204 Some(Attribute::State(state)) 205 }), 206 "type" => self.eat_symbol().map(|s| Attribute::Type(s.to_string())), 207 "width" => self.eat_number().map(Attribute::Width), 208 "height" => self.eat_number().map(Attribute::Height), 209 "depth" => self.eat_number().map(Attribute::Depth), 210 "position" => { 211 let x = self.eat_number(); 212 let y = self.eat_number(); 213 let z = self.eat_number(); 214 match (x, y, z) { 215 (Some(x), Some(y), Some(z)) => Some(Attribute::Position(x, y, z)), 216 _ => None, 217 } 218 } 219 "rotation" => { 220 let x = self.eat_number(); 221 let y = self.eat_number(); 222 let z = self.eat_number(); 223 match (x, y, z) { 224 (Some(x), Some(y), Some(z)) => Some(Attribute::Rotation(x, y, z)), 225 _ => None, 226 } 227 } 228 "model-url" => self 229 .eat_string() 230 .map(|s| Attribute::ModelUrl(s.to_string())), 231 "tileset" => { 232 let mut names = Vec::new(); 233 while let Some(s) = self.eat_string() { 234 names.push(s.to_string()); 235 } 236 if names.is_empty() { 237 None 238 } else { 239 Some(Attribute::Tileset(names)) 240 } 241 } 242 "data" => self.eat_string().map(|s| Attribute::Data(s.to_string())), 243 _ => None, 244 }; 245 246 match result { 247 Some(attr) => { 248 if self.eat_close() { 249 Some(attr) 250 } else { 251 self.restore(cp); 252 None 253 } 254 } 255 None => { 256 self.restore(cp); 257 None 258 } 259 } 260 } 261 262 /// Parse zero or more attributes, returning the count. 263 /// Attributes are pushed contiguously into self.attributes. 264 fn parse_attributes(&mut self) -> u16 { 265 let mut count = 0u16; 266 while let Some(attr) = self.try_parse_attribute() { 267 self.attributes.push(attr); 268 count += 1; 269 } 270 count 271 } 272 273 // --- Cell parsing --- 274 275 /// Parse attributes and an optional child cell (for room/space/object). 276 fn parse_cell_attrs(&mut self, cell_type: CellType) -> Option<CellId> { 277 let first_attr = self.attributes.len() as u32; 278 let attr_count = self.parse_attributes(); 279 280 // Parse optional child cell — recursion may push to child_ids 281 let opt_child = self.parse_cell(); 282 283 // Capture first_child AFTER recursion so nested children don't interleave 284 let first_child = self.child_ids.len() as u32; 285 let child_count; 286 if let Some(child_id) = opt_child { 287 self.child_ids.push(child_id); 288 child_count = 1u16; 289 } else { 290 child_count = 0; 291 } 292 293 let id = self.push_cell(Cell { 294 cell_type, 295 first_attr, 296 attr_count, 297 first_child, 298 child_count, 299 parent: None, 300 }); 301 302 // Set parent on children 303 for i in 0..child_count { 304 let child_id = self.child_ids[(first_child + i as u32) as usize]; 305 self.cells[child_id.0 as usize].parent = Some(id); 306 } 307 308 Some(id) 309 } 310 311 fn try_parse_named_cell(&mut self, name: &str, cell_type: CellType) -> Option<CellId> { 312 let cp = self.checkpoint(); 313 314 if !self.eat_symbol_match(name) { 315 self.restore(cp); 316 return None; 317 } 318 319 match self.parse_cell_attrs(cell_type) { 320 Some(id) => Some(id), 321 None => { 322 self.restore(cp); 323 None 324 } 325 } 326 } 327 328 fn try_parse_room(&mut self) -> Option<CellId> { 329 self.try_parse_named_cell("room", CellType::Room) 330 } 331 332 fn try_parse_space(&mut self) -> Option<CellId> { 333 self.try_parse_named_cell("space", CellType::Space) 334 } 335 336 fn try_parse_group(&mut self) -> Option<CellId> { 337 let cp = self.checkpoint(); 338 339 if !self.eat_symbol_match("group") { 340 self.restore(cp); 341 return None; 342 } 343 344 // Collect children — each parse_cell may recursively push to child_ids, 345 // so we collect CellIds first and append ours after recursion completes 346 let mut collected = Vec::new(); 347 while let Some(child_id) = self.parse_cell() { 348 collected.push(child_id); 349 } 350 351 if collected.is_empty() { 352 self.restore(cp); 353 return None; 354 } 355 356 // Now append our children contiguously 357 let first_child = self.child_ids.len() as u32; 358 let child_count = collected.len() as u16; 359 self.child_ids.extend_from_slice(&collected); 360 361 let id = self.push_cell(Cell { 362 cell_type: CellType::Group, 363 first_attr: 0, 364 attr_count: 0, 365 first_child, 366 child_count, 367 parent: None, 368 }); 369 370 // Set parent on children 371 for i in 0..child_count { 372 let child_id = self.child_ids[(first_child + i as u32) as usize]; 373 self.cells[child_id.0 as usize].parent = Some(id); 374 } 375 376 Some(id) 377 } 378 379 fn try_parse_tilemap(&mut self) -> Option<CellId> { 380 self.try_parse_named_cell("tilemap", CellType::Tilemap) 381 } 382 383 fn try_parse_object(&mut self) -> Option<CellId> { 384 let cp = self.checkpoint(); 385 386 let sym = self.eat_symbol()?; 387 388 let obj_type = match sym { 389 "table" => ObjectType::Table, 390 "chair" => ObjectType::Chair, 391 "door" => ObjectType::Door, 392 "light" => ObjectType::Light, 393 _ => ObjectType::Custom(sym.to_string()), 394 }; 395 396 match self.parse_cell_attrs(CellType::Object(obj_type)) { 397 Some(id) => Some(id), 398 None => { 399 self.restore(cp); 400 None 401 } 402 } 403 } 404 405 fn parse_cell(&mut self) -> Option<CellId> { 406 let cp = self.checkpoint(); 407 408 if !self.eat_open() { 409 return None; 410 } 411 412 // Try each cell type 413 let id = self 414 .try_parse_group() 415 .or_else(|| self.try_parse_room()) 416 .or_else(|| self.try_parse_space()) 417 .or_else(|| self.try_parse_tilemap()) 418 .or_else(|| self.try_parse_object()); 419 420 match id { 421 Some(id) => { 422 if self.eat_close() { 423 Some(id) 424 } else { 425 self.restore(cp); 426 None 427 } 428 } 429 None => { 430 self.restore(cp); 431 None 432 } 433 } 434 } 435 } 436 437 #[cfg(test)] 438 mod tests { 439 use super::*; 440 441 #[test] 442 fn test_parse_simple_room() { 443 let space = parse("(room (name \"Test Room\") (width 10))").unwrap(); 444 assert_eq!(space.cells.len(), 1); 445 let root = space.cell(space.root); 446 assert_eq!(root.cell_type, CellType::Room); 447 assert_eq!(root.attr_count, 2); 448 assert_eq!(space.name(space.root), Some("Test Room")); 449 } 450 451 #[test] 452 fn test_parse_object_with_child() { 453 let input = "(table (name \"desk\") (light (name \"lamp\")))"; 454 let space = parse(input).unwrap(); 455 // light is cell 0, table is cell 1 456 assert_eq!(space.cells.len(), 2); 457 let root = space.cell(space.root); 458 assert_eq!(root.cell_type, CellType::Object(ObjectType::Table)); 459 assert_eq!(root.child_count, 1); 460 461 let children = space.children(space.root); 462 let child = space.cell(children[0]); 463 assert_eq!(child.cell_type, CellType::Object(ObjectType::Light)); 464 assert_eq!(space.name(children[0]), Some("lamp")); 465 } 466 467 #[test] 468 fn test_parse_group() { 469 let input = "(room (group (table (name \"t1\")) (chair (name \"c1\"))))"; 470 let space = parse(input).unwrap(); 471 // table=0, chair=1, group=2, room=3 472 assert_eq!(space.cells.len(), 4); 473 let root = space.cell(space.root); 474 assert_eq!(root.cell_type, CellType::Room); 475 476 // room has one child (group) 477 let room_children = space.children(space.root); 478 assert_eq!(room_children.len(), 1); 479 let group = space.cell(room_children[0]); 480 assert_eq!(group.cell_type, CellType::Group); 481 482 // group has two children 483 let group_children = space.children(room_children[0]); 484 assert_eq!(group_children.len(), 2); 485 assert_eq!( 486 space.cell(group_children[0]).cell_type, 487 CellType::Object(ObjectType::Table) 488 ); 489 assert_eq!( 490 space.cell(group_children[1]).cell_type, 491 CellType::Object(ObjectType::Chair) 492 ); 493 } 494 495 #[test] 496 fn test_parse_location_variants() { 497 // Simple locations 498 let space = parse("(table (location center))").unwrap(); 499 assert_eq!(space.location(space.root), Some(&Location::Center)); 500 501 let space = parse("(table (location floor))").unwrap(); 502 assert_eq!(space.location(space.root), Some(&Location::Floor)); 503 504 let space = parse("(table (location ceiling))").unwrap(); 505 assert_eq!(space.location(space.root), Some(&Location::Ceiling)); 506 507 // Relational locations 508 let space = parse("(prop (location top-of obj1))").unwrap(); 509 assert_eq!( 510 space.location(space.root), 511 Some(&Location::TopOf("obj1".to_string())) 512 ); 513 514 let space = parse("(chair (location near desk))").unwrap(); 515 assert_eq!( 516 space.location(space.root), 517 Some(&Location::Near("desk".to_string())) 518 ); 519 520 // Custom/unknown location 521 let space = parse("(light (location somewhere))").unwrap(); 522 assert_eq!( 523 space.location(space.root), 524 Some(&Location::Custom("somewhere".to_string())) 525 ); 526 } 527 528 #[test] 529 fn test_parse_tilemap() { 530 let input = r#"(tilemap (width 10) (height 10) (tileset "grass" "stone") (data "0"))"#; 531 let space = parse(input).unwrap(); 532 let root = space.cell(space.root); 533 assert_eq!(root.cell_type, CellType::Tilemap); 534 assert_eq!(space.width(space.root), Some(10.0)); 535 assert_eq!(space.height(space.root), Some(10.0)); 536 assert_eq!( 537 space.tileset(space.root), 538 Some(&vec!["grass".to_string(), "stone".to_string()]) 539 ); 540 assert_eq!(space.data(space.root), Some("0")); 541 } 542 543 #[test] 544 fn test_parse_tilemap_in_group() { 545 let input = r#"(space (name "Test") (group (tilemap (width 5) (height 5) (tileset "grass") (data "0")) (table (id t1))))"#; 546 let space = parse(input).unwrap(); 547 let group_id = space.children(space.root)[0]; 548 let children = space.children(group_id); 549 assert_eq!(children.len(), 2); 550 assert_eq!(space.cell(children[0]).cell_type, CellType::Tilemap); 551 assert_eq!( 552 space.cell(children[1]).cell_type, 553 CellType::Object(ObjectType::Table) 554 ); 555 } 556 557 #[test] 558 fn test_location_roundtrip() { 559 use crate::serializer::serialize; 560 561 let input = r#"(room (group (table (id obj1) (position 0 0 0)) (prop (id obj2) (location top-of obj1))))"#; 562 let space1 = parse(input).unwrap(); 563 let serialized = serialize(&space1); 564 let space2 = parse(&serialized).unwrap(); 565 566 // Find obj2 in both 567 let group1 = space1.children(space1.root)[0]; 568 let obj2_1 = space1.children(group1)[1]; 569 assert_eq!( 570 space1.location(obj2_1), 571 Some(&Location::TopOf("obj1".to_string())) 572 ); 573 574 let group2 = space2.children(space2.root)[0]; 575 let obj2_2 = space2.children(group2)[1]; 576 assert_eq!( 577 space2.location(obj2_2), 578 Some(&Location::TopOf("obj1".to_string())) 579 ); 580 } 581 }