This commit is contained in:
sxyazi 2026-05-09 22:26:10 +08:00
parent f8705f00d3
commit afff0af516
No known key found for this signature in database

View file

@ -1,4 +1,4 @@
use std::str::FromStr; use std::{cmp::Ordering, str::FromStr};
use anyhow::bail; use anyhow::bail;
use serde_with::DeserializeFromStr; use serde_with::DeserializeFromStr;
@ -33,8 +33,7 @@ impl ConditionOp {
} }
} }
#[inline] fn prec(&self) -> u8 {
pub fn prec(&self) -> u8 {
match self { match self {
Self::Or => 1, Self::Or => 1,
Self::And => 2, Self::And => 2,
@ -44,6 +43,18 @@ impl ConditionOp {
} }
} }
impl PartialOrd for ConditionOp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use Ordering::*;
match self.prec().cmp(&other.prec()) {
// Keep repeated `!` right-associative by making `! >= !` false.
Equal if matches!((self, other), (Self::Not, Self::Not)) => None,
ordering => Some(ordering),
}
}
}
#[derive(Debug, DeserializeFromStr)] #[derive(Debug, DeserializeFromStr)]
pub struct Condition { pub struct Condition {
ops: Vec<ConditionOp>, ops: Vec<ConditionOp>,
@ -72,10 +83,7 @@ impl Condition {
let op = ConditionOp::new(token); let op = ConditionOp::new(token);
match op { match op {
ConditionOp::Or | ConditionOp::And | ConditionOp::Not => { ConditionOp::Or | ConditionOp::And | ConditionOp::Not => {
while matches!( while matches!(stack.last(), Some(last) if last >= &op) {
stack.last(),
Some(last) if last.prec() > op.prec() || (op != ConditionOp::Not && last.prec() == op.prec())
) {
output.push(stack.pop().unwrap()); output.push(stack.pop().unwrap());
} }
stack.push(op); stack.push(op);
@ -139,14 +147,14 @@ mod tests {
#[test] #[test]
fn test_condition_not() -> anyhow::Result<()> { fn test_condition_not() -> anyhow::Result<()> {
let cond: Condition = "!dir".parse()?;
assert!(!cond.eval(|s| s == "dir").unwrap());
assert!(cond.eval(|_| false).unwrap());
let cond: Condition = "!!dir".parse()?; let cond: Condition = "!!dir".parse()?;
assert!(cond.eval(|s| s == "dir").unwrap()); assert!(cond.eval(|s| s == "dir").unwrap());
assert!(!cond.eval(|_| false).unwrap()); assert!(!cond.eval(|_| false).unwrap());
let cond: Condition = "!!!dir".parse()?;
assert!(!cond.eval(|s| s == "dir").unwrap());
assert!(cond.eval(|_| false).unwrap());
Ok(()) Ok(())
} }
} }