1use indexmap::IndexMap;
4use pest::iterators::Pair;
5use pest::Parser;
6use pest_derive::Parser;
7
8use crate::errors::{VmfError, VmfResult};
9
10use crate::vmf::regions::{Cordon, Cordons};
11use crate::{Cameras, Entity, VersionInfo, ViewSettings, VisGroups, VmfBlock, VmfFile, World};
12
13#[derive(Parser)]
15#[grammar = "vmf.pest"]
16struct VmfParser;
17
18pub fn parse_vmf(input: &str) -> VmfResult<VmfFile> {
28 let parsed = VmfParser::parse(Rule::file, input)?.next().unwrap();
29 let mut vmf_file = VmfFile::default();
30
31 for pair in parsed.into_inner() {
32 if pair.as_rule() == Rule::block {
33 let block: VmfBlock = parse_block(pair)?;
34
35 match block.name.to_lowercase().as_str() {
36 "versioninfo" => vmf_file.versioninfo = VersionInfo::try_from(block)?,
38 "visgroups" => vmf_file.visgroups = VisGroups::try_from(block)?,
39 "viewsettings" => vmf_file.viewsettings = ViewSettings::try_from(block)?,
40
41 "world" => vmf_file.world = World::try_from(block)?,
43
44 "entity" => vmf_file.entities.push(Entity::try_from(block)?),
46 "hidden" => {
47 if let Some(hidden_block) = block.blocks.first() {
48 let mut ent = Entity::try_from(hidden_block.to_owned())?;
49 ent.is_hidden = true;
50 vmf_file.hiddens.push(ent)
51 }
52 }
53
54 "cameras" => vmf_file.cameras = Cameras::try_from(block)?,
56 "cordons" => vmf_file.cordons = Cordons::try_from(block)?,
57 "cordon" => vmf_file.cordons.push(Cordon::try_from(block)?),
59 _ => {
61 #[cfg(feature = "debug_assert_info")]
62 debug_assert!(false, "Unexpected block name: {}", block.name);
63 }
64 }
65 }
66 }
67
68 Ok(vmf_file)
69}
70
71fn parse_block(pair: Pair<Rule>) -> VmfResult<VmfBlock> {
81 let mut inner = pair.into_inner();
82 let block_name_pair = inner
83 .next()
84 .ok_or(VmfError::InvalidFormat("block name not found".to_string()))?;
85
86 let name = block_name_pair.as_str().to_string();
87
88 let mut key_values = IndexMap::new();
89 let mut blocks = Vec::new();
90
91 for item in inner {
92 match item.as_rule() {
93 Rule::key_value => {
94 let mut kv_inner = item.into_inner();
95 let key = strip_quotes(
96 kv_inner
97 .next()
98 .ok_or(VmfError::InvalidFormat("key not found".to_string()))?
99 .as_str(),
100 );
101 let value = strip_quotes(
102 kv_inner
103 .next()
104 .ok_or(VmfError::InvalidFormat("value not found".to_string()))?
105 .as_str(),
106 );
107
108 key_values
109 .entry(key)
110 .and_modify(|existing_value: &mut String| {
111 existing_value.push('\r');
112 existing_value.push_str(&value);
113 })
114 .or_insert(value);
115 }
116 Rule::block => {
117 blocks.push(parse_block(item)?);
118 }
119 _ => {}
120 }
121 }
122
123 Ok(VmfBlock {
124 name,
125 key_values,
126 blocks,
127 })
128}
129
130fn strip_quotes(s: &str) -> String {
140 s.trim_matches('"').to_string()
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 #[test]
147 fn parse_block_valid_block() {
148 let input = "entity { \"classname\" \"logic_relay\" }";
149 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
150 let block = parse_block(parsed.next().unwrap()).unwrap();
151
152 assert_eq!(block.name, "entity");
153 assert_eq!(
154 block.key_values.get("classname"),
155 Some(&"logic_relay".to_string())
156 );
157 assert!(block.blocks.is_empty());
158 }
159
160 #[test]
161 fn parse_block_nested_blocks() {
162 let input = "entity { \"classname\" \"logic_relay\" solid { \"id\" \"1\" } }";
163 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
164 let block = parse_block(parsed.next().unwrap()).unwrap();
165
166 assert_eq!(block.name, "entity");
167 assert_eq!(
168 block.key_values.get("classname"),
169 Some(&"logic_relay".to_string())
170 );
171 assert_eq!(block.blocks.len(), 1);
172 assert_eq!(block.blocks[0].name, "solid");
173 assert_eq!(block.blocks[0].key_values.get("id"), Some(&"1".to_string()));
174 }
175
176 #[test]
177 fn parse_block_empty_block() {
178 let input = "entity { }";
179 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
180 let block = parse_block(parsed.next().unwrap()).unwrap();
181
182 assert_eq!(block.name, "entity");
183 assert!(block.key_values.is_empty());
184 assert!(block.blocks.is_empty());
185 }
186}