serializer.rs (5433B)
1 use crate::ast::*; 2 use std::fmt::Write; 3 4 /// Serialize a Space back to s-expression format. 5 pub fn serialize(space: &Space) -> String { 6 serialize_from(space, space.root) 7 } 8 9 /// Serialize a subtree starting from a specific cell. 10 pub fn serialize_from(space: &Space, root: CellId) -> String { 11 let mut out = String::new(); 12 write_cell(space, root, 0, &mut out); 13 out 14 } 15 16 fn format_number(n: f64) -> String { 17 if n == n.floor() && n.abs() < i64::MAX as f64 { 18 format!("{}", n as i64) 19 } else { 20 // Round to 4 decimal places to avoid f32→f64 noise 21 // (e.g. -0.20000000298023224 → -0.2) 22 let rounded = (n * 10000.0).round() / 10000.0; 23 let s = format!("{:.4}", rounded); 24 let s = s.trim_end_matches('0'); 25 let s = s.trim_end_matches('.'); 26 s.to_string() 27 } 28 } 29 30 fn write_cell(space: &Space, id: CellId, indent: usize, out: &mut String) { 31 let cell = space.cell(id); 32 let pad = " ".repeat(indent); 33 let inner_pad = " ".repeat(indent + 1); 34 35 out.push('('); 36 out.push_str(&cell.cell_type.to_string()); 37 38 // Attributes 39 let attrs = space.attrs(id); 40 for attr in attrs { 41 let _ = write!(out, "\n{}", inner_pad); 42 write_attr(attr, out); 43 } 44 45 // Children 46 let children = space.children(id); 47 for &child_id in children { 48 let _ = write!(out, "\n{}", inner_pad); 49 write_cell(space, child_id, indent + 1, out); 50 } 51 52 // Closing paren on same line if no attrs/children, else on new line 53 if !attrs.is_empty() || !children.is_empty() { 54 // For readability, close on the last line 55 out.push(')'); 56 } else { 57 out.push(')'); 58 } 59 60 let _ = pad; // used above via inner_pad derivation 61 } 62 63 fn write_attr(attr: &Attribute, out: &mut String) { 64 match attr { 65 Attribute::Shape(s) => { 66 let _ = write!(out, "(shape {})", s); 67 } 68 Attribute::Id(s) => { 69 let _ = write!(out, "(id {})", s); 70 } 71 Attribute::Name(s) => { 72 let _ = write!(out, "(name \"{}\")", s); 73 } 74 Attribute::Material(s) => { 75 let _ = write!(out, "(material \"{}\")", s); 76 } 77 Attribute::Condition(s) => { 78 let _ = write!(out, "(condition \"{}\")", s); 79 } 80 Attribute::Location(loc) => { 81 let _ = write!(out, "(location {})", loc); 82 } 83 Attribute::State(s) => { 84 let _ = write!(out, "(state {})", s); 85 } 86 Attribute::Type(s) => { 87 let _ = write!(out, "(type {})", s); 88 } 89 Attribute::Width(n) => { 90 let _ = write!(out, "(width {})", format_number(*n)); 91 } 92 Attribute::Height(n) => { 93 let _ = write!(out, "(height {})", format_number(*n)); 94 } 95 Attribute::Depth(n) => { 96 let _ = write!(out, "(depth {})", format_number(*n)); 97 } 98 Attribute::Position(x, y, z) => { 99 let _ = write!( 100 out, 101 "(position {} {} {})", 102 format_number(*x), 103 format_number(*y), 104 format_number(*z) 105 ); 106 } 107 Attribute::Rotation(x, y, z) => { 108 let _ = write!( 109 out, 110 "(rotation {} {} {})", 111 format_number(*x), 112 format_number(*y), 113 format_number(*z) 114 ); 115 } 116 Attribute::ModelUrl(s) => { 117 let _ = write!(out, "(model-url \"{}\")", s); 118 } 119 Attribute::Tileset(names) => { 120 out.push_str("(tileset"); 121 for name in names { 122 let _ = write!(out, " \"{}\"", name); 123 } 124 out.push(')'); 125 } 126 Attribute::Data(s) => { 127 let _ = write!(out, "(data \"{}\")", s); 128 } 129 } 130 } 131 132 #[cfg(test)] 133 mod tests { 134 use super::*; 135 use crate::parser::parse; 136 137 #[test] 138 fn test_serialize_simple() { 139 let space = parse("(room (name \"Test\") (width 10))").unwrap(); 140 let output = serialize(&space); 141 assert!(output.contains("(room")); 142 assert!(output.contains("(name \"Test\")")); 143 assert!(output.contains("(width 10)")); 144 } 145 146 #[test] 147 fn test_tilemap_roundtrip() { 148 let input = 149 r#"(tilemap (width 10) (height 10) (tileset "grass" "stone") (data "0 0 1 1"))"#; 150 let space = parse(input).unwrap(); 151 let serialized = serialize(&space); 152 let reparsed = parse(&serialized).unwrap(); 153 assert_eq!(reparsed.cell(reparsed.root).cell_type, CellType::Tilemap); 154 assert_eq!( 155 reparsed.tileset(reparsed.root), 156 Some(&vec!["grass".to_string(), "stone".to_string()]) 157 ); 158 assert_eq!(reparsed.data(reparsed.root), Some("0 0 1 1")); 159 } 160 161 #[test] 162 fn test_format_number_strips_float_noise() { 163 // Integers 164 assert_eq!(format_number(10.0), "10"); 165 assert_eq!(format_number(-3.0), "-3"); 166 assert_eq!(format_number(0.0), "0"); 167 168 // Clean decimals 169 assert_eq!(format_number(1.5), "1.5"); 170 assert_eq!(format_number(-0.2), "-0.2"); 171 172 // f32→f64 noise: -0.2f32 as f64 == -0.20000000298023224 173 assert_eq!(format_number(-0.2_f32 as f64), "-0.2"); 174 assert_eq!(format_number(0.5_f32 as f64), "0.5"); 175 assert_eq!(format_number(1.125_f32 as f64), "1.125"); 176 } 177 }