Skip to content

Commit 6ff9f05

Browse files
author
Sébastien Geiser
committed
Options OptionNumberParsingDecimalSeparator, OptionFunctionArgumentsSeparator and OptionInitializersSeparator added.
1 parent 31345e7 commit 6ff9f05

File tree

2 files changed

+69
-45
lines changed

2 files changed

+69
-45
lines changed

CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,35 +1407,19 @@ public void ExceptionThrowingEvaluation(ExpressionEvaluator evaluator, string ex
14071407

14081408
#region NumbersWithCommaDecimalSeparatorCulture
14091409

1410-
[TestCase("0,5", ExpectedResult = 0.5, Category = "Numbers")]
1411-
public object NumbersWithCommaDecimalSeparatorCulture(string expression)
1410+
[TestCase("0,5", ",", ";", ExpectedResult = 0.5, Category = "Numbers")]
1411+
[TestCase("0'5", "'", ",", ExpectedResult = 0.5, Category = "Numbers")]
1412+
[TestCase("0.5", ".", ",", ExpectedResult = 0.5, Category = "Numbers")]
1413+
[TestCase("Max(0,5; 0,7)", ",", ";", ExpectedResult = 0.7, Category = "Numbers")]
1414+
public object NumbersDecimalSeparatorAndFunctionArgsSeparators(string expression, string decimalSeparator, string functionArgsSeparator)
14121415
{
1413-
object result = null;
1414-
1415-
CultureInfo oldCultureInfo = Thread.CurrentThread.CurrentCulture;
1416-
CultureInfo oldCultureUIInfo = Thread.CurrentThread.CurrentUICulture;
1417-
1418-
//CultureInfo cultureInfo = new CultureInfo("en");
1419-
1420-
//cultureInfo.NumberFormat.NumberDecimalSeparator = ",";
1421-
1422-
//Thread.CurrentThread.CurrentCulture = cultureInfo;
1423-
//Thread.CurrentThread.CurrentUICulture = cultureInfo;
1424-
1425-
try
1416+
ExpressionEvaluator evaluator = new ExpressionEvaluator
14261417
{
1418+
OptionNumberParsingDecimalSeparator = decimalSeparator,
1419+
OptionFunctionArgumentsSeparator = functionArgsSeparator
1420+
};
14271421

1428-
ExpressionEvaluator evaluator = new ExpressionEvaluator();
1429-
1430-
result = evaluator.Evaluate(expression);
1431-
}
1432-
finally
1433-
{
1434-
Thread.CurrentThread.CurrentCulture = oldCultureInfo;
1435-
Thread.CurrentThread.CurrentUICulture = oldCultureUIInfo;
1436-
}
1437-
1438-
return result;
1422+
return evaluator.Evaluate(expression);
14391423
}
14401424

14411425
#endregion

CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ public class ExpressionEvaluator
3232
private static readonly string diactiticsKeywordsRegexPattern = "a-zA-Z_" + diactitics;
3333

3434
private static readonly Regex varOrFunctionRegEx = new Regex($@"^((?<sign>[+-])|(?<prefixOperator>[+][+]|--)|(?<inObject>(?<nullConditional>[?])?\.)?)(?<name>[{ diactiticsKeywordsRegexPattern }][{ diactiticsKeywordsRegexPattern }0-9]*)\s*((?<assignationOperator>(?<assignmentPrefix>[+\-*/%&|^]|<<|>>)?=(?![=>]))|(?<postfixOperator>([+][+]|--)(?![{ diactiticsKeywordsRegexPattern}0-9]))|((?<isgeneric>[<](?>[^<>]+|(?<gentag>[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?(?<isfunction>[(])?))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
35-
private static readonly Regex numberRegex = new Regex(@"^(?<sign>[+-])?([0-9][0-9_]*[0-9]|\d)(?<hasdecimal>\.?([0-9][0-9_]*[0-9]|\d)(e[+-]?([0-9][0-9_]*[0-9]|\d))?)?(?<type>ul|[fdulm])?", RegexOptions.IgnoreCase);
35+
36+
private string numberRegexPattern = @"^(?<sign>[+-])?([0-9][0-9_]*[0-9]|\d)(?<hasdecimal>{0}?([0-9][0-9_]*[0-9]|\d)(e[+-]?([0-9][0-9_]*[0-9]|\d))?)?(?<type>ul|[fdulm])?";
37+
private Regex numberRegex = null;
38+
3639
private static readonly Regex otherBasesNumberRegex = new Regex(@"^(?<sign>[+-])?(?<value>0(?<type>x)([0-9a-f][0-9a-f_]*[0-9a-f]|[0-9a-f])|0(?<type>b)([01][01_]*[01]|[01]))", RegexOptions.IgnoreCase);
3740
private static readonly Regex stringBeginningRegex = new Regex("^(?<interpolated>[$])?(?<escaped>[@])?[\"]");
3841
private static readonly Regex internalCharRegex = new Regex(@"^['](\\[']|[^'])*[']");
@@ -163,14 +166,14 @@ private enum TryBlockEvaluatedState
163166
{ "void", typeof(void) }
164167
};
165168

