Skip to content

Commit f75fc3b

Browse files
authored
Merge pull request #99 from davideicardi/develop
Develop
2 parents 0de368b + 0499e46 commit f75fc3b

File tree

8 files changed

+489
-31
lines changed

8 files changed

+489
-31
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ Supported operators:
233233
<tr>
234234
<td>Equality</td><td><code>== !=</code></td>
235235
</tr>
236+
<tr>
237+
<td>Logical AND</td><td><code>&</code></td>
238+
</tr>
239+
<tr>
240+
<td>Logical OR</td><td><code>|</code></td>
241+
</tr>
242+
<tr>
243+
<td>Logical XOR</td><td><code>^</code></td>
244+
</tr>
236245
<tr>
237246
<td>Conditional AND</td><td><code>&&</code></td>
238247
</tr>
@@ -245,10 +254,13 @@ Supported operators:
245254
<tr>
246255
<td>Assignment</td><td><code>=</code></td>
247256
</tr>
257+
<tr>
258+
<td>Null coalescing</td><td><code>??</code></td>
259+
</tr>
248260
</tbody>
249261
</table>
250262

251-
Operators precedence is respected following [C# rules (Operator precedence and associativity)](http://msdn.microsoft.com/en-us/library/aa691323(v=vs.71).aspx).
263+
Operators precedence is respected following [C# rules (Operator precedence and associativity)](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/).
252264

253265
Some operators, like the assignment operator, can be disabled for security reason.
254266

sample/DynamicExpressoWebShell/DynamicExpressoWebShell.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.0</TargetFramework>
4+
<TargetFramework>netcoreapp2.2</TargetFramework>
55

66
<IsPackable>false</IsPackable>
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="DynamicExpresso.Core" Version="2.0.2" />
11-
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
12-
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" />
10+
<PackageReference Include="DynamicExpresso.Core" Version="2.1.0" />
11+
<PackageReference Include="Microsoft.AspNetCore.All" />
12+
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
16+
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
1717
</ItemGroup>
1818

1919
<ItemGroup>

src/DynamicExpresso.Core/Parsing/Parser.cs

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private Expression ParseExpressionSegment()
8181
{
8282
// The following methods respect the operator precedence as defined in
8383
// MSDN C# "Operator precedence and associativity"
84-
// http://msdn.microsoft.com/en-us/library/aa691323(v=vs.71).aspx
84+
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
8585

8686
return ParseAssignement();
8787
}
@@ -107,8 +107,14 @@ private Expression ParseAssignement()
107107
private Expression ParseConditional()
108108
{
109109
var errorPos = _token.pos;
110-
var expr = ParseLogicalOr();
111-
if (_token.id == TokenId.Question)
110+
var expr = ParseConditionalOr();
111+
if (_token.id == TokenId.QuestionQuestion)
112+
{
113+
NextToken();
114+
var exprRight = ParseExpressionSegment();
115+
expr = GenerateConditional(GenerateEqual(expr, ParserConstants.NullLiteralExpression), exprRight, expr, errorPos);
116+
}
117+
else if (_token.id == TokenId.Question)
112118
{
113119
NextToken();
114120
var expr1 = ParseExpressionSegment();
@@ -121,29 +127,71 @@ private Expression ParseConditional()
121127
}
122128

123129
// || operator
124-
private Expression ParseLogicalOr()
130+
private Expression ParseConditionalOr()
125131
{
126-
var left = ParseLogicalAnd();
132+
var left = ParseConditionalAnd();
127133
while (_token.id == TokenId.DoubleBar)
128134
{
129135
NextToken();
130-
var right = ParseLogicalAnd();
136+
var right = ParseConditionalAnd();
131137
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
132138
left = Expression.OrElse(left, right);
133139
}
134140
return left;
135141
}
136142

137143
// && operator
144+
private Expression ParseConditionalAnd()
145+
{
146+
var left = ParseLogicalOr();
147+
while (_token.id == TokenId.DoubleAmphersand)
148+
{
149+
NextToken();
150+
var right = ParseLogicalOr();
151+
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
152+
left = Expression.AndAlso(left, right);
153+
}
154+
return left;
155+
}
156+
157+
// | operator
158+
private Expression ParseLogicalOr()
159+
{
160+
var left = ParseLogicalXor();
161+
while (_token.id == TokenId.Bar)
162+
{
163+
NextToken();
164+
var right = ParseLogicalXor();
165+
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
166+
left = Expression.Or(left, right);
167+
}
168+
return left;
169+
}
170+
171+
// ^ operator
172+
private Expression ParseLogicalXor()
173+
{
174+
var left = ParseLogicalAnd();
175+
while (_token.id == TokenId.Caret)
176+
{
177+
NextToken();
178+
var right = ParseLogicalAnd();
179+
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
180+
left = Expression.ExclusiveOr(left, right);
181+
}
182+
return left;
183+
}
184+
185+
// & operator
138186
private Expression ParseLogicalAnd()
139187
{
140188
var left = ParseComparison();
141-
while (_token.id == TokenId.DoubleAmphersand)
189+
while (_token.id == TokenId.Amphersand)
142190
{
143191
NextToken();
144192
var right = ParseComparison();
145193
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
146-
left = Expression.AndAlso(left, right);
194+
left = Expression.And(left, right);
147195
}
148196
return left;
149197
}
@@ -380,6 +428,11 @@ private Expression ParsePrimary()
380428
NextToken();
381429
expr = ParseMemberAccess(null, expr);
382430
}
431+
else if(_token.id == TokenId.QuestionDot)
432+
{
433+
NextToken();
434+
expr = GenerateConditional(GenerateEqual(expr, ParserConstants.NullLiteralExpression), ParserConstants.NullLiteralExpression, ParseMemberAccess(null, expr), _token.pos);
435+
}
383436
else if (_token.id == TokenId.OpenBracket)
384437
{
385438
expr = ParseElementAccess(expr);
@@ -1089,10 +1142,10 @@ private static string GetTypeName(Type type)
10891142
return s;
10901143
}
10911144

1092-
//static bool IsNumericType(Type type)
1093-
//{
1094-
// return GetNumericTypeKind(type) != 0;
1095-
//}
1145+
static bool IsNumericType(Type type)
1146+
{
1147+
return GetNumericTypeKind(type) != 0;
1148+
}
10961149

10971150
private static bool IsSignedIntegralType(Type type)
10981151
{
@@ -1423,7 +1476,7 @@ private static Expression PromoteExpression(Expression expr, Type type, bool exa
14231476
}
14241477
}
14251478

1426-
if (type.IsGenericType)
1479+
if (type.IsGenericType && !IsNumericType(type))
14271480
{
14281481
var genericType = FindAssignableGenericType(expr.Type, type.GetGenericTypeDefinition());
14291482
if (genericType != null)
@@ -1604,8 +1657,8 @@ private static bool HasParamsArrayType(ParameterInfo parameterInfo)
16041657
private static Type GetParameterType(ParameterInfo parameterInfo)
16051658
{
16061659
var isParamsArray = HasParamsArrayType(parameterInfo);
1607-
var type = isParamsArray
1608-
? parameterInfo.ParameterType.GetElementType()
1660+
var type = isParamsArray
1661+
? parameterInfo.ParameterType.GetElementType()
16091662
: parameterInfo.ParameterType;
16101663
return type;
16111664
}
@@ -1735,7 +1788,7 @@ private static Expression GenerateSubtract(Expression left, Expression right)
17351788

17361789
private static Expression GenerateStringConcat(Expression left, Expression right)
17371790
{
1738-
var concatMethod = typeof(string).GetMethod("Concat", new[] {typeof(object), typeof(object)});
1791+
var concatMethod = typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) });
17391792
if (concatMethod == null)
17401793
throw new Exception("String concat method not found");
17411794

@@ -1817,7 +1870,7 @@ private void NextToken()
18171870
}
18181871
else
18191872
{
1820-
throw CreateParseException(_parsePosition, ErrorMessages.InvalidCharacter, _parseChar);
1873+
t = TokenId.Amphersand;
18211874
}
18221875
break;
18231876
case '(':
@@ -1919,7 +1972,18 @@ private void NextToken()
19191972
break;
19201973
case '?':
19211974
NextChar();
1922-
t = TokenId.Question;
1975+
if (_parseChar == '.')
1976+
{
1977+
NextChar();
1978+
t = TokenId.QuestionDot;
1979+
} else if(_parseChar == '?')
1980+
{
1981+
NextChar();
1982+
t = TokenId.QuestionQuestion;
1983+
} else
1984+
{
1985+
t = TokenId.Question;
1986+
}
19231987
break;
19241988
case '[':
19251989
NextChar();
@@ -1977,6 +2041,10 @@ private void NextToken()
19772041

