From 0a6f8eff2c3bde6b2e17f85284b40b48bfc37f76 Mon Sep 17 00:00:00 2001 From: Michael Bilodeau Date: Wed, 29 Mar 2017 14:10:42 -0700 Subject: [PATCH 1/3] Adding support for embedded quotes. --- .../DynamicExpressionTests.cs | 175 ++++++++++++++++++ .../System.Linq.Dynamic.Test.csproj | 4 +- Src/System.Linq.Dynamic/DynamicLinq.cs | 26 ++- 3 files changed, 194 insertions(+), 11 deletions(-) diff --git a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs index 8617299..be3ab2b 100644 --- a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs +++ b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs @@ -1,3 +1,37 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading; +using System.Globalization; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Test +{ + [TestClass] + public class DynamicExpressionCultureTests + { + [TestInitialize] + public void Initialize() + { + Thread.CurrentThread.CurrentCulture = new CultureInfo("pt-PT"); + } + + [TestMethod] + public void Parse_DoubleLiteral_ReturnsDoubleExpression() + { + var expression = (ConstantExpression)DynamicExpression.Parse(typeof(double), "1.0"); + Assert.AreEqual(typeof(double), expression.Type); + Assert.AreEqual(1.0, expression.Value); + } + + [TestMethod] + public void Parse_FloatLiteral_ReturnsFloatExpression() + { + var expression = (ConstantExpression)DynamicExpression.Parse(typeof(float), "1.0f"); + Assert.AreEqual(typeof(float), expression.Type); + Assert.AreEqual(1.0f, expression.Value); + } + } +} using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; @@ -28,6 +62,73 @@ public void Parse_TupleToStringMethodCall_ReturnsStringLambdaExpression() Assert.AreEqual(typeof(string), expression.ReturnType); } + [TestMethod] + public void Parse_StringLiteral_ReturnsBooleanLambdaExpression() + { + var expression = DynamicExpression.Parse(new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), "Property1 == \"test\""); + Assert.AreEqual(typeof(Boolean), expression.Type); + } + + [TestMethod] + public void Parse_StringLiteralEmpty_ReturnsBooleanLambdaExpression() + { + var expression = DynamicExpression.Parse(new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), "Property1 == \"\""); + Assert.AreEqual(typeof(Boolean), expression.Type); + } + + [TestMethod] + public void Parse_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression() + { + string expectedRightValue = "\"test \\\"string\""; + var expression = DynamicExpression.Parse( + new[] { Expression.Parameter(typeof(string), "Property1") }, + typeof(Boolean), + string.Format("Property1 == {0}", expectedRightValue)); + + string rightValue = ((BinaryExpression) expression).Right.ToString(); + Assert.AreEqual(typeof(Boolean), expression.Type); + Assert.AreEqual(expectedRightValue, rightValue); + } + + [TestMethod] + public void Parse_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression() + { + string expectedRightValue = "\"\\\"test\""; + var expression = DynamicExpression.Parse( + new[] { Expression.Parameter(typeof(string), "Property1") }, + typeof(Boolean), + string.Format("Property1 == {0}", expectedRightValue)); + + string rightValue = ((BinaryExpression)expression).Right.ToString(); + Assert.AreEqual(typeof(Boolean), expression.Type); + Assert.AreEqual(expectedRightValue, rightValue); + } + + [ExpectedException(typeof(ParseException))] + [TestMethod] + public void Parse_StringLiteral_MissingClosingQuote() + { + string expectedRightValue = "\"test\\\""; + var expression = DynamicExpression.Parse( + new[] { Expression.Parameter(typeof(string), "Property1") }, + typeof(Boolean), + string.Format("Property1 == {0}", expectedRightValue)); + } + + [TestMethod] + public void Parse_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpression() + { + string expectedRightValue = "\"test\\string\""; + var expression = DynamicExpression.Parse( + new[] { Expression.Parameter(typeof(string), "Property1") }, + typeof(Boolean), + string.Format("Property1 == {0}", expectedRightValue)); + + string rightValue = ((BinaryExpression)expression).Right.ToString(); + Assert.AreEqual(typeof(Boolean), expression.Type); + Assert.AreEqual(expectedRightValue, rightValue); + } + [TestMethod] public void ParseLambda_DelegateTypeMethodCall_ReturnsEventHandlerLambdaExpression() { @@ -71,3 +172,77 @@ public void CreateClass_TheadSafe() } } } + + + + Debug + AnyCPU + + + 2.0 + {66D3514B-0E04-4C85-879D-67E15FBD0E37} + Library + Properties + System.Linq.Dynamic.Test + System.Linq.Dynamic.Test + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + + + + 3.5 + + + + + False + + + + + + + + + + {b6edf157-6153-4684-a577-de33896dbaa8} + System.Linq.Dynamic + + + + + {b6edf157-6153-4684-a577-de33896dbaa8} + System.Linq.Dynamic + + + + + \ No newline at end of file diff --git a/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj b/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj index ad1eb1d..1b46859 100644 --- a/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj +++ b/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj @@ -33,7 +33,9 @@ 4 - + + False + 3.5 diff --git a/Src/System.Linq.Dynamic/DynamicLinq.cs b/Src/System.Linq.Dynamic/DynamicLinq.cs index 8a87987..4382741 100644 --- a/Src/System.Linq.Dynamic/DynamicLinq.cs +++ b/Src/System.Linq.Dynamic/DynamicLinq.cs @@ -1167,14 +1167,7 @@ Expression ParseStringLiteral() ValidateToken(TokenId.StringLiteral); char quote = token.text[0]; string s = token.text.Substring(1, token.text.Length - 2); - int start = 0; - while (true) - { - int i = s.IndexOf(quote, start); - if (i < 0) break; - s = s.Remove(i, 1); - start = i + 1; - } + if (quote == '\'') { if (s.Length != 1) @@ -2327,8 +2320,21 @@ void NextToken() char quote = ch; do { - NextChar(); - while (textPos < textLen && ch != quote) NextChar(); + bool escaped; + + do + { + escaped = false; + NextChar(); + + if (ch == '\\') + { + escaped = true; + if (textPos < textLen) NextChar(); + } + } + while (textPos < textLen && (ch != quote || escaped)); + if (textPos == textLen) throw ParseError(textPos, Res.UnterminatedStringLiteral); NextChar(); From 12adf298b0a3e9df5074c9c11f9cf3448a12309c Mon Sep 17 00:00:00 2001 From: Michael Bilodeau Date: Wed, 29 Mar 2017 14:19:29 -0700 Subject: [PATCH 2/3] Fixing a bad manual merge. --- .../DynamicExpressionTests.cs | 108 ------------------ 1 file changed, 108 deletions(-) diff --git a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs index be3ab2b..b1732dd 100644 --- a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs +++ b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs @@ -1,37 +1,3 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Threading; -using System.Globalization; -using System.Linq.Expressions; - -namespace System.Linq.Dynamic.Test -{ - [TestClass] - public class DynamicExpressionCultureTests - { - [TestInitialize] - public void Initialize() - { - Thread.CurrentThread.CurrentCulture = new CultureInfo("pt-PT"); - } - - [TestMethod] - public void Parse_DoubleLiteral_ReturnsDoubleExpression() - { - var expression = (ConstantExpression)DynamicExpression.Parse(typeof(double), "1.0"); - Assert.AreEqual(typeof(double), expression.Type); - Assert.AreEqual(1.0, expression.Value); - } - - [TestMethod] - public void Parse_FloatLiteral_ReturnsFloatExpression() - { - var expression = (ConstantExpression)DynamicExpression.Parse(typeof(float), "1.0f"); - Assert.AreEqual(typeof(float), expression.Type); - Assert.AreEqual(1.0f, expression.Value); - } - } -} using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; @@ -172,77 +138,3 @@ public void CreateClass_TheadSafe() } } } - - - - Debug - AnyCPU - - - 2.0 - {66D3514B-0E04-4C85-879D-67E15FBD0E37} - Library - Properties - System.Linq.Dynamic.Test - System.Linq.Dynamic.Test - v4.0 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - - - - 3.5 - - - - - False - - - - - - - - - - {b6edf157-6153-4684-a577-de33896dbaa8} - System.Linq.Dynamic - - - - - {b6edf157-6153-4684-a577-de33896dbaa8} - System.Linq.Dynamic - - - - - \ No newline at end of file From 248d7b231b1d9b0802f5e616886c1821ddbb3d11 Mon Sep 17 00:00:00 2001 From: Michael Bilodeau Date: Fri, 21 Apr 2017 11:05:46 -0700 Subject: [PATCH 3/3] Fixing code to replace escaped sequences when the parser creates the associated string literals. --- .../DynamicExpressionTests.cs | 15 +-- .../IntegrationTests.cs | 121 ++++++++++++++++++ .../System.Linq.Dynamic.Test.csproj | 11 +- Src/System.Linq.Dynamic/DynamicLinq.cs | 38 +++++- 4 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 Src/System.Linq.Dynamic.Test/IntegrationTests.cs diff --git a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs index b1732dd..b12e5d0 100644 --- a/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs +++ b/Src/System.Linq.Dynamic.Test/DynamicExpressionTests.cs @@ -45,29 +45,27 @@ public void Parse_StringLiteralEmpty_ReturnsBooleanLambdaExpression() [TestMethod] public void Parse_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression() { - string expectedRightValue = "\"test \\\"string\""; var expression = DynamicExpression.Parse( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), - string.Format("Property1 == {0}", expectedRightValue)); + string.Format("Property1 == {0}", "\"test \\\"string\"")); string rightValue = ((BinaryExpression) expression).Right.ToString(); Assert.AreEqual(typeof(Boolean), expression.Type); - Assert.AreEqual(expectedRightValue, rightValue); + Assert.AreEqual("\"test \"string\"", rightValue); } [TestMethod] public void Parse_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression() { - string expectedRightValue = "\"\\\"test\""; var expression = DynamicExpression.Parse( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), - string.Format("Property1 == {0}", expectedRightValue)); + string.Format("Property1 == {0}", "\"\\\"test\"")); string rightValue = ((BinaryExpression)expression).Right.ToString(); Assert.AreEqual(typeof(Boolean), expression.Type); - Assert.AreEqual(expectedRightValue, rightValue); + Assert.AreEqual("\"\"test\"", rightValue); } [ExpectedException(typeof(ParseException))] @@ -84,15 +82,14 @@ public void Parse_StringLiteral_MissingClosingQuote() [TestMethod] public void Parse_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpression() { - string expectedRightValue = "\"test\\string\""; var expression = DynamicExpression.Parse( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), - string.Format("Property1 == {0}", expectedRightValue)); + string.Format("Property1 == {0}", "\"test\\\\string\"")); string rightValue = ((BinaryExpression)expression).Right.ToString(); Assert.AreEqual(typeof(Boolean), expression.Type); - Assert.AreEqual(expectedRightValue, rightValue); + Assert.AreEqual("\"test\\string\"", rightValue); } [TestMethod] diff --git a/Src/System.Linq.Dynamic.Test/IntegrationTests.cs b/Src/System.Linq.Dynamic.Test/IntegrationTests.cs new file mode 100644 index 0000000..8557baf --- /dev/null +++ b/Src/System.Linq.Dynamic.Test/IntegrationTests.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System.Linq.Dynamic; + +namespace System.Linq.Dynamic.Test +{ + [TestClass] + public class IntegrationTests + { + private class TestObject + { + public int Id { get; set; } + public string Color { get; set; } + public int Number { get; set; } + } + + private IList GetTestData() + { + return new List() + { + new TestObject() {Id = 0, Color = "Red", Number = 10 }, + new TestObject() {Id = 1, Color = "Red", Number = 20 }, + new TestObject() {Id = 2, Color = "\"Bright\" Red", Number = 30 }, + new TestObject() {Id = 3, Color = "Blue", Number = 5 }, + new TestObject() {Id = 4, Color = "Brown", Number = 15 }, + new TestObject() {Id = 5, Color = "\"Faded\" Blue", Number = 25 }, + new TestObject() {Id = 6, Color = "Blue\\Green", Number = 30 }, + new TestObject() {Id = 7, Color = "Yellow\tOrange", Number = 35 }, + new TestObject() {Id = 8, Color = "Bright Orange", Number = 30 }, + new TestObject() {Id = 9, Color = "Faded\u0009Orange", Number = 40 } + }; + } + + private bool TestItemsIncluded(IList data, IEnumerable expectedIds) + { + Assert.AreEqual(expectedIds.Count(), data.Count(), "Unexpected number of items returned."); + + foreach (int id in expectedIds) + { + var results = data.Where(x => x.Id == id); + Assert.IsTrue(results.Count() > 0, string.Format("Expected item {0} to be included.", id)); + } + + return true; + } + + [TestMethod] + public void Where_StringEquality() + { + var testData = GetTestData(); + var testResults = testData.Where("Color == \"Red\"").ToList(); + + TestItemsIncluded(testResults, new List() { 0, 1 }); + } + + [TestMethod] + public void Where_StringContains() + { + var testData = GetTestData(); + var testResults = testData.Where("Color.Contains(\"Red\")").ToList(); + + TestItemsIncluded(testResults, new List() { 0, 1, 2 }); + } + + [TestMethod] + public void Where_EscapedStringContains() + { + var testData = GetTestData(); + var testResults = testData.Where("Color.Contains(\"\\\"Bright\\\"\")").ToList(); + + TestItemsIncluded(testResults, new List() { 2 }); + } + + [TestMethod] + public void Where_EscapedStringEquality() + { + var testData = GetTestData(); + var testResults = testData.Where("Color == \"\\\"Faded\\\" Blue\"").ToList(); + + TestItemsIncluded(testResults, new List() { 5 }); + } + + [TestMethod] + [ExpectedException(typeof(System.Linq.Dynamic.ParseException))] + public void Where_InvalidClause() + { + var testData = GetTestData(); + var testResults = testData.Where("Color.Contains(\\\"Bright\\\")").ToList(); + } + + [TestMethod] + public void Where_NumberRange() + { + var testData = GetTestData(); + var testResults = testData.Where("Number <= 25 && Number > 5").ToList(); + + TestItemsIncluded(testResults, new List() { 0, 1, 4, 5 }); + } + + [TestMethod] + public void Where_EscapeChar() + { + var testData = GetTestData(); + var testResults = testData.Where("Color.Contains(\"\\\\\")").ToList(); + + TestItemsIncluded(testResults, new List() { 6 }); + } + + [TestMethod] + public void Where_EscapedTab() + { + var testData = GetTestData(); + var testResults = testData.Where("Color.Contains(\"\\t\")").ToList(); + + TestItemsIncluded(testResults, new List() { 7, 9 }); + } + } +} diff --git a/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj b/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj index 1b46859..9790fa3 100644 --- a/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj +++ b/Src/System.Linq.Dynamic.Test/System.Linq.Dynamic.Test.csproj @@ -11,9 +11,10 @@ Properties System.Linq.Dynamic.Test System.Linq.Dynamic.Test - v4.0 + v4.5 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true @@ -23,6 +24,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -31,6 +33,7 @@ TRACE prompt 4 + false @@ -50,19 +53,21 @@ + {b6edf157-6153-4684-a577-de33896dbaa8} System.Linq.Dynamic - - {b6edf157-6153-4684-a577-de33896dbaa8} System.Linq.Dynamic + + +