From a83fa24e6ef98a4c52228a0bdd9e845aa124695c Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 20 Mar 2022 22:24:52 +0700 Subject: [PATCH 1/5] implicit `this` reference #232. https://github.com/dynamicexpresso/DynamicExpresso/issues/232 --- src/DynamicExpresso.Core/Parsing/Parser.cs | 23 ++++++++++-- .../IdentifiersTest.cs | 36 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index 3a585f7b..36186603 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -1019,12 +1019,31 @@ private Expression ParseIdentifier() { return ParseTypeKeyword(knownType); } - + + var token = _token; + + try + { + if (_arguments.TryGetIdentifier("this", out var thisKeywordExpression)) + { + return ParseMemberAccess(thisKeywordExpression); + } + + if (_arguments.TryGetParameters("this", out var thisParameterExpression)) + { + return ParseMemberAccess(thisParameterExpression); + } + } + catch + { + // ignore + } + // Working context implementation //if (it != null) // return ParseMemberAccess(null, it); - throw new UnknownIdentifierException(_token.text, _token.pos); + throw new UnknownIdentifierException(token.text, token.pos); } // Working context implementation diff --git a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs index 1edc1f83..9d438f12 100644 --- a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -38,5 +39,38 @@ public void Getting_the_list_of_used_identifiers() Assert.AreEqual("x", lambda.Identifiers.ElementAt(0).Name); Assert.AreEqual("true", lambda.Identifiers.ElementAt(1).Name); } + + [Test] + public void This_identifier_variable() + { + const string Name = "John"; + var context = new KeyValuePair(nameof(Name), Name); + + var interpreter = new Interpreter(); + + interpreter.SetVariable("this", context); + + Assert.AreEqual(Name, interpreter.Eval("this.Value")); + Assert.AreEqual(nameof(Name), interpreter.Eval("this.Key")); + + Assert.AreEqual(Name, interpreter.Eval("Value")); + Assert.AreEqual(nameof(Name), interpreter.Eval("Key")); + } + + [Test] + public void This_identifier_parameter() + { + const string Name = "John"; + var context = new KeyValuePair(nameof(Name), Name); + var parameter = new Parameter("this", context.GetType()); + + var interpreter = new Interpreter(); + + Assert.AreEqual(Name, interpreter.Parse("this.Value", parameter).Invoke(context)); + Assert.AreEqual(nameof(Name), interpreter.Parse("this.Key", parameter).Invoke(context)); + + Assert.AreEqual(Name, interpreter.Parse("Value", parameter).Invoke(context)); + Assert.AreEqual(nameof(Name), interpreter.Parse("Key", parameter).Invoke(context)); + } } } From 0a444dcf7aa06f384722728cf0abb9bdf2c50dea Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 20 Mar 2022 22:34:09 +0700 Subject: [PATCH 2/5] catch ParseException only --- src/DynamicExpresso.Core/Parsing/Parser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index 36186603..b10d7796 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -1034,7 +1034,7 @@ private Expression ParseIdentifier() return ParseMemberAccess(thisParameterExpression); } } - catch + catch(ParseException) { // ignore } From 3b56ca5abc44e45ee1087715d0a475c630175473 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Sun, 20 Mar 2022 23:27:08 +0700 Subject: [PATCH 3/5] LanguageConstants.This --- src/DynamicExpresso.Core/LanguageConstants.cs | 2 ++ src/DynamicExpresso.Core/Parsing/Parser.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DynamicExpresso.Core/LanguageConstants.cs b/src/DynamicExpresso.Core/LanguageConstants.cs index 7965f864..bc696bf3 100644 --- a/src/DynamicExpresso.Core/LanguageConstants.cs +++ b/src/DynamicExpresso.Core/LanguageConstants.cs @@ -6,6 +6,8 @@ namespace DynamicExpresso { public static class LanguageConstants { + public const string This = "this"; + public static readonly ReferenceType[] PrimitiveTypes = { new ReferenceType(typeof(object)), new ReferenceType(typeof(bool)), diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index b10d7796..367406cf 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -1024,12 +1024,12 @@ private Expression ParseIdentifier() try { - if (_arguments.TryGetIdentifier("this", out var thisKeywordExpression)) + if (_arguments.TryGetIdentifier(LanguageConstants.This, out var thisKeywordExpression)) { return ParseMemberAccess(thisKeywordExpression); } - if (_arguments.TryGetParameters("this", out var thisParameterExpression)) + if (_arguments.TryGetParameters(LanguageConstants.This, out var thisParameterExpression)) { return ParseMemberAccess(thisParameterExpression); } From b7c4fa9efc2a6c9c69f61a7abbe0bd88efac88a7 Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Mon, 21 Mar 2022 20:20:08 +0700 Subject: [PATCH 4/5] 'this' implicit reference readme notes --- README.md | 32 +++++++++++++++-- .../IdentifiersTest.cs | 36 +++++++++++-------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index cabc3099..550f6071 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,25 @@ Assert.AreEqual(30, myFunc.Invoke(23, 7)); Assert.AreEqual(30, myFunc.Invoke(32, -2)); ``` +### Special identifiers + +Either a variable or a parameter with name `this` can be referenced implicitly. + +```csharp +class Customer { public string Name { get; set; } } + +var target = new Interpreter(); + +// 'this' is treated as a special identifier and could by accessed implicitly +target.SetVariable("this", new Customer { Name = "John" }); + +// explicit context reference via 'this' variable +Assert.AreEqual("John", target.Eval("this.Name")); + +// 'this' variable is referenced implicitly +Assert.AreEqual("John", target.Eval("Name")); +``` + ### Built-in types and custom types Currently predefined types available are: @@ -305,12 +324,21 @@ The following character escape sequences are supported inside string or char lit ### Type's members invocation Any standard .NET method, field, property or constructor can be invoked. ```csharp -var x = new MyTestService(); -var target = new Interpreter().SetVariable("x", x); +var service = new MyTestService(); +var context = new MyTestContext(); + +var target = new Interpreter() + .SetVariable("x", service) + .SetVariable("this", context); Assert.AreEqual(x.HelloWorld(), target.Eval("x.HelloWorld()")); Assert.AreEqual(x.AProperty, target.Eval("x.AProperty")); Assert.AreEqual(x.AField, target.Eval("x.AField")); + +// implicit context reference +Assert.AreEqual(x.HelloWorld(), target.Eval("GetContextId()")); +Assert.AreEqual(x.AProperty, target.Eval("ContextName")); +Assert.AreEqual(x.AField, target.Eval("ContextField")); ``` ```csharp var target = new Interpreter(); diff --git a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs index 9d438f12..70dbac09 100644 --- a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using NUnit.Framework; namespace DynamicExpresso.UnitTest @@ -7,6 +6,16 @@ namespace DynamicExpresso.UnitTest [TestFixture] public class IdentifiersTest { + class Customer + { + public string Name { get; set; } + + public string GetName() + { + return Name; + } + } + [Test] public void Default_identifiers_are_saved_inside_the_interpreter() { @@ -44,33 +53,32 @@ public void Getting_the_list_of_used_identifiers() public void This_identifier_variable() { const string Name = "John"; - var context = new KeyValuePair(nameof(Name), Name); var interpreter = new Interpreter(); - interpreter.SetVariable("this", context); + interpreter.SetVariable("this", new Customer {Name = Name}); - Assert.AreEqual(Name, interpreter.Eval("this.Value")); - Assert.AreEqual(nameof(Name), interpreter.Eval("this.Key")); + Assert.AreEqual(Name, interpreter.Eval("this.Name")); + Assert.AreEqual(Name, interpreter.Eval("this.GetName()")); - Assert.AreEqual(Name, interpreter.Eval("Value")); - Assert.AreEqual(nameof(Name), interpreter.Eval("Key")); + Assert.AreEqual(Name, interpreter.Eval("Name")); + Assert.AreEqual(Name, interpreter.Eval("GetName()")); } [Test] public void This_identifier_parameter() { const string Name = "John"; - var context = new KeyValuePair(nameof(Name), Name); - var parameter = new Parameter("this", context.GetType()); + var context = new Customer {Name = Name}; + var parameter = new Parameter("this", context.GetType()); var interpreter = new Interpreter(); - Assert.AreEqual(Name, interpreter.Parse("this.Value", parameter).Invoke(context)); - Assert.AreEqual(nameof(Name), interpreter.Parse("this.Key", parameter).Invoke(context)); + Assert.AreEqual(Name, interpreter.Parse("this.Name", parameter).Invoke(context)); + Assert.AreEqual(Name, interpreter.Parse("this.GetName()", parameter).Invoke(context)); - Assert.AreEqual(Name, interpreter.Parse("Value", parameter).Invoke(context)); - Assert.AreEqual(nameof(Name), interpreter.Parse("Key", parameter).Invoke(context)); + Assert.AreEqual(Name, interpreter.Parse("Name", parameter).Invoke(context)); + Assert.AreEqual(Name, interpreter.Parse("GetName()", parameter).Invoke(context)); } } } From 07c97f10832e6dbc0823890d0a0f1c72f1f714ba Mon Sep 17 00:00:00 2001 From: eugenealeykin Date: Mon, 21 Mar 2022 21:20:27 +0700 Subject: [PATCH 5/5] readme fixes after review --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 550f6071..fec0e0e5 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ class Customer { public string Name { get; set; } } var target = new Interpreter(); -// 'this' is treated as a special identifier and could by accessed implicitly +// 'this' is treated as a special identifier and can be accessed implicitly target.SetVariable("this", new Customer { Name = "John" }); // explicit context reference via 'this' variable @@ -322,6 +322,7 @@ The following character escape sequences are supported inside string or char lit - `\v` - Vertical quote (character 11) ### Type's members invocation + Any standard .NET method, field, property or constructor can be invoked. ```csharp var service = new MyTestService(); @@ -331,14 +332,14 @@ var target = new Interpreter() .SetVariable("x", service) .SetVariable("this", context); -Assert.AreEqual(x.HelloWorld(), target.Eval("x.HelloWorld()")); -Assert.AreEqual(x.AProperty, target.Eval("x.AProperty")); -Assert.AreEqual(x.AField, target.Eval("x.AField")); +Assert.AreEqual(service.HelloWorld(), target.Eval("x.HelloWorld()")); +Assert.AreEqual(service.AProperty, target.Eval("x.AProperty")); +Assert.AreEqual(service.AField, target.Eval("x.AField")); // implicit context reference -Assert.AreEqual(x.HelloWorld(), target.Eval("GetContextId()")); -Assert.AreEqual(x.AProperty, target.Eval("ContextName")); -Assert.AreEqual(x.AField, target.Eval("ContextField")); +Assert.AreEqual(context.GetContextId(), target.Eval("GetContextId()")); +Assert.AreEqual(context.ContextName, target.Eval("ContextName")); +Assert.AreEqual(context.ContextField, target.Eval("ContextField")); ``` ```csharp var target = new Interpreter();