1use crate::lib::*;
3
4use super::{
5 delimited_comment_multispace0, delimited_multispace0_comment, delimited_with_multispace0,
6 type_kind::pointer,
7};
8use havok_types::{Pointer, Signature};
9use winnow::ascii::{digit1, hex_digit1, oct_digit1};
10use winnow::combinator::{delimited, dispatch, fail, seq};
11use winnow::error::{ContextError, StrContext, StrContextValue, StrContextValue::*};
12use winnow::token::{take, take_until};
13use winnow::{ModalResult, Parser};
14
15pub fn start_tag<'a>(tag: &'static str) -> impl Parser<&'a str, (), ContextError> {
17 seq!(
18 _: delimited_comment_multispace0("<"),
19 _: delimited_with_multispace0(tag),
20 _: delimited_multispace0_comment(">")
21 )
22 .context(StrContext::Label("start tag"))
23 .context(StrContext::Label(tag))
24}
25
26pub fn end_tag<'a>(tag: &'static str) -> impl Parser<&'a str, (), ContextError> {
28 seq!(
29 _: delimited_comment_multispace0("<"),
30 _: delimited_with_multispace0("/"),
31 _: delimited_with_multispace0(tag),
32 _: delimited_multispace0_comment(">")
33 )
34 .context(StrContext::Label("end tag"))
35 .context(StrContext::Label(tag))
36}
37
38pub fn class_start_tag<'a>(input: &mut &'a str) -> ModalResult<(Pointer, &'a str, Signature)> {
46 seq!(
47 _: delimited_comment_multispace0("<"),
48 _: delimited_with_multispace0("hkobject"),
49 _: delimited_with_multispace0("name"),
50 _: delimited_with_multispace0("="),
51 attr_ptr,
52
53 _: delimited_with_multispace0("class"),
54 _: delimited_with_multispace0("="),
55 attr_string,
56
57 _: delimited_with_multispace0("signature"),
58 _: delimited_with_multispace0("="),
59 _: delimited_with_multispace0("\""),
60 radix_digits.map(|digits| Signature::new(digits as u32)),
61 _: delimited_with_multispace0("\""),
62 _: delimited_multispace0_comment(">")
63 )
64 .context(StrContext::Label("Class start tag"))
65 .context(StrContext::Expected(StrContextValue::Description(
66 r##"e.g. `<hkobject name="#0010" class="hkbProjectData" signature="0x13a39ba7">`"##,
67 )))
68 .parse_next(input)
69}
70
71pub fn field_start_open_tag<'a>(
76 class_name: &'static str,
77) -> impl Parser<&'a str, (), ContextError> {
78 seq!(
79 _: delimited_comment_multispace0("<"),
80 _: delimited_with_multispace0("hkparam"),
81 _: delimited_with_multispace0("name"),
82 _: delimited_with_multispace0("="),
83 )
84 .context(StrContext::Label("field of class: start opening tag"))
85 .context(StrContext::Label(class_name))
86 .context(StrContext::Expected(StrContextValue::Description(
87 "e.g. `<hkparam name=`",
88 )))
89}
90
91pub fn field_start_close_tag(input: &mut &str) -> ModalResult<(Option<u64>, bool)> {
99 use winnow::combinator::alt;
100
101 alt((
102 seq!(
104 seq!(
105 _: delimited_with_multispace0("numelements"),
106 _: delimited_with_multispace0("="),
107 number_in_string::<u64>, ),
109 _: delimited_with_multispace0("/"),
110 _: delimited_multispace0_comment(">")
111 )
112 .map(|((n,),)| (Some(n), true)),
113 seq!(
115 seq!(
116 _: delimited_with_multispace0("numelements"),
117 _: delimited_with_multispace0("="),
118 number_in_string::<u64>, ),
120 _: delimited_multispace0_comment(">")
121 )
122 .map(|((n,),)| (Some(n), false)),
123 seq!(
125 _: delimited_with_multispace0("/"),
126 _: delimited_multispace0_comment(">")
127 )
128 .map(|()| (None, true)),
129 seq!(
131 _: delimited_multispace0_comment(">")
132 )
133 .map(|()| (None, false)),
134 ))
135 .context(StrContext::Label("field of class: start closing tag"))
136 .context(StrContext::Expected(StrContextValue::Description(
137 "e.g. `>`, `/>`, `numelements=\"0\">`, or `numelements=\"0\" />`",
138 )))
139 .parse_next(input)
140}
141
142pub fn number_in_string<Num>(input: &mut &str) -> ModalResult<Num>
151where
152 Num: FromStr,
153{
154 attr_string
155 .parse_to()
156 .context(StrContext::Label("number in string"))
157 .context(StrContext::Expected(Description(r#"Number(e.g. `"64"`)"#)))
158 .parse_next(input)
159}
160
161pub fn attr_string<'a>(input: &mut &'a str) -> ModalResult<&'a str> {
166 delimited("\"", take_until(0.., "\""), "\"")
167 .context(StrContext::Label("String in XML attribute"))
168 .context(StrContext::Expected(Description(r#"String(e.g. `"Str"`)"#)))
169 .parse_next(input)
170}
171
172fn attr_ptr(input: &mut &str) -> ModalResult<Pointer> {
177 delimited("\"", pointer, "\"").parse_next(input)
178}
179
180fn radix_digits(input: &mut &str) -> ModalResult<usize> {
185 dispatch!(take(2_usize);
186 "0b" | "0B" => digit1.try_map(|s| usize::from_str_radix(s, 2))
187 .context(StrContext::Label("digit")).context(StrContext::Expected(StrContextValue::Description("binary"))),
188 "0o" | "0O" => oct_digit1.try_map(|s| usize::from_str_radix(s, 8))
189 .context(StrContext::Label("digit")).context(StrContext::Expected(StrContextValue::Description("octal"))),
190 "0d" | "0D" => digit1.try_map(|s: &str| s.parse::<usize>())
191 .context(StrContext::Label("digit")).context(StrContext::Expected(StrContextValue::Description("decimal"))),
192 "0x" | "0X" => hex_digit1.try_map(|s|usize::from_str_radix(s, 16))
193 .context(StrContext::Label("digit")).context(StrContext::Expected(StrContextValue::Description("hexadecimal"))),
194 _ => fail.context(StrContext::Label("radix prefix"))
195 .context(StrContext::Expected(StrContextValue::StringLiteral("0b")))
196 .context(StrContext::Expected(StrContextValue::StringLiteral("0o")))
197 .context(StrContext::Expected(StrContextValue::StringLiteral("0d")))
198 .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))),
199 ).parse_next(input)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::errors::readable::ReadableError;
206
207 #[test]
208 fn test_radix_digits() {
209 assert_eq!(radix_digits.parse("0b001010"), Ok(10));
210 assert_eq!(radix_digits.parse("0o57"), Ok(47));
211 assert_eq!(radix_digits.parse("0x1234"), Ok(4660));
212 }
213
214 #[test]
215 fn test_parse_start_tag() {
216 assert!(start_tag("tag").parse("<tag>").is_ok());
217 assert!(start_tag("hkparam").parse("< hkparam \n\n>").is_ok());
218 assert!(start_tag("tag").parse("<tag >").is_ok());
219 }
220
221 #[test]
222 fn test_parse_end_tag() {
223 assert!(end_tag("tag").parse("</tag>").is_ok());
224 assert!(end_tag("tag").parse("</ tag >").is_ok());
225 assert!(end_tag("tag").parse("</ tag >").is_ok());
226
227 let input = "</ hkparam >\n";
228 if let Err(err) = end_tag("hkparam")
229 .parse(input)
230 .map_err(|e| ReadableError::from_parse(e, input).to_string())
231 {
232 panic!("{err}");
233 };
234 }
235
236 #[test]
237 fn test_parse_array_start_close_tag() {
238 fn test_parse(input: &str, expected: (Option<u64>, bool)) {
239 match field_start_close_tag
240 .parse(input)
241 .map_err(|e| ReadableError::from_parse(e, input).to_string())
242 {
243 Ok(res) => assert_eq!(res, expected),
244 Err(err) => panic!("{err}"),
245 }
246 }
247
248 let ideal_input = r#" numelements="3">"#;
250 test_parse(ideal_input, (Some(3), false));
251
252 let indent_input = r#"
253
254 numelements
255 = "85"
256
257>"#;
258 test_parse(indent_input, (Some(85), false));
259
260 let simple_closing_input = r#" >"#;
261 test_parse(simple_closing_input, (None, false));
262
263 let self_closing_input = r#" numelements="5" />"#;
265 test_parse(self_closing_input, (Some(5), true));
266
267 let simple_self_closing_input = r#" />"#;
268 test_parse(simple_self_closing_input, (None, true));
269
270 let spaced_self_closing_input = r#" numelements="0" / >"#;
272 test_parse(spaced_self_closing_input, (Some(0), true));
273
274 let spaced_simple_self_closing_input = r#" / >"#;
275 test_parse(spaced_simple_self_closing_input, (None, true));
276 }
277
278 #[test]
279 fn test_parse_number_in_string() {
280 assert_eq!(number_in_string.parse(r#""33""#), Ok(33));
281 assert_eq!(number_in_string.parse(r#""100""#), Ok(100));
282 assert_eq!(number_in_string.parse(r#""0""#), Ok(0));
283 }
284}