1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4 impl_block_properties_parser,
5 parser::{
6 close_block, key_value_boolean, key_value_numeric, open_block, InternalParser, TokenError,
7 TokenSource,
8 },
9 Parser,
10};
11
12#[derive(Debug, Default, Eq, PartialEq)]
14pub struct ViewSettings {
15 snap_to_grid: bool,
16 show_grid: bool,
17 show_logical_grid: bool,
18 grid_spacing: u32,
19 show_3d_grid: bool,
20 hide_objects: bool,
21 hide_walls: bool,
22 hide_stripes: bool,
23 hide_neighbors: bool,
24 hide_detail: bool,
25 show_brushes: bool,
26 show_entities: bool,
27 show_light_radius: bool,
28 show_lighting_preview: bool,
29 show_wireframe: bool,
30}
31
32#[derive(Debug, Clone)]
34enum ViewSettingsProperty {
35 SnapToGrid(bool),
36 ShowGrid(bool),
37 ShowLogicalGrid(bool),
38 GridSpacing(u32),
39 Show3DGrid(bool),
40 HideObjects(bool),
41 HideWalls(bool),
42 HideStripes(bool),
43 HideNeighbors(bool),
44 HideDetail(bool),
45 ShowBrushes(bool),
46 ShowEntities(bool),
47 ShowLightRadius(bool),
48 ShowLightingPreview(bool),
49 ShowWireframe(bool),
50}
51
52impl Parser<'_> for ViewSettings {}
54
55impl<'src> InternalParser<'src> for ViewSettings {
70 fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
71 where
72 I: TokenSource<'src>,
73 {
74 impl_block_properties_parser! {
75 property_list: ViewSettingsProperty = {
76 p_snap_to_grid = key_value_boolean("bSnapToGrid") => ViewSettingsProperty::SnapToGrid,
77 p_show_grid = key_value_boolean("bShowGrid") => ViewSettingsProperty::ShowGrid,
78 p_show_logical_grid = key_value_boolean("bShowLogicalGrid") => ViewSettingsProperty::ShowLogicalGrid,
79 p_grid_spacing = key_value_numeric("nGridSpacing") => ViewSettingsProperty::GridSpacing,
80 p_show_3d_grid = key_value_boolean("bShow3DGrid") => ViewSettingsProperty::Show3DGrid,
81 p_hide_objects = key_value_boolean("bHideObjects") => ViewSettingsProperty::HideObjects,
82 p_hide_walls = key_value_boolean("bHideWalls") => ViewSettingsProperty::HideWalls,
83 p_hide_stripes = key_value_boolean("bHideStripes") => ViewSettingsProperty::HideStripes,
84 p_hide_neighbors = key_value_boolean("bHideNeighbors") => ViewSettingsProperty::HideNeighbors,
85 p_hide_detail = key_value_boolean("bHideDetail") => ViewSettingsProperty::HideDetail,
86 p_show_brushes = key_value_boolean("bShowBrushes") => ViewSettingsProperty::ShowBrushes,
87 p_show_entities = key_value_boolean("bShowEntities") => ViewSettingsProperty::ShowEntities,
88 p_show_light_radius = key_value_boolean("bShowLightRadius") => ViewSettingsProperty::ShowLightRadius,
89 p_show_lighting_preview = key_value_boolean("bShowLightingPreview") => ViewSettingsProperty::ShowLightingPreview,
90 p_show_wireframe = key_value_boolean("bShowWireframe") => ViewSettingsProperty::ShowWireframe,
91 }
92 }
93 open_block("viewsettings")
94 .ignore_then(
95 property_list
96 .repeated()
97 .collect::<Vec<ViewSettingsProperty>>(),
98 )
99 .then_ignore(close_block())
100 .map(|properties: Vec<ViewSettingsProperty>| {
101 let mut settings = ViewSettings::default();
102 for prop in properties {
103 match prop {
104 ViewSettingsProperty::SnapToGrid(val) => settings.snap_to_grid = val,
105 ViewSettingsProperty::ShowGrid(val) => settings.show_grid = val,
106 ViewSettingsProperty::ShowLogicalGrid(val) => {
107 settings.show_logical_grid = val
108 }
109 ViewSettingsProperty::GridSpacing(val) => settings.grid_spacing = val,
110 ViewSettingsProperty::Show3DGrid(val) => settings.show_3d_grid = val,
111 ViewSettingsProperty::HideObjects(val) => settings.hide_objects = val,
112 ViewSettingsProperty::HideWalls(val) => settings.hide_walls = val,
113 ViewSettingsProperty::HideStripes(val) => settings.hide_stripes = val,
114 ViewSettingsProperty::HideNeighbors(val) => settings.hide_neighbors = val,
115 ViewSettingsProperty::HideDetail(val) => settings.hide_detail = val,
116 ViewSettingsProperty::ShowBrushes(val) => settings.show_brushes = val,
117 ViewSettingsProperty::ShowEntities(val) => settings.show_entities = val,
118 ViewSettingsProperty::ShowLightRadius(val) => {
119 settings.show_light_radius = val
120 }
121 ViewSettingsProperty::ShowLightingPreview(val) => {
122 settings.show_lighting_preview = val
123 }
124 ViewSettingsProperty::ShowWireframe(val) => settings.show_wireframe = val,
125 }
126 }
127 settings
128 })
129 .boxed()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::{parser::lexer::Token, util::lex, Parser};
136
137 use super::*;
138 use chumsky::error::RichReason;
139
140 fn parse_viewsettings_str(input_str: &str) -> Result<ViewSettings, Vec<RichReason<Token<'_>>>> {
141 ViewSettings::parse(lex(input_str))
142 }
143
144 #[test]
145 fn test_viewsettings_full_valid_input() {
146 let input = r#"
147 viewsettings
148 {
149 "bSnapToGrid" "1"
150 "bShowGrid" "1"
151 "bShowLogicalGrid" "0"
152 "nGridSpacing" "64"
153 "bShow3DGrid" "1"
154 "bHideObjects" "0"
155 "bHideWalls" "1"
156 "bHideStripes" "0"
157 "bHideNeighbors" "1"
158 "bHideDetail" "0"
159 "bShowBrushes" "1"
160 "bShowEntities" "0"
161 "bShowLightRadius" "1"
162 "bShowLightingPreview" "0"
163 "bShowWireframe" "1"
164 }"#;
165
166 let result = parse_viewsettings_str(input);
167 assert!(
168 result.is_ok(),
169 "Parser failed with errors: {:?}",
170 result.err()
171 );
172
173 let settings = result.unwrap();
174 assert_eq!(
175 settings,
176 ViewSettings {
177 snap_to_grid: true,
178 show_grid: true,
179 show_logical_grid: false,
180 grid_spacing: 64,
181 show_3d_grid: true,
182 hide_objects: false,
183 hide_walls: true,
184 hide_stripes: false,
185 hide_neighbors: true,
186 hide_detail: false,
187 show_brushes: true,
188 show_entities: false,
189 show_light_radius: true,
190 show_lighting_preview: false,
191 show_wireframe: true,
192 }
193 );
194 }
195
196 #[test]
197 fn test_viewsettings_partial_input_uses_defaults() {
198 let input = r#"
199 viewsettings
200 {
201 "bSnapToGrid" "1"
202 "nGridSpacing" "32"
203 "bShowWireframe" "0"
204 }"#;
205
206 let result = parse_viewsettings_str(input);
207 assert!(
208 result.is_ok(),
209 "Parser failed with errors: {:?}",
210 result.err()
211 );
212
213 let settings = result.unwrap();
214 let expected = ViewSettings {
215 snap_to_grid: true,
216 grid_spacing: 32,
217 show_wireframe: false,
218 ..Default::default()
219 };
220
221 assert_eq!(settings, expected);
222 }
223
224 #[test]
225 fn test_viewsettings_out_of_order() {
226 let input = r#"
227 viewsettings
228 {
229 "nGridSpacing" "16"
230 "bShowGrid" "0"
231 "bSnapToGrid" "1"
232 }"#;
233
234 let result = parse_viewsettings_str(input);
235 assert!(
236 result.is_ok(),
237 "Parser failed with errors: {:?}",
238 result.err()
239 );
240
241 let settings = result.unwrap();
242 let expected = ViewSettings {
243 grid_spacing: 16,
244 show_grid: false,
245 snap_to_grid: true,
246 ..Default::default()
247 };
248
249 assert_eq!(settings, expected);
250 }
251
252 #[test]
253 fn test_viewsettings_empty_block() {
254 let input = r#"
255 viewsettings
256 {
257 }"#;
258
259 let result = parse_viewsettings_str(input);
260 assert!(
261 result.is_ok(),
262 "Parser failed with errors: {:?}",
263 result.err()
264 );
265
266 let settings = result.unwrap();
267 assert_eq!(settings, ViewSettings::default());
268 }
269
270 #[test]
271 fn test_viewsettings_unknown_key_causes_error() {
272 let input = r#"
275 viewsettings
276 {
277 "bSnapToGrid" "1"
278 "bUnknownKey" "some_value"
279 "nGridSpacing" "64"
280 }"#;
281
282 let result = parse_viewsettings_str(input);
283 assert!(
284 result.is_err(),
285 "Parser should fail on unknown key mid-list"
286 );
287 }
289
290 #[test]
291 fn test_viewsettings_unknown_key_at_end_still_errors_if_not_last() {
292 let input = r#"
294 viewsettings
295 {
296 "bSnapToGrid" "1"
297 "bShowGrid" "1"
298 "bUnknownKey" "some_value"
299 }"#; let result = parse_viewsettings_str(input);
302 assert!(
303 result.is_err(),
304 "Parser should fail if block doesn't close properly after unknown key"
305 );
306 }
307
308 #[test]
309 fn test_viewsettings_invalid_value_type_for_numeric() {
310 let input = r#"
311 viewsettings
312 {
313 "nGridSpacing" "not_a_number"
314 }"#;
315
316 let result = parse_viewsettings_str(input);
317 assert!(
318 result.is_err(),
319 "Parser should fail on invalid numeric value"
320 );
321 }
323
324 #[test]
325 fn test_viewsettings_invalid_value_type_for_boolean() {
326 let input = r#"
327 viewsettings
328 {
329 "bSnapToGrid" "not_a_boolean"
330 }"#;
331
332 let result = parse_viewsettings_str(input);
333 assert!(
334 result.is_err(),
335 "Parser should fail on invalid boolean value"
336 );
337 }
339
340 #[test]
341 fn test_viewsettings_duplicate_keys_last_one_wins() {
342 let input = r#"
343 viewsettings
344 {
345 "bSnapToGrid" "0"
346 "nGridSpacing" "32"
347 "bSnapToGrid" "1"
348 "nGridSpacing" "64"
349 }"#;
350
351 let result = parse_viewsettings_str(input);
352 assert!(
353 result.is_ok(),
354 "Parser failed with errors: {:?}",
355 result.err()
356 );
357
358 let settings = result.unwrap();
359 let expected = ViewSettings {
360 snap_to_grid: true,
361 grid_spacing: 64,
362 ..Default::default()
363 };
364
365 assert_eq!(settings, expected);
366 }
367
368 #[test]
369 fn test_viewsettings_malformed_block_missing_closing_brace() {
370 let input = r#"
371 viewsettings
372 {
373 "bSnapToGrid" "1"
374 "#;
375
376 let result = parse_viewsettings_str(input);
377 assert!(
378 result.is_err(),
379 "Parser should fail on missing closing brace"
380 );
381 }
382
383 #[test]
384 fn test_viewsettings_malformed_block_missing_opening_brace() {
385 let input = r#"
386 viewsettings
387 "bSnapToGrid" "1"
388 }"#;
389
390 let result = parse_viewsettings_str(input);
391 assert!(
392 result.is_err(),
393 "Parser should fail on missing opening brace"
394 );
395 }
396
397 #[test]
398 fn test_viewsettings_invalid_block_name() {
399 let input = r#"
400 wrongblockname
401 {
402 "bSnapToGrid" "1"
403 }"#;
404
405 let result = parse_viewsettings_str(input);
406 assert!(result.is_err(), "Parser should fail on invalid block name");
407 }
408
409 #[test]
410 fn test_viewsettings_extra_content_after_closing_brace() {
411 let input = r#"
412 viewsettings
413 {
414 "bSnapToGrid" "1"
415 }
416 "extrastuff" "not good"
417 "#;
418 let result = parse_viewsettings_str(input);
419 assert!(
420 result.is_err(),
421 "Parser should fail. Errors: {:?}",
422 result.err()
423 );
424 }
425}