19782042
t = TokenId.CharLiteral;
19792043
break;
2044+
case '^':
2045+
NextChar();
2046+
t = TokenId.Caret;
2047+
break;
19802048
default:
19812049

19822050
if (char.IsLetter(_parseChar) || _parseChar == '@' || _parseChar == '_')

src/DynamicExpresso.Core/Parsing/TokenId.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,25 @@ internal enum TokenId
1818
Comma,
1919
Minus,
2020
Dot,
21+
QuestionDot,
22+
QuestionQuestion,
2123
Slash,
2224
Colon,
2325
LessThan,
2426
GreaterThan,
2527
Question,
2628
OpenBracket,
2729
CloseBracket,
28-
Bar,
2930
ExclamationEqual,
31+
Amphersand,
3032
DoubleAmphersand,
3133
LessThanEqual,
3234
DoubleEqual,
3335
GreaterThanEqual,
36+
Bar,
3437
DoubleBar,
35-
Equal
38+
Equal,
39+
Caret
3640
}
3741

3842
}

test/DynamicExpresso.UnitTest/GithubIssues.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using System;
2-
using NUnit.Framework;
3-
using System.Collections.Generic;
1+
using NUnit.Framework;
42
using System.Linq;
3+
54
// ReSharper disable SpecifyACultureInStringConversionExplicitly
65

