mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-31 14:35:18 +00:00
Calculator - Human multiplication expressions (#24655)
* fixes #20187 * handles PR reviews - fix some typos - updated dev docs - added PR examples to tests - improve method naming style * Fix typo Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> --------- Co-authored-by: José Javier Rodríguez Zas <jj.jobs2live@outlook.com> Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
c72a6cb9d4
commit
cc708e7ac5
@@ -21,7 +21,12 @@ The Calculator plugin as the name suggests is used to perform calculations on th
|
||||
|
||||
### [`CalculateHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs)
|
||||
- The [`CalculateHelper.cs`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs) class checks to see if the user entered query is a valid input to the calculator and only if the input is valid does it perform the operation.
|
||||
- It does so by matching the user query to a valid regex.
|
||||
- It does so by matching the user query to a valid regex.
|
||||
- This class also handles some human multiplication expression like `2(1+2)` and `(2+3)(3+4)` in order to be computed by `Mages` lib.
|
||||
- It does so by matching some regex and inserting `'*'` where appropriate, e.g: `2(1+2) -> 2 * (1+2)`
|
||||
- It takes into account the combination of numbers (`num`), constants (`const`), functions (`func`) and expressions in parentheses (`(exp)`).
|
||||
- The blank spaces between them are also considered.
|
||||
- Some combinations were not handled as they are not common such as `'const num'` or `'func const'`
|
||||
|
||||
### [`CalculateEngine`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs)
|
||||
- The main computation is done in the [`CalculateEngine.cs`](src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs) file using the `Mages` library.
|
||||
|
@@ -37,8 +37,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("test")]
|
||||
[DataRow("pi(2)")] // Incorrect input, constant is being treated as a function.
|
||||
[DataRow("e(2)")]
|
||||
[DataRow("[10,10]")] // '[10,10]' is interpreted as array by mages engine
|
||||
public void Interpret_NoResult_WhenCalled(string input)
|
||||
{
|
||||
|
@@ -14,7 +14,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("=pi(9+)", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||
[DataRow("=pi(9)", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||
[DataRow("=pi,", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||
[DataRow("=log()", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||
[DataRow("=0xf0x6", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||
@@ -41,7 +40,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("pi(9+)")]
|
||||
[DataRow("pi(9)")]
|
||||
[DataRow("pi,")]
|
||||
[DataRow("log()")]
|
||||
[DataRow("0xf0x6")]
|
||||
@@ -113,5 +111,111 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
||||
Assert.AreEqual(result, "Copy this number to the clipboard");
|
||||
Assert.AreEqual(resultWithKeyword, "Copy this number to the clipboard");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("pie", "pi * e")]
|
||||
[DataRow("eln(100)", "e * ln(100)")]
|
||||
[DataRow("pi(1+1)", "pi * (1+1)")]
|
||||
[DataRow("2pi", "2 * pi")]
|
||||
[DataRow("2log10(100)", "2 * log10(100)")]
|
||||
[DataRow("2(3+4)", "2 * (3+4)")]
|
||||
[DataRow("sin(pi)cos(pi)", "sin(pi) * cos(pi)")]
|
||||
[DataRow("log10(100)(2+3)", "log10(100) * (2+3)")]
|
||||
[DataRow("(1+1)cos(pi)", "(1+1) * cos(pi)")]
|
||||
[DataRow("(1+1)(2+2)", "(1+1) * (2+2)")]
|
||||
[DataRow("2(1+1)", "2 * (1+1)")]
|
||||
[DataRow("pi(1+1)", "pi * (1+1)")]
|
||||
[DataRow("pilog(100)", "pi * log(100)")]
|
||||
[DataRow("3log(100)", "3 * log(100)")]
|
||||
[DataRow("2e", "2 * e")]
|
||||
[DataRow("(1+1)(3+2)", "(1+1) * (3+2)")]
|
||||
[DataRow("(1+1)cos(pi)", "(1+1) * cos(pi)")]
|
||||
[DataRow("sin(pi)cos(pi)", "sin(pi) * cos(pi)")]
|
||||
[DataRow("2 (1+1)", "2 * (1+1)")]
|
||||
[DataRow("pi (1+1)", "pi * (1+1)")]
|
||||
[DataRow("pi log(100)", "pi * log(100)")]
|
||||
[DataRow("3 log(100)", "3 * log(100)")]
|
||||
[DataRow("2 e", "2 * e")]
|
||||
[DataRow("(1+1) (3+2)", "(1+1) * (3+2)")]
|
||||
[DataRow("(1+1) cos(pi)", "(1+1) * cos(pi)")]
|
||||
[DataRow("sin (pi) cos(pi)", "sin (pi) * cos(pi)")]
|
||||
[DataRow("2picos(pi)(1+1)", "2 * pi * cos(pi) * (1+1)")]
|
||||
[DataRow("pilog(100)log(1000)", "pi * log(100) * log(1000)")]
|
||||
[DataRow("pipipie", "pi * pi * pi * e")]
|
||||
[DataRow("(1+1)(3+2)(1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")]
|
||||
[DataRow("(1+1) (3+2) (1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")]
|
||||
public void RightHumanMultiplicationExpressionTransformation(string typedString, string expectedQuery)
|
||||
{
|
||||
// Setup
|
||||
|
||||
// Act
|
||||
var result = CalculateHelper.FixHumanMultiplicationExpressions(typedString);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(expectedQuery, result);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("2(1+1)")]
|
||||
[DataRow("pi(1+1)")]
|
||||
[DataRow("pilog(100)")]
|
||||
[DataRow("3log(100)")]
|
||||
[DataRow("2e")]
|
||||
[DataRow("(1+1)(3+2)")]
|
||||
[DataRow("(1+1)cos(pi)")]
|
||||
[DataRow("sin(pi)cos(pi)")]
|
||||
[DataRow("2 (1+1)")]
|
||||
[DataRow("pi (1+1)")]
|
||||
[DataRow("pi log(100)")]
|
||||
[DataRow("3 log(100)")]
|
||||
[DataRow("2 e")]
|
||||
[DataRow("(1+1) (3+2)")]
|
||||
[DataRow("(1+1) cos(pi)")]
|
||||
[DataRow("sin (pi) cos(pi)")]
|
||||
[DataRow("2picos(pi)(1+1)")]
|
||||
[DataRow("pilog(100)log(1000)")]
|
||||
[DataRow("pipipie")]
|
||||
[DataRow("(1+1)(3+2)(1+1)(1+1)")]
|
||||
[DataRow("(1+1) (3+2) (1+1)(1+1)")]
|
||||
public void NoErrorForHumanMultiplicationExpressions(string typedString)
|
||||
{
|
||||
// Setup
|
||||
Mock<Main> main = new();
|
||||
Query expectedQuery = new(typedString);
|
||||
Query expectedQueryWithKeyword = new("=" + typedString, "=");
|
||||
|
||||
// Act
|
||||
var result = main.Object.Query(expectedQuery).FirstOrDefault()?.SubTitle;
|
||||
var resultWithKeyword = main.Object.Query(expectedQueryWithKeyword).FirstOrDefault()?.SubTitle;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Copy this number to the clipboard", result);
|
||||
Assert.AreEqual("Copy this number to the clipboard", resultWithKeyword);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("2(1+1)", "4")]
|
||||
[DataRow("pi(1+1)", "6.2831853072")]
|
||||
[DataRow("pilog(100)", "6.2831853072")]
|
||||
[DataRow("3log(100)", "6")]
|
||||
[DataRow("2e", "5.4365636569")]
|
||||
[DataRow("(1+1)(3+2)", "10")]
|
||||
[DataRow("(1+1)cos(pi)", "-2")]
|
||||
[DataRow("log(100)cos(pi)", "-2")]
|
||||
public void RightAnswerForHumanMultiplicationExpressions(string typedString, string answer)
|
||||
{
|
||||
// Setup
|
||||
Mock<Main> main = new();
|
||||
Query expectedQuery = new(typedString);
|
||||
Query expectedQueryWithKeyword = new("=" + typedString, "=");
|
||||
|
||||
// Act
|
||||
var result = main.Object.Query(expectedQuery).FirstOrDefault()?.Title;
|
||||
var resultWithKeyword = main.Object.Query(expectedQueryWithKeyword).FirstOrDefault()?.Title;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(answer, result);
|
||||
Assert.AreEqual(answer, resultWithKeyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,6 +49,8 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator
|
||||
Replace("log(", "log10(", true, CultureInfo.CurrentCulture).
|
||||
Replace("ln(", "log(", true, CultureInfo.CurrentCulture);
|
||||
|
||||
input = CalculateHelper.FixHumanMultiplicationExpressions(input);
|
||||
|
||||
var result = _magesEngine.Interpret(input);
|
||||
|
||||
// This could happen for some incorrect queries, like pi(2)
|
||||
|
@@ -48,5 +48,141 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string FixHumanMultiplicationExpressions(string input)
|
||||
{
|
||||
var output = CheckNumberOrConstantThenParenthesisExpr(input);
|
||||
output = CheckNumberOrConstantThenFunc(output);
|
||||
output = CheckParenthesisExprThenFunc(output);
|
||||
output = CheckParenthesisExprThenParenthesisExpr(output);
|
||||
output = CheckNumberThenConstant(output);
|
||||
output = CheckConstantThenConstant(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user