Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bykiev committed Jul 1, 2024
2 parents 0ae764a + 792ba0d commit 25f25ba
Show file tree
Hide file tree
Showing 51 changed files with 802 additions and 675 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
# 5.0.0
* Overflow protection by @Bykiev in https://github.com/ncalc/ncalc/pull/256
* Consolidate NETStandard.Library package version by @Bykiev in https://github.com/ncalc/ncalc/pull/257
* Add OverflowProtection to `LambdaExpressionVisitor` by @gumbarros in https://github.com/ncalc/ncalc/pull/259
* Improve CI with `DOTNET_NOLOGO` and `DOTNET_CLI_TELEMETRY_OPTOUT` by @gumbarros in https://github.com/ncalc/ncalc/pull/260
* Fix treating an expression with whitespace in fractional part as valid by @Bykiev in https://github.com/ncalc/ncalc/pull/262
* Added `IDictionary<string,ExpressionFunction>` and `IDictionary<string,ExpressionParameter>` support by @gumbarros in https://github.com/ncalc/ncalc/pull/254
* Use decimal with exponentiation when DecimalAsDefault is used by @Bykiev in https://github.com/ncalc/ncalc/pull/269
* Add NOTRACE for the entire solution at Release by @gumbarros in https://github.com/ncalc/ncalc/pull/268
* Fix `AsyncFunctionArgs` regression by @gumbarros in https://github.com/ncalc/ncalc/pull/271
* Added `Id` property to `Identifier` by @gumbarros in https://github.com/ncalc/ncalc/pull/266
* Visitor pattern is now stateless with generics by @gumbarros in https://github.com/ncalc/ncalc/pull/272

## Breaking Changes
- `NCalcAsync` now uses `AsyncExpressionContext`
- `ExpressionContext` is now a `record` instead of a `class`, allowing support for shallow cloning
- `IEvaluationVisitor` is removed, please use `IEvaluationService` for an easier to implement interface
- `ILogicalExpressionVisitor` is now `ILogicalExpressionVisitor<T>`, where `<T>` is the return of the visitor
- `IAsyncLogicalExpressionVisitor` is removed, please use `ILogicalExpressionVisitor<Task<object?>>`
- `AdvancedExpression` and `AsyncAdvancedExpression` are removed, please use the respective constructors at `Expression` and `AsyncExpression` to prevent unnecessary casting.

# 4.3.3
* Add `MemberNotNullWhen` attribute to `HasErrors` by @gmcchessney in https://github.com/ncalc/ncalc/pull/250
* Fix tests by @Bykiev in https://github.com/ncalc/ncalc/pull/251
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ If you want to evaluate simple expressions:
```
dotnet add package NCalcSync
```
Want async support at event handlers?
Want `async` support at your functions and parameters?
```
dotnet add package NCalcAsync
```
Expand Down Expand Up @@ -72,7 +72,7 @@ Debug.Assert(0 == new Expression("Tan(0)").Evaluate());

