use std::{error::Error, fmt, str::FromStr};
use chumsky::{error::Simple, prelude::*, text::whitespace, Parser};
use crate::{
comms::Port,
geometry::{Hori, HoriSpec, Pixel, Rotation, Size, Transform, Vert, VertSpec},
info::{Connector, Resolution},
relative::{Layout, Position, Screen},
};
impl FromStr for Layout {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
layout().parse(s).map_err(ParseError)
}
}
#[derive(Debug)]
pub struct ParseError(Vec<Simple<char>>);
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let [err] = self.0.as_slice() {
writeln!(f, "{err}")?;
} else {
writeln!(f, "{} errors encountered:", self.0.len())?;
for (i, err) in self.0.iter().enumerate() {
writeln!(f, "{}: {}", i + 1, err)?;
}
}
write!(
f,
"\nfwiw this makeshift error will be replaced by ariadne... sometime"
)
}
}
impl Error for ParseError {}
#[must_use]
pub fn layout() -> impl Parser<char, Layout, Error = Simple<char>> {
screen()
.separated_by(just('+').padded())
.then_ignore(end())
.map(|screens| Layout { screens })
}
#[must_use]
pub fn screen() -> impl Parser<char, Screen, Error = Simple<char>> {
let scale = float;
port()
.then(just('@').padded().ignore_then(resolution()).or_not())
.then(just(':').padded().ignore_then(scale()).or_not())
.then(just('#').padded().ignore_then(transform()).or_not())
.then(just('/').padded().ignore_then(pos()).or_not())
.map(|((((port, resolution), scale), transform), pos)| Screen {
port,
resolution,
scale,
transform: transform.unwrap_or_default(),
pos: pos.unwrap_or_default(),
})
}
#[allow(clippy::missing_panics_doc)] #[must_use]
pub fn port() -> impl Parser<char, Port, Error = Simple<char>> {
Connector::parse_from_name()
.then(integer().or_not())
.map(|(kind, idx)| Port {
kind,
idx: idx.unwrap_or(1),
})
}
#[must_use]
pub fn resolution() -> impl Parser<char, Resolution, Error = Simple<char>> {
choice((
Resolution::parse_from_name(),
size().map(Resolution::Custom),
))
}
#[allow(clippy::cast_possible_wrap)] #[must_use]
pub fn size() -> impl Parser<char, Size, Error = Simple<char>> {
integer()
.then_ignore(just('x').padded())
.then(integer())
.map(|(width, height)| Size {
width: width as Pixel,
height: height as Pixel,
})
}
#[must_use]
pub fn transform() -> impl Parser<char, Transform, Error = Simple<char>> {
let flip = just("flip").then_ignore(whitespace());
choice((
flip.or_not().then(rotation().map(Some)),
flip.map(Some).then(rotation().or_not()),
))
.map(|(flip, rotation)| Transform {
flipped: flip.is_some(),
rotation: rotation.unwrap_or_default(),
})
}
#[must_use]
pub fn rotation() -> impl Parser<char, Rotation, Error = Simple<char>> {
let none = just('0').to(Rotation::None);
let quarter = just("90").to(Rotation::Quarter);
let half = just("180").to(Rotation::Half);
let three_quarter = just("270").to(Rotation::ThreeQuarter);
choice((none, quarter, half, three_quarter))
}
#[must_use]
pub fn pos() -> impl Parser<char, Position, Error = Simple<char>> {
let hori_then_vert = hori().then(just(',').padded().ignore_then(vert_spec()).or_not());
let vert_then_hori = vert().then(just(',').padded().ignore_then(hori_spec()).or_not());
choice((
hori_then_vert.map(|(hori, vert)| Position::Hori {
edge: hori,
spec: vert.unwrap_or_default(),
}),
vert_then_hori.map(|(vert, hori)| Position::Vert {
edge: vert,
spec: hori.unwrap_or_default(),
}),
))
}
pub fn separated<T, U>(
a: impl Parser<char, T, Error = Simple<char>>,
b: impl Parser<char, U, Error = Simple<char>>,
) -> impl Parser<char, (T, U), Error = Simple<char>> {
a.then_ignore(just(',').padded()).then(b)
}
#[must_use]
pub fn hori() -> impl Parser<char, Hori, Error = Simple<char>> {
let left = just("left").to(Hori::Left);
let right = just("right").to(Hori::Right);
choice((left, right))
}
#[must_use]
pub fn hori_spec() -> impl Parser<char, HoriSpec, Error = Simple<char>> {
choice((hori().map(Into::into), just("center").to(HoriSpec::Center)))
}
#[must_use]
pub fn vert() -> impl Parser<char, Vert, Error = Simple<char>> {
let top = just("top").map(|_| Vert::Top);
let bottom = just("bottom").map(|_| Vert::Bottom);
choice((top, bottom))
}
#[must_use]
pub fn vert_spec() -> impl Parser<char, VertSpec, Error = Simple<char>> {
choice((vert().map(Into::into), just("center").to(VertSpec::Center)))
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn integer() -> impl Parser<char, u32, Error = Simple<char>> {
text::int(10).map(|source: String| source.parse().unwrap())
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn float() -> impl Parser<char, f64, Error = Simple<char>> {
text::digits(10)
.then(just('.').ignore_then(text::digits(10)).or_not())
.map(|(natural, frac)| {
if let Some(frac) = frac {
format!("{natural}.{frac}")
} else {
natural
}
.parse()
.unwrap()
})
}