From bc5d10a06d0ff363903cedfaa43ce5566b3ac164 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 30 Nov 2023 20:45:26 +0100 Subject: [PATCH 1/6] Update function argument parsing for strings (part 2) --- .../Parser/ExpressionParser.cs | 2 +- .../DynamicExpressionParserTests.cs | 84 ++++++++++++++++++- .../Helpers/Models/User.cs | 6 ++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 88f498ad..8cf4abf0 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -917,7 +917,7 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) // While the next token is also a string, keep concatenating these strings and get next token while (_textParser.CurrentToken.Id == TokenId.StringLiteral) { - stringValue += _textParser.CurrentToken.Text; + stringValue += StringParser.ParseString(_textParser.CurrentToken.Text); _textParser.NextToken(); } diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 82d987c1..8a437e2b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -256,6 +256,11 @@ public override string ToString() } } + public class SqlExpression + { + public Expression>? Filter { get; set; } + } + public static class StaticHelper { public static Guid? GetGuid(string name) @@ -267,11 +272,65 @@ public static string Filter(string filter) { return filter; } + + public static SqlExpression SubSelect(string columnName, string objectClassName, string? filter, string order) + { + Expression>? expFilter = null; + + if (filter != null) + { + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); // Failed Here! + } + + return new SqlExpression + { + Filter = expFilter + }; + } + + public static bool In(Guid value, SqlExpression expression) + { + return value != Guid.Empty; + } + + public static Guid First(SqlExpression sqlExpression) + { + return Guid.NewGuid(); + } + + public static string ToExpressionString(Guid? value, int subQueryLevel) + { + if (value == null) + { + return "NULL"; + } + + var quote = GetQuote(subQueryLevel); + return $"Guid.Parse({quote}{value}{quote})"; + } + + public static Guid Get(string settingName) + { + return Guid.NewGuid(); + } + + private static string GetQuote(int subQueryLevel) + { + var quoteCount = (int)Math.Pow(2, subQueryLevel - 1); + + var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); + return quote; + } } public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider { - private HashSet _customTypes; + private HashSet? _customTypes; public virtual HashSet GetCustomTypes() { @@ -1438,6 +1497,29 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio resultUserName.Should().Be(@"UserName == ""x"""""""); } + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpressionString() + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + var user = new User(); + + // Act + var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = "" + StaticHelper.ToExpressionString(StaticHelper.Get(""CurrentPlace""), 2) + """""", """"""""))"", """"))"; + var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); + var func = (Expression>)lambda; + + var compile = func.Compile(); + // var result = (bool?)compile.DynamicInvoke(user); + + // Assert + // result.Should().Be(@"UserName == ""x"""""""); + } + [Theory] [InlineData(true, "c => c.Age == 8", "c => (c.Age == 8)")] [InlineData(true, "c => c.Name == \"test\"", "c => (c.Name == \"test\")")] diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs index 8b6bc5a9..d84f5eaa 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs @@ -6,6 +6,12 @@ public class User { public Guid Id { get; set; } + public Guid? ParentId { get; set; } + + public Guid? LegalPersonId { get; set; } + + public Guid? PointSiteTD { get; set; } + public SnowflakeId SnowflakeId { get; set; } public string UserName { get; set; } From 8895e091fedbd540e3a468302cb50da96b034a3e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 30 Nov 2023 21:04:45 +0100 Subject: [PATCH 2/6] . --- .../Parser/ExpressionParser.cs | 19 +++++++++++-------- .../DynamicExpressionParserTests.cs | 6 +++--- version.xml | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 8cf4abf0..9efee3e2 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -889,25 +889,26 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) { _textParser.ValidateToken(TokenId.StringLiteral); - var stringValue = StringParser.ParseString(_textParser.CurrentToken.Text); + var text = _textParser.CurrentToken.Text; + var parsedStringValue = StringParser.ParseString(_textParser.CurrentToken.Text); if (_textParser.CurrentToken.Text[0] == '\'') { - if (stringValue.Length > 1) + if (parsedStringValue.Length > 1) { throw ParseError(Res.InvalidCharacterLiteral); } _textParser.NextToken(); - return ConstantExpressionHelper.CreateLiteral(stringValue[0], stringValue); + return ConstantExpressionHelper.CreateLiteral(parsedStringValue[0], parsedStringValue); } _textParser.NextToken(); - if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && stringValue.Length > 2 && stringValue.Contains('.')) + if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && parsedStringValue.Length > 2 && parsedStringValue.Contains('.')) { // Try to resolve this string as a type - var type = _typeFinder.FindTypeByName(stringValue, null, false); + var type = _typeFinder.FindTypeByName(parsedStringValue, null, false); if (type is { }) { return type; @@ -917,11 +918,13 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) // While the next token is also a string, keep concatenating these strings and get next token while (_textParser.CurrentToken.Id == TokenId.StringLiteral) { - stringValue += StringParser.ParseString(_textParser.CurrentToken.Text); + text += _textParser.CurrentToken.Text; _textParser.NextToken(); } - - return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue); + + parsedStringValue = StringParser.ParseString(text); + + return ConstantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); } private Expression ParseIntegerLiteral() diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 8a437e2b..b8f507e8 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1464,7 +1464,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var user = new User(); // Act : char - var expressionTextChar = "StaticHelper.Filter(\"C == 'x'\")"; + var expressionTextChar = "StaticHelper.Filter(\"C == 'c'\")"; var lambdaChar = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextChar, user); var funcChar = (Expression>)lambdaChar; @@ -1472,7 +1472,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var resultChar = (string?)delegateChar.DynamicInvoke(user); // Assert : int - resultChar.Should().Be("C == 'x'"); + resultChar.Should().Be("C == 'c'"); // Act : int var expressionTextIncome = "StaticHelper.Filter(\"Income == 5\")"; @@ -1494,7 +1494,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var resultUserName = (string?)delegateUserName.DynamicInvoke(user); // Assert : string - resultUserName.Should().Be(@"UserName == ""x"""""""); + resultUserName.Should().Be(@"UserName == """"x"""""); } [Fact] diff --git a/version.xml b/version.xml index 4fe8065c..e880d021 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 7 + 8-preview-03 \ No newline at end of file From 55da37587e810a738bd56bc7706205593b126653 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 2 Dec 2023 09:31:34 +0100 Subject: [PATCH 3/6] move test classes --- .../DynamicExpressionParserTests.cs | 157 +++--------------- .../CustomClassWithStaticMethod.cs | 7 + .../TestClasses/StaticHelper.cs | 72 ++++++++ .../TestClasses/StaticHelperSqlExpression.cs | 10 ++ .../TestClasses/TestCustomTypeProvider.cs | 55 ++++++ 5 files changed, 171 insertions(+), 130 deletions(-) create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index b8f507e8..9b6fa0ff 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -6,7 +6,6 @@ using System.Linq.Dynamic.Core.Tests.TestHelpers; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; using FluentAssertions; using Moq; using NFluent; @@ -77,11 +76,6 @@ public class ComplexParseLambda3Result public int TotalIncome { get; set; } } - public class CustomClassWithStaticMethod - { - public static int GetAge(int x) => x; - } - public class CustomClassWithMethod { public int GetAge(int x) => x; @@ -121,7 +115,7 @@ public CustomTextClass(string origin) public static implicit operator string(CustomTextClass customTextValue) { - return customTextValue?.Origin; + return customTextValue.Origin; } public static implicit operator CustomTextClass(string origin) @@ -256,126 +250,6 @@ public override string ToString() } } - public class SqlExpression - { - public Expression>? Filter { get; set; } - } - - public static class StaticHelper - { - public static Guid? GetGuid(string name) - { - return Guid.NewGuid(); - } - - public static string Filter(string filter) - { - return filter; - } - - public static SqlExpression SubSelect(string columnName, string objectClassName, string? filter, string order) - { - Expression>? expFilter = null; - - if (filter != null) - { - var config = new ParsingConfig - { - CustomTypeProvider = new TestCustomTypeProvider() - }; - - expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); // Failed Here! - } - - return new SqlExpression - { - Filter = expFilter - }; - } - - public static bool In(Guid value, SqlExpression expression) - { - return value != Guid.Empty; - } - - public static Guid First(SqlExpression sqlExpression) - { - return Guid.NewGuid(); - } - - public static string ToExpressionString(Guid? value, int subQueryLevel) - { - if (value == null) - { - return "NULL"; - } - - var quote = GetQuote(subQueryLevel); - return $"Guid.Parse({quote}{value}{quote})"; - } - - public static Guid Get(string settingName) - { - return Guid.NewGuid(); - } - - private static string GetQuote(int subQueryLevel) - { - var quoteCount = (int)Math.Pow(2, subQueryLevel - 1); - - var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); - return quote; - } - } - - public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider - { - private HashSet? _customTypes; - - public virtual HashSet GetCustomTypes() - { - if (_customTypes != null) - { - return _customTypes; - } - - _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })) - { - typeof(CustomClassWithStaticMethod), - typeof(StaticHelper) - }; - return _customTypes; - } - - public Dictionary> GetExtensionMethods() - { - var types = GetCustomTypes(); - - var list = new List>(); - - foreach (var type in types) - { - var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList(); - - extensionMethods.ForEach(x => list.Add(new Tuple(x.GetParameters()[0].ParameterType, x))); - } - - return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList()); - } - - public Type ResolveType(string typeName) - { - return Type.GetType(typeName); - } - - public Type ResolveTypeBySimpleName(string typeName) - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - return ResolveTypeBySimpleName(assemblies, typeName); - } - } - [Fact] public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false_String() { @@ -1498,7 +1372,30 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio } [Fact] - public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpressionString() + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression1String() + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + var user = new User(); + + // Act + var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = 5"""", """"""""))"", """"))"; + var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); + var func = (Expression>)lambda; + + var compile = func.Compile(); + var result = (bool?)compile.DynamicInvoke(user); + + // Assert + result.Should().Be(false); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression2String() { // Arrange var config = new ParsingConfig @@ -1514,10 +1411,10 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexEx var func = (Expression>)lambda; var compile = func.Compile(); - // var result = (bool?)compile.DynamicInvoke(user); + var result = (bool?)compile.DynamicInvoke(user); // Assert - // result.Should().Be(@"UserName == ""x"""""""); + result.Should().Be(false); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs new file mode 100644 index 00000000..ab6bad96 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs @@ -0,0 +1,7 @@ +namespace System.Linq.Dynamic.Core.Tests +{ + public class CustomClassWithStaticMethod + { + public static int GetAge(int x) => x; + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs new file mode 100644 index 00000000..4c0e4438 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs @@ -0,0 +1,72 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Tests +{ + public static class StaticHelper + { + public static Guid? GetGuid(string name) + { + return Guid.NewGuid(); + } + + public static string Filter(string filter) + { + return filter; + } + + public static StaticHelperSqlExpression SubSelect(string columnName, string objectClassName, string? filter, string order) + { + Expression>? expFilter = null; + + if (filter != null) + { + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); // Failed Here! + } + + return new StaticHelperSqlExpression + { + Filter = expFilter + }; + } + + public static bool In(Guid? value, StaticHelperSqlExpression expression) + { + return value != Guid.Empty; + } + + public static Guid First(StaticHelperSqlExpression staticHelperSqlExpression) + { + return Guid.NewGuid(); + } + + public static string ToExpressionString(Guid? value, int subQueryLevel) + { + if (value == null) + { + return "NULL"; + } + + var quote = GetQuote(subQueryLevel); + return $"Guid.Parse({quote}{value}{quote})"; + } + + public static Guid Get(string settingName) + { + return Guid.NewGuid(); + } + + private static string GetQuote(int subQueryLevel) + { + var quoteCount = (int)Math.Pow(2, subQueryLevel - 1); + + var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); + return quote; + } + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs new file mode 100644 index 00000000..d381a240 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs @@ -0,0 +1,10 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class StaticHelperSqlExpression + { + public Expression>? Filter { get; set; } + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs new file mode 100644 index 00000000..ccd6be9e --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider + { + private HashSet? _customTypes; + + public virtual HashSet GetCustomTypes() + { + if (_customTypes != null) + { + return _customTypes; + } + + _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })) + { + typeof(CustomClassWithStaticMethod), + typeof(StaticHelper) + }; + return _customTypes; + } + + public Dictionary> GetExtensionMethods() + { + var types = GetCustomTypes(); + + var list = new List>(); + + foreach (var type in types) + { + var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList(); + + extensionMethods.ForEach(x => list.Add(new Tuple(x.GetParameters()[0].ParameterType, x))); + } + + return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList()); + } + + public Type ResolveType(string typeName) + { + return Type.GetType(typeName)!; + } + + public Type ResolveTypeBySimpleName(string typeName) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + return ResolveTypeBySimpleName(assemblies, typeName)!; + } + } +} \ No newline at end of file From 125e712c695e9575577344baab2e86146d096ff6 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 2 Dec 2023 11:18:50 +0100 Subject: [PATCH 4/6] ... --- .../Parser/ExpressionParser.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 9efee3e2..3a7966ff 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -2173,15 +2173,23 @@ private Expression[] ParseArgumentList() { _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); _textParser.NextToken(); - Expression[] args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + + var args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); _textParser.NextToken(); + return args; } + private bool _IsParsingArguments; + private Expression[] ParseArguments() { var argList = new List(); + + _IsParsingArguments = true; + while (true) { var argumentExpression = ParseOutKeyword(); @@ -2198,6 +2206,8 @@ private Expression[] ParseArguments() _textParser.NextToken(); } + _IsParsingArguments = false; + return argList.ToArray(); } From 801955b88e3b400acad5a1314605096af69fd0c1 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 3 Dec 2023 13:18:53 +0100 Subject: [PATCH 5/6] ok? --- System.Linq.Dynamic.Core.sln.DotSettings | 1 + src/Directory.Build.props | 2 +- .../Parser/ExpressionParser.cs | 8 +- .../Parser/StringParser.cs | 81 ++++++++++++------- .../DynamicExpressionParserTests.cs | 2 +- .../Parser/StringParserTests.cs | 2 +- .../TestClasses/StaticHelper.cs | 5 +- 7 files changed, 58 insertions(+), 43 deletions(-) diff --git a/System.Linq.Dynamic.Core.sln.DotSettings b/System.Linq.Dynamic.Core.sln.DotSettings index bc3691ec..fe4bacee 100644 --- a/System.Linq.Dynamic.Core.sln.DotSettings +++ b/System.Linq.Dynamic.Core.sln.DotSettings @@ -6,4 +6,5 @@ True True True + True True \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bc2081c3..e34f3e2a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -7,7 +7,7 @@ Copyright © ZZZ Projects en-us true - 11 + latest enable logo.png PackageReadme.md diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 3a7966ff..7e81a3ea 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -922,7 +922,7 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) _textParser.NextToken(); } - parsedStringValue = StringParser.ParseString(text); + parsedStringValue = StringParser.ParseStringAndReplaceDoubleQuotes(text, _textParser.CurrentToken.Pos); return ConstantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); } @@ -2182,14 +2182,10 @@ private Expression[] ParseArgumentList() return args; } - private bool _IsParsingArguments; - private Expression[] ParseArguments() { var argList = new List(); - _IsParsingArguments = true; - while (true) { var argumentExpression = ParseOutKeyword(); @@ -2206,8 +2202,6 @@ private Expression[] ParseArguments() _textParser.NextToken(); } - _IsParsingArguments = false; - return argList.ToArray(); } diff --git a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs index 3f22573d..18dcb2b2 100644 --- a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs @@ -2,40 +2,59 @@ using System.Linq.Dynamic.Core.Exceptions; using System.Text.RegularExpressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +/// +/// Parse a Double and Single Quoted string. +/// Some parts of the code is based on https://github.com/zzzprojects/Eval-Expression.NET +/// +internal static class StringParser { - /// - /// Parse a Double and Single Quoted string. - /// Some parts of the code is based on https://github.com/zzzprojects/Eval-Expression.NET - /// - internal static class StringParser + private const string Pattern = @""""""; + private const string Replacement = "\""; + + public static string ParseString(string s, int pos = default) { - public static string ParseString(string s) + if (s == null || s.Length < 2) + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringLength, s, 2), pos); + } + + if (s[0] != '"' && s[0] != '\'') + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringQuoteCharacter), pos); + } + + char quote = s[0]; // This can be single or a double quote + if (s.Last() != quote) + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, s.Length, s), pos); + } + + try + { + return Regex.Unescape(s.Substring(1, s.Length - 2)); + } + catch (Exception ex) + { + throw new ParseException(ex.Message, pos, ex); + } + } + + public static string ParseStringAndReplaceDoubleQuotes(string s, int pos) + { + return ReplaceDoubleQuotes(ParseString(s, pos), pos); + } + + private static string ReplaceDoubleQuotes(string s, int pos) + { + try + { + return Regex.Replace(s, Pattern, Replacement); + } + catch (Exception ex) { - if (s == null || s.Length < 2) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringLength, s, 2), 0); - } - - if (s[0] != '"' && s[0] != '\'') - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringQuoteCharacter), 0); - } - - char quote = s[0]; // This can be single or a double quote - if (s.Last() != quote) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, s.Length, s), s.Length); - } - - try - { - return Regex.Unescape(s.Substring(1, s.Length - 2)); - } - catch (Exception ex) - { - throw new ParseException(ex.Message, 0, ex); - } + throw new ParseException(ex.Message, pos, ex); } } } \ 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 9b6fa0ff..fffdcca4 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1368,7 +1368,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var resultUserName = (string?)delegateUserName.DynamicInvoke(user); // Assert : string - resultUserName.Should().Be(@"UserName == """"x"""""); + resultUserName.Should().Be(@"UserName == ""x"""); } [Fact] diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index af42df75..cc8f782d 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -58,7 +58,7 @@ public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsExcepti parseException.Which.InnerException!.Message.Should().Contain("hexadecimal digits"); - parseException.Which.StackTrace.Should().Contain("at System.Linq.Dynamic.Core.Parser.StringParser.ParseString(String s) in ").And.Contain("System.Linq.Dynamic.Core\\Parser\\StringParser.cs:line "); + parseException.Which.StackTrace.Should().Contain("at System.Linq.Dynamic.Core.Parser.StringParser.ParseString(String s, Int32 pos) in ").And.Contain("System.Linq.Dynamic.Core\\Parser\\StringParser.cs:line "); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs index 4c0e4438..464fcdcd 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs @@ -65,8 +65,9 @@ private static string GetQuote(int subQueryLevel) { var quoteCount = (int)Math.Pow(2, subQueryLevel - 1); - var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); - return quote; + //var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); + //return quote; + return new string('"', quoteCount); } } } \ No newline at end of file From 18a87e14b597512debe33378f105abbbc2476c44 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 3 Dec 2023 13:30:12 +0100 Subject: [PATCH 6/6] 8-preview-04 --- version.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.xml b/version.xml index e880d021..9f0743a9 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 8-preview-03 + 8-preview-04 \ No newline at end of file