1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4 impl_block_properties_parser,
5 parser::{close_block, key_value_numeric, open_block, InternalParser, TokenError, TokenSource},
6 types::{EditorData, Side},
7 Parser,
8};
9
10#[derive(Debug, Default, Clone)]
12pub struct Solid<'src> {
13 pub id: u32,
14 pub sides: Vec<Side<'src>>,
15 pub editor: Option<EditorData<'src>>,
16}
17
18#[derive(Debug, Clone)]
20enum SolidProperty<'src> {
21 Id(u32),
22 Side(Side<'src>),
23 Editor(EditorData<'src>),
24}
25
26impl<'src> Parser<'src> for Solid<'src> {}
28
29impl<'src> InternalParser<'src> for Solid<'src> {
62 fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
63 where
64 I: TokenSource<'src>,
65 {
66 impl_block_properties_parser! {
67 property_list: SolidProperty = {
68 p_id = key_value_numeric("id") => SolidProperty::Id,
69 }
70 }
71
72 let side_parser = Side::parser().map(SolidProperty::Side);
74 let editor_parser = EditorData::parser().map(SolidProperty::Editor);
75
76 let any_property = property_list.or(side_parser).or(editor_parser);
78
79 open_block("solid")
80 .ignore_then(any_property.repeated().collect::<Vec<SolidProperty>>())
81 .then_ignore(close_block())
82 .map(|properties: Vec<SolidProperty>| {
83 let mut solid = Solid::default();
84 for prop in properties {
85 match prop {
86 SolidProperty::Id(val) => solid.id = val,
87 SolidProperty::Side(val) => solid.sides.push(val),
88 SolidProperty::Editor(val) => solid.editor = Some(val),
89 }
90 }
91 solid
92 })
93 .boxed()
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::util::lex;
101
102 #[test]
103 fn test_solid_complete() {
104 let input = r#"
105 solid
106 {
107 "id" "9"
108 side
109 {
110 "id" "1"
111 "plane" "(-320 -320 0) (-320 320 0) (320 320 0)"
112 "material" "DEV/DEV_MEASUREGENERIC01B"
113 "uaxis" "[1 0 0 0] 0.25"
114 "vaxis" "[0 -1 0 0] 0.25"
115 "rotation" "0"
116 "lightmapscale" "16"
117 "smoothing_groups" "0"
118 }
119 side
120 {
121 "id" "2"
122 "plane" "(-320 320 -64) (-320 -320 -64) (320 -320 -64)"
123 "material" "DEV/DEV_MEASUREGENERIC01B"
124 "uaxis" "[1 0 0 0] 0.25"
125 "vaxis" "[0 -1 0 0] 0.25"
126 "rotation" "0"
127 "lightmapscale" "16"
128 "smoothing_groups" "0"
129 }
130 editor
131 {
132 "color" "0 111 152"
133 "visgroupshown" "1"
134 "visgroupautoshown" "1"
135 }
136 }
137 "#;
138
139 let stream = lex(input);
140 let result = Solid::parse(stream);
141 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
142
143 let solid = result.unwrap();
144 assert_eq!(solid.id, 9);
145 assert_eq!(solid.sides.len(), 2);
146 assert!(solid.editor.is_some());
147 assert_eq!(solid.sides[0].id, 1);
148 assert_eq!(solid.sides[1].id, 2);
149 }
150
151 #[test]
152 fn test_solid_minimal() {
153 let input = r#"
154 solid
155 {
156 "id" "100"
157 side
158 {
159 "id" "1"
160 "plane" "(0 0 0) (1 0 0) (1 1 0)"
161 "material" "DEV/DEV_MEASUREGENERIC01B"
162 "uaxis" "[1 0 0 0] 0.25"
163 "vaxis" "[0 -1 0 0] 0.25"
164 }
165 }
166 "#;
167
168 let stream = lex(input);
169 let result = Solid::parse(stream);
170 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
171
172 let solid = result.unwrap();
173 assert_eq!(solid.id, 100);
174 assert_eq!(solid.sides.len(), 1);
175 assert!(solid.editor.is_none());
176 }
177
178 #[test]
179 fn test_solid_multiple_sides() {
180 let input = r#"
181 solid
182 {
183 "id" "50"
184 side
185 {
186 "id" "1"
187 "plane" "(0 0 0) (1 0 0) (1 1 0)"
188 "material" "BRICK/BRICKWALL001A"
189 "uaxis" "[1 0 0 0] 0.25"
190 "vaxis" "[0 -1 0 0] 0.25"
191 }
192 side
193 {
194 "id" "2"
195 "plane" "(0 0 0) (1 0 0) (1 1 0)"
196 "material" "CONCRETE/CONCRETEWALL001"
197 "uaxis" "[1 0 0 0] 0.25"
198 "vaxis" "[0 -1 0 0] 0.25"
199 }
200 side
201 {
202 "id" "3"
203 "plane" "(0 0 0) (1 0 0) (1 1 0)"
204 "material" "METAL/METALWALL001"
205 "uaxis" "[1 0 0 0] 0.25"
206 "vaxis" "[0 -1 0 0] 0.25"
207 }
208 editor
209 {
210 "color" "255 128 64"
211 "visgroupshown" "1"
212 "visgroupautoshown" "1"
213 }
214 }
215 "#;
216
217 let stream = lex(input);
218 let result = Solid::parse(stream);
219 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
220
221 let solid = result.unwrap();
222 assert_eq!(solid.id, 50);
223 assert_eq!(solid.sides.len(), 3);
224 assert_eq!(solid.sides[0].material, "BRICK/BRICKWALL001A");
225 assert_eq!(solid.sides[1].material, "CONCRETE/CONCRETEWALL001");
226 assert_eq!(solid.sides[2].material, "METAL/METALWALL001");
227 }
228
229 #[test]
230 fn test_solid_out_of_order() {
231 let input = r#"
232 solid
233 {
234 side
235 {
236 "id" "1"
237 "plane" "(0 0 0) (1 0 0) (1 1 0)"
238 "material" "DEV/DEV_MEASUREGENERIC01B"
239 "uaxis" "[1 0 0 0] 0.25"
240 "vaxis" "[0 -1 0 0] 0.25"
241 }
242 "id" "75"
243 editor
244 {
245 "color" "0 255 0"
246 "visgroupshown" "1"
247 "visgroupautoshown" "1"
248 }
249 }
250 "#;
251
252 let stream = lex(input);
253 let result = Solid::parse(stream);
254 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
255
256 let solid = result.unwrap();
257 assert_eq!(solid.id, 75);
258 assert_eq!(solid.sides.len(), 1);
259 assert!(solid.editor.is_some());
260 }
261
262 #[test]
263 fn test_solid_empty_block() {
264 let input = r#"
265 solid
266 {
267 }
268 "#;
269
270 let stream = lex(input);
271 let result = Solid::parse(stream);
272 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
273
274 let solid = result.unwrap();
275 assert_eq!(solid.id, 0);
276 assert_eq!(solid.sides.len(), 0);
277 }
278
279 #[test]
280 fn test_solid_no_editor() {
281 let input = r#"
282 solid
283 {
284 "id" "200"
285 side
286 {
287 "id" "1"
288 "plane" "(0 0 0) (1 0 0) (1 1 0)"
289 "material" "DEV/DEV_MEASUREGENERIC01B"
290 "uaxis" "[1 0 0 0] 0.25"
291 "vaxis" "[0 -1 0 0] 0.25"
292 }
293 }
294 "#;
295
296 let stream = lex(input);
297 let result = Solid::parse(stream);
298 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
299
300 let solid = result.unwrap();
301 assert_eq!(solid.id, 200);
302 assert_eq!(solid.sides.len(), 1);
303 assert!(solid.editor.is_none());
304 }
305
306 #[test]
307 fn test_solid_invalid_block_name() {
308 let input = r#"
309 wrongname
310 {
311 "id" "1"
312 }
313 "#;
314
315 let stream = lex(input);
316 let result = Solid::parse(stream);
317 assert!(result.is_err(), "Parser should fail on invalid block name");
318 }
319
320 #[test]
321 fn test_solid_missing_closing_brace() {
322 let input = r#"
323 solid
324 {
325 "id" "1"
326 side
327 {
328 "id" "1"
329 "plane" "(0 0 0) (1 0 0) (1 1 0)"
330 "material" "DEV/DEV_MEASUREGENERIC01B"
331 "uaxis" "[1 0 0 0] 0.25"
332 "vaxis" "[0 -1 0 0] 0.25"
333 }
334 "#;
335
336 let stream = lex(input);
337 let result = Solid::parse(stream);
338 assert!(
339 result.is_err(),
340 "Parser should fail on missing closing brace"
341 );
342 }
343}