using System.Text.RegularExpressions; namespace Inspectron.Epson.Templates.Interpreter; public class ExpressionEvaluator { private static readonly Regex ComparisonRegex = new( @"^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$", RegexOptions.Compiled); public bool Evaluate(string expression, DataContext context) { expression = expression.Trim(); if (string.IsNullOrEmpty(expression)) { return false; } // Handle negation: !property if (expression.StartsWith("!")) { var inner = expression[1..].Trim(); return !Evaluate(inner, context); } // Handle comparison operators var match = ComparisonRegex.Match(expression); if (match.Success) { var left = match.Groups[1].Value.Trim(); var op = match.Groups[2].Value; var right = match.Groups[3].Value.Trim(); return EvaluateComparison(left, op, right, context); } // Handle logical AND: property1 && property2 if (expression.Contains("&&")) { var parts = expression.Split("&&", 2); return Evaluate(parts[0], context) && Evaluate(parts[1], context); } // Handle logical OR: property1 || property2 if (expression.Contains("||")) { var parts = expression.Split("||", 2); return Evaluate(parts[0], context) || Evaluate(parts[1], context); } // Simple truthy check return context.IsTruthy(expression); } private bool EvaluateComparison(string left, string op, string right, DataContext context) { var leftValue = ResolveValue(left, context); var rightValue = ResolveValue(right, context); // Handle null comparisons if (leftValue == null && rightValue == null) { return op == "==" || op == ">=" || op == "<="; } if (leftValue == null || rightValue == null) { return op switch { "==" => false, "!=" => true, _ => false }; } // Try numeric comparison first if (TryGetNumeric(leftValue, out var leftNum) && TryGetNumeric(rightValue, out var rightNum)) { return op switch { "==" => Math.Abs(leftNum - rightNum) < 0.0001, "!=" => Math.Abs(leftNum - rightNum) >= 0.0001, ">" => leftNum > rightNum, "<" => leftNum < rightNum, ">=" => leftNum >= rightNum, "<=" => leftNum <= rightNum, _ => false }; } // Fall back to string comparison var leftStr = leftValue.ToString() ?? ""; var rightStr = rightValue.ToString() ?? ""; return op switch { "==" => leftStr.Equals(rightStr, StringComparison.OrdinalIgnoreCase), "!=" => !leftStr.Equals(rightStr, StringComparison.OrdinalIgnoreCase), ">" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) > 0, "<" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) < 0, ">=" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) >= 0, "<=" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) <= 0, _ => false }; } private object? ResolveValue(string expression, DataContext context) { expression = expression.Trim(); // Check for quoted string literal if ((expression.StartsWith("\"") && expression.EndsWith("\"")) || (expression.StartsWith("'") && expression.EndsWith("'"))) { return expression[1..^1]; } // Check for numeric literal if (double.TryParse(expression, out var num)) { return num; } // Check for boolean literal if (expression.Equals("true", StringComparison.OrdinalIgnoreCase)) { return true; } if (expression.Equals("false", StringComparison.OrdinalIgnoreCase)) { return false; } // Check for null literal if (expression.Equals("null", StringComparison.OrdinalIgnoreCase)) { return null; } // Resolve as path return context.GetValue(expression); } private bool TryGetNumeric(object? value, out double result) { result = 0; if (value == null) return false; if (value is double d) { result = d; return true; } if (value is long l) { result = l; return true; } if (value is int i) { result = i; return true; } if (value is string s && double.TryParse(s, out result)) { return true; } return false; } }