Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable default numeric type #146

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Dynamic Expresso
# Dynamic Expresso

[![NuGet version](https://badge.fury.io/nu/DynamicExpresso.Core.svg)](http://badge.fury.io/nu/DynamicExpresso.Core)
[![Build Status](https://public-davideicardi.visualstudio.com/dynamic-expresso/_apis/build/status/dynamic-expresso-CI?branchName=master)](https://public-davideicardi.visualstudio.com/dynamic-expresso/_build?definitionId=4
Expand Down Expand Up @@ -360,6 +360,22 @@ CollectionAssert.AreEqual(new[] { "x", "y" },
detectedIdentifiers.UnknownIdentifiers.ToArray());
```

## Default number type
In C #, numbers are usually interpreted as integers or doubles if they have decimal places.

In some cases it may be useful to be able to configure the default type of numbers if no particular suffix is ​​specified: for example in financial calculations, where usually numbers are interpreted as decimal type.

In these cases you can set the default number type using `Interpreter.SetDefaultNumberType` method.

```csharp
var target = new Interpreter();

target.SetDefaultNumberType(DefaultNumberType.Decimal);

Assert.IsInstanceOf(typeof(System.Decimal), target.Eval("45"));
Assert.AreEqual(10M/3M, target.Eval("10/3")); // 3.33333333333 instead of 3
```

## Limitations
Not every C# syntaxes are supported. Here some examples of NOT supported features:

Expand Down
19 changes: 19 additions & 0 deletions src/DynamicExpresso.Core/DefaultNumberType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace DynamicExpresso
{
/// <summary>
/// Setting the default number types when no suffix is specified
/// </summary>
public enum DefaultNumberType
{
Default = 0, //(Int by default or Double if real number)
Int = 1,
Long = 2,
Single = 3,
Double = 4,
Decimal = 5
}
}
14 changes: 13 additions & 1 deletion src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using DynamicExpresso.Parsing;
using DynamicExpresso.Parsing;
using DynamicExpresso.Reflection;
using DynamicExpresso.Visitors;
using System;
Expand Down Expand Up @@ -102,6 +102,18 @@ public AssignmentOperators AssignmentOperators
#endregion

#region Options

/// <summary>
/// Allow to set de default numeric type when no suffix is specified (Int by default, Double if real number)
/// </summary>
/// <param name="defaultNumberType"></param>
/// <returns></returns>
public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType)
{
_settings.DefaultNumberType = defaultNumberType;
return this;
}

/// <summary>
/// Allows to enable/disable assignment operators.
/// For security when expression are generated by the users is more safe to disable assignment operators.
Expand Down
45 changes: 39 additions & 6 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ public static Expression Parse(ParserArguments arguments)
private readonly BindingFlags _bindingCase;
private readonly MemberFilter _memberFilterCase;

private readonly DefaultNumberType _defaultNumberType;

private Parser(ParserArguments arguments)
{
_arguments = arguments;

_bindingCase = arguments.Settings.CaseInsensitive ? BindingFlags.IgnoreCase : BindingFlags.Default;
_memberFilterCase = arguments.Settings.CaseInsensitive ? Type.FilterNameIgnoreCase : Type.FilterName;

_defaultNumberType = arguments.Settings.DefaultNumberType;

_expressionText = arguments.ExpressionText ?? string.Empty;
_expressionTextLength = _expressionText.Length;
SetTextPos(0);
Expand Down Expand Up @@ -656,6 +660,9 @@ private Expression ParseIntegerLiteral()

text = text.Substring(0, numberEnd + 1);

// No suffix find, verify if DefaultNumberType.Long is specified
if (_defaultNumberType == DefaultNumberType.Long) isLong = true;

if (text[0] != '-')
{
if (!ulong.TryParse(text, ParseLiteralUnsignedNumberStyle, ParseCulture, out ulong value))
Expand Down Expand Up @@ -703,14 +710,30 @@ private Expression ParseRealLiteral()
if (decimal.TryParse(text.Substring(0, text.Length - 1), ParseLiteralDecimalNumberStyle, ParseCulture, out decimal dc))
value = dc;
}
else
else if (last == 'D' || last == 'd')
{
if (last == 'D' || last == 'd')
text = text.Substring(0, text.Length - 1);

if (double.TryParse(text, ParseLiteralDoubleNumberStyle, ParseCulture, out double d))
if (double.TryParse(text.Substring(0, text.Length - 1), ParseLiteralDoubleNumberStyle, ParseCulture, out double d))
value = d;
}
else
{
// No suffix find, use DefaultNumberType settigns if specified (Double default)
if (_defaultNumberType == DefaultNumberType.Decimal)
{
if (decimal.TryParse(text, ParseLiteralDecimalNumberStyle, ParseCulture, out decimal dc))
value = dc;
}
else if (_defaultNumberType == DefaultNumberType.Single)
{
if (float.TryParse(text, ParseLiteralDecimalNumberStyle, ParseCulture, out float f))
value = f;
}
else
{
if (double.TryParse(text, ParseLiteralDoubleNumberStyle, ParseCulture, out double d))
value = d;
}
}

if (value == null)
throw CreateParseException(_token.pos, ErrorMessages.InvalidRealLiteral, text);
Expand Down Expand Up @@ -2348,7 +2371,17 @@ private void NextToken()

if (char.IsDigit(_parseChar))
{
t = TokenId.IntegerLiteral;
//RealLiteral if DefaultNumberType settings is set to real type
if (_defaultNumberType == DefaultNumberType.Single || _defaultNumberType == DefaultNumberType.Double || _defaultNumberType == DefaultNumberType.Decimal)
{
t = TokenId.RealLiteral;
}
else
{
//IntegerLiteral by default
t = TokenId.IntegerLiteral;
}

do
{
NextChar();
Expand Down
10 changes: 9 additions & 1 deletion src/DynamicExpresso.Core/Parsing/ParserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class ParserSettings
private readonly Dictionary<string, Identifier> _identifiers;
private readonly Dictionary<string, ReferenceType> _knownTypes;
private readonly HashSet<MethodInfo> _extensionMethods;

public ParserSettings(bool caseInsensitive)
{
CaseInsensitive = caseInsensitive;
Expand All @@ -25,6 +25,8 @@ public ParserSettings(bool caseInsensitive)
_extensionMethods = new HashSet<MethodInfo>();

AssignmentOperators = AssignmentOperators.All;

DefaultNumberType = DefaultNumberType.Default;
}

public IDictionary<string, ReferenceType> KnownTypes
Expand All @@ -48,6 +50,12 @@ public bool CaseInsensitive
private set;
}

public DefaultNumberType DefaultNumberType
{
get;
set;
}

public StringComparison KeyComparison
{
get;
Expand Down
Loading