1use indexmap::IndexMap;
4use pest::Parser;
5use pest::iterators::Pair;
6use pest_derive::Parser;
7use std::mem;
8
9use crate::errors::{VmfError, VmfResult};
10
11use crate::VmfBlock;
12use crate::prelude::{Cameras, Entity, VersionInfo, ViewSettings, VisGroups, VmfFile, World};
13use crate::vmf::regions::{Cordon, Cordons};
14
15#[derive(Parser)]
17#[grammar = "vmf.pest"]
18struct VmfParser;
19
20pub fn parse_vmf(input: &str) -> VmfResult<VmfFile> {
30 let parsed = VmfParser::parse(Rule::file, input)
31 .map_err(|e| VmfError::Parse(Box::new(e)))?
32 .next()
33 .unwrap(); let mut vmf_file = VmfFile::default();
35
36 for pair in parsed.into_inner() {
37 if pair.as_rule() == Rule::block {
38 let block: VmfBlock = parse_block(pair)?;
39
40 match block.name.to_lowercase().as_str() {
41 "versioninfo" => vmf_file.versioninfo = VersionInfo::try_from(block)?,
43 "visgroups" => vmf_file.visgroups = VisGroups::try_from(block)?,
44 "viewsettings" => vmf_file.viewsettings = ViewSettings::try_from(block)?,
45
46 "world" => vmf_file.world = World::try_from(block)?,
48
49 "entity" => vmf_file.entities.push(Entity::try_from(block)?),
51 "hidden" => {
52 let mut blocks = block.blocks;
53 if !blocks.is_empty() {
54 let first_block = mem::take(&mut blocks[0]);
56 let mut ent = Entity::try_from(first_block)?;
57 ent.is_hidden = true;
58 vmf_file.hiddens.push(ent)
59 }
60 }
61
62 "cameras" => vmf_file.cameras = Cameras::try_from(block)?,
64 "cordons" => vmf_file.cordons = Cordons::try_from(block)?,
65 "cordon" => vmf_file.cordons.push(Cordon::try_from(block)?),
67 _ => {
69 #[cfg(feature = "debug_assert_info")]
70 debug_assert!(false, "Unexpected block name: {}", block.name);
71 }
72 }
73 }
74 }
75
76 Ok(vmf_file)
77}
78
79fn parse_block(pair: Pair<Rule>) -> VmfResult<VmfBlock> {
89 let mut inner = pair.into_inner();
90 let block_name_pair = inner
91 .next()
92 .ok_or_else(|| VmfError::InvalidFormat("block name not found".to_string()))?;
93
94 let name = block_name_pair.as_str().to_string();
95
96 let mut key_values = IndexMap::with_capacity(8);
98 let mut blocks = Vec::with_capacity(16);
99
100 for item in inner {
101 match item.as_rule() {
102 Rule::key_value => {
103 let mut kv_inner = item.into_inner();
104 let key_pair = kv_inner
105 .next()
106 .ok_or_else(|| VmfError::InvalidFormat("key not found".to_string()))?;
107 let value_pair = kv_inner
108 .next()
109 .ok_or_else(|| VmfError::InvalidFormat("value not found".to_string()))?;
110
111 let key = strip_quotes(key_pair.as_str());
112 let value = strip_quotes(value_pair.as_str());
113
114 key_values
115 .entry(key)
116 .and_modify(|existing_value: &mut String| {
117 existing_value.push('\r');
118 existing_value.push_str(&value);
119 })
120 .or_insert(value);
121 }
122 Rule::block => {
123 blocks.push(parse_block(item)?);
124 }
125 _ => {}
126 }
127 }
128
129 Ok(VmfBlock {
130 name,
131 key_values,
132 blocks,
133 })
134}
135
136#[inline]
146fn strip_quotes(s: &str) -> String {
147 if s.starts_with('"') && s.ends_with('"') {
148 s[1..s.len() - 1].to_string()
149 } else {
150 s.to_string()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 #[test]
158 fn parse_block_valid_block() {
159 let input = "entity { \"classname\" \"logic_relay\" }";
160 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
161 let block = parse_block(parsed.next().unwrap()).unwrap();
162
163 assert_eq!(block.name, "entity");
164 assert_eq!(
165 block.key_values.get("classname"),
166 Some(&"logic_relay".to_string())
167 );
168 assert!(block.blocks.is_empty());
169 }
170
171 #[test]
172 fn parse_block_nested_blocks() {
173 let input = "entity { \"classname\" \"logic_relay\" solid { \"id\" \"1\" } }";
174 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
175 let block = parse_block(parsed.next().unwrap()).unwrap();
176
177 assert_eq!(block.name, "entity");
178 assert_eq!(
179 block.key_values.get("classname"),
180 Some(&"logic_relay".to_string())
181 );
182 assert_eq!(block.blocks.len(), 1);
183 assert_eq!(block.blocks[0].name, "solid");
184 assert_eq!(block.blocks[0].key_values.get("id"), Some(&"1".to_string()));
185 }
186
187 #[test]
188 fn parse_block_empty_block() {
189 let input = "entity { }";
190 let mut parsed = VmfParser::parse(Rule::block, input).unwrap();
191 let block = parse_block(parsed.next().unwrap()).unwrap();
192
193 assert_eq!(block.name, "entity");
194 assert!(block.key_values.is_empty());
195 assert!(block.blocks.is_empty());
196 }
197}