diff --git a/README.md b/README.md index 86cc690..f513c4c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # NCalc -![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ncalc/ncalc/dotnet.yml) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ncalc/ncalc/build-test.yml) [![Coverage](https://img.shields.io/codecov/c/github/ncalc/ncalc.svg)](https://codecov.io/gh/ncalc/ncalc) [![NuGet](https://img.shields.io/nuget/v/NCalcSync.signed.svg?label=nuget&color=004880 )](https://nuget.org/packages/NCalcSync.signed) +![NuGet Downloads](https://img.shields.io/nuget/dt/NCalcSync.svg?color=004880) [![Discord](https://img.shields.io/discord/1237181265426387005?color=5b62ef&label=discord )](https://discord.gg/TeJkmXbqFk) @@ -165,9 +166,9 @@ A TypeScript/JavaScript port of NCalc. NCalc 101 is a simple web application that allows you to try out the NCalc expression evaluator, developed by [Panoramic Data](https://github.com/panoramicdata). -### [JJMasterData.NCalc](https://md.jjconsulting.tech/articles/plugins/ncalc.html) +### [JJMasterData](https://github.com/JJConsulting/JJMasterData/) -Plugin of NCalc used to evaluate [JJMasterData](https://github.com/jjconsulting/jjmasterdata) expressions. JJMasterData is a runtime form generator from database metadata. +JJMasterData is a runtime form generator from database metadata. It uses NCalc to evaluate expressions used in field visibility and other dynamic behaviors. # NCalc versioning diff --git a/src/NCalc.Async/AsyncExpression.cs b/src/NCalc.Async/AsyncExpression.cs index e733ba9..b4a245a 100644 --- a/src/NCalc.Async/AsyncExpression.cs +++ b/src/NCalc.Async/AsyncExpression.cs @@ -260,12 +260,28 @@ public bool HasErrors() } /// - /// Returns a list with all parameters names from the expression. + /// Returns a list with all parameter names from the expression. /// - public List GetParametersNames() + public List GetParameterNames() { var parameterExtractionVisitor = new ParameterExtractionVisitor(); LogicalExpression ??= LogicalExpressionFactory.Create(ExpressionString!, Context.Options); return LogicalExpression.Accept(parameterExtractionVisitor); } + + [Obsolete("Please use GetParameterNames (correct english spelling).")] + public List GetParametersNames() + { + return GetParameterNames(); + } + + /// + /// Returns a list with all function names from the expression. + /// + public List GetFunctionNames() + { + var functionExtractionVisitor = new FunctionExtractionVisitor(); + LogicalExpression ??= LogicalExpressionFactory.Create(ExpressionString!, Context.Options); + return LogicalExpression.Accept(functionExtractionVisitor); + } } \ No newline at end of file diff --git a/src/NCalc.Core/Visitors/FunctionExtractionVisitor.cs b/src/NCalc.Core/Visitors/FunctionExtractionVisitor.cs new file mode 100644 index 0000000..4e5eea7 --- /dev/null +++ b/src/NCalc.Core/Visitors/FunctionExtractionVisitor.cs @@ -0,0 +1,63 @@ +using NCalc.Domain; + +namespace NCalc.Visitors; + +/// +/// Visitor dedicated to extract names from a . +/// +public sealed class FunctionExtractionVisitor : ILogicalExpressionVisitor> +{ + public List Visit(Identifier identifier) => []; + + public List Visit(LogicalExpressionList list) + { + var functions = new List(); + foreach (var value in list) + { + if (value is Function function) + { + if (!functions.Contains(function.Identifier.Name)) + { + functions.Add(function.Identifier.Name); + } + } + } + return functions; + } + + public List Visit(UnaryExpression expression) => expression.Expression.Accept(this); + + public List Visit(BinaryExpression expression) + { + var leftParameters = expression.LeftExpression.Accept(this); + var rightParameters = expression.RightExpression.Accept(this); + + leftParameters.AddRange(rightParameters); + return leftParameters.Distinct().ToList(); + } + + public List Visit(TernaryExpression expression) + { + var leftParameters = expression.LeftExpression.Accept(this); + var middleParameters = expression.MiddleExpression.Accept(this); + var rightParameters = expression.RightExpression.Accept(this); + + leftParameters.AddRange(middleParameters); + leftParameters.AddRange(rightParameters); + return leftParameters.Distinct().ToList(); + } + + public List Visit(Function function) + { + var functions = new List { function.Identifier.Name }; + + foreach (var expression in function.Expressions) + { + var exprParameters = expression.Accept(this); + functions.AddRange(exprParameters); + } + return functions.Distinct().ToList(); + } + + public List Visit(ValueExpression expression) => []; +} \ No newline at end of file diff --git a/src/NCalc.Core/Visitors/ParameterExtractionVisitor.cs b/src/NCalc.Core/Visitors/ParameterExtractionVisitor.cs index da4ba31..0ac8e19 100644 --- a/src/NCalc.Core/Visitors/ParameterExtractionVisitor.cs +++ b/src/NCalc.Core/Visitors/ParameterExtractionVisitor.cs @@ -3,7 +3,7 @@ namespace NCalc.Visitors; /// -/// Visitor dedicated to extract parameters from a . +/// Visitor dedicated to extract names from a . /// public sealed class ParameterExtractionVisitor : ILogicalExpressionVisitor> { @@ -22,12 +22,12 @@ public List Visit(LogicalExpressionList list) var parameters = new List(); foreach (var value in list) { - if (value is not Identifier identifier) - continue; - - if (!parameters.Contains(identifier.Name)) + if (value is Identifier identifier) { - parameters.Add(identifier.Name); + if (!parameters.Contains(identifier.Name)) + { + parameters.Add(identifier.Name); + } } } return parameters; diff --git a/src/NCalc.Core/Visitors/SerializationVisitor.cs b/src/NCalc.Core/Visitors/SerializationVisitor.cs index c9c37a6..998de23 100644 --- a/src/NCalc.Core/Visitors/SerializationVisitor.cs +++ b/src/NCalc.Core/Visitors/SerializationVisitor.cs @@ -4,7 +4,7 @@ namespace NCalc.Visitors; /// -/// Class responsible to converting an into a representation. +/// Class responsible to converting a into a representation. /// public class SerializationVisitor : ILogicalExpressionVisitor { diff --git a/src/NCalc.Sync/Expression.cs b/src/NCalc.Sync/Expression.cs index 95efe8e..9a17f1f 100644 --- a/src/NCalc.Sync/Expression.cs +++ b/src/NCalc.Sync/Expression.cs @@ -254,12 +254,28 @@ public bool HasErrors() } /// - /// Returns a list with all parameters names from the expression. + /// Returns a list with all parameter names from the expression. /// - public List GetParametersNames() + public List GetParameterNames() { var parameterExtractionVisitor = new ParameterExtractionVisitor(); LogicalExpression ??= LogicalExpressionFactory.Create(ExpressionString!, Context.Options); return LogicalExpression.Accept(parameterExtractionVisitor); } + + [Obsolete("Please use GetParameterNames (correct english spelling).")] + public List GetParametersNames() + { + return GetParameterNames(); + } + + /// + /// Returns a list with all function names from the expression. + /// + public List GetFunctionNames() + { + var functionExtractionVisitor = new FunctionExtractionVisitor(); + LogicalExpression ??= LogicalExpressionFactory.Create(ExpressionString!, Context.Options); + return LogicalExpression.Accept(functionExtractionVisitor); + } } \ No newline at end of file diff --git a/test/NCalc.Tests/ExtractionTests.cs b/test/NCalc.Tests/ExtractionTests.cs new file mode 100644 index 0000000..353ed57 --- /dev/null +++ b/test/NCalc.Tests/ExtractionTests.cs @@ -0,0 +1,65 @@ +namespace NCalc.Tests; + +[Trait("Category","Extraction")] +public class ExtractionTests +{ + [Fact] + public void ShouldGetParametersIssue103() + { + var expression = new Expression("PageState == 'LIST' && a == 1 && customFunction() == true || in(1 + 1, 1, 2, 3)", ExpressionOptions.CaseInsensitiveStringComparer) + { + Parameters = + { + ["a"] = 1 + } + }; + expression.DynamicParameters["PageState"] = _ => "List"; + expression.Functions["customfunction"] = _ => true; + + var parameters = expression.GetParameterNames(); + Assert.Contains("a", parameters); + Assert.Contains("PageState", parameters); + Assert.Equal(2, parameters.Count); + } + + [Fact] + public void ShouldGetParametersOneTimeIssue141() + { + var expression = + new Expression("if(x=0,x,y)", + ExpressionOptions.CaseInsensitiveStringComparer); + var parameters = expression.GetParameterNames(); + + Assert.Equal(2,parameters.Count); + } + + [Fact] + public void ShouldGetParametersWithUnary() + { + var expression = new Expression("-0.68"); + var p = expression.GetParameterNames(); + Assert.Empty(p); + } + + [Theory] + [InlineData("(a, b, c)", 3)] + [InlineData("725 - 1 == result * secret_operation(secretValue)", 2)] + [InlineData("getLightsaberColor(selectedJedi) == selectedColor", 2)] + public void ShouldGetParameters(string formula, int expectedCount) + { + var expression = new Expression(formula); + var p = expression.GetParameterNames(); + Assert.Equal(expectedCount, p.Count); + } + + [InlineData("(a, drop_database(), c) == toUpper(getName())", 3)] + [InlineData("Abs(523/2) == Abs(523/2)", 1)] + [InlineData("getLightsaberColor('Yoda') == selectedColor", 1)] + [Theory] + public void ShouldGetFunctions(string formula, int expectedCount) + { + var expression = new Expression(formula); + var functions = expression.GetFunctionNames(); + Assert.Equal(expectedCount, functions.Count); + } +} \ No newline at end of file diff --git a/test/NCalc.Tests/ParameterExtractionTests.cs b/test/NCalc.Tests/ParameterExtractionTests.cs deleted file mode 100644 index c2429e5..0000000 --- a/test/NCalc.Tests/ParameterExtractionTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NCalc.Tests; - -[Trait("Category","Parameter Extraction")] -public class ParameterExtractionTests -{ - [Fact] - public void Should_Get_Parameters_Issue_103() - { - var expression = new Expression("PageState == 'LIST' && a == 1 && customFunction() == true || in(1 + 1, 1, 2, 3)", ExpressionOptions.CaseInsensitiveStringComparer) - { - Parameters = - { - ["a"] = 1 - } - }; - expression.DynamicParameters["PageState"] = _ => "List"; - expression.Functions["customfunction"] = _ => true; - - var parameters = expression.GetParametersNames(); - Assert.Contains("a", parameters); - Assert.Contains("PageState", parameters); - Assert.Equal(2, parameters.Count); - } - - [Fact] - public void Should_Get_Parameters_One_Time_Issue_141() - { - var expression = - new Expression("if(x=0,x,y)", - ExpressionOptions.CaseInsensitiveStringComparer); - var parameters = expression.GetParametersNames(); - - Assert.Equal(2,parameters.Count); - } - - [Fact] - public void Should_Get_Parameters_With_Unary() - { - var expression = new Expression("-0.68"); - var p = expression.GetParametersNames(); - Assert.Empty(p); - } - - [Fact] - public void ShouldGetParametersInsideArray() - { - var expression = new Expression("(a,b,c)"); - var p = expression.GetParametersNames(); - Assert.Equal(3, p.Count); - } -} \ No newline at end of file