Skip to content

Commit efb5e88

Browse files
authored
Merge pull request #68 from jogibear9988/wip66
Work on #66 -> Should work now. Tests will follow on VS2017 support!
2 parents 47ce481 + 5bee33c commit efb5e88

File tree

6 files changed

+155
-14
lines changed

6 files changed

+155
-14
lines changed

src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs

+18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ namespace System.Linq.Dynamic.Core
99
/// </summary>
1010
public static class DynamicExpressionParser
1111
{
12+
/// <summary>
13+
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
14+
/// </summary>
15+
/// <param name="itType">The main type from the dynamic class expression.</param>
16+
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
17+
/// <param name="expression">The expression.</param>
18+
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
19+
/// <returns>The generated <see cref="LambdaExpression"/></returns>
20+
public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
21+
{
22+
Check.NotEmpty(expression, nameof(expression));
23+
24+
25+
var parser = new ExpressionParser(new ParameterExpression[0], expression, values);
26+
27+
return Expression.Lambda(parser.Parse(resultType, true));
28+
}
29+
1230
/// <summary>
1331
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
1432
/// </summary>

src/System.Linq.Dynamic.Core/ExpressionParser.cs

+84-13
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ interface IEnumerableSignatures
149149
void Take(int count);
150150
void TakeWhile(bool predicate);
151151
void Distinct();
152+
void GroupBy(object selector);
152153

153154
//Executors
154155
void Single();
@@ -222,6 +223,7 @@ interface IEnumerableSignatures
222223

223224
readonly Dictionary<string, object> _symbols;
224225
IDictionary<string, object> _externals;
226+
readonly Dictionary<string, object> _internals;
225227
readonly Dictionary<Expression, string> _literals;
226228
ParameterExpression _it;
227229
ParameterExpression _parent;
@@ -269,6 +271,7 @@ public ExpressionParser(ParameterExpression[] parameters, string expression, obj
269271
{
270272
if (_keywords == null) _keywords = CreateKeywords();
271273
_symbols = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
274+
_internals = new Dictionary<string, object>();
272275
_literals = new Dictionary<Expression, string>();
273276
if (parameters != null) ProcessParameters(parameters);
274277
if (values != null) ProcessValues(values);
@@ -388,7 +391,7 @@ Expression ParseExpression()
388391
// ?? (null-coalescing) operator
389392
Expression ParseNullCoalescing()
390393
{
391-
Expression expr = ParseConditionalOr();
394+
Expression expr = ParseLambda();
392395
if (_textParser.CurrentToken.Id == TokenId.NullCoalescing)
393396
{
394397
_textParser.NextToken();
@@ -398,6 +401,24 @@ Expression ParseNullCoalescing()
398401
return expr;
399402
}
400403

404+
// => operator - Added Support for projection operator
405+
Expression ParseLambda()
406+
{
407+
Expression expr = ParseConditionalOr();
408+
if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == expr.Type)
409+
{
410+
_textParser.NextToken();
411+
if (_textParser.CurrentToken.Id == TokenId.Identifier ||
412+
_textParser.CurrentToken.Id == TokenId.OpenParen)
413+
{
414+
var right = ParseExpression();
415+
return Expression.Lambda(right, new[] {(ParameterExpression) expr});
416+
}
417+
_textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
418+
}
419+
return expr;
420+
}
421+
401422
// isnull(a,b) operator
402423
Expression ParseIsNull()
403424
{
@@ -1003,7 +1024,8 @@ Expression ParseIdentifier()
10031024
}
10041025

10051026
if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) ||
1006-
_externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value))
1027+
_externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) ||
1028+
_internals.TryGetValue(_textParser.CurrentToken.Text, out value))
10071029
{
10081030
Expression expr = value as Expression;
10091031
if (expr == null)
@@ -1093,7 +1115,24 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp
10931115
Expression ParseNew()
10941116
{
10951117
_textParser.NextToken();
1096-
_textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
1118+
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
1119+
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen &&
1120+
_textParser.CurrentToken.Id != TokenId.Identifier)
1121+
throw ParseError(Res.OpenParenOrIdentifierExpected);
1122+
1123+
Type newType = null;
1124+
if (_textParser.CurrentToken.Id == TokenId.Identifier)
1125+
{
1126+
var newTypeName = _textParser.CurrentToken.Text;
1127+
newType = FindType(newTypeName);
1128+
if (newType == null)
1129+
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
1130+
_textParser.NextToken();
1131+
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
1132+
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen)
1133+
throw ParseError(Res.OpenParenExpected);
1134+
}
1135+
10971136
_textParser.NextToken();
10981137

10991138
var properties = new List<DynamicProperty>();
@@ -1125,16 +1164,18 @@ Expression ParseNew()
11251164
_textParser.NextToken();
11261165
}
11271166

1128-
_textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
1167+
if (_textParser.CurrentToken.Id != TokenId.CloseParen &&
1168+
_textParser.CurrentToken.Id != TokenId.CloseCurlyParen)
1169+
throw ParseError(Res.CloseParenOrCommaExpected);
11291170
_textParser.NextToken();
11301171

1131-
return CreateNewExpression(properties, expressions);
1172+
return CreateNewExpression(properties, expressions, newType);
11321173
}
11331174

