Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2965_CancelablePersistence/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/QueryExpressionExpander.cs @ 16386

Last change on this file since 16386 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 17.9 KB
Line 
1// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Diagnostics;
22using System.Globalization;
23using System.Linq;
24using System.Text;
25using ICSharpCode.NRefactory.PatternMatching;
26
27namespace ICSharpCode.NRefactory.CSharp {
28  public class QueryExpressionExpansionResult {
29    public AstNode AstNode { get; private set; }
30
31    /// <summary>
32    /// Maps original range variables to some node in the new tree that represents them.
33    /// </summary>
34    public IDictionary<Identifier, AstNode> RangeVariables { get; private set; }
35
36    /// <summary>
37    /// Maps clauses to method calls. The keys will always be either a <see cref="QueryClause"/> or a <see cref="QueryOrdering"/>
38    /// </summary>
39    public IDictionary<AstNode, Expression> Expressions { get; private set; }
40
41    public QueryExpressionExpansionResult(AstNode astNode, IDictionary<Identifier, AstNode> rangeVariables, IDictionary<AstNode, Expression> expressions) {
42      AstNode = astNode;
43      RangeVariables = rangeVariables;
44      Expressions = expressions;
45    }
46  }
47
48  public class QueryExpressionExpander {
49    class Visitor : DepthFirstAstVisitor<AstNode> {
50      internal IEnumerator<string> TransparentIdentifierNamePicker;
51
52      protected override AstNode VisitChildren(AstNode node) {
53        List<AstNode> newChildren = null;
54       
55        int i = 0;
56        foreach (var child in node.Children) {
57          var newChild = child.AcceptVisitor(this);
58          if (newChild != null) {
59            newChildren = newChildren ?? Enumerable.Repeat((AstNode)null, i).ToList();
60            newChildren.Add(newChild);
61          }
62          else if (newChildren != null) {
63            newChildren.Add(null);
64          }
65          i++;
66        }
67
68        if (newChildren == null)
69          return null;
70
71        var result = node.Clone();
72
73        i = 0;
74        foreach (var children in result.Children) {
75          if (newChildren[i] != null)
76            children.ReplaceWith(newChildren[i]);
77          i++;
78        }
79
80        return result;
81      }
82
83      Expression MakeNestedMemberAccess(Expression target, IEnumerable<string> members) {
84        return members.Aggregate(target, (current, m) => current.Member(m));
85      }
86
87      Expression VisitNested(Expression node, ParameterDeclaration transparentParameter) {
88        var oldRangeVariableSubstitutions = activeRangeVariableSubstitutions;
89        try {
90          if (transparentParameter != null && currentTransparentType.Count > 1) {
91            activeRangeVariableSubstitutions = new Dictionary<string, Expression>(activeRangeVariableSubstitutions);
92            foreach (var t in currentTransparentType)
93              activeRangeVariableSubstitutions[t.Item1.Name] = MakeNestedMemberAccess(new IdentifierExpression(transparentParameter.Name), t.Item2);
94          }
95          var result = node.AcceptVisitor(this);
96          return (Expression)(result ?? node.Clone());
97        }
98        finally {
99          activeRangeVariableSubstitutions = oldRangeVariableSubstitutions;
100        }
101      }
102
103      QueryClause GetNextQueryClause(QueryClause clause) {
104        for (AstNode node = clause.NextSibling; node != null; node = node.NextSibling) {
105          if (node.Role == QueryExpression.ClauseRole)
106            return (QueryClause)node;
107        }
108        return null;
109      }
110
111      public IDictionary<Identifier, AstNode> rangeVariables = new Dictionary<Identifier, AstNode>();
112      public IDictionary<AstNode, Expression> expressions = new Dictionary<AstNode, Expression>();
113
114      Dictionary<string, Expression> activeRangeVariableSubstitutions = new Dictionary<string, Expression>();
115      List<Tuple<Identifier, List<string>>> currentTransparentType = new List<Tuple<Identifier, List<string>>>();
116      Expression currentResult;
117      bool eatSelect;
118
119      void MapExpression(AstNode orig, Expression newExpr) {
120        Debug.Assert(orig is QueryClause || orig is QueryOrdering);
121        expressions[orig] = newExpr;
122      }
123
124      internal static IEnumerable<string> FallbackTransparentIdentifierNamePicker()
125      {
126        const string TransparentParameterNameTemplate = "x{0}";
127        int currentTransparentParameter = 0;
128        for (;;) {
129          yield return string.Format(CultureInfo.InvariantCulture, TransparentParameterNameTemplate, currentTransparentParameter++);
130        }
131      }
132
133      ParameterDeclaration CreateParameterForCurrentRangeVariable() {
134        var param = new ParameterDeclaration();
135
136        if (currentTransparentType.Count == 1) {
137          var clonedRangeVariable = (Identifier)currentTransparentType[0].Item1.Clone();
138          if (!rangeVariables.ContainsKey(currentTransparentType[0].Item1))
139            rangeVariables[currentTransparentType[0].Item1] = param;
140          param.AddChild(clonedRangeVariable, Roles.Identifier);
141        }
142        else {
143          if (!TransparentIdentifierNamePicker.MoveNext()) {
144            TransparentIdentifierNamePicker = FallbackTransparentIdentifierNamePicker().GetEnumerator();
145            TransparentIdentifierNamePicker.MoveNext();
146          }
147          string name = TransparentIdentifierNamePicker.Current;
148          param.AddChild(Identifier.Create(name), Roles.Identifier);
149        }
150        return param;
151      }
152
153      LambdaExpression CreateLambda(IList<ParameterDeclaration> parameters, Expression body) {
154        var result = new LambdaExpression();
155        if (parameters.Count > 1)
156          result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.LPar), Roles.LPar);
157        result.AddChild(parameters[0], Roles.Parameter);
158        for (int i = 1; i < parameters.Count; i++) {
159          result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.Comma), Roles.Comma);
160          result.AddChild(parameters[i], Roles.Parameter);
161        }
162        if (parameters.Count > 1)
163          result.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.RPar), Roles.RPar);
164        result.AddChild(body, LambdaExpression.BodyRole);
165
166        return result;
167      }
168
169      ParameterDeclaration CreateParameter(Identifier identifier) {
170        var result = new ParameterDeclaration();
171        result.AddChild(identifier, Roles.Identifier);
172        return result;
173      }
174
175      Expression AddMemberToCurrentTransparentType(ParameterDeclaration param, Identifier name, Expression value, bool namedExpression) {
176        Expression newAssignment = VisitNested(value, param);
177        if (namedExpression) {
178          newAssignment = new NamedExpression(name.Name, VisitNested(value, param));
179          if (!rangeVariables.ContainsKey(name) )
180            rangeVariables[name] = ((NamedExpression)newAssignment).NameToken;
181        }
182
183        foreach (var t in currentTransparentType)
184          t.Item2.Insert(0, param.Name);
185
186        currentTransparentType.Add(Tuple.Create(name, new List<string> { name.Name }));
187        return new AnonymousTypeCreateExpression(new[] { new IdentifierExpression(param.Name), newAssignment });
188      }
189
190      void AddFirstMemberToCurrentTransparentType(Identifier identifier) {
191        Debug.Assert(currentTransparentType.Count == 0);
192        currentTransparentType.Add(Tuple.Create(identifier, new List<string>()));
193      }
194
195      public override AstNode VisitQueryExpression(QueryExpression queryExpression) {
196        var oldTransparentType = currentTransparentType;
197        var oldResult = currentResult;
198        var oldEatSelect = eatSelect;
199        try {
200          currentTransparentType = new List<Tuple<Identifier, List<string>>>();
201          currentResult = null;
202          eatSelect = false;
203
204          foreach (var clause in queryExpression.Clauses) {
205            var result = (Expression)clause.AcceptVisitor(this);
206            MapExpression(clause, result ?? currentResult);
207            currentResult = result;
208          }
209
210          return currentResult;
211        }
212        finally {
213          currentTransparentType = oldTransparentType;
214          currentResult = oldResult;
215          eatSelect = oldEatSelect;
216        }
217      }
218
219      public override AstNode VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause) {
220        var prev = VisitNested(queryContinuationClause.PrecedingQuery, null);
221        AddFirstMemberToCurrentTransparentType(queryContinuationClause.IdentifierToken);
222        return prev;
223      }
224
225      static bool NeedsToBeParenthesized(Expression expr)
226      {
227        UnaryOperatorExpression unary = expr as UnaryOperatorExpression;
228        if (unary != null) {
229          if (unary.Operator == UnaryOperatorType.PostIncrement || unary.Operator == UnaryOperatorType.PostDecrement) {
230            return false;
231          }
232          return true;
233        }
234     
235        if (expr is BinaryOperatorExpression || expr is ConditionalExpression || expr is AssignmentExpression) {
236          return true;
237        }
238
239        return false;
240      }
241
242      static Expression ParenthesizeIfNeeded(Expression expr)
243      {
244        return NeedsToBeParenthesized(expr) ? new ParenthesizedExpression(expr.Clone()) : expr;
245      }
246
247      public override AstNode VisitQueryFromClause(QueryFromClause queryFromClause) {
248        if (currentResult == null) {
249          AddFirstMemberToCurrentTransparentType(queryFromClause.IdentifierToken);
250          if (queryFromClause.Type.IsNull) {
251            return VisitNested(ParenthesizeIfNeeded(queryFromClause.Expression), null);
252          }
253          else {
254            return VisitNested(ParenthesizeIfNeeded(queryFromClause.Expression), null).Invoke("Cast", new[] { queryFromClause.Type.Clone() }, new Expression[0]);
255          }
256        }
257        else {
258          var innerSelectorParam = CreateParameterForCurrentRangeVariable();
259          var lambdaContent = VisitNested(queryFromClause.Expression, innerSelectorParam);
260          if (!queryFromClause.Type.IsNull) {
261            lambdaContent = lambdaContent.Invoke("Cast", new[] { queryFromClause.Type.Clone() }, new Expression[0]);
262          }
263          var innerSelector = CreateLambda(new[] { innerSelectorParam }, lambdaContent);
264
265          var clonedIdentifier = (Identifier)queryFromClause.IdentifierToken.Clone();
266
267          var resultParam = CreateParameterForCurrentRangeVariable();
268          Expression body;
269          // Second from clause - SelectMany
270          var select = GetNextQueryClause(queryFromClause) as QuerySelectClause;
271          if (select != null) {
272            body = VisitNested(select.Expression, resultParam);
273            eatSelect = true;
274          }
275          else {
276            body = AddMemberToCurrentTransparentType(resultParam, queryFromClause.IdentifierToken, new IdentifierExpression(queryFromClause.Identifier), false);
277          }
278
279          var resultSelectorParam2 = CreateParameter(clonedIdentifier);
280          var resultSelector = CreateLambda(new[] { resultParam, resultSelectorParam2 }, body);
281          rangeVariables[queryFromClause.IdentifierToken] = resultSelectorParam2;
282
283          return currentResult.Invoke("SelectMany", innerSelector, resultSelector);
284        }
285      }
286
287      public override AstNode VisitQueryLetClause(QueryLetClause queryLetClause) {
288        var param = CreateParameterForCurrentRangeVariable();
289        var body = AddMemberToCurrentTransparentType(param, queryLetClause.IdentifierToken, queryLetClause.Expression, true);
290        var lambda = CreateLambda(new[] { param }, body);
291
292        return currentResult.Invoke("Select", lambda);
293      }
294
295      public override AstNode VisitQueryWhereClause(QueryWhereClause queryWhereClause) {
296        var param = CreateParameterForCurrentRangeVariable();
297        return currentResult.Invoke("Where", CreateLambda(new[] { param }, VisitNested(queryWhereClause.Condition, param)));
298      }
299
300      public override AstNode VisitQueryJoinClause(QueryJoinClause queryJoinClause) {
301        Expression resultSelectorBody = null;
302        var inExpression = VisitNested(queryJoinClause.InExpression, null);
303        if (!queryJoinClause.Type.IsNull) {
304          inExpression = inExpression.Invoke("Cast", new[] { queryJoinClause.Type.Clone() }, EmptyList<Expression>.Instance);
305        }
306        var key1SelectorFirstParam = CreateParameterForCurrentRangeVariable();
307        var key1Selector = CreateLambda(new[] { key1SelectorFirstParam }, VisitNested(queryJoinClause.OnExpression, key1SelectorFirstParam));
308        var key2Param = CreateParameter(Identifier.Create(queryJoinClause.JoinIdentifier));
309        var key2Selector = CreateLambda(new[] { key2Param }, VisitNested(queryJoinClause.EqualsExpression, null));
310
311        var resultSelectorFirstParam = CreateParameterForCurrentRangeVariable();
312
313        var select = GetNextQueryClause(queryJoinClause) as QuerySelectClause;
314        if (select != null) {
315          resultSelectorBody = VisitNested(select.Expression, resultSelectorFirstParam);
316          eatSelect = true;
317        }
318
319        if (queryJoinClause.IntoKeyword.IsNull) {
320          // Normal join
321          if (resultSelectorBody == null)
322            resultSelectorBody = AddMemberToCurrentTransparentType(resultSelectorFirstParam, queryJoinClause.JoinIdentifierToken, new IdentifierExpression(queryJoinClause.JoinIdentifier), false);
323
324          var resultSelector = CreateLambda(new[] { resultSelectorFirstParam, CreateParameter(Identifier.Create(queryJoinClause.JoinIdentifier)) }, resultSelectorBody);
325          rangeVariables[queryJoinClause.JoinIdentifierToken] = key2Param;
326          return currentResult.Invoke("Join", inExpression, key1Selector, key2Selector, resultSelector);
327        }
328        else {
329          // Group join
330          if (resultSelectorBody == null)
331            resultSelectorBody = AddMemberToCurrentTransparentType(resultSelectorFirstParam, queryJoinClause.IntoIdentifierToken, new IdentifierExpression(queryJoinClause.IntoIdentifier), false);
332
333          var intoParam = CreateParameter(Identifier.Create(queryJoinClause.IntoIdentifier));
334          var resultSelector = CreateLambda(new[] { resultSelectorFirstParam, intoParam }, resultSelectorBody);
335          rangeVariables[queryJoinClause.IntoIdentifierToken] = intoParam;
336
337          return currentResult.Invoke("GroupJoin", inExpression, key1Selector, key2Selector, resultSelector);
338        }
339      }
340
341      public override AstNode VisitQueryOrderClause(QueryOrderClause queryOrderClause) {
342        var current = currentResult;
343        bool first = true;
344        foreach (var o in queryOrderClause.Orderings) {
345          string methodName = first ? (o.Direction == QueryOrderingDirection.Descending ? "OrderByDescending" : "OrderBy")
346                                    : (o.Direction == QueryOrderingDirection.Descending ? "ThenByDescending" : "ThenBy");
347
348          var param = CreateParameterForCurrentRangeVariable();
349          current = current.Invoke(methodName, CreateLambda(new[] { param }, VisitNested(o.Expression, param)));
350          MapExpression(o, current);
351          first = false;
352        }
353        return current;
354      }
355
356      bool IsSingleRangeVariable(Expression expr) {
357        if (currentTransparentType.Count > 1)
358          return false;
359        var unpacked = ParenthesizedExpression.UnpackParenthesizedExpression(expr);
360        return unpacked is IdentifierExpression && ((IdentifierExpression)unpacked).Identifier == currentTransparentType[0].Item1.Name;
361      }
362
363      public override AstNode VisitQuerySelectClause(QuerySelectClause querySelectClause) {
364        if (eatSelect) {
365          eatSelect = false;
366          return currentResult;
367        }
368        else if (((QueryExpression)querySelectClause.Parent).Clauses.Count > 2 && IsSingleRangeVariable(querySelectClause.Expression)) {
369          // A simple query that ends with a trivial select should be removed.
370          return currentResult;
371        }
372
373        var param = CreateParameterForCurrentRangeVariable();
374        var lambda = CreateLambda(new[] { param }, VisitNested(querySelectClause.Expression, param));
375        return currentResult.Invoke("Select", lambda);
376      }
377
378      public override AstNode VisitQueryGroupClause(QueryGroupClause queryGroupClause) {
379        var param = CreateParameterForCurrentRangeVariable();
380        var keyLambda = CreateLambda(new[] { param }, VisitNested(queryGroupClause.Key, param));
381
382        if (IsSingleRangeVariable(queryGroupClause.Projection)) {
383          // We are grouping by the single active range variable, so we can use the single argument form of GroupBy
384          return currentResult.Invoke("GroupBy", keyLambda);
385        }
386        else {
387          var projectionParam = CreateParameterForCurrentRangeVariable();
388          var projectionLambda = CreateLambda(new[] { projectionParam }, VisitNested(queryGroupClause.Projection, projectionParam));
389          return currentResult.Invoke("GroupBy", keyLambda, projectionLambda);
390        }
391      }
392
393      public override AstNode VisitIdentifierExpression(IdentifierExpression identifierExpression) {
394        Expression subst;
395        activeRangeVariableSubstitutions.TryGetValue(identifierExpression.Identifier, out subst);
396        return subst != null ? subst.Clone() : null;
397      }
398    }
399
400    /// <summary>
401    /// 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.
402    /// </summary>
403    /// <param name="node"></param>
404    /// <param name="transparentIdentifierNamePicker">A sequence of names to use for transparent identifiers. Once the sequence is over, a fallback name generator is used</param>
405    /// <returns></returns>
406    public QueryExpressionExpansionResult ExpandQueryExpressions(AstNode node, IEnumerable<string> transparentIdentifierNamePicker) {
407      var visitor = new Visitor();
408      visitor.TransparentIdentifierNamePicker = transparentIdentifierNamePicker.GetEnumerator();
409      var astNode = node.AcceptVisitor(visitor);
410      if (astNode != null) {
411        astNode.Freeze();
412        return new QueryExpressionExpansionResult(astNode, visitor.rangeVariables, visitor.expressions);
413      }
414      else {
415        return null;
416      }
417    }
418
419   
420    /// <summary>
421    /// 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.
422    /// </summary>
423    /// <param name="node"></param>
424    /// <returns></returns>
425    public QueryExpressionExpansionResult ExpandQueryExpressions(AstNode node)
426    {
427      return ExpandQueryExpressions(node, Enumerable.Empty<string>());
428    }
429  }
430}
Note: See TracBrowser for help on using the repository browser.