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 { int currentTransparentParameter; const string TransparentParameterNameTemplate = "<>x{0}"; 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; } 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] = clonedRangeVariable; param.AddChild(clonedRangeVariable, Roles.Identifier); } else { param.AddChild(Identifier.Create(string.Format(CultureInfo.InvariantCulture, TransparentParameterNameTemplate, currentTransparentParameter++)), 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); result.AddChild(parameters[0], Roles.Parameter); for (int i = 1; i < parameters.Count; i++) { result.AddChild(new CSharpTokenNode(TextLocation.Empty), Roles.Comma); result.AddChild(parameters[i], Roles.Parameter); } if (parameters.Count > 1) result.AddChild(new CSharpTokenNode(TextLocation.Empty), 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; } public override AstNode VisitQueryFromClause(QueryFromClause queryFromClause) { if (currentResult == null) { AddFirstMemberToCurrentTransparentType(queryFromClause.IdentifierToken); if (queryFromClause.Type.IsNull) { return VisitNested(queryFromClause.Expression, null); } else { return VisitNested(queryFromClause.Expression, null).Invoke("Cast", new[] { queryFromClause.Type.Clone() }, new Expression[0]); } } else { var innerSelectorParam = CreateParameterForCurrentRangeVariable(); var innerSelector = CreateLambda(new[] { innerSelectorParam }, VisitNested(queryFromClause.Expression, innerSelectorParam)); 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 resultSelector = CreateLambda(new[] { resultParam, CreateParameter(clonedIdentifier) }, body); rangeVariables[queryFromClause.IdentifierToken] = clonedIdentifier; 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); var key1SelectorFirstParam = CreateParameterForCurrentRangeVariable(); var key1Selector = CreateLambda(new[] { key1SelectorFirstParam }, VisitNested(queryJoinClause.OnExpression, key1SelectorFirstParam)); var key2Param = Identifier.Create(queryJoinClause.JoinIdentifier); var key2Selector = CreateLambda(new[] { CreateParameter(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 = Identifier.Create(queryJoinClause.IntoIdentifier); var resultSelector = CreateLambda(new[] { resultSelectorFirstParam, CreateParameter(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; } public override AstNode VisitQuerySelectClause(QuerySelectClause querySelectClause) { if (eatSelect) { eatSelect = false; return currentResult; } else if (currentTransparentType.Count == 1 && ((QueryExpression)querySelectClause.Parent).Clauses.Count > 2 && querySelectClause.Expression is IdentifierExpression && ((IdentifierExpression)querySelectClause.Expression).Identifier == currentTransparentType[0].Item1.Name) { // 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 (currentTransparentType.Count == 1 && queryGroupClause.Projection is IdentifierExpression && ((IdentifierExpression)queryGroupClause.Projection).Identifier == currentTransparentType[0].Item1.Name) { // 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. /// /// /// public QueryExpressionExpansionResult ExpandQueryPattern(AstNode node) { var visitor = new Visitor(); var astNode = node.AcceptVisitor(visitor); if (astNode != null) { astNode.Freeze(); return new QueryExpressionExpansionResult(astNode, visitor.rangeVariables, visitor.expressions); } else { return null; } } } }