76
namespace DynamicExpresso.UnitTest
@@ -27,7 +26,7 @@ public void GitHub_Issue_43()
2726

2827
Assert.AreEqual((-.5).ToString(), interpreter.Eval("-.5.ToString()"));
2928
Assert.AreEqual((.1).ToString(), interpreter.Eval(".1.ToString()"));
30-
Assert.AreEqual((-1-.1-0.1).ToString(), interpreter.Eval("(-1-.1-0.1).ToString()"));
29+
Assert.AreEqual((-1 - .1 - 0.1).ToString(), interpreter.Eval("(-1-.1-0.1).ToString()"));
3130
}
3231

3332
[Test]
@@ -42,5 +41,43 @@ public void GitHub_Issue_68()
4241
Assert.AreEqual(array.Contains(5), interpreter.Eval("array.Contains(5)"));
4342
Assert.AreEqual(array.Contains(3), interpreter.Eval("array.Contains(3)"));
4443
}
44+
45+
[Test]
46+
public void GitHub_Issue_64()
47+
{
48+
var interpreter = new Interpreter();
49+
Assert.AreEqual(null, interpreter.Eval("null ?? null"));
50+
Assert.AreEqual("hallo", interpreter.Eval("\"hallo\" ?? null"));
51+
Assert.AreEqual("hallo", interpreter.Eval("null ?? \"hallo\""));
52+
}
53+
54+
[Test]
55+
public void GitHub_Issue_65_Part1()
56+
{
57+
var interpreter = new Interpreter();
58+
59+
var x = new
60+
{
61+
var1 = "hallo",
62+
var2 = (string)null
63+
};
64+
65+
interpreter.SetVariable("x", x);
66+
Assert.AreEqual("hallo", interpreter.Eval("x.var1?.ToString()"));
67+
Assert.AreEqual(null, interpreter.Eval("x.var2?.ToString()"));
68+
Assert.AreEqual("allo", interpreter.Eval("x.var1?.Substring(1)"));
69+
}
70+
71+
[Test]
72+
public void GitHub_Issue_88()
73+
{
74+
var interpreter = new Interpreter();
75+
76+
interpreter.SetVariable("a", 1, typeof(int));
77+
interpreter.SetVariable("b", 1.2, typeof(double?));
78+
var result = interpreter.Eval("a + b");
79+
80+
Assert.AreEqual(result, 2.2);
81+
}
4582
}
4683
}

test/DynamicExpresso.UnitTest/MemberInvocationTest.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ public void Method_Property_Field_basic_test()
1919
Assert.AreEqual(x.AField, target.Eval("x.AField"));
2020
}
2121

22+
[Ignore("See issue 65")]
23+
[Test]
24+
public void Null_conditional_property()
25+
{
26+
var target = new Interpreter().SetVariable("x", null, typeof(MyTestService));
27+
MyTestService x = null;
28+
Assert.IsNull(target.Eval("x?.AProperty"));
29+
}
30+
2231
[Test]
2332
public void Indexer()
2433
{

0 commit comments

Comments
 (0)