From 110f571b40840e798237c304216b2435d9845796 Mon Sep 17 00:00:00 2001 From: dcog989 Date: Sat, 13 Sep 2025 13:17:08 +0100 Subject: [PATCH] Rework solution for nested Mages Previous solution missed e.g. `pow(min(2,3), 4)` --- .../Flow.Launcher.Plugin.Calculator/Main.cs | 89 +++++++++++++++---- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index c81eb9b1c..99f0f8395 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -16,6 +16,8 @@ namespace Flow.Launcher.Plugin.Calculator private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets(); private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex(); private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex(); + private static readonly Regex PowRegex = new(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft); + private static Engine MagesEngine; private const string Comma = ","; @@ -43,11 +45,23 @@ namespace Flow.Launcher.Plugin.Calculator public List Query(Query query) { - if (!CanCalculate(query)) + if (string.IsNullOrWhiteSpace(query.Search)) { return new List(); } + if (!IsBracketComplete(query.Search)) + { + return new List + { + new Result + { + Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(), + IcoPath = "Images/calculator.png" + } + }; + } + try { var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value)); @@ -60,7 +74,7 @@ namespace Flow.Launcher.Plugin.Calculator do { previous = expression; - expression = Regex.Replace(previous, @"\bpow\s*\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)", "($1^$2)"); + expression = PowRegex.Replace(previous, PowMatchEvaluator); } while (previous != expression); // WORKAROUND END @@ -131,6 +145,57 @@ namespace Flow.Launcher.Plugin.Calculator return new List(); } + private static string PowMatchEvaluator(Match m) + { + // m.Groups[1].Value will be `(...)` with parens + var contentWithParen = m.Groups[1].Value; + // remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4` + var argsContent = contentWithParen.Substring(1, contentWithParen.Length - 2); + + var bracketCount = 0; + var splitIndex = -1; + + // Find the top-level comma that separates the two arguments of pow. + for (var i = 0; i < argsContent.Length; i++) + { + switch (argsContent[i]) + { + case '(': + case '[': + bracketCount++; + break; + case ')': + case ']': + bracketCount--; + break; + case ',' when bracketCount == 0: + splitIndex = i; + break; + } + + if (splitIndex != -1) + break; + } + + if (splitIndex == -1) + { + // This indicates malformed arguments for pow, e.g., pow(5) or pow(). + // Return original string to let Mages handle the error. + return m.Value; + } + + var arg1 = argsContent.Substring(0, splitIndex).Trim(); + var arg2 = argsContent.Substring(splitIndex + 1).Trim(); + + // Check for empty arguments which can happen with stray commas, e.g., pow(,5) + if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2)) + { + return m.Value; + } + + return $"({arg1}^{arg2})"; + } + /// /// Parses a string representation of a number using the system's current culture. /// @@ -208,21 +273,6 @@ namespace Flow.Launcher.Plugin.Calculator return decimalSeparator == Dot ? Comma : Dot; } - private bool CanCalculate(Query query) - { - if (string.IsNullOrWhiteSpace(query.Search)) - { - return false; - } - - if (!IsBracketComplete(query.Search)) - { - return false; - } - - return true; - } - private string GetDecimalSeparator() { string systemDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; @@ -249,6 +299,11 @@ namespace Flow.Launcher.Plugin.Calculator { leftBracketCount--; } + + if (leftBracketCount < 0) + { + return false; + } } return leftBracketCount == 0;