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 "␀</"), Ok("\u{2400}".into()));
413/// let mut escaped =
414/// "This is a <test> & "example" ' ␀ ␀</";
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}