1134-
private Expression CreateNewExpression(List<DynamicProperty> properties, List<Expression> expressions)
1175+
private Expression CreateNewExpression(List<DynamicProperty> properties, List<Expression> expressions, Type newType)
11351176
{
11361177
// http://solutionizing.net/category/linq/
1137-
Type type = _resultType;
1178+
Type type = newType ?? _resultType;
11381179

11391180
if (type == null)
11401181
{
@@ -1323,7 +1364,19 @@ Expression ParseMemberAccess(Type type, Expression instance)
13231364
MemberInfo member = FindPropertyOrField(type, id, instance == null);
13241365
if (member == null)
13251366
{
1326-
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
1367+
if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type)
1368+
{
1369+
// This might be an internal variable for use within a lambda expression, so store it as such
1370+
_internals.Add(id, _it);
1371+
_textParser.NextToken();
1372+
var right = ParseExpression();
1373+
return right;
1374+
}
1375+
else
1376+
{
1377+
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
1378+
}
1379+
13271380
}
13281381

13291382
var property = member as PropertyInfo;
@@ -1352,6 +1405,28 @@ static Type FindGenericType(Type generic, Type type)
13521405
return null;
13531406
}
13541407

1408+
Type FindType(string name)
1409+
{
1410+
object type;
1411+
_keywords.TryGetValue(name, out type);
1412+
var result = type as Type;
1413+
if (result != null)
1414+
return result;
1415+
if (_it != null && _it.Type.Name == name)
1416+
return _it.Type;
1417+
if (_parent != null && _parent.Type.Name == name)
1418+
return _parent.Type;
1419+
if (_root != null && _root.Type.Name == name)
1420+
return _root.Type;
1421+
if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name)
1422+
return _it.Type;
1423+
if (_parent != null && _parent.Type.Namespace + "." + _parent.Type.Name == name)
1424+
return _parent.Type;
1425+
if (_root != null && _root.Type.Namespace + "." + _root.Type.Name == name)
1426+
return _root.Type;
1427+
return null;
1428+
}
1429+
13551430
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
13561431
{
13571432
var oldParent = _parent;
@@ -1381,7 +1456,7 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
13811456
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);
13821457

13831458
Type[] typeArgs;
1384-
if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" }.Contains(signature.Name))
1459+
if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(signature.Name))
13851460
{
13861461
typeArgs = new[] { elementType, args[0].Type };
13871462
}
@@ -2360,10 +2435,6 @@ static Expression OptimizeStringForEqualityIfPossible(string text, Type type)
23602435
return null;
23612436
}
23622437

2363-
2364-
2365-
2366-
23672438
bool TokenIdentifierIs(string id)
23682439
{
23692440
return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase);

src/System.Linq.Dynamic.Core/Res.cs

+1
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@ internal static class Res
5555
public const string IdentifierExpected = "Identifier expected";
5656
public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected";
5757
public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected";
58+
public const string TypeNotFound = "Type '{0}' not Found";
5859
}
5960
}

src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs

+15
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ public void NextToken()
100100
t = TokenId.CloseParen;
101101
break;
102102

103+
case '{':
104+
NextChar();
105+
t = TokenId.OpenCurlyParen;
106+
break;
107+
108+
case '}':
109+
NextChar();
110+
t = TokenId.CloseCurlyParen;
111+
break;
112+
103113
case '*':
104114
NextChar();
105115
t = TokenId.Asterisk;
@@ -164,6 +174,11 @@ public void NextToken()
164174
NextChar();
165175
t = TokenId.DoubleEqual;
166176
}
177+
else if (_ch == '>')
178+
{
179+
NextChar();
180+
t = TokenId.Lambda;
181+
}
167182
else
168183
{
169184
t = TokenId.Equal;

src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ internal enum TokenId
1313
Amphersand,
1414
OpenParen,
1515
CloseParen,
16+
OpenCurlyParen,
17+
CloseCurlyParen,
1618
Asterisk,
1719
Plus,
1820
Comma,
@@ -36,6 +38,7 @@ internal enum TokenId
3638
DoubleBar,
3739
DoubleGreaterThan,
3840
DoubleLessThan,
39-
NullCoalescing
41+
NullCoalescing,
42+
Lambda
4043
}
4144
}

test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs

+33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
3+
using System.Linq.Expressions;
34
using Xunit;
45

56
namespace System.Linq.Dynamic.Core.Tests
@@ -94,5 +95,37 @@ public void GroupByAndSelect_TestDynamicSelectMember()
9495
selectQry.AsEnumerable().Select(x => x.TotalIncome).Cast<int>().ToArray());
9596
#endif
9697
}
98+
99+
[Fact]
100+
public void ComplexString1()
101+
{
102+
//Arrange
103+
var testList = User.GenerateSampleModels(51);
104+
var qry = testList.AsQueryable();
105+
106+
var externals = new Dictionary<string, object>();
107+
externals.Add("Users", qry);
108+
109+
var query = "Users.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new (j.Key.Age, j.Sum(k=>k.Income) As TotalIncome))";
110+
var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
111+
var del = expression.Compile();
112+
var selectQry = del.DynamicInvoke();
113+
}
114+
115+
[Fact]
116+
public void ComplexString2()
117+
{
118+
//Arrange
119+
var testList = User.GenerateSampleModels(51);
120+
var qry = testList.AsQueryable();
121+
122+
var externals = new Dictionary<string, object>();
123+
externals.Add("Users", qry);
124+
125+
var query = "Users.Select(j => new User(j.Income As Income))";
126+
var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
127+
var del = expression.Compile();
128+
var res = del.DynamicInvoke();
129+
}
97130
}
98131
}

0 commit comments

Comments
 (0)