Skip to main content

quake_map/
parser.rs

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