Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ namespace System.Linq.Dynamic.Core
/// </summary>
public static class DynamicExpressionParser
{
/// <summary>
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
/// </summary>
/// <param name="itType">The main type from the dynamic class expression.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
{
Check.NotEmpty(expression, nameof(expression));


var parser = new ExpressionParser(new ParameterExpression[0], expression, values);

return Expression.Lambda(parser.Parse(resultType, true));
}

/// <summary>
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
/// </summary>
Expand Down
97 changes: 84 additions & 13 deletions src/System.Linq.Dynamic.Core/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ interface IEnumerableSignatures
void Take(int count);
void TakeWhile(bool predicate);
void Distinct();
void GroupBy(object selector);

//Executors
void Single();
Expand Down Expand Up @@ -222,6 +223,7 @@ interface IEnumerableSignatures

readonly Dictionary<string, object> _symbols;
IDictionary<string, object> _externals;
readonly Dictionary<string, object> _internals;
readonly Dictionary<Expression, string> _literals;
ParameterExpression _it;
ParameterExpression _parent;
Expand Down Expand Up @@ -269,6 +271,7 @@ public ExpressionParser(ParameterExpression[] parameters, string expression, obj
{
if (_keywords == null) _keywords = CreateKeywords();
_symbols = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_internals = new Dictionary<string, object>();
_literals = new Dictionary<Expression, string>();
if (parameters != null) ProcessParameters(parameters);
if (values != null) ProcessValues(values);
Expand Down Expand Up @@ -388,7 +391,7 @@ Expression ParseExpression()
// ?? (null-coalescing) operator
Expression ParseNullCoalescing()
{
Expression expr = ParseConditionalOr();
Expression expr = ParseLambda();
if (_textParser.CurrentToken.Id == TokenId.NullCoalescing)
{
_textParser.NextToken();
Expand All @@ -398,6 +401,24 @@ Expression ParseNullCoalescing()
return expr;
}

// => operator - Added Support for projection operator
Expression ParseLambda()
{
Expression expr = ParseConditionalOr();
if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == expr.Type)
{
_textParser.NextToken();
if (_textParser.CurrentToken.Id == TokenId.Identifier ||
_textParser.CurrentToken.Id == TokenId.OpenParen)
{
var right = ParseExpression();
return Expression.Lambda(right, new[] {(ParameterExpression) expr});
}
_textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
}
return expr;
}

// isnull(a,b) operator
Expression ParseIsNull()
{
Expand Down Expand Up @@ -1003,7 +1024,8 @@ Expression ParseIdentifier()
}

if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) ||
_externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value))
_externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) ||
_internals.TryGetValue(_textParser.CurrentToken.Text, out value))
{
Expression expr = value as Expression;
if (expr == null)
Expand Down Expand Up @@ -1093,7 +1115,24 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp
Expression ParseNew()
{
_textParser.NextToken();
_textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen &&
_textParser.CurrentToken.Id != TokenId.Identifier)
throw ParseError(Res.OpenParenOrIdentifierExpected);

Type newType = null;
if (_textParser.CurrentToken.Id == TokenId.Identifier)
{
var newTypeName = _textParser.CurrentToken.Text;
newType = FindType(newTypeName);
if (newType == null)
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
_textParser.NextToken();
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen)
throw ParseError(Res.OpenParenExpected);
}

_textParser.NextToken();

var properties = new List<DynamicProperty>();
Expand Down Expand Up @@ -1125,16 +1164,18 @@ Expression ParseNew()
_textParser.NextToken();
}

_textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
if (_textParser.CurrentToken.Id != TokenId.CloseParen &&
_textParser.CurrentToken.Id != TokenId.CloseCurlyParen)
throw ParseError(Res.CloseParenOrCommaExpected);
_textParser.NextToken();

return CreateNewExpression(properties, expressions);
return CreateNewExpression(properties, expressions, newType);
}

