156 lines
4.2 KiB
Rust
156 lines
4.2 KiB
Rust
use nom::{
|
|
branch::alt,
|
|
character::complete::{char, none_of, one_of, space0},
|
|
combinator::{opt, recognize},
|
|
error::{Error, ErrorKind},
|
|
multi::{many0, many1},
|
|
sequence::{preceded, terminated, tuple},
|
|
Err, IResult, InputTakeAtPosition, Parser,
|
|
};
|
|
use rust_decimal::Decimal;
|
|
use rust_decimal_macros::dec;
|
|
|
|
use crate::document::DirectiveAmount;
|
|
|
|
pub fn account(input: &str) -> IResult<&str, &str> {
|
|
input.split_at_position1_complete(|item| item == ' ' || item == '\t', ErrorKind::AlphaNumeric)
|
|
}
|
|
|
|
pub fn amount(input: &str) -> IResult<&str, DirectiveAmount> {
|
|
alt((suffix_amount, prefix_amount)).parse(input)
|
|
}
|
|
|
|
pub fn decimal(input: &str) -> IResult<&str, Decimal> {
|
|
let (new_input, decimal_str) = recognize(tuple((
|
|
opt(one_of("+-")),
|
|
opt(number_int),
|
|
opt(char('.')),
|
|
opt(number_int),
|
|
)))
|
|
.parse(input)?;
|
|
|
|
if decimal_str.contains(',') {
|
|
match Decimal::from_str_exact(&decimal_str.replace(",", "")) {
|
|
Ok(decimal) => Ok((new_input, decimal)),
|
|
Err(_) => Err(Err::Error(Error::new(input, ErrorKind::Eof))),
|
|
}
|
|
} else {
|
|
match Decimal::from_str_exact(decimal_str) {
|
|
Ok(decimal) => Ok((new_input, decimal)),
|
|
Err(_) => Err(Err::Error(Error::new(input, ErrorKind::Eof))),
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////
|
|
// Private //
|
|
///////////////
|
|
|
|
fn prefix_amount(input: &str) -> IResult<&str, DirectiveAmount> {
|
|
tuple((
|
|
opt(one_of("+-")),
|
|
unit,
|
|
preceded(space0, decimal),
|
|
))
|
|
.map(|(sign, unit_symbol, mut value)| {
|
|
if let Some(s) = sign {
|
|
if s == '-' {
|
|
value = value * dec!(-1);
|
|
}
|
|
}
|
|
DirectiveAmount {
|
|
value,
|
|
unit_symbol: unit_symbol.to_string(),
|
|
is_unit_prefix: true,
|
|
}
|
|
})
|
|
.parse(input)
|
|
}
|
|
|
|
fn suffix_amount(input: &str) -> IResult<&str, DirectiveAmount> {
|
|
tuple((
|
|
decimal,
|
|
preceded(space0, unit),
|
|
))
|
|
.map(|(value, unit_symbol)| DirectiveAmount {
|
|
value,
|
|
unit_symbol: unit_symbol.to_string(),
|
|
is_unit_prefix: false,
|
|
})
|
|
.parse(input)
|
|
}
|
|
|
|
fn unit(input: &str) -> IResult<&str, &str> {
|
|
recognize(many1(none_of("0123456789,+-_()*/.{} \t"))).parse(input)
|
|
}
|
|
|
|
fn number_int(input: &str) -> IResult<&str, &str> {
|
|
recognize(many1(terminated(one_of("0123456789"), many0(one_of("_,")))))(input)
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use rust_decimal_macros::dec;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parse_decimal_good() {
|
|
assert_eq!(decimal("1").unwrap().1, dec!(1));
|
|
assert_eq!(decimal("+10").unwrap().1, dec!(10));
|
|
assert_eq!(decimal("-10").unwrap().1, dec!(-10));
|
|
assert_eq!(decimal("10.1").unwrap().1, dec!(10.1));
|
|
assert_eq!(decimal("100_000.01").unwrap().1, dec!(100000.01));
|
|
assert_eq!(decimal(".1").unwrap().1, dec!(0.1));
|
|
assert_eq!(decimal("-.1").unwrap().1, dec!(-0.1));
|
|
assert_eq!(decimal("2.").unwrap().1, dec!(2.));
|
|
assert_eq!(decimal("1,000").unwrap().1, dec!(1000));
|
|
}
|
|
|
|
|
|
#[test]
|
|
fn amount_good() {
|
|
assert_eq!(
|
|
amount("$10").unwrap().1,
|
|
DirectiveAmount {
|
|
value: dec!(10),
|
|
unit_symbol: "$".into(),
|
|
is_unit_prefix: true
|
|
}
|
|
);
|
|
assert_eq!(
|
|
amount("10 USD").unwrap().1,
|
|
DirectiveAmount {
|
|
value: dec!(10),
|
|
unit_symbol: "USD".into(),
|
|
is_unit_prefix: false
|
|
}
|
|
);
|
|
assert_eq!(
|
|
amount("-$10.01").unwrap().1,
|
|
DirectiveAmount {
|
|
value: dec!(-10.01),
|
|
unit_symbol: "$".into(),
|
|
is_unit_prefix: true
|
|
}
|
|
);
|
|
assert_eq!(
|
|
amount("-10€").unwrap().1,
|
|
DirectiveAmount {
|
|
value: dec!(-10),
|
|
unit_symbol: "€".into(),
|
|
is_unit_prefix: false
|
|
}
|
|
);
|
|
assert_eq!(
|
|
amount("-€10").unwrap().1,
|
|
DirectiveAmount {
|
|
value: dec!(-10),
|
|
unit_symbol: "€".into(),
|
|
is_unit_prefix: true
|
|
}
|
|
);
|
|
}
|
|
|
|
} |