extern crate alloc;
use alloc:: {
vec::Vec,
};
use const_array_attrs::sorted;
use const_frac::Frac;
use keyword_parser::bin_searcher::{ longest_matcher, simple_matcher };
use combine:: {
Parser, RangeStream,
attempt, choice, from_str, optional, satisfy, many1,
error::StringStreamError,
parser:: {
char:: { char, string },
range::take_while1,
},
};
use crate:: {
DynDim, Unit,
predefined::dim,
unit::Conv,
};
#[sorted]
const SI_PREFIX: [(&str, i8); 31] = [
("Y", 24),
("Z", 21),
("E", 18),
("P", 15),
("T", 12),
("G", 9), ("\u{3310}", 9), ("M", 6), ("\u{334B}", 6), ("k", 3), ("\u{3314}", 3), ("h", 2),
("da", 1), ("\u{3372}", 1), ("d", -1), ("\u{3325}", -1), ("c", -2), ("\u{3322}", -2), ("m", -3), ("\u{3349}", -3), ("\u{00B5}", -6), ("\u{03BC}", -6), ("\u{3343}", -6), ("n", -9), ("\u{3328}", -9), ("p", -12), ("\u{3330}", -12), ("f", -15),
("a", -18),
("z", -21),
("y", -24),
];
fn prefix<'a, I>() -> impl 'a + Parser<I, Output = i8>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
{
longest_matcher(&SI_PREFIX)
}
fn is_delim(c: char) -> bool {
match c {
| '*'
| '\u{00B7}' | '\u{00D7}' | '\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{2022}' | '\u{2219}' | '\u{22C5}' | '\u{2715}' | '\u{2716}' | '\u{30FB}' | '\u{FF65}' => true,
c => c.is_whitespace()
}
}
fn delim<'a, I>() -> impl 'a + Parser<I, Output = ()>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
{
take_while1(is_delim).map(|_| ())
}
fn is_slash(c: char) -> bool {
match c {
| '/'
| '\u{00F7}' | '\u{2044}' | '\u{2215}' | '\u{27CB}' | '\u{29F8}' | '\u{6BCE}' => true,
_ => false,
}
}
fn is_character(c: char) -> bool {
match c {
| '-'
| '+'
| '[' | ']'
| '(' | ')' | '{' | '}' | '<' | '>' => false,
c => !c.is_ascii_digit() && !is_slash(c) && !is_delim(c),
}
}
fn name<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac,DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
K: AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
take_while1(is_character).flat_map(move |s| match simple_matcher(def).parse(s) {
Ok((result, _)) => Ok(result.into()),
Err(e) => Err(e),
})
}
fn term<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac, DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
K: 'a + AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
choice((
attempt((prefix(), name(def))).map(|(i, info)| Unit {
a: info.a * Frac::from_exp10(i as i16),
b: info.b * Frac::from_exp10(i as i16),
.. info
}),
name(def).map(|info| info)
))
}
fn exp<'a, I>() -> impl 'a + Parser<I, Output = i8>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
{
(
optional(char('-')).map(|sign| sign.map_or(1, |_| -1)),
(from_str(take_while1(|c: I::Token| c.is_ascii_digit())))
).map(|(sign, i): (i8, i8)| sign * i)
}
fn element<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac, DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
K: 'a + AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
(term(def), optional(exp())).map(|(u, exp)| match exp {
Some(exp) => u.powi(exp),
None => u,
})
}
fn prod<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac, DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
K: 'a + AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
many1((element(def), optional(delim())).map(|(e, _)| e)).map(|v: Vec<_>| {
let mut it = v.iter();
match it.next() {
Some(&spec) => it.fold(spec, |ret, spec| ret * spec),
None => Default::default(),
}
})
}
fn comp<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac, DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
K: 'a + AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
(prod(def), optional((satisfy(is_slash), optional(delim()), prod(def))))
.map(|(num, denom)| match denom {
Some((_, _, denom)) => num / &denom,
None => num,
})
}
fn modifier<'a, I>() -> impl 'a + Parser<I, Output = &'a str>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
{
choice((
attempt(string("abs")),
attempt(string("dif")),
attempt(string("gage")),
))
}
pub fn unit<'a, I, K, T>(def: T) -> impl 'a + Parser<I, Output = Unit<Frac, DynDim>>
where
I: 'a + RangeStream<Token = char, Range = &'a str, Error = StringStreamError>,
I::Position: Default,
K: 'a + AsRef<str>,
T: 'a + AsRef<[(K, (Conv, DynDim))]> + Copy,
{
(comp(def), optional((char('['), modifier(), char(']')).map(|(_, attr, _)| attr)))
.map(|(u, attr)| {
if u.dim == dim::PRESSURE {
match attr {
Some(attr) if attr == "gage" => Unit {
b: Frac::from_int(101325000),
.. u
},
_ => u,
}
} else {
u
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::predefined;
#[test]
fn test_prefix() {
let s = "m";
assert_eq!(
prefix().parse(s).unwrap(),
(-3, "")
);
}
#[test]
fn test_name() {
let s = "m";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
assert_eq!(
name(&def).parse(s).unwrap().0,
Unit {
a: Frac::from_int(1),
b: Frac::from_int(0),
dim: DynDim::from(dim::LENGTH),
}
);
}
#[test]
fn test_dimensionless() {
let s = "\u{2014}";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
assert_eq!(
name(&def).parse(s).unwrap().0,
Unit {
a: Frac::from_int(1),
b: Frac::from_int(0),
dim: DynDim::from(dim::DIMENSIONLESS),
}
);
}
#[test]
fn test_term() {
let s = "kg";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
let mut p = term(&def);
let result = p.parse(s);
assert_eq!(
result.unwrap().0,
Unit {
a: Frac::from_int(1000),
b: Frac::from_int(0),
dim: DynDim::from(dim::MASS),
}
);
}
#[test]
fn test_element() {
let s = "m2";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
assert_eq!(
element(&def).parse(s).unwrap().0,
Unit {
a: Frac::from_int(1),
b: Frac::from_int(0),
dim: DynDim::from(dim::AREA),
}
);
}
#[test]
fn test_prod() {
let s = "N・m";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
assert_eq!(
prod(&def).parse(s).unwrap().0,
Unit {
a: Frac::from_int(1000),
b: Frac::from_int(0),
dim: DynDim::from(dim::ENERGY),
}
);
}
#[test]
fn test_unit() {
let s = "kg/m*s2";
let def = &predefined::unit::DEFAULT_UNIT_DEF[..];
assert_eq!(
unit(&def).parse(s).unwrap().0,
Unit {
a: Frac::from_int(1000),
b: Frac::from_int(0),
dim: DynDim::from(dim::PRESSURE),
}
);
}
}