From 3e9e70b32e8b1f77c81bd8652d9e53a3882d7297 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 9 Aug 2026 19:04:19 +0800 Subject: feat(rule_solver): support `!=` comparison and reject trailing tokens Add `!=` operator support in rule expressions and ensure the entire expression is consumed during parsing, invalidating any trailing garbage tokens that would previously be silently ignored. --- mingling_cli/src/proj_mgr/rule_solver.rs | 41 ++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) (limited to 'mingling_cli') diff --git a/mingling_cli/src/proj_mgr/rule_solver.rs b/mingling_cli/src/proj_mgr/rule_solver.rs index 7dfb7c7..c33cc1e 100644 --- a/mingling_cli/src/proj_mgr/rule_solver.rs +++ b/mingling_cli/src/proj_mgr/rule_solver.rs @@ -241,7 +241,15 @@ pub fn validate_mutexes( /// - parentheses for grouping pub fn eval_rule(rule: &str, answers: &HashMap) -> bool { let mut parser = RuleParser::new(rule, answers); - parser.parse_or().unwrap_or(false) + let Some(value) = parser.parse_or() else { + return false; + }; + // The whole expression must be consumed; trailing garbage invalidates it. + parser.skip_ws(); + if parser.pos != parser.chars.len() { + return false; + } + value } /// A key is truthy when it is present with a non-empty, non-`"false"` value. @@ -310,7 +318,7 @@ impl<'a> RuleParser<'a> { self.parse_primary() } - /// `primary := '(' or ')' | ident ('==' ident)?` + /// `primary := '(' or ')' | ident (('==' | '!=') ident)?` fn parse_primary(&mut self) -> Option { if self.eat('(') { let value = self.parse_or()?; @@ -322,6 +330,10 @@ impl<'a> RuleParser<'a> { let other = self.parse_ident()?; return Some(self.answers.get(&ident).map(String::as_str) == Some(other.as_str())); } + if self.eat('!') && self.eat('=') { + let other = self.parse_ident()?; + return Some(self.answers.get(&ident).map(String::as_str) != Some(other.as_str())); + } Some(is_truthy(&ident, self.answers)) } @@ -585,4 +597,29 @@ name = "tokio" assert!(!eval_rule("parser == clap", &answers)); assert!(eval_rule("use_parser && parser == picker", &answers)); } + + #[test] + fn eval_not_equal_comparison() { + let mut answers = HashMap::new(); + answers.insert("parser".into(), "picker".into()); + answers.insert("use_parser".into(), "true".into()); + + assert!(!eval_rule("parser != picker", &answers)); + assert!(eval_rule("parser != clap", &answers)); + + // The template's NOT_PARSER_PICKER rule. + assert!(!eval_rule("!use_parser || parser != picker", &answers)); + answers.remove("use_parser"); + assert!(eval_rule("!use_parser || parser != picker", &answers)); + } + + #[test] + fn eval_rejects_trailing_garbage() { + // Unsupported tokens must invalidate the expression instead of being + // silently ignored. + let mut answers = HashMap::new(); + answers.insert("parser".into(), "picker".into()); + assert!(!eval_rule("parser >>> picker", &answers)); + assert!(!eval_rule("parser ||", &answers)); + } } -- cgit