diff --git a/README.md b/README.md index cabc3099..fec0e0e5 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 can be 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: @@ -303,14 +322,24 @@ 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 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(service.HelloWorld(), target.Eval("x.HelloWorld()")); +Assert.AreEqual(service.AProperty, target.Eval("x.AProperty")); +Assert.AreEqual(service.AField, target.Eval("x.AField")); -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(context.GetContextId(), target.Eval("GetContextId()")); +Assert.AreEqual(context.ContextName, target.Eval("ContextName")); +Assert.AreEqual(context.ContextField, target.Eval("ContextField")); ``` ```csharp var target = new Interpreter(); 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 3a585f7b..367406cf 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(LanguageConstants.This, out var thisKeywordExpression)) + { + return ParseMemberAccess(thisKeywordExpression); + } + + if (_arguments.TryGetParameters(LanguageConstants.This, out var thisParameterExpression)) + { + return ParseMemberAccess(thisParameterExpression); + } + } + catch(ParseException) + { + // 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..70dbac09 100644 --- a/test/DynamicExpresso.UnitTest/IdentifiersTest.cs +++ b/test/DynamicExpresso.UnitTest/IdentifiersTest.cs @@ -6,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() { @@ -38,5 +48,37 @@ 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 interpreter = new Interpreter(); + + interpreter.SetVariable("this", new Customer {Name = Name}); + + Assert.AreEqual(Name, interpreter.Eval("this.Name")); + Assert.AreEqual(Name, interpreter.Eval("this.GetName()")); + + 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 Customer {Name = Name}; + var parameter = new Parameter("this", context.GetType()); + var interpreter = new Interpreter(); + + 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("Name", parameter).Invoke(context)); + Assert.AreEqual(Name, interpreter.Parse("GetName()", parameter).Invoke(context)); + } } }