Rename the identifiers that carried the upstream name
Everything clients, users and operators meet now carries the fork's name, with no aliases (SPEC.md §2.4, changed here from "protocol identifiers stay"): - JMAP: upstream's registry capability is urn:inbuxa:jmap:registry, beside the fork's own urn:inbuxa:jmap. - WebDAV lock and sync tokens are urn:inbuxa:dav*; clients resync once. - Sieve: vnd.inbuxa.while and vnd.inbuxa.expressions. sieve-rs spells these into its compiler, so it's vendored (vendor/sieve-rs, 0.7.3) and patched in; a unit test fails if Cargo.lock ever moves past the vendored copy. The trusted runtime now names itself too, rather than answering sieve-rs's default. - The web interface's OAuth client is inbuxa-webui. On every start the old stalwart-webui client is removed and any application naming it is moved over. - The spam filter's blobs are INBUXA_SPAM_*; every start moves any left under the old keys, so a trained model survives. - SQL stores and log files default to inbuxa, in the code and in the schema served to the admin (checksum regenerated). - Settings are INBUXA_* only. A STALWART_* variable that's set where its INBUXA_* one isn't stops the server at startup, naming it. - The version-upgrade messages link docs.inbuxa.org's migration page, and the OpenAPI description, smtp crate metadata and web-push test fixtures lose the name. Kept on purpose, allowlisted with reasons: the OAuth key-derivation contexts (renaming them would end every session and invalidate every sealed client id) and the hashed application prefix. Also fixes a latent start-up failure: ensure_client updated an existing first-party client with a revision of 0, which the registry's assertion never matches, so adding a redirect URI or changing the webmail secret failed start-up. And the principal session test now expects legacyProtocols (C-1, added 2026-09-21), which it had missed. Tested: the server builds without warnings; common's 106 unit tests, including the vendoring check; a new integration test for the two start-up migrations; and the webdav, jmap, imap and SMTP Sieve suites.
This commit is contained in:
+641
@@ -0,0 +1,641 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{cmp::Ordering, fmt::Display};
|
||||
|
||||
use crate::Event;
|
||||
use crate::compiler::grammar::expr::parser::ID_EXTERNAL;
|
||||
use crate::{Context, compiler::Number, runtime::Variable};
|
||||
|
||||
use crate::compiler::grammar::expr::{BinaryOperator, Constant, Expression, UnaryOperator};
|
||||
|
||||
impl Context<'_> {
|
||||
pub(crate) fn eval_expression(&mut self, expr: &[Expression]) -> Result<Variable, Event> {
|
||||
let mut exprs = expr.iter().skip(self.expr_pos);
|
||||
while let Some(expr) = exprs.next() {
|
||||
self.expr_pos += 1;
|
||||
match expr {
|
||||
Expression::Variable(v) => {
|
||||
self.expr_stack.push(self.variable(v).unwrap_or_default());
|
||||
}
|
||||
Expression::Constant(val) => {
|
||||
self.expr_stack.push(Variable::from(val));
|
||||
}
|
||||
Expression::UnaryOperator(op) => {
|
||||
let value = self.expr_stack.pop().unwrap_or_default();
|
||||
self.expr_stack.push(match op {
|
||||
UnaryOperator::Not => value.op_not(),
|
||||
UnaryOperator::Minus => value.op_minus(),
|
||||
});
|
||||
}
|
||||
Expression::BinaryOperator(op) => {
|
||||
let right = self.expr_stack.pop().unwrap_or_default();
|
||||
let left = self.expr_stack.pop().unwrap_or_default();
|
||||
self.expr_stack.push(match op {
|
||||
BinaryOperator::Add => left.op_add(right),
|
||||
BinaryOperator::Subtract => left.op_subtract(right),
|
||||
BinaryOperator::Multiply => left.op_multiply(right),
|
||||
BinaryOperator::Divide => left.op_divide(right),
|
||||
BinaryOperator::And => left.op_and(right),
|
||||
BinaryOperator::Or => left.op_or(right),
|
||||
BinaryOperator::Xor => left.op_xor(right),
|
||||
BinaryOperator::Eq => left.op_eq(right),
|
||||
BinaryOperator::Ne => left.op_ne(right),
|
||||
BinaryOperator::Lt => left.op_lt(right),
|
||||
BinaryOperator::Le => left.op_le(right),
|
||||
BinaryOperator::Gt => left.op_gt(right),
|
||||
BinaryOperator::Ge => left.op_ge(right),
|
||||
});
|
||||
}
|
||||
Expression::Function { id, num_args } => {
|
||||
let num_args = *num_args as usize;
|
||||
|
||||
if let Some(fnc) = self.runtime.functions.get(*id as usize) {
|
||||
let mut arguments = vec![Variable::Integer(0); num_args];
|
||||
for arg_num in 0..num_args {
|
||||
arguments[num_args - arg_num - 1] =
|
||||
self.expr_stack.pop().unwrap_or_default();
|
||||
}
|
||||
self.expr_stack.push((fnc)(self, arguments));
|
||||
} else {
|
||||
let mut arguments = vec![Variable::Integer(0); num_args];
|
||||
for arg_num in 0..num_args {
|
||||
arguments[num_args - arg_num - 1] =
|
||||
self.expr_stack.pop().unwrap_or_default();
|
||||
}
|
||||
self.pos -= 1; // We need to re-evaluate the function call
|
||||
return Err(Event::Function {
|
||||
id: ID_EXTERNAL - *id,
|
||||
arguments,
|
||||
});
|
||||
}
|
||||
}
|
||||
Expression::JmpIf { val, pos } => {
|
||||
if self.expr_stack.last().is_some_and(|v| v.to_bool()) == *val {
|
||||
self.expr_pos += *pos as usize;
|
||||
for _ in 0..*pos {
|
||||
exprs.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::ArrayAccess => {
|
||||
let index = self.expr_stack.pop().unwrap_or_default().to_usize();
|
||||
let array = self.expr_stack.pop().unwrap_or_default().into_array();
|
||||
self.expr_stack
|
||||
.push(array.get(index).cloned().unwrap_or_default());
|
||||
}
|
||||
Expression::ArrayBuild(num_items) => {
|
||||
let num_items = *num_items as usize;
|
||||
let mut items = vec![Variable::Integer(0); num_items];
|
||||
for arg_num in 0..num_items {
|
||||
items[num_items - arg_num - 1] = self.expr_stack.pop().unwrap_or_default();
|
||||
}
|
||||
self.expr_stack.push(Variable::Array(items.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = self.expr_stack.pop().unwrap_or_default();
|
||||
self.expr_stack.clear();
|
||||
self.expr_pos = 0;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
pub fn op_add(self, other: Variable) -> Variable {
|
||||
match (self, other) {
|
||||
(Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_add(b)),
|
||||
(Variable::Float(a), Variable::Float(b)) => Variable::Float(a + b),
|
||||
(Variable::Integer(i), Variable::Float(f))
|
||||
| (Variable::Float(f), Variable::Integer(i)) => Variable::Float(i as f64 + f),
|
||||
(Variable::Array(a), Variable::Array(b)) => {
|
||||
Variable::Array(a.iter().chain(b.iter()).cloned().collect::<Vec<_>>().into())
|
||||
}
|
||||
(Variable::Array(a), b) => a.iter().cloned().chain([b]).collect::<Vec<_>>().into(),
|
||||
(a, Variable::Array(b)) => [a]
|
||||
.into_iter()
|
||||
.chain(b.iter().cloned())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
(Variable::String(a), b) => {
|
||||
if !a.is_empty() {
|
||||
Variable::String(format!("{}{}", a, b).into())
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
(a, Variable::String(b)) => {
|
||||
if !b.is_empty() {
|
||||
Variable::String(format!("{}{}", a, b).into())
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op_subtract(self, other: Variable) -> Variable {
|
||||
match (self, other) {
|
||||
(Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_sub(b)),
|
||||
(Variable::Float(a), Variable::Float(b)) => Variable::Float(a - b),
|
||||
(Variable::Integer(a), Variable::Float(b)) => Variable::Float(a as f64 - b),
|
||||
(Variable::Float(a), Variable::Integer(b)) => Variable::Float(a - b as f64),
|
||||
(Variable::Array(a), b) | (b, Variable::Array(a)) => Variable::Array(
|
||||
a.iter()
|
||||
.filter(|v| *v != &b)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
),
|
||||
(a, b) => a.parse_number().op_subtract(b.parse_number()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op_multiply(self, other: Variable) -> Variable {
|
||||
match (self, other) {
|
||||
(Variable::Integer(a), Variable::Integer(b)) => Variable::Integer(a.saturating_mul(b)),
|
||||
(Variable::Float(a), Variable::Float(b)) => Variable::Float(a * b),
|
||||
(Variable::Integer(i), Variable::Float(f))
|
||||
| (Variable::Float(f), Variable::Integer(i)) => Variable::Float(i as f64 * f),
|
||||
(a, b) => a.parse_number().op_multiply(b.parse_number()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op_divide(self, other: Variable) -> Variable {
|
||||
match (self, other) {
|
||||
(Variable::Integer(a), Variable::Integer(b)) => {
|
||||
Variable::Float(if b != 0 { a as f64 / b as f64 } else { 0.0 })
|
||||
}
|
||||
(Variable::Float(a), Variable::Float(b)) => {
|
||||
Variable::Float(if b != 0.0 { a / b } else { 0.0 })
|
||||
}
|
||||
(Variable::Integer(a), Variable::Float(b)) => {
|
||||
Variable::Float(if b != 0.0 { a as f64 / b } else { 0.0 })
|
||||
}
|
||||
(Variable::Float(a), Variable::Integer(b)) => {
|
||||
Variable::Float(if b != 0 { a / b as f64 } else { 0.0 })
|
||||
}
|
||||
(a, b) => a.parse_number().op_divide(b.parse_number()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op_and(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self.to_bool() & other.to_bool()))
|
||||
}
|
||||
|
||||
pub fn op_or(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self.to_bool() | other.to_bool()))
|
||||
}
|
||||
|
||||
pub fn op_xor(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self.to_bool() ^ other.to_bool()))
|
||||
}
|
||||
|
||||
pub fn op_eq(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self == other))
|
||||
}
|
||||
|
||||
pub fn op_ne(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self != other))
|
||||
}
|
||||
|
||||
pub fn op_lt(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self < other))
|
||||
}
|
||||
|
||||
pub fn op_le(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self <= other))
|
||||
}
|
||||
|
||||
pub fn op_gt(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self > other))
|
||||
}
|
||||
|
||||
pub fn op_ge(self, other: Variable) -> Variable {
|
||||
Variable::Integer(i64::from(self >= other))
|
||||
}
|
||||
|
||||
pub fn op_not(self) -> Variable {
|
||||
Variable::Integer(i64::from(!self.to_bool()))
|
||||
}
|
||||
|
||||
pub fn op_minus(self) -> Variable {
|
||||
match self {
|
||||
Variable::Integer(n) => Variable::Integer(-n),
|
||||
Variable::Float(n) => Variable::Float(-n),
|
||||
_ => self.parse_number().op_minus(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_number(&self) -> Variable {
|
||||
match self {
|
||||
Variable::String(s) if !s.is_empty() => {
|
||||
if let Ok(n) = s.parse::<i64>() {
|
||||
Variable::Integer(n)
|
||||
} else if let Ok(n) = s.parse::<f64>() {
|
||||
Variable::Float(n)
|
||||
} else {
|
||||
Variable::Integer(0)
|
||||
}
|
||||
}
|
||||
Variable::Integer(n) => Variable::Integer(*n),
|
||||
Variable::Float(n) => Variable::Float(*n),
|
||||
Variable::Array(l) => Variable::Integer(l.is_empty() as i64),
|
||||
_ => Variable::Integer(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bool(&self) -> bool {
|
||||
match self {
|
||||
Variable::Float(f) => *f != 0.0,
|
||||
Variable::Integer(n) => *n != 0,
|
||||
Variable::String(s) => !s.is_empty(),
|
||||
Variable::Array(a) => !a.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Variable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Integer(a), Self::Integer(b)) => a == b,
|
||||
(Self::Float(a), Self::Float(b)) => a == b,
|
||||
(Self::Integer(a), Self::Float(b)) | (Self::Float(b), Self::Integer(a)) => {
|
||||
*a as f64 == *b
|
||||
}
|
||||
(Self::String(a), Self::String(b)) => a == b,
|
||||
(Self::String(_), Self::Integer(_) | Self::Float(_)) => &self.parse_number() == other,
|
||||
(Self::Integer(_) | Self::Float(_), Self::String(_)) => self == &other.parse_number(),
|
||||
(Self::Array(a), Self::Array(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Variable {}
|
||||
|
||||
#[allow(clippy::non_canonical_partial_ord_impl)]
|
||||
impl PartialOrd for Variable {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Integer(a), Self::Integer(b)) => a.partial_cmp(b),
|
||||
(Self::Float(a), Self::Float(b)) => a.partial_cmp(b),
|
||||
(Self::Integer(a), Self::Float(b)) => (*a as f64).partial_cmp(b),
|
||||
(Self::Float(a), Self::Integer(b)) => a.partial_cmp(&(*b as f64)),
|
||||
(Self::String(a), Self::String(b)) => a.partial_cmp(b),
|
||||
(Self::String(_), Self::Integer(_) | Self::Float(_)) => {
|
||||
self.parse_number().partial_cmp(other)
|
||||
}
|
||||
(Self::Integer(_) | Self::Float(_), Self::String(_)) => {
|
||||
self.partial_cmp(&other.parse_number())
|
||||
}
|
||||
(Self::Array(a), Self::Array(b)) => a.partial_cmp(b),
|
||||
(Self::Array(_) | Self::String(_), _) => Ordering::Greater.into(),
|
||||
(_, Self::Array(_)) => Ordering::Less.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Variable {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap_or(Ordering::Greater)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Variable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Variable::String(v) => v.fmt(f),
|
||||
Variable::Integer(v) => v.fmt(f),
|
||||
Variable::Float(v) => v.fmt(f),
|
||||
Variable::Array(v) => {
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
v.fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Number {
|
||||
pub fn is_non_zero(&self) -> bool {
|
||||
match self {
|
||||
Number::Integer(n) => *n != 0,
|
||||
Number::Float(n) => *n != 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Number {
|
||||
fn default() -> Self {
|
||||
Number::Integer(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Number {
|
||||
#[inline(always)]
|
||||
fn from(b: bool) -> Self {
|
||||
Number::Integer(i64::from(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Number {
|
||||
#[inline(always)]
|
||||
fn from(n: i64) -> Self {
|
||||
Number::Integer(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Number {
|
||||
#[inline(always)]
|
||||
fn from(n: f64) -> Self {
|
||||
Number::Float(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Number {
|
||||
#[inline(always)]
|
||||
fn from(n: i32) -> Self {
|
||||
Number::Integer(n as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<&'x Constant> for Variable {
|
||||
fn from(value: &'x Constant) -> Self {
|
||||
match value {
|
||||
Constant::Integer(i) => Variable::Integer(*i),
|
||||
Constant::Float(f) => Variable::Float(*f),
|
||||
Constant::String(s) => Variable::String(s.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
|
||||
use crate::{
|
||||
compiler::{
|
||||
VariableType,
|
||||
grammar::expr::{
|
||||
BinaryOperator, Expression, Token, UnaryOperator, parser::ExpressionParser,
|
||||
tokenizer::Tokenizer,
|
||||
},
|
||||
},
|
||||
runtime::Variable,
|
||||
};
|
||||
|
||||
use evalexpr::*;
|
||||
|
||||
pub trait EvalExpression {
|
||||
fn eval(&self, variables: &HashMap<String, Variable>) -> Option<Variable>;
|
||||
}
|
||||
|
||||
impl EvalExpression for Vec<Expression> {
|
||||
fn eval(&self, variables: &HashMap<String, Variable>) -> Option<Variable> {
|
||||
let mut stack = Vec::with_capacity(self.len());
|
||||
let mut exprs = self.iter();
|
||||
|
||||
while let Some(expr) = exprs.next() {
|
||||
match expr {
|
||||
Expression::Variable(VariableType::Global(v)) => {
|
||||
stack.push(variables.get(v)?.clone());
|
||||
}
|
||||
Expression::Constant(val) => {
|
||||
stack.push(Variable::from(val));
|
||||
}
|
||||
Expression::UnaryOperator(op) => {
|
||||
let value = stack.pop()?;
|
||||
stack.push(match op {
|
||||
UnaryOperator::Not => value.op_not(),
|
||||
UnaryOperator::Minus => value.op_minus(),
|
||||
});
|
||||
}
|
||||
Expression::BinaryOperator(op) => {
|
||||
let right = stack.pop()?;
|
||||
let left = stack.pop()?;
|
||||
stack.push(match op {
|
||||
BinaryOperator::Add => left.op_add(right),
|
||||
BinaryOperator::Subtract => left.op_subtract(right),
|
||||
BinaryOperator::Multiply => left.op_multiply(right),
|
||||
BinaryOperator::Divide => left.op_divide(right),
|
||||
BinaryOperator::And => left.op_and(right),
|
||||
BinaryOperator::Or => left.op_or(right),
|
||||
BinaryOperator::Xor => left.op_xor(right),
|
||||
BinaryOperator::Eq => left.op_eq(right),
|
||||
BinaryOperator::Ne => left.op_ne(right),
|
||||
BinaryOperator::Lt => left.op_lt(right),
|
||||
BinaryOperator::Le => left.op_le(right),
|
||||
BinaryOperator::Gt => left.op_gt(right),
|
||||
BinaryOperator::Ge => left.op_ge(right),
|
||||
});
|
||||
}
|
||||
Expression::JmpIf { val, pos } => {
|
||||
if stack.last()?.to_bool() == *val {
|
||||
for _ in 0..*pos {
|
||||
exprs.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Invalid expression"),
|
||||
}
|
||||
}
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_expression() {
|
||||
let mut variables = HashMap::from_iter([
|
||||
("A".to_string(), Variable::Integer(0)),
|
||||
("B".to_string(), Variable::Integer(0)),
|
||||
("C".to_string(), Variable::Integer(0)),
|
||||
("D".to_string(), Variable::Integer(0)),
|
||||
("E".to_string(), Variable::Integer(0)),
|
||||
("F".to_string(), Variable::Integer(0)),
|
||||
("G".to_string(), Variable::Integer(0)),
|
||||
("H".to_string(), Variable::Integer(0)),
|
||||
("I".to_string(), Variable::Integer(0)),
|
||||
("J".to_string(), Variable::Integer(0)),
|
||||
]);
|
||||
let num_vars = variables.len();
|
||||
|
||||
for expr in [
|
||||
"A + B",
|
||||
"A * B",
|
||||
"A / B",
|
||||
"A - B",
|
||||
"-A",
|
||||
"A == B",
|
||||
"A != B",
|
||||
"A > B",
|
||||
"A < B",
|
||||
"A >= B",
|
||||
"A <= B",
|
||||
"A + B * C - D / E",
|
||||
"A + B + C - D - E",
|
||||
"(A + B) * (C - D) / E",
|
||||
"A - B + C * D / E * F - G",
|
||||
"A + B * C - D / E",
|
||||
"(A + B) * (C - D) / E",
|
||||
"A - B + C / D * E",
|
||||
"(A + B) / (C - D) + E",
|
||||
"A * (B + C) - D / E",
|
||||
"A / (B - C + D) * E",
|
||||
"(A + B) * C - D / (E + F)",
|
||||
"A * B - C + D / E",
|
||||
"A + B - C * D / E",
|
||||
"(A * B + C) / D - E",
|
||||
"A - B / C + D * E",
|
||||
"A + B * (C - D) / E",
|
||||
"A * B / C + (D - E)",
|
||||
"(A - B) * C / D + E",
|
||||
"A * (B / C) - D + E",
|
||||
"(A + B) / (C + D) * E",
|
||||
"A - B * C / D + E",
|
||||
"A + (B - C) * D / E",
|
||||
"(A + B) * (C / D) - E",
|
||||
"A - B / (C * D) + E",
|
||||
"(A + B) > (C - D) && E <= F",
|
||||
"A * B == C / D || E - F != G + H",
|
||||
"A / B >= C * D && E + F < G - H",
|
||||
"(A * B - C) != (D / E + F) && G > H",
|
||||
"A - B < C && D + E >= F * G",
|
||||
"(A * B) > C && (D / E) < F || G == H",
|
||||
"(A + B) <= (C - D) || E > F && G != H",
|
||||
"A * B != C + D || E - F == G / H",
|
||||
"A >= B * C && D < E - F || G != H + I",
|
||||
"(A / B + C) > D && E * F <= G - H",
|
||||
"A * (B - C) == D && E / F > G + H",
|
||||
"(A - B + C) != D || E * F >= G && H < I",
|
||||
"A < B / C && D + E * F == G - H",
|
||||
"(A + B * C) <= D && E > F / G",
|
||||
"(A * B - C) > D || E <= F + G && H != I",
|
||||
"A != B / C && D == E * F - G",
|
||||
"A <= B + C - D && E / F > G * H",
|
||||
"(A - B * C) < D || E >= F + G && H != I",
|
||||
"(A + B) / C == D && E - F < G * H",
|
||||
"A * B != C && D >= E + F / G || H < I",
|
||||
"!(A * B != C) && !(D >= E + F / G) || !(H < I)",
|
||||
"-A - B - (- C - D) - E - (-F)",
|
||||
] {
|
||||
println!("Testing {}", expr);
|
||||
for (pos, v) in variables.values_mut().enumerate() {
|
||||
*v = Variable::Integer(pos as i64 + 1);
|
||||
}
|
||||
|
||||
assert_expr(expr, &variables);
|
||||
|
||||
for (pos, v) in variables.values_mut().enumerate() {
|
||||
*v = Variable::Integer((num_vars - pos) as i64);
|
||||
}
|
||||
|
||||
assert_expr(expr, &variables);
|
||||
}
|
||||
|
||||
for expr in [
|
||||
"true && false",
|
||||
"!true || false",
|
||||
"true && !false",
|
||||
"!(true && false)",
|
||||
"true || true && false",
|
||||
"!false && (true || false)",
|
||||
"!(true || !false) && true",
|
||||
"!(!true && !false)",
|
||||
"true || false && !true",
|
||||
"!(true && true) || !false",
|
||||
"!(!true || !false) && (!false) && !(!true)",
|
||||
] {
|
||||
let pexp = parse_expression(expr.replace("true", "1").replace("false", "0").as_str());
|
||||
let result = pexp.eval(&HashMap::new()).unwrap();
|
||||
|
||||
//println!("{} => {:?}", expr, result);
|
||||
|
||||
match (eval(expr).expect(expr), result) {
|
||||
(Value::Float(a), Variable::Float(b)) if a == b => (),
|
||||
(Value::Float(a), Variable::Integer(b)) if a == b as f64 => (),
|
||||
(Value::Boolean(a), Variable::Integer(b)) if a == (b != 0) => (),
|
||||
(a, b) => {
|
||||
panic!("{} => {:?} != {:?}", expr, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_expr(expr: &str, variables: &HashMap<String, Variable>) {
|
||||
let e = parse_expression(expr);
|
||||
|
||||
let result = e.eval(variables).unwrap();
|
||||
|
||||
let mut str_expr = expr.to_string();
|
||||
let mut str_expr_float = expr.to_string();
|
||||
for (k, v) in variables {
|
||||
let v = v.to_string();
|
||||
|
||||
if v.contains('.') {
|
||||
str_expr_float = str_expr_float.replace(k, &v);
|
||||
} else {
|
||||
str_expr_float = str_expr_float.replace(k, &format!("{}.0", v));
|
||||
}
|
||||
str_expr = str_expr.replace(k, &v);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_expression(&str_expr)
|
||||
.eval(&HashMap::new())
|
||||
.unwrap()
|
||||
.to_number()
|
||||
.to_float(),
|
||||
result.to_number().to_float()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_expression(&str_expr_float)
|
||||
.eval(&HashMap::new())
|
||||
.unwrap()
|
||||
.to_number()
|
||||
.to_float(),
|
||||
result.to_number().to_float()
|
||||
);
|
||||
|
||||
//println!("{str_expr} ({e:?}) => {result:?}");
|
||||
|
||||
match (
|
||||
eval(&str_expr_float)
|
||||
.map(|v| {
|
||||
// Divisions by zero are converted to 0.0
|
||||
if matches!(&v, Value::Float(f) if f64::is_infinite(*f)) {
|
||||
Value::Float(0.0)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
})
|
||||
.expect(&str_expr),
|
||||
result,
|
||||
) {
|
||||
(Value::Float(a), Variable::Float(b)) if a == b => (),
|
||||
(Value::Float(a), Variable::Integer(b)) if a == b as f64 => (),
|
||||
(Value::Boolean(a), Variable::Integer(b)) if a == (b != 0) => (),
|
||||
(a, b) => {
|
||||
panic!("{} => {:?} != {:?}", str_expr, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expression(expr: &str) -> Vec<Expression> {
|
||||
ExpressionParser::from_tokenizer(Tokenizer::new(expr, |var_name: &str, _: bool| {
|
||||
Ok::<_, String>(Token::Variable(VariableType::Global(var_name.to_string())))
|
||||
}))
|
||||
.parse()
|
||||
.unwrap()
|
||||
.output
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user