using System.Globalization; using System.Text; namespace Oms.Application.Services.Orders.Issues; public static class ChineseFormulaEvaluator { private const string ContainsKeyword = "包含"; private const string HasValueKeyword = "有值"; private const string IsEmptyKeyword = "为空"; public static bool TryEvaluate(string? expression, OrderRuleEvaluationContext context, out bool result) { result = false; var text = expression?.Trim(); if (string.IsNullOrWhiteSpace(text)) { result = true; return true; } try { var parser = new Parser(text, context); result = parser.ParseExpression(); parser.ExpectEnd(); return true; } catch (FormulaParseException) { return false; } } private sealed class FormulaParseException(string message) : Exception(message); private enum TokenKind { Number, String, Label, And, Or, Not, Plus, Minus, Multiply, Divide, Greater, GreaterEqual, Less, LessEqual, Equal, NotEqual, LeftParen, RightParen, Contains, HasValue, IsEmpty, End } private readonly record struct Token(TokenKind Kind, string Text, decimal Number = 0); private sealed class Lexer(string source) { private int _pos; public Token Next() { SkipWhitespace(); if (_pos >= source.Length) { return new Token(TokenKind.End, string.Empty); } var ch = source[_pos]; if (ch is >= '0' and <= '9' || (ch == '.' && _pos + 1 < source.Length && source[_pos + 1] is >= '0' and <= '9')) { return ReadNumber(); } if (ch == '\'') { return ReadString(); } if (TryReadOperator(out var opToken)) { return opToken; } if (TryReadLabel(out var labelToken)) { return labelToken; } throw new FormulaParseException($"无法识别的字符:{source[_pos]}"); } public Token Peek() { var saved = _pos; var token = Next(); _pos = saved; return token; } private void SkipWhitespace() { while (_pos < source.Length && char.IsWhiteSpace(source[_pos])) { _pos++; } } private Token ReadNumber() { var start = _pos; while (_pos < source.Length && (char.IsDigit(source[_pos]) || source[_pos] == '.')) { _pos++; } var text = source[start.._pos]; if (!decimal.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) { throw new FormulaParseException($"无效数字:{text}"); } return new Token(TokenKind.Number, text, number); } private Token ReadString() { _pos++; var sb = new StringBuilder(); while (_pos < source.Length) { var ch = source[_pos++]; if (ch == '\'') { if (_pos < source.Length && source[_pos] == '\'') { sb.Append('\''); _pos++; continue; } return new Token(TokenKind.String, sb.ToString()); } sb.Append(ch); } throw new FormulaParseException("字符串未闭合"); } private bool TryReadOperator(out Token token) { token = default; if (_pos + 1 < source.Length) { var two = source.Substring(_pos, 2); token = two switch { "&&" => new Token(TokenKind.And, two), "||" => new Token(TokenKind.Or, two), ">=" => new Token(TokenKind.GreaterEqual, two), "<=" => new Token(TokenKind.LessEqual, two), "==" => new Token(TokenKind.Equal, two), "!=" => new Token(TokenKind.NotEqual, two), _ => default }; if (token.Kind != default) { _pos += 2; return true; } } var ch = source[_pos]; token = ch switch { '+' => new Token(TokenKind.Plus, "+"), '-' => new Token(TokenKind.Minus, "-"), '*' => new Token(TokenKind.Multiply, "*"), '/' => new Token(TokenKind.Divide, "/"), '>' => new Token(TokenKind.Greater, ">"), '<' => new Token(TokenKind.Less, "<"), '!' => new Token(TokenKind.Not, "!"), '(' => new Token(TokenKind.LeftParen, "("), ')' => new Token(TokenKind.RightParen, ")"), _ => default }; if (token.Kind == default) { return false; } _pos++; return true; } private bool TryReadLabel(out Token token) { token = default; foreach (var label in AutoTriggerFormulaMeta.LabelsByLengthDesc) { if (!source.AsSpan(_pos).StartsWith(label, StringComparison.Ordinal)) { continue; } var afterLabel = _pos + label.Length; _pos = afterLabel; SkipWhitespace(); if (_pos + ContainsKeyword.Length <= source.Length && source.AsSpan(_pos, ContainsKeyword.Length).SequenceEqual(ContainsKeyword.AsSpan())) { var afterContainsKeyword = _pos + ContainsKeyword.Length; _pos = afterContainsKeyword; SkipWhitespace(); if (_pos < source.Length && source[_pos] == '(') { _pos++; token = new Token(TokenKind.Contains, label); return true; } // 「包含」后无「(」则回退,继续尝试有值()/为空()/() _pos = afterLabel; SkipWhitespace(); } if (TryConsumeKeywordWithEmptyParens(HasValueKeyword)) { token = new Token(TokenKind.HasValue, label); return true; } if (TryConsumeKeywordWithEmptyParens(IsEmptyKeyword)) { token = new Token(TokenKind.IsEmpty, label); return true; } if (_pos + 2 <= source.Length && source.AsSpan(_pos, 2).SequenceEqual("()".AsSpan())) { _pos += 2; token = new Token(TokenKind.Label, label); return true; } _pos = afterLabel - label.Length; } return false; } /// 匹配「有值()」「为空()」这类无参关键字。 private bool TryConsumeKeywordWithEmptyParens(string keyword) { var saved = _pos; if (_pos + keyword.Length > source.Length || !source.AsSpan(_pos, keyword.Length).SequenceEqual(keyword.AsSpan())) { return false; } _pos += keyword.Length; SkipWhitespace(); if (_pos + 2 <= source.Length && source.AsSpan(_pos, 2).SequenceEqual("()".AsSpan())) { _pos += 2; return true; } _pos = saved; return false; } public int SavePosition() => _pos; public void RestorePosition(int pos) => _pos = pos; } private sealed class Parser { private readonly Lexer _lexer; private readonly OrderRuleEvaluationContext _context; private Token _current; public Parser(string source, OrderRuleEvaluationContext context) { _lexer = new Lexer(source); _context = context; _current = _lexer.Next(); } public bool ParseExpression() => ParseOr(); public void ExpectEnd() { if (_current.Kind != TokenKind.End) { throw new FormulaParseException($"公式未预期结束:{_current.Text}"); } } private bool ParseOr() { var value = ParseAnd(); while (_current.Kind == TokenKind.Or) { Advance(); value = value || ParseAnd(); } return value; } private bool ParseAnd() { var value = ParseNot(); while (_current.Kind == TokenKind.And) { Advance(); value = value && ParseNot(); } return value; } private bool ParseNot() { if (_current.Kind == TokenKind.Not) { Advance(); return !ParseNot(); } return ParseComparison(); } private bool ParseComparison() { if (_current.Kind is TokenKind.Contains) { return ParseContains(); } if (_current.Kind is TokenKind.HasValue or TokenKind.IsEmpty) { return ParsePresence(_current.Kind == TokenKind.HasValue); } var left = ParseAdditive(); if (_current.Kind is TokenKind.Greater or TokenKind.GreaterEqual or TokenKind.Less or TokenKind.LessEqual or TokenKind.Equal or TokenKind.NotEqual) { var op = _current.Kind; Advance(); var right = ParseAdditive(); return Compare(left, op, right); } return left != 0; } private bool ParseContains() { var label = _current.Text; Advance(); if (_current.Kind != TokenKind.String) { throw new FormulaParseException("包含函数缺少字符串参数"); } var needle = _current.Text; Advance(); if (_current.Kind != TokenKind.RightParen) { throw new FormulaParseException("包含函数缺少右括号"); } Advance(); if (!AutoTriggerFormulaMeta.TryResolve(label, out var fieldRef) || fieldRef == null) { throw new FormulaParseException($"未知字段:{label}"); } return OrderFormulaFieldResolver.StringContains(fieldRef, _context, needle); } private bool ParsePresence(bool expectHasValue) { var label = _current.Text; Advance(); if (!AutoTriggerFormulaMeta.TryResolve(label, out var fieldRef) || fieldRef == null) { throw new FormulaParseException($"未知字段:{label}"); } var hasValue = OrderFormulaFieldResolver.HasValue(fieldRef, _context); return expectHasValue ? hasValue : !hasValue; } private decimal ParseAdditive() { var value = ParseMultiplicative(); while (_current.Kind is TokenKind.Plus or TokenKind.Minus) { var op = _current.Kind; Advance(); var right = ParseMultiplicative(); value = op == TokenKind.Plus ? value + right : value - right; } return value; } private decimal ParseMultiplicative() { var value = ParseUnary(); while (_current.Kind is TokenKind.Multiply or TokenKind.Divide) { var op = _current.Kind; Advance(); var right = ParseUnary(); value = op == TokenKind.Multiply ? value * right : right == 0 ? 0 : value / right; } return value; } private decimal ParseUnary() { if (_current.Kind == TokenKind.Minus) { Advance(); return -ParseUnary(); } return ParsePrimary(); } private decimal ParsePrimary() { switch (_current.Kind) { case TokenKind.Number: { var number = _current.Number; Advance(); return number; } case TokenKind.Label: { var label = _current.Text; Advance(); if (!AutoTriggerFormulaMeta.TryResolve(label, out var fieldRef) || fieldRef == null) { throw new FormulaParseException($"未知字段:{label}"); } return OrderFormulaFieldResolver.GetNumericValue(fieldRef, _context); } case TokenKind.LeftParen: { Advance(); decimal value; if (LooksLikeBooleanGroup()) { var inside = ParseOr(); if (_current.Kind != TokenKind.RightParen) { throw new FormulaParseException("缺少右括号"); } Advance(); value = inside ? 1 : 0; } else { value = ParseAdditive(); if (_current.Kind != TokenKind.RightParen) { throw new FormulaParseException("缺少右括号"); } Advance(); } return value; } default: throw new FormulaParseException($"无法解析:{_current.Text}"); } } private static bool Compare(decimal left, TokenKind op, decimal right) => op switch { TokenKind.Greater => left > right, TokenKind.GreaterEqual => left >= right, TokenKind.Less => left < right, TokenKind.LessEqual => left <= right, TokenKind.Equal => left == right, TokenKind.NotEqual => left != right, _ => false }; private bool LooksLikeBooleanGroup() { var savedPos = _lexer.SavePosition(); var depth = 1; while (depth > 0) { var token = _lexer.Next(); if (token.Kind == TokenKind.End) { break; } if (token.Kind == TokenKind.LeftParen) { depth++; continue; } if (token.Kind == TokenKind.RightParen) { depth--; continue; } if (depth == 1 && token.Kind is TokenKind.And or TokenKind.Or) { _lexer.RestorePosition(savedPos); return true; } } _lexer.RestorePosition(savedPos); return false; } private void Advance() => _current = _lexer.Next(); } }