```c#
var expression = new Expression("SecretOperation(3, 6)");
expression.Functions["SecretOperation"] = (args, context) => {
expression.Functions["SecretOperation"] = (args) => {
return (int)args[0].Evaluate() + (int)args[1].Evaluate();
};

Expand All @@ -96,7 +96,7 @@ var expression = new Expression("Round(Pow([Pi], 2) + Pow([Pi2], 2) + [X], 2)");
expression.Parameters["Pi2"] = new Expression("Pi * [Pi]");
expression.Parameters["X"] = 10;

expression.DynamicParameters["Pi"] = (context) => {
expression.DynamicParameters["Pi"] = _ => {
Console.WriteLine("I'm evaluating π!");
return 3.14;
};
Expand Down
58 changes: 58 additions & 0 deletions docs/articles/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Architecture

The entire process of evaluating an expression can be demonstrated at this flowchart:

```mermaid
flowchart TB
A["1+1"] -->|Parsing| B("new BinaryExpression(new ValueExpression(1), new ValueExpression(1), BinaryExpressionType.Plus)")
B --> |Evaluation|2
```

## Parsing

Parsing is the process of analyzing the input expression and converting it into a structured format that can be easily
evaluated. We use [Parlot](https://github.com/sebastienros/parlot) to handle parsing, but you can use any parser you
want if you implement the interface <xref:NCalc.Factories.ILogicalExpressionFactory>.
For our example, "1+1", the parsing step converts the string into an abstract syntax tree (AST).
This tree is made up of different types of expressions, such as binary expressions, value expressions our even
functions.
Our AST is represented by <xref:NCalc.Domain.LogicalExpression> class.

## Evaluation

Evaluation refers to the process of determining the value of an expression. We use the visitor pattern at evaluation.
This pattern allows you to add new operations to existing object structures without modifying those structures.
With the method <xref:NCalc.Domain.LogicalExpression.Accept``1(NCalc.Visitors.ILogicalExpressionVisitor{``0})> is possible to accept any kind of visitor that
implements <xref:NCalc.Visitors.ILogicalExpressionVisitor`1>. Example implementations
include <xref:NCalc.Visitors.EvaluationVisitor> that returns a <xref:System.Object>
and <xref:NCalc.Visitors.SerializationVisitor> that converts the AST into a <xref:System.String>.

If you are creating your custom implementation, beware it should be stateless to be easier to debug and read. This is
enforced by the [PureAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.contracts.pureattribute0) and generic return at
the <xref:NCalc.Domain.LogicalExpression.Accept``1(NCalc.Visitors.ILogicalExpressionVisitor{``0})> method.

## <xref:NCalc.Expression> Class

This is the main class of NCalc. It abstracts the process of parsing and evaluating the string.
The method <xref:NCalc.Expression.Evaluate> returns the actual value of its <xref:System.String> representation.

Example:

```c#
var expression = new Expression("2 * 3");
var result = expression.Evaluate();

Console.WriteLine(result);
```

This example above first creates an instance of <xref:NCalc.Expression> using a valued constructor. This constructor
takes a <xref:System.String> as parameter.
Then the method <xref:NCalc.Expression.Evaluate> is called to parse the <xref:System.String> and returns the actual
value represented by the <xref:System.String>.

To create expressions you can combine several [Operators](operators.md) and [Values](values.md).

## Learn More
For additional information on the technique we used to create this library please read [this
article](http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx).
2 changes: 1 addition & 1 deletion docs/articles/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dotnet add package NCalcAsync
```cs
var expression = new AsyncExpression("database_operation('SELECT FOO') == 'FOO'");

expression.Functions["database_operation"] = async (args, context) => {
expression.Functions["database_operation"] = async (args) => {
// My heavy database work.
await Task.Delay(100);

Expand Down
2 changes: 1 addition & 1 deletion docs/articles/case_sensitivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var expression = new Expression("secretOperation()")
{
Functions = new Dictionary<string, ExpressionFunction>(StringComparer.InvariantCultureIgnoreCase)
{
{ "SecretOperation", (arguments, context) => 42 }
{ "SecretOperation", (arguments) => 42 }
}
};
```
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/expression_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ builder.Services.AddSingleton<ExpressionContext>(_ =>
Options = ExpressionOptions.OverflowProtection,
Functions = new Dictionary<string, ExpressionFunction>()
{
{"SecretOperation", (arguments, context) => 42}
{"SecretOperation", (arguments) => 42}
}.ToFrozenDictionary()
};
});
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ You can get the functions from the <xref:NCalc.ExpressionBuiltInFuctions> class.
## Custom Functions
Custom functions are created using the <xref:NCalc.ExpressionFunction> delegate. The parameters are <xref:NCalc.Expression> instances that can be lazy evaluated.
```csharp
expression.Functions["SecretOperation"] = (args, context) => {
expression.Functions["SecretOperation"] = (args) => {
return (int)args[0].Evaluate() + (int)args[1].Evaluate();
};

Expand Down
3 changes: 2 additions & 1 deletion docs/articles/handling_errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ Though, you can also detect syntax errors before the evaluation by using the <xr
if(expression.HasErrors())
{
Console.WriteLine(expression.Error);
}
}
```
26 changes: 3 additions & 23 deletions docs/articles/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ NCalc is a mathematical expression evaluator in .NET. NCalc can parse any expres

