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 12, 2024
2 parents e11d880 + 73070f0 commit 693b0f2
Show file tree
Hide file tree
Showing 21 changed files with 463 additions and 136 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# 5.1.0
* Fix typo in MathHelper by @Bykiev in https://github.com/ncalc/ncalc/pull/273
* Small refactoring of Expression.IterateParameters by @Bykiev in https://github.com/ncalc/ncalc/pull/274
* Added `ShouldHandleBinaryExpression` unit test by @gumbarros in https://github.com/ncalc/ncalc/pull/278
* Add new `ExpressionOptions.StringConcat` to concat values as string by @Bykiev in https://github.com/ncalc/ncalc/pull/276
* Added `ExpressionOptions.AllowCharValues` by @gumbarros in https://github.com/ncalc/ncalc/pull/279
* Remove appveyor and use GH Actions with Coverlet by @gumbarros in https://github.com/ncalc/ncalc/pull/284
* Small performance improvement for `BinaryExpression` by @gumbarros in https://github.com/ncalc/ncalc/pull/283
* Move benchmarks to a separate workflow by @gumbarros in https://github.com/ncalc/ncalc/pull/285
* Improve string_concatenation.md docs by @gumbarros in https://github.com/ncalc/ncalc/pull/281
* Move event handlers to `ExpressionContext` by @gumbarros in https://github.com/ncalc/ncalc/pull/286
* Add support for Parlot parser compilation via AppContext switch by @Bykiev in https://github.com/ncalc/ncalc/pull/288
* Update Parlot parser by @Bykiev in https://github.com/ncalc/ncalc/pull/289
* Added `LogicalExpressionList` and `in` operator by @gumbarros in https://github.com/ncalc/ncalc/pull/287

# 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
Expand Down
93 changes: 63 additions & 30 deletions docs/articles/operators.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,115 @@
# Operators

Expressions can be combined using operators. Each operator as a precedence priority. Here is the list of those expression's priority.
1. primary
2. unary
3. power
4. multiplicative
5. additive
6. relational
7. logical
Expressions can be combined using operators, each of which has a precedence priority. Below is the list of expression priorities in descending order:

1. Primary
2. Unary
3. Power
4. Multiplicative
5. Additive
6. Relational
7. Logical

These operators follow the precedence rules to determine the order in which operations are performed in an expression.

## Logical

These operators can do some logical comparison between other expressions:
Logical operators perform logical comparisons between expressions.

* or, ||
* and, &&
* `or`, `||`
* `and`, `&&`

Examples:
```
true or false and true
true or false and true
(1 == 1) || false
```

The **and** operator has more priority than the **or**, thus in the example above, **false and true** is evaluated first.
The `and` operator has higher priority than the `or` operator, thus in the example above, `false and true` is evaluated first.

## Relational

* =, ==, !=, <>
* <, <=, >, >=
Relational operators compare two values and return a boolean result.
The `in` and `not in` operators right value must be a <xref:string> or <xref:System.Collections.IEnumerable>.

* `=`, `==`, `!=`, `<>`
* `<`, `<=`, `>`, `>=`
* `in`, `not in`

Examples:
```
1 < 2
3 < 2
42 == 42
'Insert' in ('Insert', 'Update')
"Sergio" in "Sergio is at Argentina"
"Mozart" not in ("Chopin", "Beethoven", GetComposer())
945 != 202
```

## Additive

* +, -
Additive operators perform addition and subtraction.

* `+`, `-`

Example:
```
1 + 2 - 3
1 + 2 - 3
```

## Multiplicative

* *, /, %
Multiplicative operators perform multiplication, division, and modulus operations.

* `*`, `/`, `%`

Example:
```
1 * 2 % 3
1 * 2 % 3
```

## Bitwise

* & (bitwise and), | (bitwise or), ^(bitwise xor), << (left shift), >>(right shift)
Bitwise operators perform bitwise operations on integers.

* `&` (bitwise and), `|` (bitwise or), `^` (bitwise xor), `<<` (left shift), `>>` (right shift)

Example:
```
2 >> 3
2 >> 3
```

## Unary

* !, not, -, ~ (bitwise not)
Unary operators operate on a single operand.

* `!`, `not`, `-`, `~` (bitwise not)

Example:
```
not true
not true
```

## Exponential

* **
Exponential operators perform exponentiation.

* `**`

Example:
```
2 ** 2
2 ** 2
```


## Primary

* (, )
Primary operators include grouping of expressions, lists and direct values. Check [Values](values.md) for more info.

* `(`, `)`
* values

