mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
* Parse scientific notation properly
After adding logic to replace `e` as a mathematical constant, there are bugs
when trying to use expressions like `5e3`. This change parses the
`<number>e<number>` format into expanded form to prevent replacement with
the constant.
Regex explanation: `(\d+\.*\d*)[eE](-*\d+)
`(\d+\.*\d*)`: Match any number of digits, followed by an optional `.` and
more optional digits. The expression is used to capture things
like: `5.0`, `1.`, `1` before the `e`
`[eE]`: Match either upper or lowercase `e`
`(-*\d+)`: Capture an optional `-` sign as well as a number of digits
* Update regex to be more tolerant of weird entries
The new regex captures a wider variety of numbers. See this post for details
on the regex used:
https://stackoverflow.com/a/23872060
* Fix regular expression failing unit tests
Using `[` didn't capture the expression properly. Had to use `(` instead.
* Allow only for uppercase E
* Only allow one decimal in second grouping
* Support various decimal separator types
Previous regular expression did not allow decimal separators that were not ".".
The new expression uses the culture info decimal separator instead, which
should work better.
* Only allow integers after `E`
* Remove single use variable
* Update regex to only accept integers after `E`
Missed this expression in my last update. Whoops
* Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs
* Update NumberTranslator to parse hex as a whole
* Remove `hexRegex` as object member
The hex regex stuff is only used once, there's no need to keep track of it in
the object's state. Just create it during the translation process.
* Add unit tests for sci notation in other cultures
---------
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
209 lines
6.9 KiB
C#
209 lines
6.9 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Microsoft.PowerToys.Run.Plugin.Calculator
|
|
{
|
|
public static class CalculateHelper
|
|
{
|
|
private static readonly Regex RegValidExpressChar = new Regex(
|
|
@"^(" +
|
|
@"%|" +
|
|
@"ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|" +
|
|
@"factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(|" +
|
|
@"sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|" +
|
|
@"sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|" +
|
|
@"pi|" +
|
|
@"==|~=|&&|\|\||" +
|
|
@"((-?(\d+(\.\d*)?)|-?(\.\d+))[E](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */
|
|
@"e|[0-9]|0x[0-9a-fA-F]+|0b[01]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
|
@")+$",
|
|
RegexOptions.Compiled);
|
|
|
|
public static bool InputValid(string input)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(input))
|
|
{
|
|
throw new ArgumentNullException(paramName: nameof(input));
|
|
}
|
|
|
|
if (!RegValidExpressChar.IsMatch(input))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!BracketHelper.IsBracketComplete(input))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the input ends with a binary operator then it is not a valid input to mages and the Interpret function would throw an exception. Because we expect here that the user has not finished typing we block those inputs.
|
|
string trimmedInput = input.TrimEnd();
|
|
if (trimmedInput.EndsWith('+') || trimmedInput.EndsWith('-') || trimmedInput.EndsWith('*') || trimmedInput.EndsWith('|') || trimmedInput.EndsWith('\\') || trimmedInput.EndsWith('^') || trimmedInput.EndsWith('=') || trimmedInput.EndsWith('&') || trimmedInput.EndsWith('/') || trimmedInput.EndsWith('%'))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static string FixHumanMultiplicationExpressions(string input)
|
|
{
|
|
var output = CheckScientificNotation(input);
|
|
output = CheckNumberOrConstantThenParenthesisExpr(output);
|
|
output = CheckNumberOrConstantThenFunc(output);
|
|
output = CheckParenthesisExprThenFunc(output);
|
|
output = CheckParenthesisExprThenParenthesisExpr(output);
|
|
output = CheckNumberThenConstant(output);
|
|
output = CheckConstantThenConstant(output);
|
|
return output;
|
|
}
|
|
|
|
private static string CheckScientificNotation(string input)
|
|
{
|
|
/**
|
|
* NOTE: By the time the expression gets to us, it's already in English format.
|
|
*
|
|
* Regex explanation:
|
|
* (-?(\d+({0}\d*)?)|-?({0}\d+)): Used to capture one of two types:
|
|
* -?(\d+({0}\d*)?): Captures a decimal number starting with a number (e.g. "-1.23")
|
|
* -?({0}\d+): Captures a decimal number without leading number (e.g. ".23")
|
|
* E: Captures capital 'E'
|
|
* (-?\d+): Captures an integer number (e.g. "-1" or "23")
|
|
*/
|
|
var p = @"(-?(\d+(\.\d*)?)|-?(\.\d+))E(-?\d+)";
|
|
return Regex.Replace(input, p, "($1 * 10^($5))");
|
|
}
|
|
|
|
/*
|
|
* num (exp)
|
|
* const (exp)
|
|
*/
|
|
private static string CheckNumberOrConstantThenParenthesisExpr(string input)
|
|
{
|
|
var output = input;
|
|
do
|
|
{
|
|
input = output;
|
|
output = Regex.Replace(input, @"(\d+|pi|e)\s*(\()", m =>
|
|
{
|
|
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
|
{
|
|
return m.Value;
|
|
}
|
|
|
|
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
|
});
|
|
}
|
|
while (output != input);
|
|
|
|
return output;
|
|
}
|
|
|
|
/*
|
|
* num func
|
|
* const func
|
|
*/
|
|
private static string CheckNumberOrConstantThenFunc(string input)
|
|
{
|
|
var output = input;
|
|
do
|
|
{
|
|
input = output;
|
|
output = Regex.Replace(input, @"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()", m =>
|
|
{
|
|
if (input[m.Index] == 'e' && input[m.Index + 1] == 'x' && input[m.Index + 2] == 'p')
|
|
{
|
|
return m.Value;
|
|
}
|
|
|
|
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
|
{
|
|
return m.Value;
|
|
}
|
|
|
|
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
|
});
|
|
}
|
|
while (output != input);
|
|
|
|
return output;
|
|
}
|
|
|
|
/*
|
|
* (exp) func
|
|
* func func
|
|
*/
|
|
private static string CheckParenthesisExprThenFunc(string input)
|
|
{
|
|
var p = @"(\))\s*([a-zA-Z]+[0-9]*\s*\()";
|
|
var r = "$1 * $2";
|
|
return Regex.Replace(input, p, r);
|
|
}
|
|
|
|
/*
|
|
* (exp) (exp)
|
|
* func (exp)
|
|
*/
|
|
private static string CheckParenthesisExprThenParenthesisExpr(string input)
|
|
{
|
|
var p = @"(\))\s*(\()";
|
|
var r = "$1 * $2";
|
|
return Regex.Replace(input, p, r);
|
|
}
|
|
|
|
/*
|
|
* num const
|
|
*/
|
|
private static string CheckNumberThenConstant(string input)
|
|
{
|
|
var output = input;
|
|
do
|
|
{
|
|
input = output;
|
|
output = Regex.Replace(input, @"(\d+)\s*(pi|e)", m =>
|
|
{
|
|
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
|
{
|
|
return m.Value;
|
|
}
|
|
|
|
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
|
});
|
|
}
|
|
while (output != input);
|
|
|
|
return output;
|
|
}
|
|
|
|
/*
|
|
* const const
|
|
*/
|
|
private static string CheckConstantThenConstant(string input)
|
|
{
|
|
var output = input;
|
|
do
|
|
{
|
|
input = output;
|
|
output = Regex.Replace(input, @"(pi|e)\s*(pi|e)", m =>
|
|
{
|
|
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
|
{
|
|
return m.Value;
|
|
}
|
|
|
|
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
|
});
|
|
}
|
|
while (output != input);
|
|
|
|
return output;
|
|
}
|
|
}
|
|
}
|