NCalc is a mathematical expression evaluator in .NET. NCalc can parse any expression and evaluate the result, including static or dynamic parameters and custom functions.

For additional information on the technique we used to create this framework please read this article: http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx.

## Table of Contents
- [Operators](operators.md): Available standard operators and structures.
- [Values](values.md): Authorized values like types and functions.
Expand All @@ -20,26 +18,9 @@ For additional information on the technique we used to create this framework ple
- [Caching](caching.md): How caching works.
- [Improve performance](lambda_compilation.md): How to use compilation of expressions to CLR lambdas.
- [Dependency Injection](dependency_injection.md): Bring expressions to the next level with dependency injection.
- [Architecture](architecture.md): Check this article to learn how NCalc works.
- [Benchmarks](benchmarks.md): Check some numbers about the speed of some NCalc components.

## <xref:NCalc.Expression>
This is the main class of NCalc.
The method <xref:NCalc.Expression.Evaluate> returns the actual value of its <xref:System.String> representation.

Example:

```c#
var expression = new Expression("2 * 3");
object result = expression.Evaluate();

Console.WriteLine(result);
```

This example above first creates an instance of <xref:NCalc.Expression> using a valued constructor. This constructor takes a <xref:System.String> as parameter.
Then the method <xref:NCalc.Expression.Evaluate> is called to parse the <xref:System.String>, and returns the actual value represented by the <xref:System.String>.

To create expressions you can combine several [Operators](operators.md) and [Values](values.md).

## Functionalities

### Simple Expressions
Expand Down Expand Up @@ -71,7 +52,7 @@ Debug.Assert(0 == new Expression("Tan(0)").Evaluate());

```c#
var expression = new Expression("SecretOperation(3, 6)");
expression.Functions["SecretOperation"] = (args, context) => {
expression.Functions["SecretOperation"] = (args) => {
return (int)args[0].Evaluate() + (int)args[1].Evaluate();
};

Expand All @@ -95,15 +76,14 @@ var expression = new Expression("Round(Pow([Pi], 2) + Pow([Pi2], 2) + [X], 2)");
expression.Parameters["Pi2"] = new Expression("Pi * [Pi]");
expression.Parameters["X"] = 10;

expression.DynamicParameters["Pi"] = (context) => {
expression.DynamicParameters["Pi"] = _ => {
Console.WriteLine("I'm evaluating π!");
return 3.14;
};

Debug.Assert(117.07 == expression.Evaluate());
```