166-
private static Dictionary<string, Func<string, object>> numberSuffixToParse = new Dictionary<string, Func<string, object>>(StringComparer.OrdinalIgnoreCase) // Always Case insensitive, like in C#
169+
private static Dictionary<string, Func<string, CultureInfo, object>> numberSuffixToParse = new Dictionary<string, Func<string, CultureInfo, object>>(StringComparer.OrdinalIgnoreCase) // Always Case insensitive, like in C#
167170
{
168-
{ "f", number => float.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) },
169-
{ "d", number => double.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) },
170-
{ "u", number => uint.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) },
171-
{ "l", number => long.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) },
172-
{ "ul", number => ulong.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) },
173-
{ "m", number => decimal.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }
171+
{ "f", (number, culture) => float.Parse(number, NumberStyles.Any, culture) },
172+
{ "d", (number, culture) => double.Parse(number, NumberStyles.Any, culture) },
173+
{ "u", (number, culture) => uint.Parse(number, NumberStyles.Any, culture) },
174+
{ "l", (number, culture) => long.Parse(number, NumberStyles.Any, culture) },
175+
{ "ul", (number, culture) => ulong.Parse(number, NumberStyles.Any, culture) },
176+
{ "m", (number, culture) => decimal.Parse(number, NumberStyles.Any, culture) }
174177
};
175178

176179
private static Dictionary<char, string> stringEscapedCharDict = new Dictionary<char, string>()
@@ -529,6 +532,42 @@ private StringComparer StringComparerForCasing
529532
}
530533
}
531534

535+
private CultureInfo cultureInfoForNumberParsing = CultureInfo.InvariantCulture.Clone() as CultureInfo;
536+
private string optionNumberParsingDecimalSeparator = ".";
537+
538+
/// <summary>
539+
/// Allow to change the decimal separator of numbers when parsing expressions.
540+
/// By default "."
541+
/// Warning if using comma change also OptionFunctionArgumentsSeparator and OptionInitializersSeparator otherwise it will create conflicts
542+
/// </summary>
543+
public string OptionNumberParsingDecimalSeparator
544+
{
545+
546+
get => optionNumberParsingDecimalSeparator;
547+
548+
set
549+
{
550+
optionNumberParsingDecimalSeparator = value;
551+
cultureInfoForNumberParsing.NumberFormat.NumberDecimalSeparator = value;
552+
553+
numberRegex = new Regex(string.Format(numberRegexPattern, Regex.Escape(optionNumberParsingDecimalSeparator)), RegexOptions.IgnoreCase);
554+
}
555+
}
556+
557+
/// <summary>
558+
/// Allow to change the separator of functions arguments.
559+
/// By default ","
560+
/// Warning must to be changed if OptionNumberParsingDecimalSeparator = "," otherwise it will create conflicts
561+
/// </summary>
562+
public string OptionFunctionArgumentsSeparator { get; set; } = ",";
563+
564+
/// <summary>
565+
/// Allow to change the separator of Object and collections Initialization between { and } after the keyword new.
566+
/// By default ","
567+
/// Warning must to be changed if OptionNumberParsingDecimalSeparator = "," otherwise it will create conflicts
568+
/// </summary>
569+
public string OptionInitializersSeparator { get; set; } = ",";
570+
532571
/// <summary>
533572
/// if <c>true</c> allow to add the prefix Fluid or Fluent before void methods names to return back the instance on which the method is call.
534573
/// if <c>false</c> unactive this functionality.
@@ -746,6 +785,7 @@ public ExpressionEvaluator()
746785
{
747786
Assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
748787
instanceCreationWithNewKeywordRegex = new Regex(InstanceCreationWithNewKeywordRegexPattern);
788+
numberRegex = new Regex(string.Format(numberRegexPattern, @"\."), RegexOptions.IgnoreCase);
749789
castRegex = new Regex(CastRegexPattern);
750790
}
751791

