mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 19:57:57 +01: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)
|
### [`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.
|
- 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)
|
### [`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.
|
- 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]
|
[DataTestMethod]
|
||||||
[DataRow("test")]
|
[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
|
[DataRow("[10,10]")] // '[10,10]' is interpreted as array by mages engine
|
||||||
public void Interpret_NoResult_WhenCalled(string input)
|
public void Interpret_NoResult_WhenCalled(string input)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
|||||||
{
|
{
|
||||||
[DataTestMethod]
|
[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(9)", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
|
||||||
[DataRow("=pi,", "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("=log()", "Expression wrong or incomplete (Did you forget some parentheses?)")]
|
||||||
[DataRow("=0xf0x6", "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]
|
[DataTestMethod]
|
||||||
[DataRow("pi(9+)")]
|
[DataRow("pi(9+)")]
|
||||||
[DataRow("pi(9)")]
|
|
||||||
[DataRow("pi,")]
|
[DataRow("pi,")]
|
||||||
[DataRow("log()")]
|
[DataRow("log()")]
|
||||||
[DataRow("0xf0x6")]
|
[DataRow("0xf0x6")]
|
||||||
@@ -113,5 +111,111 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
|
|||||||
Assert.AreEqual(result, "Copy this number to the clipboard");
|
Assert.AreEqual(result, "Copy this number to the clipboard");
|
||||||
Assert.AreEqual(resultWithKeyword, "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("log(", "log10(", true, CultureInfo.CurrentCulture).
|
||||||
Replace("ln(", "log(", true, CultureInfo.CurrentCulture);
|
Replace("ln(", "log(", true, CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
|
input = CalculateHelper.FixHumanMultiplicationExpressions(input);
|
||||||
|
|
||||||
var result = _magesEngine.Interpret(input);
|
var result = _magesEngine.Interpret(input);
|
||||||
|
|
||||||
// This could happen for some incorrect queries, like pi(2)
|
// This could happen for some incorrect queries, like pi(2)
|
||||||
|
|||||||
@@ -48,5 +48,141 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator
|
|||||||
|
|
||||||
return true;
|
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