Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2839_HiveProjectManagement/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/IntroduceQueryExpressions.cs @ 15761

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

#2077: created branch and added first version

File size: 14.7 KB
Line 
1//
2// IntroduceQueryExpressions.cs
3//
4// Modified by Luís Reis <luiscubal@gmail.com> (Copyright (C) 2013)
5//
6// Copyright header of the original version follows:
7//
8// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
9//
10// Permission is hereby granted, free of charge, to any person obtaining a copy of this
11// software and associated documentation files (the "Software"), to deal in the Software
12// without restriction, including without limitation the rights to use, copy, modify, merge,
13// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
14// to whom the Software is furnished to do so, subject to the following conditions:
15//
16// The above copyright notice and this permission notice shall be included in all copies or
17// substantial portions of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
20// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
22// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24// DEALINGS IN THE SOFTWARE.
25using System;
26using System.Diagnostics;
27using System.Linq;
28using ICSharpCode.NRefactory.CSharp;
29using ICSharpCode.NRefactory.PatternMatching;
30
31namespace ICSharpCode.NRefactory.CSharp
32{
33  static class NRefactoryExtensions
34  {
35    public static T Detach<T>(this T node) where T : AstNode
36    {
37      node.Remove();
38      return node;
39    }
40
41    public static T CopyAnnotationsFrom<T>(this T node, AstNode other) where T : AstNode
42    {
43      foreach (object annotation in other.Annotations) {
44        node.AddAnnotation(annotation);
45      }
46      return node;
47    }
48
49    public static Expression WithName(this Expression node, string patternGroupName)
50    {
51      return new NamedNode(patternGroupName, node);
52    }
53  }
54
55  /// <summary>
56  /// Decompiles query expressions.
57  /// Based on C# 4.0 spec, §7.16.2 Query expression translation
58  /// </summary>
59  public class IntroduceQueryExpressions
60  {
61    static readonly InvocationExpression castPattern = new InvocationExpression {
62      Target = new MemberReferenceExpression {
63        Target = new AnyNode("inExpr"),
64        MemberName = "Cast",
65        TypeArguments = { new AnyNode("targetType") }
66      }};
67
68    public Expression ConvertFluentToQuery(Expression node)
69    {
70      node = node.Clone();
71
72      var artificialParent = new ExpressionStatement();
73      artificialParent.Expression = node;
74
75      DecompileQueries(node);
76      // After all queries were decompiled, detect degenerate queries (queries not property terminated with 'select' or 'group')
77      // and fix them, either by adding a degenerate select, or by combining them with another query.
78      foreach (QueryExpression query in artificialParent.Descendants.OfType<QueryExpression>()) {
79        QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
80        if (IsDegenerateQuery(query)) {
81          string identifierName = fromClause.Identifier;
82
83          // introduce select for degenerate query
84          query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(identifierName) });
85        }
86
87        if (fromClause.Type.IsNull) {
88          // See if the data source of this query is a degenerate query,
89          // and combine the queries if possible.
90          QueryExpression innerQuery = fromClause.Expression as QueryExpression;
91          while (IsDegenerateQuery(innerQuery)) {
92            QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
93            if (fromClause.Identifier != innerFromClause.Identifier && !innerFromClause.Identifier.StartsWith("<>"))
94              break;
95            // Replace the fromClause with all clauses from the inner query
96            fromClause.Remove();
97            foreach (var identifierChild in innerQuery.Descendants.OfType<Identifier>().Where(identifier => identifier.Name == innerFromClause.Identifier)) {
98              //When the identifier is "<>X", then replace it with the outer one
99              identifierChild.ReplaceWith(fromClause.IdentifierToken.Clone());
100            }
101            QueryClause insertionPos = null;
102            foreach (var clause in innerQuery.Clauses) {
103              query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
104            }
105            fromClause = innerFromClause;
106            innerQuery = fromClause.Expression as QueryExpression;
107          }
108        }
109      }
110
111      return artificialParent.Expression.Clone();
112    }
113
114    bool IsDegenerateQuery(QueryExpression query)
115    {
116      if (query == null)
117        return false;
118      var lastClause = query.Clauses.LastOrDefault();
119      return !(lastClause is QuerySelectClause || lastClause is QueryGroupClause);
120    }
121
122    void DecompileQueries(AstNode node)
123    {
124      QueryExpression query = DecompileQuery(node as InvocationExpression);
125      if (query != null)
126        node.ReplaceWith(query);
127    }
128
129    Expression ExtractQuery(MemberReferenceExpression mre)
130    {
131      var inExpression = mre.Target.Clone();
132      var inContent = DecompileQuery(inExpression as InvocationExpression) ?? inExpression;
133      return inContent;
134    }
135
136    QueryExpression DecompileQuery(InvocationExpression invocation)
137    {
138      if (invocation == null)
139        return null;
140      MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression;
141      if (mre == null)
142        return null;
143
144      switch (mre.MemberName) {
145        case "Select":
146          {
147            if (invocation.Arguments.Count != 1)
148              return null;
149            string parameterName;
150            Expression body;
151            if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) {
152              QueryExpression query = new QueryExpression();
153              query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = ExtractQuery(mre) });
154              query.Clauses.Add(new QuerySelectClause { Expression = body.Detach() });
155              return query;
156            }
157            return null;
158          }
159        case "Cast":
160          {
161            if (invocation.Arguments.Count == 0 && mre.TypeArguments.Count == 1) {
162              var typeArgument = mre.TypeArguments.First();
163
164              QueryExpression query = new QueryExpression();
165              string varName = GenerateVariableName();
166              query.Clauses.Add(new QueryFromClause {
167                Identifier = varName,
168                Expression = ExtractQuery(mre),
169                Type = typeArgument.Detach()
170              });
171              return query;
172
173            }
174            return null;
175          }
176        case "GroupBy":
177          {
178            if (invocation.Arguments.Count == 2) {
179              string parameterName1, parameterName2;
180              Expression keySelector, elementSelector;
181              if (MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName1, out keySelector)
182                && MatchSimpleLambda(invocation.Arguments.ElementAt(1), out parameterName2, out elementSelector)
183                && parameterName1 == parameterName2) {
184                QueryExpression query = new QueryExpression();
185                query.Clauses.Add(new QueryFromClause { Identifier = parameterName1, Expression = ExtractQuery(mre) });
186                query.Clauses.Add(new QueryGroupClause { Projection = elementSelector.Detach(), Key = keySelector.Detach() });
187                return query;
188              }
189            } else if (invocation.Arguments.Count == 1) {
190              string parameterName;
191              Expression keySelector;
192              if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out keySelector)) {
193                QueryExpression query = new QueryExpression();
194                query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = ExtractQuery(mre) });
195                query.Clauses.Add(new QueryGroupClause {
196                  Projection = new IdentifierExpression(parameterName),
197                  Key = keySelector.Detach()
198                });
199                return query;
200              }
201            }
202            return null;
203          }
204        case "SelectMany":
205          {
206            if (invocation.Arguments.Count != 2)
207              return null;
208            string parameterName;
209            Expression collectionSelector;
210            if (!MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName, out collectionSelector))
211              return null;
212            LambdaExpression lambda = invocation.Arguments.ElementAt(1) as LambdaExpression;
213            if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) {
214              ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
215              ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
216              QueryExpression query = new QueryExpression();
217              query.Clauses.Add(new QueryFromClause { Identifier = p1.Name, Expression = ExtractQuery(mre) });
218              query.Clauses.Add(new QueryFromClause { Identifier = p2.Name, Expression = collectionSelector.Detach() });
219              query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() });
220              return query;
221            }
222            return null;
223          }
224        case "Where":
225          {
226            if (invocation.Arguments.Count != 1)
227              return null;
228            string parameterName;
229            Expression body;
230            if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) {
231              QueryExpression query = new QueryExpression();
232              query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = ExtractQuery(mre) });
233              query.Clauses.Add(new QueryWhereClause { Condition = body.Detach() });
234              return query;
235            }
236            return null;
237          }
238        case "OrderBy":
239        case "OrderByDescending":
240        case "ThenBy":
241        case "ThenByDescending":
242          {
243            if (invocation.Arguments.Count != 1)
244              return null;
245            string parameterName;
246            Expression orderExpression;
247            if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out orderExpression)) {
248              if (ValidateThenByChain(invocation, parameterName)) {
249                QueryOrderClause orderClause = new QueryOrderClause();
250                InvocationExpression tmp = invocation;
251                while (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") {
252                  // insert new ordering at beginning
253                  orderClause.Orderings.InsertAfter(
254                    null, new QueryOrdering {
255                    Expression = orderExpression.Detach(),
256                    Direction = (mre.MemberName == "ThenBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
257                  });
258
259                  tmp = (InvocationExpression)mre.Target;
260                  mre = (MemberReferenceExpression)tmp.Target;
261                  MatchSimpleLambda(tmp.Arguments.Single(), out parameterName, out orderExpression);
262                }
263                // insert new ordering at beginning
264                orderClause.Orderings.InsertAfter(
265                  null, new QueryOrdering {
266                  Expression = orderExpression.Detach(),
267                  Direction = (mre.MemberName == "OrderBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
268                });
269
270                QueryExpression query = new QueryExpression();
271                query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = ExtractQuery(mre) });
272                query.Clauses.Add(orderClause);
273                return query;
274              }
275            }
276            return null;
277          }
278        case "Join":
279        case "GroupJoin":
280          {
281            if (invocation.Arguments.Count != 4)
282              return null;
283            Expression source1 = mre.Target;
284            Expression source2 = invocation.Arguments.ElementAt(0);
285            string elementName1, elementName2;
286            Expression key1, key2;
287            if (!MatchSimpleLambda(invocation.Arguments.ElementAt(1), out elementName1, out key1))
288              return null;
289            if (!MatchSimpleLambda(invocation.Arguments.ElementAt(2), out elementName2, out key2))
290              return null;
291            LambdaExpression lambda = invocation.Arguments.ElementAt(3) as LambdaExpression;
292            if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) {
293              ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
294              ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
295              QueryExpression query = new QueryExpression();
296              query.Clauses.Add(new QueryFromClause { Identifier = elementName1, Expression = source1.Detach() });
297              QueryJoinClause joinClause = new QueryJoinClause();
298             
299              joinClause.JoinIdentifier = elementName2;    // join elementName2
300              joinClause.InExpression = source2.Detach();  // in source2
301
302              Match castMatch = castPattern.Match(source2);
303              if (castMatch.Success) {
304                Expression target = castMatch.Get<Expression>("inExpr").Single().Detach();
305                joinClause.Type = castMatch.Get<AstType>("targetType").Single().Detach();
306                joinClause.InExpression = target;
307              }
308             
309              joinClause.OnExpression = key1.Detach();     // on key1
310              joinClause.EqualsExpression = key2.Detach(); // equals key2
311              if (mre.MemberName == "GroupJoin") {
312                joinClause.IntoIdentifier = p2.Name; // into p2.Name
313              }
314              query.Clauses.Add(joinClause);
315              Expression resultExpr = ((Expression)lambda.Body).Detach();
316              if (p1.Name != elementName1) {
317                foreach (var identifier in resultExpr.Descendants.OfType<Identifier>().Where(id => id.Name == p1.Name))
318                {
319                  identifier.Name = elementName1;
320                }
321              }
322              if (p2.Name != elementName2 && mre.MemberName != "GroupJoin") {
323                foreach (var identifier in resultExpr.Descendants.OfType<Identifier>().Where(id => id.Name == p2.Name))
324                {
325                  identifier.Name = elementName2;
326                }
327              }
328              query.Clauses.Add(new QuerySelectClause { Expression = resultExpr });
329              return query;
330            }
331            return null;
332          }
333        default:
334          return null;
335      }
336    }
337
338    int id = 1;
339    string GenerateVariableName()
340    {
341      return "<>" + id++;
342    }
343
344    /// <summary>
345    /// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation.
346    /// </summary>
347    bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName)
348    {
349      if (invocation == null || invocation.Arguments.Count != 1)
350        return false;
351      MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression;
352      if (mre == null)
353        return false;
354      string parameterName;
355      Expression body;
356      if (!MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body))
357        return false;
358      if (parameterName != expectedParameterName)
359        return false;
360
361      if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending")
362        return true;
363      else if (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending")
364        return ValidateThenByChain(mre.Target as InvocationExpression, expectedParameterName);
365      else
366        return false;
367    }
368
369    /// <summary>Matches simple lambdas of the form "a => b"</summary>
370    bool MatchSimpleLambda(Expression expr, out string parameterName, out Expression body)
371    {
372      LambdaExpression lambda = expr as LambdaExpression;
373      if (lambda != null && lambda.Parameters.Count == 1 && lambda.Body is Expression) {
374        ParameterDeclaration p = lambda.Parameters.Single();
375        if (p.ParameterModifier == ParameterModifier.None) {
376          parameterName = p.Name;
377          body = (Expression)lambda.Body;
378          return true;
379        }
380      }
381      parameterName = null;
382      body = null;
383      return false;
384    }
385  }
386}
Note: See TracBrowser for help on using the repository browser.