serde_hkx/xml/de/parser/
type_kind.rs

1//! TypeKind XML parsers
2
3use crate::{lib::*, tri};
4
5use super::delimited_with_multispace0;
6use havok_types::*;
7use std::borrow::Cow;
8use winnow::ascii::{Caseless, digit1, float, multispace0};
9use winnow::combinator::{alt, opt, preceded, seq};
10use winnow::error::{StrContext, StrContextValue};
11use winnow::token::take_until;
12use winnow::{ModalResult, Parser};
13
14/// Parses [`bool`]. `true` or `false`
15/// - The corresponding type kind: `Bool`
16///
17/// # Examples
18///
19/// ```
20/// use serde_hkx::xml::de::parser::type_kind::boolean;
21/// use winnow::Parser as _;
22///
23/// assert_eq!(boolean.parse("true"), Ok(true));
24/// assert_eq!(boolean.parse("false"), Ok(false));
25/// assert!(boolean.parse("invalid").is_err());
26/// ```
27///
28/// # Errors
29/// When parse failed.
30pub fn boolean(input: &mut &str) -> ModalResult<bool> {
31    alt(("true".value(true), "false".value(false)))
32        .context(StrContext::Label("bool"))
33        .context(StrContext::Expected(StrContextValue::Description(
34            "`true` or `false`",
35        )))
36        .parse_next(input)
37}
38
39// Unsigned integers -> use `dec_unit`
40//   Signed integers -> use `dec_nit`
41
42/// Parses [`f32`](`Real`)
43///
44/// # Examples
45///
46/// ```
47/// use serde_hkx::xml::de::parser::type_kind::real;
48/// use winnow::Parser as _;
49///
50/// assert_eq!(real.parse("1.0"), Ok(1.0_f32));
51/// assert_eq!(real.parse("0.1"), Ok(0.1_f32));
52/// assert_eq!(real.parse("0"), Ok(0.0_f32)); // Integer format
53/// assert_eq!(real.parse("1"), Ok(1.0_f32)); // Integer format
54/// assert_eq!(real.parse("-1"), Ok(-1.0_f32)); // Negative integer format
55///
56/// // C++ indeterminate
57/// assert!(real.parse("1.#IND").unwrap().is_nan());
58/// assert!(real.parse("1.#IND0").unwrap().is_nan());
59/// assert!(real.parse("1.#IND00").unwrap().is_nan());
60/// assert!(real.parse("-1.#IND0").unwrap().is_nan());
61/// assert!(real.parse("-1.#IND00").unwrap().is_nan());
62///
63/// // C++ infinity
64/// assert_eq!(real.parse("1.#INF"), Ok(f32::INFINITY));
65/// // assert_eq!(real.parse("-1.#INF00"), Ok(f32::NEG_INFINITY));
66///
67/// assert!(real.parse("invalid").is_err());
68/// ```
69///
70/// # Errors
71/// When parse failed.
72pub fn real(input: &mut &str) -> ModalResult<f32> {
73    use winnow::ascii::digit1;
74    use winnow::combinator::opt;
75
76    // NOTE: For some reason, early errors occur if the display digits, such as 00, are not parsed first.
77
78    // Need to support special representation of decimal points in C++
79    let nan = alt((
80        "1.#IND00",
81        "1.#IND0",
82        "1.#IND",
83        "-1.#IND00",
84        "-1.#IND0",
85        "-1.#IND",
86    ))
87    .value(f32::NAN);
88    let pos_inf = alt(("1.#INF00", "1.#INF0", "1.#INF")).value(f32::INFINITY);
89    let neg_inf = alt(("-1.#INF00", "-1.#INF0", "-1.#INF")).value(f32::NEG_INFINITY);
90
91    // Parse integers as floats (e.g., "0" -> 0.0, "1" -> 1.0, "-1" -> -1.0)
92    let integer_as_float = seq!(opt("-"), digit1)
93        .take()
94        .try_map(|s: &str| s.parse::<f32>());
95
96    alt((nan, pos_inf, neg_inf, float, integer_as_float))
97        .context(StrContext::Label("real(f32)"))
98        .context(StrContext::Expected(StrContextValue::Description(
99            "Real(e.g. `0.100000` or `0`)",
100        )))
101        .parse_next(input)
102}
103
104////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
105// Math
106
107/// Parse as [`Vector4`]
108///
109/// # Examples
110///
111/// ```
112/// use havok_types::Vector4;
113/// use serde_hkx::xml::de::parser::type_kind::vector4;
114/// use winnow::Parser as _;
115///
116/// assert_eq!(vector4.parse("(1.000000 1.000000 1.000000 0.000000)"), Ok(Vector4::new(1.0, 1.0, 1.0, 0.0)));
117/// assert_eq!(vector4.parse("(-0.000000 0.000000 -0.000000 1.000000)"), Ok(Vector4::new(-0.0, 0.0, -0.0, 1.0)));
118/// assert_eq!(vector4.parse("   (   -0.000000 0.000000 -0.000000 1.000000  ) "), Ok(Vector4::new(-0.0, 0.0, -0.0, 1.0)));
119/// assert_eq!(vector4.parse("(0,0,0,0)"), Ok(Vector4::new(0.0, 0.0, 0.0, 0.0)));
120/// assert_eq!(vector4.parse("(1,2,3,4)"), Ok(Vector4::new(1.0, 2.0, 3.0, 4.0)));
121/// ```
122///
123/// # Errors
124/// When parse failed.
125pub fn vector4(input: &mut &str) -> ModalResult<Vector4> {
126    use winnow::combinator::alt;
127
128    // Helper function to parse separator (either comma or space)
129    let mut separator = alt((
130        delimited_with_multispace0(","), // comma-separated format: (0,1,2,3)
131        multispace0,                     // space-separated format: (0 1 2 3)
132    ));
133
134    seq!(Vector4 {
135        _: opt(delimited_with_multispace0("(")),
136        x: real.context(StrContext::Label("x")),
137        _: separator,
138        y: real.context(StrContext::Label("y")),
139        _: separator,
140        z: real.context(StrContext::Label("z")),
141        _: separator,
142        w: real.context(StrContext::Label("w")),
143        _: opt(delimited_with_multispace0(")")),
144    })
145    .context(StrContext::Label("Vector4"))
146    .parse_next(input)
147}
148
149/// Parse as [`Quaternion`]
150///
151/// # Examples
152/// ```
153/// use havok_types::Quaternion;
154/// use serde_hkx::xml::de::parser::type_kind::quaternion;
155/// use winnow::Parser as _;
156///
157/// assert_eq!(
158///     quaternion.parse("   (   -0.000000 0.000000 -0.000000 1.000000  ) "),
159///     Ok(Quaternion::new(-0.0, 0.0, -0.0, 1.0))
160/// );
161/// ```
162///
163/// # Errors
164/// When parse failed.
165#[inline]
166pub fn quaternion(input: &mut &str) -> ModalResult<Quaternion> {
167    let Vector4 { x, y, z, w } = tri!(
168        vector4
169            .context(StrContext::Label("Quaternion"))
170            .parse_next(input)
171    );
172    Ok(Quaternion { x, y, z, scaler: w })
173}
174
175/// Parse as [`Matrix3`]
176///
177/// # Examples
178/// ```
179/// use havok_types::{Matrix3, Vector4};
180/// use serde_hkx::xml::de::parser::type_kind::matrix3;
181/// use winnow::Parser as _;
182///
183/// assert_eq!(matrix3.parse("
184///    (0.000000 0.000000 0.000000)
185///    (-0.000000 0.000000 -0.000000)
186///    (1.000000 1.000000 1.000000)
187/// "), Ok(Matrix3::new(
188///    Vector4::default(),
189///    Vector4::new(-0.0, 0.0, -0.0, 0.0),
190///    Vector4::new(1.0, 1.0, 1.0, 0.0),
191/// )));
192/// ```
193///
194/// # Errors
195/// When parse failed.
196pub fn matrix3(input: &mut &str) -> ModalResult<Matrix3> {
197    seq!(Matrix3 {
198        x: vector3.context(StrContext::Label("x")),
199        y: vector3.context(StrContext::Label("y")),
200        z: vector3.context(StrContext::Label("z")),
201    })
202    .context(StrContext::Label("Matrix3"))
203    .parse_next(input)
204}
205
206/// Parse as [`Rotation`]
207///
208/// # Examples
209/// ```
210/// use havok_types::{Rotation, Vector4};
211/// use serde_hkx::xml::de::parser::type_kind::rotation;
212/// use winnow::Parser as _;
213///
214/// assert_eq!(rotation.parse("
215///    (0.000000 0.000000 0.000000)
216///    (-0.000000 0.000000 -0.000000)
217///    (1.000000 1.000000 1.000000)
218/// "), Ok(Rotation::new(
219///    Vector4::default(),
220///    Vector4::new(-0.0, 0.0, -0.0, 0.0),
221///    Vector4::new(1.0, 1.0, 1.0, 0.0),
222/// )));
223/// ```
224///
225/// # Errors
226/// When parse failed.
227pub fn rotation(input: &mut &str) -> ModalResult<Rotation> {
228    seq!(Rotation {
229        x: vector3.context(StrContext::Label("x")),
230        y: vector3.context(StrContext::Label("y")),
231        z: vector3.context(StrContext::Label("z")),
232    })
233    .context(StrContext::Label("Rotation"))
234    .parse_next(input)
235}
236
237/// Parse as [`QsTransform`]
238///
239/// # Examples
240/// ```
241/// use havok_types::{QsTransform, Quaternion,Vector4};
242/// use serde_hkx::xml::de::parser::type_kind::qstransform;
243/// use winnow::Parser as _;
244///
245/// assert_eq!(qstransform.parse("
246///    (0.000000 0.000000 0.000000)
247///    (-0.000000 0.000000 -0.000000 0.000000)
248///    (1.000000 1.000000 1.000000)
249/// "), Ok(QsTransform::new(
250///    Vector4::default(),
251///    Quaternion::new(-0.0, 0.0, -0.0, 0.0),
252///    Vector4::new(1.0, 1.0, 1.0, 0.0),
253/// )));
254/// ```
255///
256/// # Errors
257/// When parse failed.
258pub fn qstransform(input: &mut &str) -> ModalResult<QsTransform> {
259    seq!(QsTransform {
260        transition: vector3.context(StrContext::Label("transition")),
261        quaternion: quaternion.context(StrContext::Label("quaternion")),
262        scale: vector3.context(StrContext::Label("scale")),
263    })
264    .context(StrContext::Label("QsTransform"))
265    .parse_next(input)
266}
267
268/// Parse as [`Matrix4`]
269///
270/// # Examples
271/// ```
272/// use havok_types::{Matrix4, Vector4};
273/// use serde_hkx::xml::de::parser::type_kind::matrix4;
274/// use winnow::Parser as _;
275///
276/// assert_eq!(matrix4.parse("
277///    (0.000000 0.000000 0.000000 0.000000)
278///    (-0.000000 0.000000 -0.000000 0.000000)
279///    (1.000000 1.000000 1.000000 0.000000)
280///    (1.000000 1.000000 1.000000 0.000000)
281/// "), Ok(Matrix4::new(
282///    Vector4::default(),
283///    Vector4::new(-0.0, 0.0, -0.0, 0.0),
284///    Vector4::new(1.0, 1.0, 1.0, 0.0),
285///    Vector4::new(1.0, 1.0, 1.0, 0.0),
286/// )));
287/// ```
288///
289/// # Errors
290/// When parse failed.
291pub fn matrix4(input: &mut &str) -> ModalResult<Matrix4> {
292    seq!(Matrix4 {
293        x: vector4.context(StrContext::Label("x")),
294        y: vector4.context(StrContext::Label("y")),
295        z: vector4.context(StrContext::Label("z")),
296        w: vector4.context(StrContext::Label("w")),
297    })
298    .context(StrContext::Label("Matrix4"))
299    .parse_next(input)
300}
301
302/// Parse as [`Transform`]
303///
304/// # Examples
305/// ```
306/// use havok_types::{Rotation, Transform, Vector4};
307/// use serde_hkx::xml::de::parser::type_kind::transform;
308/// use winnow::Parser as _;
309///
310/// assert_eq!(transform.parse("
311///    (0.000000 0.000000 0.000000)
312///    (-0.000000 0.000000 -0.000000)
313///    (1.000000 1.000000 1.000000)
314///
315///    (0.000000 0.000000 1.000000)
316/// "), Ok(Transform::new(
317///    Rotation::new(
318///         Vector4::default(),
319///         Vector4::new(-0.0, 0.0, -0.0, 0.0),
320///         Vector4::new(1.0, 1.0, 1.0, 0.0),
321///    ),
322///    Vector4::new(0.0, 0.0, 1.0, 1.0),
323/// )));
324/// ```
325///
326/// # Errors
327/// When parse failed.
328///
329/// # Why is the w(of transition) in transform 1.0?
330/// Must be 1.0 for affine conversion.
331/// ```txt
332/// [
333///  // Rotation
334///  [1, 0, 0, tx],
335///  [0, 1, 0, ty],
336///  [0, 0, 1, tz],
337///
338///  [0, 0, 0,  1], // transition
339/// ]
340/// ```
341pub fn transform(input: &mut &str) -> ModalResult<Transform> {
342    seq!(Transform {
343        rotation: rotation.context(StrContext::Label("rotation")),
344        transition: vector3
345            .context(StrContext::Label("transition"))
346            .map(|mut vec4| {
347                vec4.w = 1.0; // To affine conversion.
348                vec4
349            }),
350    })
351    .context(StrContext::Label("Transform"))
352    .parse_next(input)
353}
354
355////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
356
357///
358/// Parse as [`Pointer`]
359/// - Class pointer. (e.g. `#0050`, `null`, etc.)
360///
361/// # Examples
362/// ```
363/// use havok_types::Pointer;
364/// use serde_hkx::xml::de::parser::type_kind::pointer;
365/// use winnow::Parser as _;
366///
367/// assert_eq!(pointer.parse("null"), Ok(Pointer::new(0))); // null pointer
368/// assert_eq!(pointer.parse("#0000"), Ok(Pointer::new(0))); // null pointer
369/// assert_eq!(pointer.parse("#0100"), Ok(Pointer::new(100)));
370/// assert!(pointer.parse("Non_Prefix#").is_err());
371/// ```
372///
373/// # Errors
374/// When parse failed.
375pub fn pointer(input: &mut &str) -> ModalResult<Pointer> {
376    let null_ptr = Caseless("null")
377        .value(Pointer::new(0))
378        .context(StrContext::Label("Pointer"))
379        .context(StrContext::Expected(StrContextValue::Description(
380            "Pointer(e.g. `null`)",
381        )));
382
383    alt((null_ptr, move |input: &mut &str| {
384        let digit = tri!(
385            preceded("#", digit1)
386                .parse_to()
387                .context(StrContext::Label("Pointer"))
388                .context(StrContext::Expected(StrContextValue::Description(
389                    "Pointer(e.g. `#0001`)"
390                )))
391                .parse_next(input)
392        );
393        Ok(Pointer::new(digit))
394    }))
395    .parse_next(input)
396}
397
398/// Parses a string literal until `</`, e.g., `example` in (`example</`).
399/// - The corresponding type kind: `CString`, `StringPtr`
400///
401/// # Examples
402/// ```
403/// use serde_hkx::xml::de::parser::type_kind::string;
404/// use std::borrow::Cow;
405/// use winnow::Parser as _;
406/// assert_eq!(string.parse_next(&mut "example</"), Ok("example".into()));
407/// assert_eq!(
408///     string.parse_next(&mut "example</not_hkparam>"),
409///     Ok("example".into())
410/// );
411///
412/// assert_eq!(string.parse_next(&mut "&#9216;</"), Ok("\u{2400}".into()));
413/// let mut escaped =
414///     "This is a &lt;test&gt; &amp; &quot;example&quot; &apos; &#9216; &#x2400;</";
415/// assert_eq!(
416///     string.parse_next(&mut escaped),
417///     Ok(Cow::Borrowed(
418///         "This is a <test> & \"example\" ' \u{2400} \u{2400}"
419///     ))
420/// );
421/// ```
422///
423/// # Errors
424/// When parse failed.
425pub fn string<'a>(input: &mut &'a str) -> ModalResult<Cow<'a, str>> {
426    take_until(0.., "</")
427        .map(|s| html_escape::decode_html_entities(s))
428        .context(StrContext::Label(
429            "end of string tag(`</hkparam>`, `</hkcstring>` in Array)",
430        ))
431        .context(StrContext::Expected(StrContextValue::Description(
432            "e.g. Hello</hkparam>",
433        )))
434        .parse_next(input)
435}
436
437////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
438// Support functions(These are not used as Havok types)
439
440/// Parse as `Vector3`
441/// - After reading off whitespace including line breaks and interpreting it as `Vector3`, put `0.0`
442///   in the w element to make `Vector4`.
443///
444/// # Examples
445///
446/// ```
447/// use havok_types::Vector4;
448/// use serde_hkx::xml::de::parser::type_kind::vector3;
449/// use winnow::Parser as _;
450///
451/// assert_eq!(vector3.parse("(1.000000 1.000000 1.000000)"), Ok(Vector4::new(1.0, 1.0, 1.0, 0.0)));
452/// assert_eq!(vector3.parse("(-0.000000 0.000000 -0.000000)"), Ok(Vector4::new(-0.0, 0.0, -0.0, 0.0)));
453/// assert_eq!(vector3.parse("   (   -0.000000 0.000000 -0.000000) "), Ok(Vector4::new(-0.0, 0.0, -0.0, 0.0)));
454/// assert_eq!(vector3.parse("(0,0,0)"), Ok(Vector4::new(0.0, 0.0, 0.0, 0.0)));
455/// assert_eq!(vector3.parse("(1,2,3)"), Ok(Vector4::new(1.0, 2.0, 3.0, 0.0)));
456/// ```
457///
458/// # Errors
459/// When parse failed.
460pub fn vector3(input: &mut &str) -> ModalResult<Vector4> {
461    use winnow::combinator::alt;
462
463    // Helper function to parse separator (either comma or space)
464    let mut separator = alt((
465        delimited_with_multispace0(","), // comma-separated format: (0,1,2)
466        multispace0,                     // space-separated format: (0 1 2)
467    ));
468
469    struct Vector3 {
470        x: f32,
471        y: f32,
472        z: f32,
473    }
474
475    let Vector3 { x, y, z } = tri!(
476        seq!(Vector3 {
477            _: opt(delimited_with_multispace0("(")),
478            x: real.context(StrContext::Label("x")),
479            _: separator,
480            y: real.context(StrContext::Label("y")),
481            _: separator,
482            z: real.context(StrContext::Label("z")),
483            _: opt(delimited_with_multispace0(")")),
484        })
485        .context(StrContext::Label("Vector3"))
486        .parse_next(input)
487    );
488
489    Ok(Vector4 { x, y, z, w: 0.0 })
490}
491
492////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
493
494#[cfg(test)]
495mod tests {
496    use super::*;
497    use pretty_assertions::assert_eq;
498
499    #[test]
500    fn test_matrix3() {
501        assert_eq!(
502            matrix3.parse("(0.000000 0.000000 0.000000)(-0.000000 0.000000 1.000000)(1.000000 1.000000 0.000000)"),
503            Ok(Matrix3 {
504                x: Vector4::default(),
505                y: Vector4 { x: -0.0, y: 0.0, z: 1.0, w: 0.0 },
506                z: Vector4 { x: 1.0, y: 1.0, z: 0.0, w: 0.0 }
507            })
508        );
509    }
510}