// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory.PatternMatching;
namespace ICSharpCode.NRefactory.CSharp {
public class QueryExpressionExpansionResult {
public AstNode AstNode { get; private set; }
///
/// Maps original range variables to some node in the new tree that represents them.
///
public IDictionary RangeVariables { get; private set; }
///
/// Maps clauses to method calls. The keys will always be either a or a
///
public IDictionary Expressions { get; private set; }
public QueryExpressionExpansionResult(AstNode astNode, IDictionary rangeVariables, IDictionary expressions) {
AstNode = astNode;
RangeVariables = rangeVariables;
Expressions = expressions;
}
}
public class QueryExpressionExpander {
class Visitor : DepthFirstAstVisitor {
internal IEnumerator TransparentIdentifierNamePicker;
protected override AstNode VisitChildren(AstNode node) {
List newChildren = null;
int i = 0;
foreach (var child in node.Children) {
var newChild = child.AcceptVisitor(this);
if (newChild != null) {
newChildren = newChildren ?? Enumerable.Repeat((AstNode)null, i).ToList();
newChildren.Add(newChild);
}
else if (newChildren != null) {
newChildren.Add(null);
}
i++;
}
if (newChildren == null)
return null;
var result = node.Clone();
i = 0;
foreach (var children in result.Children) {
if (newChildren[i] != null)
children.ReplaceWith(newChildren[i]);
i++;
}
return result;
}
Expression MakeNestedMemberAccess(Expression target, IEnumerable members) {
return members.Aggregate(target, (current, m) => current.Member(m));
}
Expression VisitNested(Expression node, ParameterDeclaration transparentParameter) {
var oldRangeVariableSubstitutions = activeRangeVariableSubstitutions;
try {
if (transparentParameter != null && currentTransparentType.Count > 1) {
activeRangeVariableSubstitutions = new Dictionary(activeRangeVariableSubstitutions);
foreach (var t in currentTransparentType)
activeRangeVariableSubstitutions[t.Item1.Name] = MakeNestedMemberAccess(new IdentifierExpression(transparentParameter.Name), t.Item2);
}
var result = node.AcceptVisitor(this);
return (Expression)(result ?? node.Clone());
}
finally {
activeRangeVariableSubstitutions = oldRangeVariableSubstitutions;
}
}
QueryClause GetNextQueryClause(QueryClause clause) {
for (AstNode node = clause.NextSibling; node != null; node = node.NextSibling) {
if (node.Role == QueryExpression.ClauseRole)
return (QueryClause)node;
}
return null;
}
public IDictionary rangeVariables = new Dictionary();
public IDictionary expressions = new Dictionary();
Dictionary activeRangeVariableSubstitutions = new Dictionary();
List>> currentTransparentType = new List>>();
Expression currentResult;
bool eatSelect;
void MapExpression(AstNode orig, Expression newExpr) {
Debug.Assert(orig is QueryClause || orig is QueryOrdering);
expressions[orig] = newExpr;
}
internal static IEnumerable FallbackTransparentIdentifierNamePicker()
{
const string TransparentParameterNameTemplate = "x{0}";
int currentTransparentParameter = 0;
for (;;) {
yield return string.Format(CultureInfo.InvariantCulture, TransparentParameterNameTemplate, currentTransparentParameter++);
}
}
ParameterDeclaration CreateParameterForCurrentRangeVariable() {
var param = new ParameterDeclaration();
if (currentTransparentType.Count == 1) {
var clonedRangeVariable = (Identifier)currentTransparentType[0].Item1.Clone();
if (!rangeVariables.ContainsKey(currentTransparentType[0].Item1))
rangeVariables[currentTransparentType[0].Item1] = param;
param.AddChild(clonedRangeVariable, Roles.Identifier);
}
else {
if (!TransparentIdentifierNamePicker.MoveNext()) {
TransparentIdentifierNamePicker = FallbackTransparentIdentifierNamePicker().GetEnumerator();
TransparentIdentifierNamePicker.MoveNext();
}
string name = TransparentIdentifierNamePicker.Current;
param.AddChild(Identifier.Create(name), Roles.Identifier);
}
return param;
}
LambdaExpression CreateLambda(IList parameters, Expression body) {
var result = new LambdaExpression();
if (parameters.Count > 1)
result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.LPar), Roles.LPar);
result.AddChild(parameters[0], Roles.Parameter);
for (int i = 1; i < parameters.Count; i++) {
result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.Comma), Roles.Comma);
result.AddChild(parameters[i], Roles.Parameter);
}
if (parameters.Count > 1)
result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.RPar), Roles.RPar);
result.AddChild(body, LambdaExpression.BodyRole);
return result;
}
ParameterDeclaration CreateParameter(Identifier identifier) {
var result = new ParameterDeclaration();
result.AddChild(identifier, Roles.Identifier);
return result;
}
Expression AddMemberToCurrentTransparentType(ParameterDeclaration param, Identifier name, Expression value, bool namedExpression) {
Expression newAssignment = VisitNested(value, param);
if (namedExpression) {
newAssignment = new NamedExpression(name.Name, VisitNested(value, param));
if (!rangeVariables.ContainsKey(name) )
rangeVariables[name] = ((NamedExpression)newAssignment).NameToken;
}
foreach (var t in currentTransparentType)
t.Item2.Insert(0, param.Name);
currentTransparentType.Add(Tuple.Create(name, new List { name.Name }));
return new AnonymousTypeCreateExpression(new[] { new IdentifierExpression(param.Name), newAssignment });
}
void AddFirstMemberToCurrentTransparentType(Identifier identifier) {
Debug.Assert(currentTransparentType.Count == 0);
currentTransparentType.Add(Tuple.Create(identifier, new List()));
}
public override AstNode VisitQueryExpression(QueryExpression queryExpression) {
var oldTransparentType = currentTransparentType;
var oldResult = currentResult;
var oldEatSelect = eatSelect;
try {
currentTransparentType = new List>>();
currentResult = null;
eatSelect = false;
foreach (var clause in queryExpression.Clauses) {
var result = (Expression)clause.AcceptVisitor(this);
MapExpression(clause, result ?? currentResult);
currentResult = result;
}
return currentResult;
}
finally {
currentTransparentType = oldTransparentType;
currentResult = oldResult;
eatSelect = oldEatSelect;
}
}
public override AstNode VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause) {
var prev = VisitNested(queryContinuationClause.PrecedingQuery, null);
AddFirstMemberToCurrentTransparentType(queryContinuationClause.IdentifierToken);
return prev;
}
static bool NeedsToBeParenthesized(Expression expr)
{
UnaryOperatorExpression unary = expr as UnaryOperatorExpression;
if (unary != null) {
if (unary.Operator == UnaryOperatorType.PostIncrement || unary.Operator == UnaryOperatorType.PostDecrement) {
return false;
}
return true;
}
if (expr is BinaryOperatorExpression || expr is ConditionalExpression || expr is AssignmentExpression) {
return true;
}
return false;
}
static Expression ParenthesizeIfNeeded(Expression expr)
{
return NeedsToBeParenthesized(expr) ? new ParenthesizedExpression(expr.Clone()) : expr;
}
public override AstNode VisitQueryFromClause(QueryFromClause queryFromClause) {
if (currentResult == null) {
AddFirstMemberToCurrentTransparentType(queryFromClause.IdentifierToken);
if (queryFromClause.Type.IsNull) {
return VisitNested(ParenthesizeIfNeeded(queryFromClause.Expression), null);
}
else {
return VisitNested(ParenthesizeIfNeeded(queryFromClause.Expression), null).Invoke("Cast", new[] { queryFromClause.Type.Clone() }, new Expression[0]);
}
}
else {
var innerSelectorParam = CreateParameterForCurrentRangeVariable();
var lambdaContent = VisitNested(queryFromClause.Expression, innerSelectorParam);
if (!queryFromClause.Type.IsNull) {
lambdaContent = lambdaContent.Invoke("Cast", new[] { queryFromClause.Type.Clone() }, new Expression[0]);
}
var innerSelector = CreateLambda(new[] { innerSelectorParam }, lambdaContent);
var clonedIdentifier = (Identifier)queryFromClause.IdentifierToken.Clone();
var resultParam = CreateParameterForCurrentRangeVariable();
Expression body;
// Second from clause - SelectMany
var select = GetNextQueryClause(queryFromClause) as QuerySelectClause;
if (select != null) {
body = VisitNested(select.Expression, resultParam);
eatSelect = true;
}
else {
body = AddMemberToCurrentTransparentType(resultParam, queryFromClause.IdentifierToken, new IdentifierExpression(queryFromClause.Identifier), false);
}
var resultSelectorParam2 = CreateParameter(clonedIdentifier);
var resultSelector = CreateLambda(new[] { resultParam, resultSelectorParam2 }, body);
rangeVariables[queryFromClause.IdentifierToken] = resultSelectorParam2;
return currentResult.Invoke("SelectMany", innerSelector, resultSelector);
}
}
public override AstNode VisitQueryLetClause(QueryLetClause queryLetClause) {
var param = CreateParameterForCurrentRangeVariable();
var body = AddMemberToCurrentTransparentType(param, queryLetClause.IdentifierToken, queryLetClause.Expression, true);
var lambda = CreateLambda(new[] { param }, body);
return currentResult.Invoke("Select", lambda);
}
public override AstNode VisitQueryWhereClause(QueryWhereClause queryWhereClause) {
var param = CreateParameterForCurrentRangeVariable();
return currentResult.Invoke("Where", CreateLambda(new[] { param }, VisitNested(queryWhereClause.Condition, param)));
}
public override AstNode VisitQueryJoinClause(QueryJoinClause queryJoinClause) {
Expression resultSelectorBody = null;
var inExpression = VisitNested(queryJoinClause.InExpression, null);
if (!queryJoinClause.Type.IsNull) {
inExpression = inExpression.Invoke("Cast", new[] { queryJoinClause.Type.Clone() }, EmptyList.Instance);
}
var key1SelectorFirstParam = CreateParameterForCurrentRangeVariable();
var key1Selector = CreateLambda(new[] { key1SelectorFirstParam }, VisitNested(queryJoinClause.OnExpression, key1SelectorFirstParam));
var key2Param = CreateParameter(Identifier.Create(queryJoinClause.JoinIdentifier));
var key2Selector = CreateLambda(new[] { key2Param }, VisitNested(queryJoinClause.EqualsExpression, null));
var resultSelectorFirstParam = CreateParameterForCurrentRangeVariable();
var select = GetNextQueryClause(queryJoinClause) as QuerySelectClause;
if (select != null) {
resultSelectorBody = VisitNested(select.Expression, resultSelectorFirstParam);
eatSelect = true;
}
if (queryJoinClause.IntoKeyword.IsNull) {
// Normal join
if (resultSelectorBody == null)
resultSelectorBody = AddMemberToCurrentTransparentType(resultSelectorFirstParam, queryJoinClause.JoinIdentifierToken, new IdentifierExpression(queryJoinClause.JoinIdentifier), false);
var resultSelector = CreateLambda(new[] { resultSelectorFirstParam, CreateParameter(Identifier.Create(queryJoinClause.JoinIdentifier)) }, resultSelectorBody);
rangeVariables[queryJoinClause.JoinIdentifierToken] = key2Param;
return currentResult.Invoke("Join", inExpression, key1Selector, key2Selector, resultSelector);
}
else {
// Group join
if (resultSelectorBody == null)
resultSelectorBody = AddMemberToCurrentTransparentType(resultSelectorFirstParam, queryJoinClause.IntoIdentifierToken, new IdentifierExpression(queryJoinClause.IntoIdentifier), false);
var intoParam = CreateParameter(Identifier.Create(queryJoinClause.IntoIdentifier));
var resultSelector = CreateLambda(new[] { resultSelectorFirstParam, intoParam }, resultSelectorBody);
rangeVariables[queryJoinClause.IntoIdentifierToken] = intoParam;
return currentResult.Invoke("GroupJoin", inExpression, key1Selector, key2Selector, resultSelector);
}
}
public override AstNode VisitQueryOrderClause(QueryOrderClause queryOrderClause) {
var current = currentResult;
bool first = true;
foreach (var o in queryOrderClause.Orderings) {
string methodName = first ? (o.Direction == QueryOrderingDirection.Descending ? "OrderByDescending" : "OrderBy")
: (o.Direction == QueryOrderingDirection.Descending ? "ThenByDescending" : "ThenBy");
var param = CreateParameterForCurrentRangeVariable();
current = current.Invoke(methodName, CreateLambda(new[] { param }, VisitNested(o.Expression, param)));
MapExpression(o, current);
first = false;
}
return current;
}
bool IsSingleRangeVariable(Expression expr) {
if (currentTransparentType.Count > 1)
return false;
var unpacked = ParenthesizedExpression.UnpackParenthesizedExpression(expr);
return unpacked is IdentifierExpression && ((IdentifierExpression)unpacked).Identifier == currentTransparentType[0].Item1.Name;
}
public override AstNode VisitQuerySelectClause(QuerySelectClause querySelectClause) {
if (eatSelect) {
eatSelect = false;
return currentResult;
}
else if (((QueryExpression)querySelectClause.Parent).Clauses.Count > 2 && IsSingleRangeVariable(querySelectClause.Expression)) {
// A simple query that ends with a trivial select should be removed.
return currentResult;
}
var param = CreateParameterForCurrentRangeVariable();
var lambda = CreateLambda(new[] { param }, VisitNested(querySelectClause.Expression, param));
return currentResult.Invoke("Select", lambda);
}
public override AstNode VisitQueryGroupClause(QueryGroupClause queryGroupClause) {
var param = CreateParameterForCurrentRangeVariable();
var keyLambda = CreateLambda(new[] { param }, VisitNested(queryGroupClause.Key, param));
if (IsSingleRangeVariable(queryGroupClause.Projection)) {
// We are grouping by the single active range variable, so we can use the single argument form of GroupBy
return currentResult.Invoke("GroupBy", keyLambda);
}
else {
var projectionParam = CreateParameterForCurrentRangeVariable();
var projectionLambda = CreateLambda(new[] { projectionParam }, VisitNested(queryGroupClause.Projection, projectionParam));
return currentResult.Invoke("GroupBy", keyLambda, projectionLambda);
}
}
public override AstNode VisitIdentifierExpression(IdentifierExpression identifierExpression) {
Expression subst;
activeRangeVariableSubstitutions.TryGetValue(identifierExpression.Identifier, out subst);
return subst != null ? subst.Clone() : null;
}
}
///
/// Expands all occurances of query patterns in the specified node. Returns a clone of the node with all query patterns expanded, or null if there was no query pattern to expand.
///
///
/// A sequence of names to use for transparent identifiers. Once the sequence is over, a fallback name generator is used
///
public QueryExpressionExpansionResult ExpandQueryExpressions(AstNode node, IEnumerable transparentIdentifierNamePicker) {
var visitor = new Visitor();
visitor.TransparentIdentifierNamePicker = transparentIdentifierNamePicker.GetEnumerator();
var astNode = node.AcceptVisitor(visitor);
if (astNode != null) {
astNode.Freeze();
return new QueryExpressionExpansionResult(astNode, visitor.rangeVariables, visitor.expressions);
}
else {
return null;
}
}
///
/// Expands all occurances of query patterns in the specified node. Returns a clone of the node with all query patterns expanded, or null if there was no query pattern to expand.
///
///
///
public QueryExpressionExpansionResult ExpandQueryExpressions(AstNode node)
{
return ExpandQueryExpressions(node, Enumerable.Empty());
}
}
}