use crate::{Chunk, Error};
use itertools::Itertools;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{alphanumeric1, char, digit1};
use nom::combinator::eof;
use nom::combinator::{map_res, opt, peek, recognize, value};
use nom::multi::separated_list1;
use nom::{IResult, Parser};
use std::cmp::Ordering;
use std::cmp::Ordering::{Equal, Greater, Less};
use std::hash::Hash;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
pub struct Mess {
pub chunks: Vec<MChunk>,
pub next: Option<(Sep, Box<Mess>)>,
}
impl Mess {
pub fn new<S>(s: S) -> Option<Mess>
where
S: AsRef<str>,
{
match Mess::parse(s.as_ref()) {
Ok(("", m)) => Some(m),
_ => None,
}
}
pub fn nth(&self, x: usize) -> Option<u32> {
if let Some((Sep::Colon, next)) = self.next.as_ref() {
next.nth(x)
} else {
self.chunks.get(x).and_then(|chunk| match chunk {
MChunk::Digits(i, _) => Some(*i),
_ => None,
})
}
}
pub(crate) fn nth_chunk(&self, x: usize) -> Option<Chunk> {
let chunk = self.chunks.get(x)?.text();
let (i, c) = Chunk::parse_without_hyphens(chunk).ok()?;
match i {
"" => Some(c),
_ => None,
}
}
pub fn parse(i: &str) -> IResult<&str, Mess> {
let (i, chunks) = separated_list1(char('.'), MChunk::parse).parse(i)?;
let (i, next) = opt(Mess::next).parse(i)?;
let m = Mess {
chunks,
next: next.map(|(s, m)| (s, Box::new(m))),
};
Ok((i, m))
}
fn next(i: &str) -> IResult<&str, (Sep, Mess)> {
let (i, sep) = Mess::sep(i)?;
let (i, mess) = Mess::parse(i)?;
Ok((i, (sep, mess)))
}
fn sep(i: &str) -> IResult<&str, Sep> {
alt((
value(Sep::Colon, char(':')),
value(Sep::Hyphen, char('-')),
value(Sep::Plus, char('+')),
value(Sep::Underscore, char('_')),
value(Sep::Tilde, char('~')),
))
.parse(i)
}
}
impl PartialOrd for Mess {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Mess {
fn cmp(&self, other: &Self) -> Ordering {
match self.chunks.cmp(&other.chunks) {
Equal => {
let an = self.next.as_ref().map(|(_, m)| m);
let bn = other.next.as_ref().map(|(_, m)| m);
an.cmp(&bn)
}
ord => ord,
}
}
}
impl std::fmt::Display for Mess {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.chunks.iter().join("."))?;
if let Some((sep, m)) = &self.next {
write!(f, "{}{}", sep, m)?;
}
Ok(())
}
}
impl FromStr for Mess {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Mess::new(s).ok_or_else(|| Error::IllegalMess(s.to_string()))
}
}
impl TryFrom<&str> for Mess {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Mess::from_str(value)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum MChunk {
Digits(u32, String),
Rev(u32, String),
Plain(String),
}
impl MChunk {
pub fn text(&self) -> &str {
match self {
MChunk::Digits(_, s) => s,
MChunk::Rev(_, s) => s,
MChunk::Plain(s) => s,
}
}
pub(crate) fn parse(i: &str) -> IResult<&str, MChunk> {
alt((MChunk::digits, MChunk::rev, MChunk::plain)).parse(i)
}
fn digits(i: &str) -> IResult<&str, MChunk> {
let (i, (u, s)) = map_res(recognize(digit1), |s: &str| {
s.parse::<u32>().map(|u| (u, s))
})
.parse(i)?;
let (i, _) = alt((peek(recognize(char('.'))), peek(recognize(Mess::sep)), eof)).parse(i)?;
let chunk = MChunk::Digits(u, s.to_string());
Ok((i, chunk))
}
fn rev(i: &str) -> IResult<&str, MChunk> {
let (i, _) = tag("r")(i)?;
let (i, (u, s)) = map_res(recognize(digit1), |s: &str| {
s.parse::<u32>().map(|u| (u, s))
})
.parse(i)?;
let (i, _) = alt((peek(recognize(char('.'))), peek(recognize(Mess::sep)), eof)).parse(i)?;
let chunk = MChunk::Rev(u, format!("r{}", s));
Ok((i, chunk))
}
fn plain(i: &str) -> IResult<&str, MChunk> {
let (i, s) = alphanumeric1(i)?;
let chunk = MChunk::Plain(s.to_string());
Ok((i, chunk))
}
}
impl PartialOrd for MChunk {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MChunk {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(MChunk::Digits(a, _), MChunk::Digits(b, _)) => a.cmp(b),
(MChunk::Rev(a, _), MChunk::Rev(b, _)) => a.cmp(b),
(MChunk::Digits(_, _), MChunk::Rev(_, _)) => Greater,
(MChunk::Rev(_, _), MChunk::Digits(_, _)) => Less,
(a, b) => a.text().cmp(b.text()),
}
}
}
impl std::fmt::Display for MChunk {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MChunk::Digits(_, s) => write!(f, "{}", s),
MChunk::Rev(_, s) => write!(f, "{}", s),
MChunk::Plain(s) => write!(f, "{}", s),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Sep {
Colon,
Hyphen,
Plus,
Underscore,
Tilde,
}
impl std::fmt::Display for Sep {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let c = match self {
Sep::Colon => ':',
Sep::Hyphen => '-',
Sep::Plus => '+',
Sep::Underscore => '_',
Sep::Tilde => '~',
};
write!(f, "{}", c)
}
}