private Expression CreateNewExpression(List<DynamicProperty> properties, List<Expression> expressions)
private Expression CreateNewExpression(List<DynamicProperty> properties, List<Expression> expressions, Type newType)
{
// http://solutionizing.net/category/linq/
Type type = _resultType;
Type type = newType ?? _resultType;

if (type == null)
{
Expand Down Expand Up @@ -1323,7 +1364,19 @@ Expression ParseMemberAccess(Type type, Expression instance)
MemberInfo member = FindPropertyOrField(type, id, instance == null);
if (member == null)
{
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type)
{
// This might be an internal variable for use within a lambda expression, so store it as such
_internals.Add(id, _it);
_textParser.NextToken();
var right = ParseExpression();
return right;
}
else
{
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type));
}

}

var property = member as PropertyInfo;
Expand Down Expand Up @@ -1352,6 +1405,28 @@ static Type FindGenericType(Type generic, Type type)
return null;
}

Type FindType(string name)
{
object type;
_keywords.TryGetValue(name, out type);
var result = type as Type;
if (result != null)
return result;
if (_it != null && _it.Type.Name == name)
return _it.Type;
if (_parent != null && _parent.Type.Name == name)
return _parent.Type;
if (_root != null && _root.Type.Name == name)
return _root.Type;
if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name)
return _it.Type;
if (_parent != null && _parent.Type.Namespace + "." + _parent.Type.Name == name)
return _parent.Type;
if (_root != null && _root.Type.Namespace + "." + _root.Type.Name == name)
return _root.Type;
return null;
}

Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
var oldParent = _parent;
Expand Down Expand Up @@ -1381,7 +1456,7 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);

Type[] typeArgs;
if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" }.Contains(signature.Name))
if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(signature.Name))
{
typeArgs = new[] { elementType, args[0].Type };
}
Expand Down Expand Up @@ -2360,10 +2435,6 @@ static Expression OptimizeStringForEqualityIfPossible(string text, Type type)
return null;
}





bool TokenIdentifierIs(string id)
{
return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase);
Expand Down
1 change: 1 addition & 0 deletions src/System.Linq.Dynamic.Core/Res.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ internal static class Res
public const string IdentifierExpected = "Identifier expected";
public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected";
public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected";
public const string TypeNotFound = "Type '{0}' not Found";
}
}
15 changes: 15 additions & 0 deletions src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ public void NextToken()
t = TokenId.CloseParen;
break;

case '{':
NextChar();
t = TokenId.OpenCurlyParen;
break;

case '}':
NextChar();
t = TokenId.CloseCurlyParen;
break;

case '*':
NextChar();
t = TokenId.Asterisk;
Expand Down Expand Up @@ -164,6 +174,11 @@ public void NextToken()
NextChar();
t = TokenId.DoubleEqual;
}
else if (_ch == '>')
{
NextChar();
t = TokenId.Lambda;
}
else
{
t = TokenId.Equal;
Expand Down
5 changes: 4 additions & 1 deletion src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ internal enum TokenId
Amphersand,
OpenParen,
CloseParen,
OpenCurlyParen,
CloseCurlyParen,
Asterisk,
Plus,
Comma,
Expand All @@ -36,6 +38,7 @@ internal enum TokenId
DoubleBar,
DoubleGreaterThan,
DoubleLessThan,
NullCoalescing
NullCoalescing,
Lambda
}
}
33 changes: 33 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Expressions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests
Expand Down Expand Up @@ -94,5 +95,37 @@ public void GroupByAndSelect_TestDynamicSelectMember()
selectQry.AsEnumerable().Select(x => x.TotalIncome).Cast<int>().ToArray());
#endif
}

[Fact]
public void ComplexString1()
{
//Arrange
var testList = User.GenerateSampleModels(51);
var qry = testList.AsQueryable();

var externals = new Dictionary<string, object>();
externals.Add("Users", qry);

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))";
var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
var del = expression.Compile();
var selectQry = del.DynamicInvoke();
}

[Fact]
public void ComplexString2()
{
//Arrange
var testList = User.GenerateSampleModels(51);
var qry = testList.AsQueryable();

var externals = new Dictionary<string, object>();
externals.Add("Users", qry);

var query = "Users.Select(j => new User(j.Income As Income))";
var expression = DynamicExpressionParser.ParseLambda(null, query, externals);
var del = expression.Compile();
var res = del.DynamicInvoke();
}
}
}