Examples:
```
2 * ( 3 + 2 )
2 * (3 + 2)
("foo","bar", 5)
drop_database()
```
19 changes: 16 additions & 3 deletions docs/articles/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ A value is a terminal token representing a concrete element. This can be:
- A <xref:System.String>
- A <xref:System.Char>
- A <xref:NCalc.Domain.Function>
- An <xref:NCalc.Domain.Identifier> (parameter).
- An <xref:NCalc.Domain.Identifier> (parameter)
- A <xref:NCalc.Domain.LogicalExpressionList> (List of other expressions)

## Integers

Expand Down Expand Up @@ -94,8 +95,13 @@ Debug.Assert(result); // 'g' -> System.Char
A function is made of a name followed by braces, containing optionally any value as arguments.

```
Abs(1), doSomething(1, 'dummy')
Abs(1)
```

```
doSomething(1, 'dummy')
```

Please read the [functions page](functions.md) for details.

## Parameters
Expand All @@ -106,4 +112,11 @@ A parameter as a name, and can be optionally contained inside brackets or double
2 + x, 2 + [x]
```

Please read the [parameters page](parameters.md) for details.
Please read the [parameters page](parameters.md) for details.

## Lists

Lists are collections of expressions enclosed in parentheses. They are the equivalent of `List<LogicalExpression>` at CLR.
```
('Chers', secretOperation(), 3.14)
```
115 changes: 76 additions & 39 deletions src/NCalc.Async/Visitors/AsyncEvaluationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,104 +31,128 @@ public class AsyncEvaluationVisitor(AsyncExpressionContext context) : ILogicalEx

public async Task<object?> Visit(BinaryExpression expression)
{
var leftValue = new Lazy<ValueTask<object?>>(() => EvaluateAsync(expression.LeftExpression), LazyThreadSafetyMode.None);
var rightValue = new Lazy<ValueTask<object?>>(() => EvaluateAsync(expression.RightExpression), LazyThreadSafetyMode.None);
var left = new Lazy<ValueTask<object?>>(() => EvaluateAsync(expression.LeftExpression), LazyThreadSafetyMode.None);
var right = new Lazy<ValueTask<object?>>(() => EvaluateAsync(expression.RightExpression), LazyThreadSafetyMode.None);

switch (expression.Type)
{
case BinaryExpressionType.And:
return Convert.ToBoolean(await leftValue.Value, context.CultureInfo) &&
Convert.ToBoolean(await rightValue.Value, context.CultureInfo);
return Convert.ToBoolean(await left.Value, context.CultureInfo) &&
Convert.ToBoolean(await right.Value, context.CultureInfo);

case BinaryExpressionType.Or:
return Convert.ToBoolean(await leftValue.Value, context.CultureInfo) ||
Convert.ToBoolean(await rightValue.Value, context.CultureInfo);
return Convert.ToBoolean(await left.Value, context.CultureInfo) ||
Convert.ToBoolean(await right.Value, context.CultureInfo);

case BinaryExpressionType.Div:
return TypeHelper.IsReal(await leftValue.Value) || TypeHelper.IsReal(await rightValue.Value)
? MathHelper.Divide(await leftValue.Value, await rightValue.Value, context)
: MathHelper.Divide(Convert.ToDouble(await leftValue.Value, context.CultureInfo),
await rightValue.Value,
return TypeHelper.IsReal(await left.Value) || TypeHelper.IsReal(await right.Value)
? MathHelper.Divide(await left.Value, await right.Value, context)
: MathHelper.Divide(Convert.ToDouble(await left.Value, context.CultureInfo),
await right.Value,
context);

case BinaryExpressionType.Equal:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) == 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) == 0;

case BinaryExpressionType.Greater:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) > 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) > 0;

case BinaryExpressionType.GreaterOrEqual:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) >= 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) >= 0;

case BinaryExpressionType.Lesser:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) < 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) < 0;

case BinaryExpressionType.LesserOrEqual:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) <= 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) <= 0;

case BinaryExpressionType.Minus:
return MathHelper.Subtract(await leftValue.Value, await rightValue.Value, context);
return MathHelper.Subtract(await left.Value, await right.Value, context);

case BinaryExpressionType.Modulo:
return MathHelper.Modulo(await leftValue.Value, await rightValue.Value, context);
return MathHelper.Modulo(await left.Value, await right.Value, context);

case BinaryExpressionType.NotEqual:
return CompareUsingMostPreciseType(await leftValue.Value, await rightValue.Value) != 0;
return CompareUsingMostPreciseType(await left.Value, await right.Value) != 0;

case BinaryExpressionType.Plus:
{
var left = await leftValue.Value;
var right = await rightValue.Value;
var leftValue = await left.Value;
var rightValue = await right.Value;

if (context.Options.HasFlag(ExpressionOptions.StringConcat))
return string.Concat(left, right);
return string.Concat(leftValue, rightValue);

try
{
return MathHelper.Add(left, right, context);
return MathHelper.Add(leftValue, rightValue, context);
}
catch (FormatException) when (left is string && right is string)
catch (FormatException) when (leftValue is string && rightValue is string)
{
return string.Concat(left, right);
return string.Concat(leftValue, rightValue);
}
}

case BinaryExpressionType.Times:
return MathHelper.Multiply(await leftValue.Value, await rightValue.Value, context);
return MathHelper.Multiply(await left.Value, await right.Value, context);

case BinaryExpressionType.BitwiseAnd:
return Convert.ToUInt64(await leftValue.Value, context.CultureInfo) &
Convert.ToUInt64(await rightValue.Value, context.CultureInfo);
return Convert.ToUInt64(await left.Value, context.CultureInfo) &
Convert.ToUInt64(await right.Value, context.CultureInfo);

case BinaryExpressionType.BitwiseOr:
return Convert.ToUInt64(await leftValue.Value, context.CultureInfo) |
Convert.ToUInt64(await rightValue.Value, context.CultureInfo);
return Convert.ToUInt64(await left.Value, context.CultureInfo) |
Convert.ToUInt64(await right.Value, context.CultureInfo);

case BinaryExpressionType.BitwiseXOr:
return Convert.ToUInt64(await leftValue.Value, context.CultureInfo) ^
Convert.ToUInt64(await rightValue.Value, context.CultureInfo);
return Convert.ToUInt64(await left.Value, context.CultureInfo) ^
Convert.ToUInt64(await right.Value, context.CultureInfo);

case BinaryExpressionType.LeftShift:
return Convert.ToUInt64(await leftValue.Value, context.CultureInfo) <<
Convert.ToInt32(await rightValue.Value, context.CultureInfo);
return Convert.ToUInt64(await left.Value, context.CultureInfo) <<
Convert.ToInt32(await right.Value, context.CultureInfo);

case BinaryExpressionType.RightShift:
return Convert.ToUInt64(await leftValue.Value, context.CultureInfo) >>
Convert.ToInt32(await rightValue.Value, context.CultureInfo);
return Convert.ToUInt64(await left.Value, context.CultureInfo) >>
Convert.ToInt32(await right.Value, context.CultureInfo);

case BinaryExpressionType.Exponentiation:
{
if (context.Options.HasFlag(ExpressionOptions.DecimalAsDefault))
{
BigDecimal @base = new BigDecimal(Convert.ToDecimal(leftValue.Value));
BigInteger exponent = new BigInteger(Convert.ToDecimal(rightValue.Value));
BigDecimal @base = new BigDecimal(Convert.ToDecimal(left.Value));
BigInteger exponent = new BigInteger(Convert.ToDecimal(right.Value));

return (decimal)BigDecimal.Pow(@base, exponent);
}

return Math.Pow(Convert.ToDouble(await leftValue.Value, context.CultureInfo),
Convert.ToDouble(await rightValue.Value, context.CultureInfo));
return Math.Pow(Convert.ToDouble(await left.Value, context.CultureInfo),
Convert.ToDouble(await right.Value, context.CultureInfo));
}
case BinaryExpressionType.In:
{
var rightValue = await right.Value;
var leftValue = await left.Value;
return rightValue switch
{
IEnumerable<object> rightValueEnumerable => rightValueEnumerable.Contains(leftValue),
string rightValueString => rightValueString.Contains(leftValue?.ToString() ?? string.Empty),
_ => throw new NCalcEvaluationException(
"'in' operator right value must implement IEnumerable or be a string.")
};
}
case BinaryExpressionType.NotIn:
{
var rightValue = await right.Value;
var leftValue = await left.Value;
return rightValue switch
{
IEnumerable<object> rightValueEnumerable => rightValueEnumerable.Contains(leftValue),
string rightValueString => rightValueString.Contains(leftValue?.ToString() ?? string.Empty),
_ => throw new NCalcEvaluationException(
"'not in' operator right value must implement IEnumerable or be a string.")
};
}
}

return null;
Expand Down Expand Up @@ -223,6 +247,19 @@ public class AsyncEvaluationVisitor(AsyncExpressionContext context) : ILogicalEx

public Task<object?> Visit(ValueExpression expression) => Task.FromResult(expression.Value);


public async Task<object?> Visit(LogicalExpressionList list)
{
List<object?> result = [];

foreach (var value in list)
{
result.Add(await EvaluateAsync(value));
}

return result.ToArray();
}

protected int CompareUsingMostPreciseType(object? a, object? b)
{
return TypeHelper.CompareUsingMostPreciseType(a, b, context);
Expand Down
Loading

0 comments on commit 693b0f2

Please sign in to comment.