@@ -1419,20 +1459,20 @@ private bool EvaluateNumber(string restOfExpression, Stack<object> stack, ref in
14191459
string type = numberMatch.Groups["type"].Value;
14201460
string numberNoType = numberMatch.Value.Replace(type, string.Empty).Replace("_", "");
14211461

1422-
if (numberSuffixToParse.TryGetValue(type, out Func<string, object> parseFunc))
1462+
if (numberSuffixToParse.TryGetValue(type, out Func<string, CultureInfo, object> parseFunc))
14231463
{
1424-
stack.Push(parseFunc(numberNoType));
1464+
stack.Push(parseFunc(numberNoType, cultureInfoForNumberParsing));
14251465
}
14261466
}
14271467
else
14281468
{
14291469
if (numberMatch.Groups["hasdecimal"].Success)
14301470
{
1431-
stack.Push(double.Parse(numberMatch.Value.Replace("_",""), NumberStyles.Any, CultureInfo.InvariantCulture));
1471+
stack.Push(double.Parse(numberMatch.Value.Replace("_",""), NumberStyles.Any, cultureInfoForNumberParsing));
14321472
}
14331473
else
14341474
{
1435-
stack.Push(int.Parse(numberMatch.Value.Replace("_", ""), NumberStyles.Any, CultureInfo.InvariantCulture));
1475+
stack.Push(int.Parse(numberMatch.Value.Replace("_", ""), NumberStyles.Any, cultureInfoForNumberParsing));
14361476
}
14371477
}
14381478

@@ -1478,7 +1518,7 @@ void Init(object element, List<string> initArgs)
14781518
{
14791519
int subIndex = subExpr.IndexOf("{") + 1;
14801520

1481-
List<string> subArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(subExpr, ref subIndex, true, ",", "{", "}");
1521+
List<string> subArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(subExpr, ref subIndex, true, OptionInitializersSeparator, "{", "}");
14821522

14831523
if(subArgs.Count == 2)
14841524
{
@@ -1514,7 +1554,7 @@ void Init(object element, List<string> initArgs)
15141554

15151555
if (instanceCreationMatch.Groups["isfunction"].Success)
15161556
{
1517-
List<string> constructorArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true);
1557+
List<string> constructorArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionFunctionArgumentsSeparator);
15181558
i++;
15191559

15201560
List<object> cArgs = constructorArgs.ConvertAll(arg => Evaluate(arg));
@@ -1527,7 +1567,7 @@ void Init(object element, List<string> initArgs)
15271567
{
15281568
i += blockBeginningMatch.Length;
15291569

1530-
List<string> initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, ",", "{", "}");
1570+
List<string> initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionInitializersSeparator, "{", "}");
15311571

15321572
Init(element, initArgs);
15331573
}
@@ -1540,15 +1580,15 @@ void Init(object element, List<string> initArgs)
15401580
{
15411581
object element = Activator.CreateInstance(type, new object[0]);
15421582

1543-
List<string> initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, ",", "{", "}");
1583+
List<string> initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionInitializersSeparator, "{", "}");
15441584

15451585
Init(element, initArgs);
15461586

15471587
stack.Push(element);
15481588
}
15491589
else if(instanceCreationMatch.Groups["isArray"].Success)
15501590
{
1551-
List<string> arrayArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, ",", "[", "]");
1591+
List<string> arrayArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionInitializersSeparator, "[", "]");
15521592
i++;
15531593
Array array = null;
15541594

@@ -1563,7 +1603,7 @@ void Init(object element, List<string> initArgs)
15631603
{
15641604
i += initInNewBeginningMatch.Length;
15651605

1566-
List<string> arrayElements = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, ",", "{", "}");
1606+
List<string> arrayElements = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionInitializersSeparator, "{", "}");
15671607
i++;
15681608

15691609
if (array == null)
@@ -1602,7 +1642,7 @@ private bool EvaluateVarOrFunc(string expr, string restOfExpression, Stack<objec
16021642

16031643
if (varFuncMatch.Groups["isfunction"].Success)
16041644
{
1605-
List<string> funcArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true);
1645+
List<string> funcArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expr, ref i, true, OptionFunctionArgumentsSeparator);
16061646
if (varFuncMatch.Groups["inObject"].Success)
16071647
{
16081648
if (stack.Count == 0 || stack.Peek() is ExpressionOperator)

0 commit comments

Comments
 (0)