use super::strip_prefix;
struct CalcParser<'a> {
s: &'a str,
idx: usize,
}
impl<'a> CalcParser<'a> {
fn new(s: &'a str) -> Self {
Self { s, idx: 0 }
}
fn operand(&mut self) -> Option<&'a str> {
if self.is_end() {
return None;
}
let start = self.idx;
match self.s.as_bytes()[self.idx] {
b'-' => self.idx += 1,
b'+' => return None,
b'*' => return None,
b'/' => return None,
_ => (),
}
let mut nesting = 0i32;
while self.idx < self.s.len() {
let ch = self.s.as_bytes()[self.idx];
match ch {
b'(' => {
nesting += 1;
self.idx += 1;
}
b')' => {
if nesting > 0 {
nesting -= 1;
}
self.idx += 1;
}
b'+' | b'-' | b'*' | b'/' | b' ' => {
if nesting == 0 {
break;
}
self.idx += 1;
}
_ => self.idx += 1,
}
}
Some(&self.s[start..self.idx])
}
fn operator(&mut self) -> Option<u8> {
if self.is_end() {
return None;
}
let ch = self.s.as_bytes()[self.idx];
match ch {
b'+' | b'-' | b'*' | b'/' => {
self.idx += 1;
Some(ch)
}
_ => None,
}
}
fn is_end(&mut self) -> bool {
while self.idx < self.s.len() && self.s.as_bytes()[self.idx] == b' ' {
self.idx += 1;
}
self.idx >= self.s.len()
}
fn parse(&mut self) -> Option<(&str, u8, &str)> {
if let (Some(va), Some(op), Some(vb), true) = (
self.operand(),
self.operator(),
self.operand(),
self.is_end(),
) {
Some((va, op, vb))
} else {
None
}
}
}
pub fn parse_values(values: [&str; 4], variables: [(&str, f32); 4]) -> Option<[f32; 4]> {
let parse_v = |s: &str| -> Option<f32> {
if let Ok(value) = s.parse() {
return Some(value);
};
for (var, value) in variables {
if s.eq_ignore_ascii_case(var) {
return Some(value);
}
}
None
};
let mut result = [0.0; 4];
let mut i = 0;
for s in values {
if let Some(t) = parse_v(s) {
result[i] = t;
i += 1;
continue;
}
if let Some(s) = strip_prefix(s, "calc") {
if let Some(t) = parse_calc(s, &parse_v) {
result[i] = t;
i += 1;
continue;
}
}
return None;
}
Some(result)
}
fn parse_calc<F>(s: &str, f: &F) -> Option<f32>
where
F: Fn(&str) -> Option<f32>,
{
if let Some(s) = s.strip_prefix('(') {
if let Some(s) = s.strip_suffix(')') {
let mut p = CalcParser::new(s);
let (va, op, vb) = p.parse()?;
let va = if let Some(v) = f(va) {
v
} else if let Some(v) = parse_calc(va, f) {
v
} else {
return None;
};
let vb = if let Some(v) = f(vb) {
v
} else if let Some(v) = parse_calc(vb, f) {
v
} else {
return None;
};
match op {
b'+' => return Some(va + vb),
b'-' => return Some(va - vb),
b'*' => return Some(va * vb),
b'/' => {
if vb == 0.0 {
return None;
}
return Some(va / vb);
}
_ => unreachable!(),
}
}
}
None
}
#[cfg(test)]
mod t {
use super::*;
#[test]
fn calc_parser() {
let s = "78+0.573";
let mut p = CalcParser::new(s);
assert_eq!(p.operator(), None);
assert_eq!(p.operand(), Some("78"));
assert_eq!(p.operand(), None);
assert_eq!(p.operator(), Some(b'+'));
assert_eq!(p.operator(), None);
assert_eq!(p.operand(), Some("0.573"));
assert_eq!(p.operator(), None);
assert_eq!(p.operand(), None);
assert!(p.is_end());
assert_eq!(p.parse(), None);
#[rustfmt::skip]
let test_data = [
(
"78+0.573",
("78", b'+', "0.573"),
),
(
"g-100",
("g", b'-', "100"),
),
(
" 9 * alpha ",
("9", b'*', "alpha"),
),
(
"alpha/2",
("alpha", b'/', "2"),
),
(
"-360+-55.07",
("-360", b'+', "-55.07"),
),
(
"-7--5",
("-7", b'-', "-5"),
),
(
"h+(4*0.75)",
("h", b'+', "(4*0.75)"),
),
(
"(0.35*r) / (alpha - 10)",
("(0.35*r)", b'/', "(alpha - 10)"),
),
];
for (s, expected) in test_data {
let mut p = CalcParser::new(s);
assert_eq!(p.parse(), Some(expected), "{:?}", s);
assert!(p.is_end(), "{:?}", s);
}
#[rustfmt::skip]
let invalids = [
"",
" ",
"5",
"g+",
"-",
"7---3",
"*3+2",
"4+5/",
];
for s in invalids {
let mut p = CalcParser::new(s);
assert_eq!(p.parse(), None, "{:?}", s);
}
}
#[test]
fn parse_calc_() {
fn f(s: &str) -> Option<f32> {
s.parse().ok()
}
let test_data = [
("(1+3.7)", 4.7),
("( 0.35 - -0.5 )", 0.85),
("(2.0*(7-5))", 4.0),
("((5*10) / (7+3))", 5.0),
("(0.5 * (5 + (7 * (9 - (3 * (1 + 1))))))", 13.0),
];
for (s, expected) in test_data {
assert_eq!(parse_calc(s, &f), Some(expected), "{:?}", s);
}
let invalids = [
"",
"5",
"g",
"1+7",
"()",
"(())",
"(())",
"(()+(1*5))",
"(9)",
"(4/0)",
"(1-8",
"7+0.3)",
"(5+(3*2)",
"((5-1)",
"((1+2))",
"(5+(1+2/3))",
"(4+5(1*3))",
"((1+2)1*5)",
];
for s in invalids {
assert_eq!(parse_calc(s, &f), None, "{:?}", s);
}
}
#[test]
fn parse_values_() {
fn parse_value(s: &str, variables: [(&str, f32); 4]) -> Option<f32> {
if let Some([t, ..]) = parse_values([s; 4], variables) {
return Some(t);
}
None
}
let vars = [("r", 255.0), ("g", 127.0), ("b", 0.0), ("alpha", 0.5)];
let test_data = [
("130", 130.0),
("-0.5", -0.5),
("g", 127.0),
("calc(4+5.5)", 9.5),
("calc( 10 - 7 )", 3.0),
("CALC(2.5 *2)", 5.0),
("CaLc(21.0/ 3)", 7.0),
("calc(r-55)", 200.0),
("calc(10 + g)", 137.0),
("calc(alpha*1.5)", 0.75),
("calc(-97+-18)", -115.0),
("calc( -1 * -45)", 45.0),
("calc(100--35)", 135.0),
("calc(100 - -35)", 135.0),
("calc(1.5*(4/2))", 3.0),
("calc( ( 19 + 6 ) / 5 )", 5.0),
("calc((2/(1.5+0.5)) - (0.75 - 0.25))", 0.5),
("calc((r + g) / 2)", 191.0),
];
for (s, expected) in test_data {
assert_eq!(parse_value(s, vars), Some(expected), "{:?}", s);
}
let invalids = [
"",
"7x",
"h",
"(4+5)",
"cal(4+5)",
"calcs(4+5)",
"calc()",
"calc(-)",
"calc(5)",
"calc(+5)",
"calc(b)",
"calc(g-)",
"calc(5+1-4)",
"calc(1 * 7 +)",
"calc(5 + (1.5))",
"calc(5 + (1.5 * 2 / 3))",
"calc(5 + (2 - ab))",
];
for s in invalids {
assert_eq!(parse_value(s, vars), None, "{:?}", s);
}
}
}