diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 4cc77a00..b0fb8157 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -984,11 +984,12 @@ private Expression ParseRealLiteral() { _textParser.ValidateToken(TokenId.RealLiteral); - string text = _textParser.CurrentToken.Text; + var text = _textParser.CurrentToken.Text; + var textOriginal = text; _textParser.NextToken(); - return _numberParser.ParseRealLiteral(text, text[text.Length - 1], true); + return _numberParser.ParseRealLiteral(text, textOriginal, text[text.Length - 1], true); } private Expression ParseParenExpression() diff --git a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs index 7fdfdaad..798ff097 100644 --- a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs @@ -4,244 +4,234 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +/// +/// NumberParser +/// +public class NumberParser { + private static readonly Regex RegexBinary32 = new("^[01]{1,32}$", RegexOptions.Compiled); + private static readonly Regex RegexBinary64 = new("^[01]{1,64}$", RegexOptions.Compiled); + private static readonly char[] Qualifiers = { 'U', 'u', 'L', 'l', 'F', 'f', 'D', 'd', 'M', 'm' }; + private static readonly char[] QualifiersHex = { 'U', 'u', 'L', 'l' }; + private static readonly string[] QualifiersReal = { "F", "f", "D", "d", "M", "m" }; + private readonly ConstantExpressionHelper _constantExpressionHelper; + + private readonly CultureInfo _culture; + /// - /// NumberParser + /// Initializes a new instance of the class. /// - public class NumberParser + /// The ParsingConfig. + public NumberParser(ParsingConfig? config) { - private static readonly Regex RegexBinary32 = new("^[01]{1,32}$", RegexOptions.Compiled); - private static readonly Regex RegexBinary64 = new("^[01]{1,64}$", RegexOptions.Compiled); - private static readonly char[] Qualifiers = { 'U', 'u', 'L', 'l', 'F', 'f', 'D', 'd', 'M', 'm' }; - private static readonly char[] QualifiersHex = { 'U', 'u', 'L', 'l' }; - private static readonly string[] QualifiersReal = { "F", "f", "D", "d", "M", "m" }; - private readonly ConstantExpressionHelper _constantExpressionHelper; - - private readonly CultureInfo _culture; - - /// - /// Initializes a new instance of the class. - /// - /// The ParsingConfig. - public NumberParser(ParsingConfig? config) - { - _culture = config?.NumberParseCulture ?? CultureInfo.InvariantCulture; - _constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(config ?? ParsingConfig.Default); - } + _culture = config?.NumberParseCulture ?? CultureInfo.InvariantCulture; + _constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(config ?? ParsingConfig.Default); + } - /// - /// Tries to parse the text into a IntegerLiteral ConstantExpression. - /// - /// The current token position (needed for error reporting). - /// The text. - public Expression ParseIntegerLiteral(int tokenPosition, string text) - { - Check.NotEmpty(text, nameof(text)); + /// + /// Tries to parse the text into a IntegerLiteral ConstantExpression. + /// + /// The current token position (needed for error reporting). + /// The text. + public Expression ParseIntegerLiteral(int tokenPosition, string text) + { + Check.NotEmpty(text); - var last = text[text.Length - 1]; - var isNegative = text[0] == '-'; - var isHexadecimal = text.StartsWith(isNegative ? "-0x" : "0x", StringComparison.OrdinalIgnoreCase); - var isBinary = text.StartsWith(isNegative ? "-0b" : "0b", StringComparison.OrdinalIgnoreCase); - var qualifiers = isHexadecimal ? QualifiersHex : Qualifiers; + var textOriginal = text; + var last = text[text.Length - 1]; + var isNegative = text[0] == '-'; + var isHexadecimal = text.StartsWith(isNegative ? "-0x" : "0x", StringComparison.OrdinalIgnoreCase); + var isBinary = text.StartsWith(isNegative ? "-0b" : "0b", StringComparison.OrdinalIgnoreCase); + var qualifiers = isHexadecimal ? QualifiersHex : Qualifiers; - string? qualifier = null; - if (qualifiers.Contains(last)) + string? qualifier = null; + if (qualifiers.Contains(last)) + { + int pos = text.Length - 1, count = 0; + while (qualifiers.Contains(text[pos])) { - int pos = text.Length - 1, count = 0; - while (qualifiers.Contains(text[pos])) - { - ++count; - --pos; - } - qualifier = text.Substring(text.Length - count, count); - text = text.Substring(0, text.Length - count); + ++count; + --pos; } + qualifier = text.Substring(text.Length - count, count); + text = text.Substring(0, text.Length - count); + } - if (!isNegative) + if (!isNegative) + { + if (isHexadecimal || isBinary) { - if (isHexadecimal || isBinary) - { - text = text.Substring(2); - } - - if (isBinary) - { - return ParseAsBinary(tokenPosition, text, isNegative); - } + text = text.Substring(2); + } - if (!ulong.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, _culture, out ulong unsignedValue)) - { - throw new ParseException(string.Format(_culture, Res.InvalidIntegerLiteral, text), tokenPosition); - } + if (isBinary) + { + return ParseAsBinary(tokenPosition, text, textOriginal, isNegative); + } - if (!string.IsNullOrEmpty(qualifier) && qualifier!.Length > 0) - { - if (qualifier == "U" || qualifier == "u") - { - return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, text); - } - - if (qualifier == "L" || qualifier == "l") - { - return _constantExpressionHelper.CreateLiteral((long)unsignedValue, text); - } - - if (QualifiersReal.Contains(qualifier)) - { - return ParseRealLiteral(text, qualifier[0], false); - } - - return _constantExpressionHelper.CreateLiteral(unsignedValue, text); - } + if (!ulong.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, _culture, out ulong unsignedValue)) + { + throw new ParseException(string.Format(_culture, Res.InvalidIntegerLiteral, text), tokenPosition); + } - if (unsignedValue <= int.MaxValue) + if (!string.IsNullOrEmpty(qualifier) && qualifier!.Length > 0) + { + if (qualifier == "U" || qualifier == "u") { - return _constantExpressionHelper.CreateLiteral((int)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, textOriginal); } - if (unsignedValue <= uint.MaxValue) + if (qualifier == "L" || qualifier == "l") { - return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((long)unsignedValue, textOriginal); } - if (unsignedValue <= long.MaxValue) + if (QualifiersReal.Contains(qualifier)) { - return _constantExpressionHelper.CreateLiteral((long)unsignedValue, text); + return ParseRealLiteral(text, textOriginal, qualifier[0], false); } - return _constantExpressionHelper.CreateLiteral(unsignedValue, text); + return _constantExpressionHelper.CreateLiteral(unsignedValue, textOriginal); } - if (isHexadecimal || isBinary) + if (unsignedValue <= int.MaxValue) { - text = text.Substring(3); + return _constantExpressionHelper.CreateLiteral((int)unsignedValue, textOriginal); } - if (isBinary) + if (unsignedValue <= uint.MaxValue) { - return ParseAsBinary(tokenPosition, text, isNegative); + return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, textOriginal); } - if (!long.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, _culture, out long value)) + if (unsignedValue <= long.MaxValue) { - throw new ParseException(string.Format(_culture, Res.InvalidIntegerLiteral, text), tokenPosition); + return _constantExpressionHelper.CreateLiteral((long)unsignedValue, textOriginal); } - if (isHexadecimal) - { - value = -value; - } + return _constantExpressionHelper.CreateLiteral(unsignedValue, textOriginal); + } - if (!string.IsNullOrEmpty(qualifier) && qualifier!.Length > 0) - { - if (qualifier == "L" || qualifier == "l") - { - return _constantExpressionHelper.CreateLiteral(value, text); - } + if (isHexadecimal || isBinary) + { + text = text.Substring(3); + } - if (QualifiersReal.Contains(qualifier)) - { - return ParseRealLiteral(text, qualifier[0], false); - } + if (isBinary) + { + return ParseAsBinary(tokenPosition, text, textOriginal, isNegative); + } - throw new ParseException(Res.MinusCannotBeAppliedToUnsignedInteger, tokenPosition); + if (!long.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, _culture, out long value)) + { + throw new ParseException(string.Format(_culture, Res.InvalidIntegerLiteral, text), tokenPosition); + } + + if (isHexadecimal) + { + value = -value; + } + + if (!string.IsNullOrEmpty(qualifier) && qualifier!.Length > 0) + { + if (qualifier == "L" || qualifier == "l") + { + return _constantExpressionHelper.CreateLiteral(value, textOriginal); } - if (value <= int.MaxValue) + if (QualifiersReal.Contains(qualifier)) { - return _constantExpressionHelper.CreateLiteral((int)value, text); + return ParseRealLiteral(text, textOriginal, qualifier[0], false); } - return _constantExpressionHelper.CreateLiteral(value, text); + throw new ParseException(Res.MinusCannotBeAppliedToUnsignedInteger, tokenPosition); } - /// - /// Parse the text into a Real ConstantExpression. - /// - public Expression ParseRealLiteral(string text, char qualifier, bool stripQualifier) + if (value <= int.MaxValue) { - if (stripQualifier) - { - var pos = text.Length - 1; - while (pos >= 0 && Qualifiers.Contains(text[pos])) - { - pos--; - } + return _constantExpressionHelper.CreateLiteral((int)value, textOriginal); + } - if (pos < text.Length - 1) - { - qualifier = text[pos + 1]; - text = text.Substring(0, pos + 1); - } - } + return _constantExpressionHelper.CreateLiteral(value, textOriginal); + } - switch (qualifier) + /// + /// Parse the text into a Real ConstantExpression. + /// + public Expression ParseRealLiteral(string text, string textOriginal, char qualifier, bool stripQualifier) + { + if (stripQualifier) + { + var pos = text.Length - 1; + while (pos >= 0 && Qualifiers.Contains(text[pos])) { - case 'f': - case 'F': - return _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(float))!, text); - - case 'm': - case 'M': - return _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(decimal))!, text); - - case 'd': - case 'D': - return _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(double))!, text); + pos--; + } - default: - return _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(double))!, text); + if (pos < text.Length - 1) + { + qualifier = text[pos + 1]; + text = text.Substring(0, pos + 1); } } - /// - /// Tries to parse the number (text) into the specified type. - /// - /// The text. - /// The type. - /// The result. - public bool TryParseNumber(string text, Type type, out object? result) + return qualifier switch { - result = ParseNumber(text, type); - return result != null; - } + 'f' or 'F' => _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(float))!, textOriginal), + 'm' or 'M' => _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(decimal))!, textOriginal), + _ => _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(double))!, textOriginal) + }; + } - /// - /// Parses the number (text) into the specified type. - /// - /// The text. - /// The type. - public object? ParseNumber(string text, Type type) + /// + /// Tries to parse the number (text) into the specified type. + /// + /// The text. + /// The type. + /// The result. + public bool TryParseNumber(string text, Type type, out object? result) + { + result = ParseNumber(text, type); + return result != null; + } + + /// + /// Parses the number (text) into the specified type. + /// + /// The text. + /// The type. + public object? ParseNumber(string text, Type type) + { + try { - try - { #if !(UAP10_0 || NETSTANDARD) - switch (Type.GetTypeCode(TypeHelper.GetNonNullableType(type))) - { - case TypeCode.SByte: - return sbyte.Parse(text, _culture); - case TypeCode.Byte: - return byte.Parse(text, _culture); - case TypeCode.Int16: - return short.Parse(text, _culture); - case TypeCode.UInt16: - return ushort.Parse(text, _culture); - case TypeCode.Int32: - return int.Parse(text, _culture); - case TypeCode.UInt32: - return uint.Parse(text, _culture); - case TypeCode.Int64: - return long.Parse(text, _culture); - case TypeCode.UInt64: - return ulong.Parse(text, _culture); - case TypeCode.Single: - return float.Parse(text, _culture); - case TypeCode.Double: - return double.Parse(text, _culture); - case TypeCode.Decimal: - return decimal.Parse(text, _culture); - } + switch (Type.GetTypeCode(TypeHelper.GetNonNullableType(type))) + { + case TypeCode.SByte: + return sbyte.Parse(text, _culture); + case TypeCode.Byte: + return byte.Parse(text, _culture); + case TypeCode.Int16: + return short.Parse(text, _culture); + case TypeCode.UInt16: + return ushort.Parse(text, _culture); + case TypeCode.Int32: + return int.Parse(text, _culture); + case TypeCode.UInt32: + return uint.Parse(text, _culture); + case TypeCode.Int64: + return long.Parse(text, _culture); + case TypeCode.UInt64: + return ulong.Parse(text, _culture); + case TypeCode.Single: + return float.Parse(text, _culture); + case TypeCode.Double: + return double.Parse(text, _culture); + case TypeCode.Decimal: + return decimal.Parse(text, _culture); + } #else var tp = TypeHelper.GetNonNullableType(type); if (tp == typeof(sbyte)) @@ -289,28 +279,27 @@ public bool TryParseNumber(string text, Type type, out object? result) return decimal.Parse(text, _culture); } #endif - } - catch - { - return null; - } - + } + catch + { return null; } - private Expression ParseAsBinary(int tokenPosition, string text, bool isNegative) - { - if (RegexBinary32.IsMatch(text)) - { - return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt32(text, 2), text); - } + return null; + } - if (RegexBinary64.IsMatch(text)) - { - return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt64(text, 2), text); - } + private Expression ParseAsBinary(int tokenPosition, string text, string textOriginal, bool isNegative) + { + if (RegexBinary32.IsMatch(text)) + { + return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt32(text, 2), textOriginal); + } - throw new ParseException(string.Format(_culture, Res.InvalidBinaryIntegerLiteral, text), tokenPosition); + if (RegexBinary64.IsMatch(text)) + { + return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt64(text, 2), textOriginal); } + + throw new ParseException(string.Format(_culture, Res.InvalidBinaryIntegerLiteral, text), tokenPosition); } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 4d5d19b5..70d5ce68 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -924,6 +924,7 @@ public void DynamicExpressionParser_ParseLambda_Float(string? culture, string ex [null, "1.2345E4", 12345d] ]; } + [Theory] [MemberData(nameof(Doubles))] public void DynamicExpressionParser_ParseLambda_Double(string? culture, string expression, double expected) @@ -945,6 +946,42 @@ public void DynamicExpressionParser_ParseLambda_Double(string? culture, string e result.Should().Be(expected); } + [Theory] + [InlineData("0x0", 0)] + [InlineData("0xa", 10)] + [InlineData("0xA", 10)] + [InlineData("0x10", 16)] + public void DynamicExpressionParser_ParseLambda_HexToLong(string expression, long expected) + { + // Arrange + var parameters = Array.Empty(); + + // Act + var lambda = DynamicExpressionParser.ParseLambda( parameters, typeof(long), expression); + var result = lambda.Compile().DynamicInvoke(); + + // Assert + result.Should().Be(expected); + } + + [Theory] + [InlineData("0b0", 0)] + [InlineData("0B0", 0)] + [InlineData("0b1000", 8)] + [InlineData("0b1001", 9)] + public void DynamicExpressionParser_ParseLambda_BinaryToLong(string expression, long expected) + { + // Arrange + var parameters = Array.Empty(); + + // Act + var lambda = DynamicExpressionParser.ParseLambda(parameters, typeof(long), expression); + var result = lambda.Compile().DynamicInvoke(); + + // Assert + result.Should().Be(expected); + } + public class EntityDbo { public string Name { get; set; } = string.Empty; @@ -1711,9 +1748,9 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexEx } [Theory] - [InlineData(true, "c => c.Age == 8", "c => (c.Age == 8)")] + [InlineData(true, "c => c.Age == 8", "c => (c.Age == Convert(8, Nullable`1))")] [InlineData(true, "c => c.Name == \"test\"", "c => (c.Name == \"test\")")] - [InlineData(false, "c => c.Age == 8", "Param_0 => (Param_0.Age == 8)")] + [InlineData(false, "c => c.Age == 8", "Param_0 => (Param_0.Age == Convert(8, Nullable`1))")] [InlineData(false, "c => c.Name == \"test\"", "Param_0 => (Param_0.Name == \"test\")")] public void DynamicExpressionParser_ParseLambda_RenameParameterExpression(bool renameParameterExpression, string expressionAsString, string expected) { @@ -1732,7 +1769,7 @@ public void DynamicExpressionParser_ParseLambda_RenameParameterExpression(bool r } [Theory] - [InlineData("c => c.Age == 8", "([a-z]{16}) =\\> \\(\\1\\.Age == 8\\)")] + [InlineData("c => c.Age == 8", "([a-z]{16}) =\\> \\(\\1\\.Age == .+")] [InlineData("c => c.Name == \"test\"", "([a-z]{16}) =\\> \\(\\1\\.Name == \"test\"\\)")] public void DynamicExpressionParser_ParseLambda_RenameEmptyParameterExpressionNames(string expressionAsString, string expected) { diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/NumberParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/NumberParserTests.cs index e291c34e..f8ccc496 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/NumberParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/NumberParserTests.cs @@ -159,7 +159,7 @@ public void NumberParser_ParseIntegerLiteral(string text, double expected) public void NumberParser_ParseDecimalLiteral(string text, char qualifier, decimal expected) { // Act - var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, qualifier, true) as ConstantExpression; + var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, text, qualifier, true) as ConstantExpression; // Assert result?.Value.Should().Be(expected); @@ -175,7 +175,7 @@ public void NumberParser_ParseDoubleLiteral(string text, char qualifier, double // Arrange // Act - var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, qualifier, true) as ConstantExpression; + var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, text, qualifier, true) as ConstantExpression; // Assert result?.Value.Should().Be(expected); @@ -191,7 +191,7 @@ public void NumberParser_ParseFloatLiteral(string text, char qualifier, float ex // Arrange // Act - var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, qualifier, true) as ConstantExpression; + var result = new NumberParser(_parsingConfig).ParseRealLiteral(text, text, qualifier, true) as ConstantExpression; // Assert result?.Value.Should().Be(expected);