1use chumsky::input::Stream;
2use std::path::Path;
3
4use crate::error::VMFError;
5use crate::parser::lexer::TokenIter;
6use crate::parser::{skip_unknown_block, InternalParser};
7use crate::types::*;
8
9use chumsky::primitive::choice;
10use chumsky::IterParser;
11use chumsky::Parser as ChumskyParser;
12
13#[derive(Debug)]
15pub enum VMFValue<'src> {
16 VersionInfo(VersionInfo),
17 VisGroups(Box<VisGroups<'src>>),
18 ViewSettings(Box<ViewSettings>),
19 World(Box<World<'src>>),
20 Entity(Box<Entity<'src>>),
21 Cameras(Box<Cameras<'src>>),
22 Cordon(Box<Cordon>),
23}
24
25#[allow(clippy::upper_case_acronyms)]
28pub struct VMF {
29 data: String,
30}
31
32impl VMF {
33 pub fn open(path: impl AsRef<Path>) -> Result<Self, VMFError> {
42 let data = std::fs::read_to_string(path)?;
43 Ok(VMF { data })
44 }
45
46 pub fn parse(&self) -> Result<Vec<VMFValue>, VMFError> {
49 parse_vmf_from_str(&self.data)
50 }
51
52 pub fn as_str(&self) -> &str {
54 &self.data
55 }
56}
57
58fn parse_vmf_from_str<'src>(src: &'src str) -> Result<Vec<VMFValue<'src>>, VMFError> {
61 let token_iter = TokenIter::new(src).map(|tok| tok.expect("valid token"));
62 let token_stream = Stream::from_iter(token_iter);
63
64 let any_block = choice((
65 VersionInfo::parser().map(VMFValue::VersionInfo),
66 VisGroups::parser().map(|v| VMFValue::VisGroups(Box::new(v))),
67 ViewSettings::parser().map(|v| VMFValue::ViewSettings(Box::new(v))),
68 World::parser().map(|v| VMFValue::World(Box::new(v))),
69 Entity::parser().map(|v| VMFValue::Entity(Box::new(v))),
70 Cameras::parser().map(|v| VMFValue::Cameras(Box::new(v))),
71 Cordon::parser().map(|v| VMFValue::Cordon(Box::new(v))),
72 ));
73
74 let any_block = any_block
75 .map(|v| Some(v))
76 .or(skip_unknown_block().map(|_| None));
77
78 let all_blocks_parser = any_block.repeated().collect::<Vec<_>>();
79
80 all_blocks_parser
81 .parse(token_stream)
82 .into_result()
83 .map(|blocks| blocks.into_iter().flatten().collect())
84 .map_err(|errors| {
85 let error_msg = errors
86 .into_iter()
87 .map(|e| format!("{:?}", e.reason()))
88 .collect::<Vec<_>>()
89 .join("; ");
90 VMFError::ParseError(format!("Failed to parse VMF: {}", error_msg))
91 })
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn full_parser_test() {
100 let vmf = VMF::open("test.vmf").expect("Failed to open VMF");
101 let data = vmf.parse().expect("Failed to parse VMF");
102
103 verify_parsed_data(&data);
104 }
105
106 fn verify_parsed_data(data: &[VMFValue]) {
107 assert!(!data.is_empty(), "VMF data should not be empty");
108 println!("Successfully parsed {} top-level blocks", data.len());
109
110 for value in data {
111 match value {
112 VMFValue::VersionInfo(v) => {
113 println!(
114 "VersionInfo: editor v{}, build {}",
115 v.editor_version, v.editor_build
116 );
117 assert_eq!(v.editor_version, 400);
118 assert_eq!(v.editor_build, 6157);
119 }
120 VMFValue::VisGroups(_) => println!("VisGroups parsed"),
121 VMFValue::ViewSettings(_) => println!("ViewSettings parsed"),
122 VMFValue::World(w) => {
123 println!("World parsed with {} solids", w.solids.len());
124 assert!(w.id == 1);
125 assert!(w.classname == "worldspawn");
126 }
127 VMFValue::Entity(e) => println!("Entity: {:?}", e.classname),
128 VMFValue::Cameras(c) => {
129 println!("Cameras: activecamera={}", c.activecamera);
130 assert_eq!(c.activecamera, -1);
131 }
132 VMFValue::Cordon(_) => println!("Cordon parsed"),
133 }
134 }
135 }
136
137 #[test]
138 fn test_large_real_map() {
139 let path = Path::new("Gm_RunDownTown.vmf");
140
141 if !path.exists() {
142 eprintln!("Skipping large map test - file not found");
143 return;
144 }
145
146 println!("Parsing Gm_RunDownTown.vmf...");
147
148 let start = std::time::Instant::now();
149 let vmf = VMF::open(path).expect("Failed to open large VMF");
150 let open_time = start.elapsed();
151 println!("Open time: {:?}", open_time);
152
153 let start = std::time::Instant::now();
154 let data = vmf.parse().expect("Failed to parse large VMF");
155 let parse_time = start.elapsed();
156 println!("Parse time: {:?}", parse_time);
157
158 println!("Total blocks parsed: {}", data.len());
159
160 let mut world_count = 0;
162 let mut entity_count = 0;
163 let mut solid_count = 0;
164
165 for value in &data {
166 match value {
167 VMFValue::World(w) => {
168 world_count += 1;
169 solid_count += w.solids.len();
170 }
171 VMFValue::Entity(e) => {
172 entity_count += 1;
173 solid_count += e.solids.len();
174 }
175 _ => {}
176 }
177 }
178
179 println!("Worlds: {}", world_count);
180 println!("Entities: {}", entity_count);
181 println!("Total solids: {}", solid_count);
182 println!("Total time: {:?}", open_time + parse_time);
183 }
184}