quake_util/qmap/
parser.rs

1extern crate std;
2
3use std::{io::Read, iter::Peekable, num::NonZeroU8, str::FromStr, vec::Vec};
4
5use crate::{common, qmap, TextParseError, TextParseResult};
6use common::CellOptionExt;
7use qmap::lexer::{Token, TokenIterator};
8use qmap::repr::{
9    Alignment, Brush, Edict, Entity, Point, Quake2SurfaceExtension, QuakeMap,
10    Surface,
11};
12
13type TokenPeekable<R> = Peekable<TokenIterator<R>>;
14
15trait Extract {
16    fn extract(&mut self) -> TextParseResult<Option<Token>>;
17}
18
19impl<R> Extract for TokenPeekable<R>
20where
21    R: Read,
22{
23    fn extract(&mut self) -> Result<Option<Token>, TextParseError> {
24        self.next().transpose().map_err(|e| e.into_unwrapped())
25    }
26}
27
28const MIN_BRUSH_SURFACES: usize = 4;
29
30/// Parses a Quake source map
31///
32/// Maps must be in the Quake 1 or 2 format (Quake 3 `brushDef`s/`patchDef`s are
33/// not presently supported) but may have texture alignment in either "Valve220"
34/// format or the "legacy" predecessor (i.e. without texture axes)
35pub fn parse<R: Read>(reader: &mut R) -> TextParseResult<QuakeMap> {
36    let mut entities: Vec<Entity> = Vec::new();
37    let mut peekable_tokens = TokenIterator::new(reader).peekable();
38
39    while peekable_tokens.peek().is_some() {
40        let entity = parse_entity(&mut peekable_tokens)?;
41        entities.push(entity);
42    }
43
44    Ok(QuakeMap { entities })
45}
46
47fn parse_entity<R: Read>(
48    tokens: &mut TokenPeekable<R>,
49) -> TextParseResult<Entity> {
50    expect_byte(&tokens.extract()?, b'{')?;
51
52    let edict = parse_edict(tokens)?;
53    let brushes = parse_brushes(tokens)?;
54
55    expect_byte(&tokens.extract()?, b'}')?;
56
57    Ok(Entity { edict, brushes })
58}
59
60fn parse_edict<R: Read>(
61    tokens: &mut TokenPeekable<R>,
62) -> TextParseResult<Edict> {
63    let mut edict = Edict::new();
64
65    while let Some(tok_res) = tokens.peek() {
66        if tok_res
67            .as_ref()
68            .map_err(CellOptionExt::steal)?
69            .match_quoted()
70        {
71            let key = strip_quoted(&tokens.extract()?.unwrap().text)
72                .to_vec()
73                .into();
74            let maybe_value = tokens.extract()?;
75            expect_quoted(&maybe_value)?;
76            let value =
77                strip_quoted(&maybe_value.unwrap().text).to_vec().into();
78            edict.push((key, value));
79        } else {
80            break;
81        }
82    }
83
84    Ok(edict)
85}
86
87fn parse_brushes<R: Read>(
88    tokens: &mut TokenPeekable<R>,
89) -> TextParseResult<Vec<Brush>> {
90    let mut brushes = Vec::new();
91
92    while let Some(tok_res) = tokens.peek() {
93        if tok_res.as_ref().map_err(|e| e.steal())?.match_byte(b'{') {
94            brushes.push(parse_brush(tokens)?);
95        } else {
96            break;
97        }
98    }
99
100    Ok(brushes)
101}
102
103fn parse_brush<R: Read>(
104    tokens: &mut TokenPeekable<R>,
105) -> TextParseResult<Brush> {
106    let mut surfaces = Vec::with_capacity(MIN_BRUSH_SURFACES);
107    expect_byte(&tokens.extract()?, b'{')?;
108
109    while let Some(tok_res) = tokens.peek() {
110        if tok_res.as_ref().map_err(|e| e.steal())?.match_byte(b'(') {
111            surfaces.push(parse_surface(tokens)?);
112        } else {
113            break;
114        }
115    }
116
117    expect_byte_or(&tokens.extract()?, b'}', b"(")?;
118    Ok(surfaces)
119}
120
121fn parse_surface<R: Read>(
122    tokens: &mut TokenPeekable<R>,
123) -> TextParseResult<Surface> {
124    let pt1 = parse_point(tokens)?;
125    let pt2 = parse_point(tokens)?;
126    let pt3 = parse_point(tokens)?;
127
128    let half_space = [pt1, pt2, pt3];
129
130    let texture_token = &tokens.extract()?.ok_or_else(TextParseError::eof)?;
131
132    let texture = if b'"' == (&texture_token.text)[0].into() {
133        strip_quoted(&texture_token.text[..]).to_vec().into()
134    } else {
135        texture_token.text.clone().into()
136    };
137
138    let alignment = if let Some(tok_res) = tokens.peek() {
139        if tok_res.as_ref().map_err(|e| e.steal())?.match_byte(b'[') {
140            parse_valve_alignment(tokens)?
141        } else {
142            parse_legacy_alignment(tokens)?
143        }
144    } else {
145        return Err(TextParseError::eof());
146    };
147
148    let q2ext = if let Some(tok_res) = tokens.peek() {
149        if tok_res.as_ref().map_err(|e| e.steal())?.starts_numeric() {
150            parse_q2_ext(tokens)?
151        } else {
152            Default::default()
153        }
154    } else {
155        return Err(TextParseError::eof());
156    };
157
158    Ok(Surface {
159        half_space,
160        texture,
161        alignment,
162        q2ext,
163    })
164}
165
166fn parse_point<R: Read>(
167    tokens: &mut TokenPeekable<R>,
168) -> TextParseResult<Point> {
169    expect_byte(&tokens.extract()?, b'(')?;
170    let x = expect_float(&tokens.extract()?)?;
171    let y = expect_float(&tokens.extract()?)?;
172    let z = expect_float(&tokens.extract()?)?;
173    expect_byte(&tokens.extract()?, b')')?;
174
175    Ok([x, y, z])
176}
177
178fn parse_legacy_alignment<R: Read>(
179    tokens: &mut TokenPeekable<R>,
180) -> TextParseResult<Alignment> {
181    let offset_x = expect_float(&tokens.extract()?)?;
182    let offset_y = expect_float(&tokens.extract()?)?;
183    let rotation = expect_float(&tokens.extract()?)?;
184    let scale_x = expect_float(&tokens.extract()?)?;
185    let scale_y = expect_float(&tokens.extract()?)?;
186
187    Ok(Alignment {
188        offset: [offset_x, offset_y],
189        rotation,
190        scale: [scale_x, scale_y],
191        axes: None,
192    })
193}
194
195fn parse_q2_ext<R: Read>(
196    tokens: &mut TokenPeekable<R>,
197) -> TextParseResult<Quake2SurfaceExtension> {
198    let content_flags = expect_int(&tokens.extract()?)?;
199    let surface_flags = expect_int(&tokens.extract()?)?;
200    let surface_value = expect_float(&tokens.extract()?)?;
201
202    Ok(Quake2SurfaceExtension {
203        content_flags,
204        surface_flags,
205        surface_value,
206    })
207}
208
209fn parse_valve_alignment<R: Read>(
210    tokens: &mut TokenPeekable<R>,
211) -> TextParseResult<Alignment> {
212    expect_byte(&tokens.extract()?, b'[')?;
213    let u_x = expect_float(&tokens.extract()?)?;
214    let u_y = expect_float(&tokens.extract()?)?;
215    let u_z = expect_float(&tokens.extract()?)?;
216    let offset_x = expect_float(&tokens.extract()?)?;
217    expect_byte(&tokens.extract()?, b']')?;
218
219    expect_byte(&tokens.extract()?, b'[')?;
220    let v_x = expect_float(&tokens.extract()?)?;
221    let v_y = expect_float(&tokens.extract()?)?;
222    let v_z = expect_float(&tokens.extract()?)?;
223    let offset_y = expect_float(&tokens.extract()?)?;
224    expect_byte(&tokens.extract()?, b']')?;
225
226    let rotation = expect_float(&tokens.extract()?)?;
227    let scale_x = expect_float(&tokens.extract()?)?;
228    let scale_y = expect_float(&tokens.extract()?)?;
229
230    Ok(Alignment {
231        offset: [offset_x, offset_y],
232        rotation,
233        scale: [scale_x, scale_y],
234        axes: Some([[u_x, u_y, u_z], [v_x, v_y, v_z]]),
235    })
236}
237
238fn expect_byte(token: &Option<Token>, byte: u8) -> TextParseResult<()> {
239    match token.as_ref() {
240        Some(payload) if payload.match_byte(byte) => Ok(()),
241        Some(payload) => Err(TextParseError::from_parser(
242            format!(
243                "Expected `{}`, got `{}`",
244                char::from(byte),
245                payload.text_as_string()
246            ),
247            payload.line_number,
248        )),
249        _ => Err(TextParseError::eof()),
250    }
251}
252
253fn expect_byte_or(
254    token: &Option<Token>,
255    byte: u8,
256    rest: &[u8],
257) -> TextParseResult<()> {
258    match token.as_ref() {
259        Some(payload) if payload.match_byte(byte) => Ok(()),
260        Some(payload) => {
261            let rest_str = rest
262                .iter()
263                .copied()
264                .map(|b| format!("`{}`", char::from(b)))
265                .collect::<Vec<_>>()[..]
266                .join(", ");
267
268            Err(TextParseError::from_parser(
269                format!(
270                    "Expected {} or `{}`, got `{}`",
271                    rest_str,
272                    char::from(byte),
273                    payload.text_as_string()
274                ),
275                payload.line_number,
276            ))
277        }
278        _ => Err(TextParseError::eof()),
279    }
280}
281
282fn expect_quoted(token: &Option<Token>) -> TextParseResult<()> {
283    match token.as_ref() {
284        Some(payload) if payload.match_quoted() => Ok(()),
285        Some(payload) => Err(TextParseError::from_parser(
286            format!("Expected quoted, got `{}`", payload.text_as_string()),
287            payload.line_number,
288        )),
289        _ => Err(TextParseError::eof()),
290    }
291}
292
293fn expect_float(token: &Option<Token>) -> TextParseResult<f64> {
294    match token.as_ref() {
295        Some(payload) => match f64::from_str(&payload.text_as_string()) {
296            Ok(num) => Ok(num),
297            Err(_) => Err(TextParseError::from_parser(
298                format!("Expected number, got `{}`", payload.text_as_string()),
299                payload.line_number,
300            )),
301        },
302        None => Err(TextParseError::eof()),
303    }
304}
305
306fn expect_int(token: &Option<Token>) -> TextParseResult<i32> {
307    match token.as_ref() {
308        Some(payload) => match i32::from_str(&payload.text_as_string()) {
309            Ok(num) => Ok(num),
310            Err(_) => Err(TextParseError::from_parser(
311                format!("Expected integer, got `{}`", payload.text_as_string()),
312                payload.line_number,
313            )),
314        },
315        None => Err(TextParseError::eof()),
316    }
317}
318
319fn strip_quoted(quoted_text: &[NonZeroU8]) -> &[NonZeroU8] {
320    &quoted_text[1..quoted_text.len() - 1]
321}