### Lambda Expressions
```cs
var expression = new Expression("1 + 2");
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ using the <xref:NCalc.ExpressionParameter> delegate.
expression.Parameters["Pi2"] = new Expression("Pi * [Pi]");
expression.Parameters["X"] = 10;

expression.DynamicParameters["Pi"] = (context) => {
expression.DynamicParameters["Pi"] = _ => {
Console.WriteLine("I'm evaluating π!");
return 3.14;
};
Expand Down
10 changes: 6 additions & 4 deletions docs/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
href: lambda_compilation.md
- name: Dependency Injection
href: dependency_injection.md
- name: Benchmarks
href: benchmarks.md
- name: Changelog
href: changelog.md
- name: Plugins
items:
- name: ANTLR
href: plugins/antlr.md
- name: Memory Cache
href: plugins/memory_cache.md
- name: Architecture
href: architecture.md
- name: Benchmarks
href: benchmarks.md
- name: Changelog
href: changelog.md
- name: Publishing a New Release
href: new_release.md
8 changes: 6 additions & 2 deletions src/NCalc.Async/AsyncExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

namespace NCalc;

/// <summary>
/// This class represents a mathematical or logical expression that can be evaluated.
/// It supports caching, custom parameter and function evaluation, and options for handling null parameters and iterating over parameter collections.
/// The class manages the parsing, validation, and evaluation of expressions, and provides mechanisms for error detection and reporting.
/// </summary>
public class AsyncExpression
{
/// <summary>
Expand Down Expand Up @@ -295,7 +300,6 @@ public List<string> GetParametersNames()
{
var parameterExtractionVisitor = new ParameterExtractionVisitor();
LogicalExpression ??= LogicalExpressionFactory.Create(ExpressionString!, Context);
LogicalExpression.Accept(parameterExtractionVisitor);
return parameterExtractionVisitor.Parameters.ToList();
return LogicalExpression.Accept(parameterExtractionVisitor);
}
}
2 changes: 1 addition & 1 deletion src/NCalc.Async/AsyncExpressionFunction.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace NCalc;

public delegate ValueTask<object?> AsyncExpressionFunction(AsyncExpression[] arguments, AsyncExpressionContext context);
public delegate ValueTask<object?> AsyncExpressionFunction(AsyncExpressionFunctionData data);
24 changes: 24 additions & 0 deletions src/NCalc.Async/AsyncExpressionFunctionData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace NCalc;

public class AsyncExpressionFunctionData(Guid id, AsyncExpression[] arguments, AsyncExpressionContext context) : IEnumerable<AsyncExpression>
{
public Guid Id { get; } = id;
private AsyncExpression[] Arguments { get; } = arguments;
public AsyncExpressionContext Context { get; } = context;

public AsyncExpression this[int index]
{
get => Arguments[index];
set => Arguments[index] = value;
}

public IEnumerator<AsyncExpression> GetEnumerator()
{
return ((IEnumerable<AsyncExpression>)Arguments).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return Arguments.GetEnumerator();
}
}
2 changes: 1 addition & 1 deletion src/NCalc.Async/AsyncExpressionParameter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace NCalc;

public delegate ValueTask<object?> AsyncExpressionParameter(AsyncExpressionContext context);
public delegate ValueTask<object?> AsyncExpressionParameter(AsyncExpressionParameterData data);
7 changes: 7 additions & 0 deletions src/NCalc.Async/AsyncExpressionParameterData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NCalc;

public class AsyncExpressionParameterData(Guid id, AsyncExpressionContext context)
{
public Guid Id { get; } = id;
public AsyncExpressionContext Context { get; } = context;
}
21 changes: 0 additions & 21 deletions src/NCalc.Async/Extensions/LogicalExpressionExtensions.cs

This file was deleted.

10 changes: 6 additions & 4 deletions src/NCalc.Async/Handlers/AsyncFunctionArgs.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace NCalc.Handlers;

public class AsyncFunctionArgs(AsyncExpression[] parameters) : EventArgs
public class AsyncFunctionArgs(Guid id,AsyncExpression[] parameters) : EventArgs
{
public Guid Id { get; } = id;

private object? _result;

public object? Result
Expand All @@ -13,17 +15,17 @@ public object? Result
HasResult = true;
}
}

public AsyncExpression[] Parameters { get; } = parameters;

public bool HasResult { get; private set; }

public object?[] EvaluateParameters()
public async Task<object?[]> EvaluateParametersAsync()
{
var values = new object?[Parameters.Length];
for (var i = 0; i < values.Length; i++)
{
values[i] = Parameters[i].EvaluateAsync();
values[i] = await Parameters[i].EvaluateAsync();
}

return values;
Expand Down
4 changes: 3 additions & 1 deletion src/NCalc.Async/Handlers/AsyncParameterArgs.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace NCalc.Handlers;

public class AsyncParameterArgs : EventArgs
public class AsyncParameterArgs(Guid id) : EventArgs
{
public Guid Id { get; } = id;

private object? _result;
public object? Result
{
Expand Down
Loading

0 comments on commit 25f25ba

Please sign in to comment.