use anyhow::anyhow;
use winnow::{
Parser,
ascii::{dec_uint, float, space1},
binary::length_repeat,
combinator::{cond, dispatch, empty, fail, opt, preceded as P, repeat},
token::literal,
};
use super::types::*;
use crate::file_parsers::{
VersionedResult, VersionedResultExt,
shared::{
lift::{SliceParser, lift},
winnow::{
WinnowParser, nullable_uint, quoted_str, repeat_array, separated_array, unquoted_str,
version_line,
},
},
};
fn animation_stage<'a>() -> impl SliceParser<'a, &'a str, AnimationStage> {
winnow::trace!(
"animation_stage",
(
lift(quoted_str), lift(dec_uint),
lift(separated_array(space1, float)),
)
.map(|(name, time, floats)| AnimationStage { name, time, floats })
)
}
fn bone_rotation<'a>(num_coords: usize) -> impl WinnowParser<&'a str, BoneRotation> {
winnow::trace!("bone_rotation", move |input: &mut &str| {
let bone = quoted_str(input)?;
let coord_order = P(space1, unquoted_str).parse_next(input)?;
let coords = repeat(
num_coords * coord_order.len(), P(space1, float::<_, f32, _>),
)
.parse_next(input)?;
let rotation = BoneRotation {
bone,
coord_order,
coords,
};
Ok(rotation)
})
}
fn bone_rotations<'a>() -> impl SliceParser<'a, &'a str, Vec<BoneRotation>> {
winnow::trace!("bone_rotations", |input: &mut &[&str]| {
let (num_rotations, num_coords): (usize, Option<usize>) =
lift((dec_uint, opt(P(space1, dec_uint)))).parse_next(input)?;
let rotations = repeat(
num_rotations, lift(bone_rotation(num_coords.unwrap_or(0))),
)
.parse_next(input)?;
Ok(rotations)
})
}
fn floats<'a>() -> impl WinnowParser<&'a str, Vec<f32>> {
winnow::trace!(
"floats",
length_repeat(
dec_uint::<_, u32, _>, P(space1, float::<_, f32, _>),
)
)
}
fn group<'a>(version: u32) -> impl SliceParser<'a, &'a str, Group> {
winnow::trace!(
"group",
(
lift(quoted_str),
lift(unquoted_str),
lift(dec_uint),
length_repeat(
lift(dec_uint::<_, u32, _>), animation_stage(),
),
dispatch! {
empty.value(version);
1 => empty.value(None),
2 => opt(lift(floats())),
3.. => lift(floats()).map(Some),
_ => fail,
},
cond(version >= 4, bone_rotations()),
opt(repeat_array(lift(nullable_uint()))),
)
.map(
|(
name,
animation_type,
animation_time,
animation_stages,
float_group,
bone_rotations,
extra_ints,
)| {
Group {
name,
animation_type,
animation_time,
animation_stages,
float_group,
bone_rotations,
extra_ints,
}
},
)
)
}
fn bone_group<'a>() -> impl WinnowParser<&'a str, BoneGroup> {
winnow::trace!(
"bone_group",
(
quoted_str, P(
space1,
length_repeat(
dec_uint::<_, u32, _>, P(space1, quoted_str),
),
),
)
.map(|(name, bones)| BoneGroup { name, bones })
)
}
fn bone_groups<'a>() -> impl SliceParser<'a, &'a str, Vec<BoneGroup>> {
winnow::trace!(
"bone_groups",
length_repeat(
lift(P(
(literal("BoneGroups"), space1), dec_uint::<_, u32, _>,
)), lift(bone_group()),
)
)
}
pub fn parse_amd_str(contents: &str) -> VersionedResult<AMDFile> {
let lines = contents
.split_inclusive(['\n', '\t'])
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with("//"))
.collect::<Vec<_>>();
let mut lines = lines.as_slice();
let version = lift(version_line())
.parse_next(&mut lines)
.map_err(|e| anyhow!("Failed to parse file: {e:?}"))?;
let mut parser = (
length_repeat(
lift(dec_uint::<_, u32, _>), group(version),
),
cond(version >= 5, bone_groups()),
)
.map(|(groups, bone_groups)| AMDFile {
version,
groups,
bone_groups,
});
parser
.parse(lines)
.map_err(|e| anyhow!("Failed to parse file: {e:?}"))
.with_version(Some(version))
}