Skip to main content

mnk_vmf/types/
group.rs

1use chumsky::{prelude::recursive, 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,
7    Parser,
8};
9
10#[derive(Debug, Default, Clone)]
11pub struct Group<'src> {
12    pub id: u32,
13    pub editor: Option<EditorData<'src>>,
14    pub groups: Vec<Group<'src>>,
15}
16
17#[derive(Debug, Clone)]
18enum GroupProperty<'src> {
19    Id(u32),
20    Editor(EditorData<'src>),
21    Child(Group<'src>),
22}
23
24/// Public parser trait implementation that allows [`Group`] to use ::parse(input) call.
25impl<'src> Parser<'src> for Group<'src> {}
26
27/// A [`InternalParser`] implementation for [`Group`].
28///
29/// usage: `let editor = Group::parser().parse(input);`.
30///
31/// The format that is being parsed here is:
32/// ```ignore
33/// group
34/// 	{
35/// 		"id" "772983"
36/// 		editor
37/// 		{
38/// 			"color" "254 255 0"
39/// 			"groupid" "772977"
40/// 			"visgroupshown" "1"
41/// 			"visgroupautoshown" "1"
42/// 		}
43/// 	}
44///```
45impl<'src> InternalParser<'src> for Group<'src> {
46    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
47    where
48        I: TokenSource<'src>,
49    {
50        recursive(|group_parser| {
51            impl_block_properties_parser! {
52                property_list: GroupProperty = {
53                    p_id     = key_value_numeric("id") => GroupProperty::Id,
54                    p_editor = EditorData::parser()    => GroupProperty::Editor,
55                    p_child  = group_parser.clone()    => GroupProperty::Child,
56                }
57            }
58
59            open_block("group")
60                .boxed()
61                .ignore_then(property_list.repeated().collect::<Vec<GroupProperty>>())
62                .then_ignore(close_block())
63                .map(|properties: Vec<GroupProperty>| {
64                    let mut group = Group::default();
65                    for prop in properties {
66                        match prop {
67                            GroupProperty::Id(val) => group.id = val,
68                            GroupProperty::Editor(val) => group.editor = Some(val),
69                            GroupProperty::Child(val) => group.groups.push(val),
70                        }
71                    }
72                    group
73                })
74                .boxed()
75        })
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::util::lex;
83
84    #[test]
85    fn parse_simple_group() {
86        let input = lex(r#"
87            group
88            {
89                "id" "10"
90                editor
91                {
92                    "color" "255 0 0"
93                    "visgroupshown" "1"
94                    "visgroupautoshown" "1"
95                }
96            }
97        "#);
98
99        let result = Group::parse(input);
100        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
101        let group = result.unwrap();
102        assert_eq!(group.id, 10);
103        assert!(group.editor.is_some());
104    }
105
106    #[test]
107    fn parse_nested_groups() {
108        let input = lex(r#"
109            group
110            {
111                "id" "100"
112                group
113                {
114                    "id" "101"
115                }
116            }
117        "#);
118
119        let result = Group::parse(input);
120        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
121        let group = result.unwrap();
122        assert_eq!(group.id, 100);
123        assert_eq!(group.groups.len(), 1);
124        assert_eq!(group.groups[0].id, 101);
125    }
126}