use anyhow::anyhow;
use winnow::{
Parser,
ascii::{dec_uint, space1},
combinator::{alt, opt, preceded as P, repeat, separated_pair},
token::{literal, take_while},
};
use super::types::*;
use crate::file_parsers::{
VersionedResult, VersionedResultExt,
shared::{
lift::{SliceParser, lift},
winnow::{
WinnowParser, filename, parse_bool, quoted, unquoted, unquoted_str, version_line,
},
},
};
fn d4_rotation<'a>() -> impl WinnowParser<&'a str, Rotation> {
winnow::trace!(
"d4_rotation",
(
opt(literal('F')).map(|f| f.is_some()),
alt((
literal('I').value(0), P(literal('R'), dec_uint),
)),
)
.map(|(flip, angle)| Rotation { flip, angle })
)
}
fn flag<'a>() -> impl WinnowParser<&'a str, String> {
winnow::trace!(
"flag",
(
alt((
literal('+'), literal('-'),
)),
unquoted(),
)
.map(|(prefix, name)| [prefix, name].concat())
)
}
fn header<'a>() -> impl WinnowParser<&'a str, Header> {
winnow::trace!(
"header",
(
parse_bool,
P(space1, parse_bool),
opt(P(
space1,
alt((
literal("FileOrder").value(Order::File),
literal("SizeOrder").value(Order::Size),
)),
)),
repeat(0.., P(space1, flag())),
)
.map(|(uint1, uint2, file_order, flags)| Header {
bool1: uint1,
bool2: uint2,
file_order,
flags,
})
)
}
fn entry<'a>() -> impl WinnowParser<&'a str, Entry> {
let primary_stuff = (
dec_uint, P(space1, quoted('"').and_then(filename("arm"))),
);
enum Tail {
KV(String, String),
NotFlag(String),
AddFlag(String),
Rotation(Rotation),
}
let tail_stuff = repeat(
0..,
P(
space1,
alt((
P("!", unquoted_str).map(Tail::NotFlag),
d4_rotation().map(Tail::Rotation),
flag().map(Tail::AddFlag),
separated_pair(
take_while(1.., |c: char| c != '=' && !c.is_whitespace()).map(String::from),
"=",
unquoted_str,
)
.map(|(k, v)| Tail::KV(k, v)),
)),
),
)
.fold(
|| (vec![], vec![], vec![], vec![]),
|(mut kvs, mut not_flags, mut add_flags, mut rotations), item| {
match item {
Tail::KV(k, v) => kvs.push((k, v)),
Tail::NotFlag(f) => not_flags.push(f),
Tail::Rotation(r) => rotations.push(r),
Tail::AddFlag(f) => add_flags.push(f),
};
(kvs, not_flags, add_flags, rotations)
},
);
winnow::trace!(
"entry",
(
primary_stuff, tail_stuff,
)
.map(
|((weight, arm_file), (key_values, not_flags, add_flags, rotations))| Entry {
weight,
arm_file,
key_values,
not_flags,
add_flags,
rotations,
},
)
)
}
fn group<'a>() -> impl SliceParser<'a, &'a str, Group> {
winnow::trace!(
"group",
(
lift(header()), repeat(0.., lift(entry())),
)
.map(|(header, entries)| Group { header, entries })
)
}
pub fn parse_toy_str(contents: &str) -> VersionedResult<TOYFile> {
let lines = contents
.lines()
.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 = repeat(0.., group()).map(|groups| TOYFile { version, groups });
parser
.parse(lines)
.map_err(|e| anyhow!("Failed to parse file: {e:?}"))
.with_version(Some(version))
}