use std::borrow::Borrow;
use std::{
fmt::Debug,
fmt::{Display, Formatter},
ops::{Add, Div, Mul, Neg, Sub},
str::FromStr,
sync::Arc,
};
use nom::{
branch::alt,
bytes::complete::{take_while, take_while1},
character::complete::{char, one_of, satisfy, space0, space1},
combinator::all_consuming,
combinator::{iterator, map_res, opt, recognize, verify},
sequence::{delimited, preceded, terminated, tuple},
Finish,
};
use crate::{IResult, Span};
#[derive(Debug, Clone, PartialEq)]
pub struct Price<D> {
pub currency: Currency,
pub amount: Amount<D>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Amount<D> {
pub value: D,
pub currency: Currency,
}
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Currency(Arc<str>);
impl Currency {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for Currency {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl AsRef<str> for Currency {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Borrow<str> for Currency {
fn borrow(&self) -> &str {
self.0.borrow()
}
}
impl<'a> TryFrom<&'a str> for Currency {
type Error = crate::ConversionError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
match all_consuming(currency)(Span::new(value)).finish() {
Ok((_, currency)) => Ok(currency),
Err(_) => Err(crate::ConversionError),
}
}
}
pub(crate) fn parse<D: Decimal>(input: Span<'_>) -> IResult<'_, Amount<D>> {
let (input, value) = expression(input)?;
let (input, _) = space1(input)?;
let (input, currency) = currency(input)?;
Ok((input, Amount { value, currency }))
}
pub(super) fn expression<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
exp_p2(input)
}
fn exp_p2<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
let (input, value) = exp_p1(input)?;
let mut iter = iterator(
input,
tuple((delimited(space0, one_of("+-"), space0), exp_p1)),
);
let value = iter.fold(value, |a, (op, b)| match op {
'+' => a + b,
'-' => a - b,
op => unreachable!("unsupported operator: {}", op),
});
let (input, ()) = iter.finish()?;
Ok((input, value))
}
fn exp_p1<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
let (input, value) = exp_p0(input)?;
let mut iter = iterator(
input,
tuple((delimited(space0, one_of("*/"), space0), exp_p0)),
);
let value = iter.fold(value, |a, (op, b)| match op {
'*' => a * b,
'/' => a / b,
op => unreachable!("unsupported operator: {}", op),
});
let (input, ()) = iter.finish()?;
Ok((input, value))
}
fn exp_p0<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
alt((
literal,
delimited(
terminated(char('('), space0),
expression,
preceded(space0, char(')')),
),
))(input)
}
fn literal<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
map_res(
recognize(tuple((
opt(char('-')),
take_while1(|c: char| c.is_numeric() || c == '.' || c == ','),
))),
|s: Span<'_>| s.fragment().replace(',', "").parse(),
)(input)
}
pub(crate) fn price<D: Decimal>(input: Span<'_>) -> IResult<'_, Price<D>> {
let (input, currency) = currency(input)?;
let (input, _) = space1(input)?;
let (input, amount) = parse(input)?;
Ok((input, Price { currency, amount }))
}
pub(crate) fn currency(input: Span<'_>) -> IResult<'_, Currency> {
let (input, currency) = recognize(tuple((
satisfy(char::is_uppercase),
verify(
take_while(|c: char| {
c.is_uppercase() || c.is_numeric() || c == '-' || c == '_' || c == '.' || c == '\''
}),
|s: &Span<'_>| {
s.fragment()
.chars()
.last()
.map_or(true, |c| c.is_uppercase() || c.is_numeric())
},
),
)))(input)?;
Ok((input, Currency(Arc::from(*currency.fragment()))))
}
pub trait Decimal:
FromStr
+ Default
+ Clone
+ Debug
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Neg<Output = Self>
+ PartialEq
+ PartialOrd
{
}
impl<D> Decimal for D where
D: FromStr
+ Default
+ Clone
+ Debug
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Neg<Output = Self>
+ PartialEq
+ PartialOrd
{
}