2007-07-03 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / gmcs / linq.cs
1 //
2 // linq.cs: support for query expressions
3 //
4 // Authors: Marek Safar (marek.safar@gmail.com)
5 //
6 // Licensed under the terms of the GNU GPL
7 //
8 // (C) 2007 Novell, Inc
9 //
10
11 using System;
12 using System.Reflection;
13 using System.Collections;
14
15 namespace Mono.CSharp.Linq
16 {
17         // NOTES:
18         // Expression should be IExpression to save some memory and make a few things
19         // easier to read
20         //
21         //
22
23         public class QueryExpression : Expression
24         {
25                 public readonly Block Block;
26                 public readonly TypeContainer Host;
27
28                 AQueryClause query;
29                 Expression from;
30
31                 public QueryExpression (TypeContainer host, Block block, Expression from, AQueryClause query)
32                 {
33                         this.Host = host;
34                         this.Block = block;
35                         this.from = from;
36                         this.query = query;
37                 }
38
39                 public override Expression DoResolve (EmitContext ec)
40                 {
41                         from = from.DoResolve (ec);
42                         if (from == null)
43                                 return null;
44
45                         ICollection values = Block.Variables.Values;
46                         if (values.Count != 1)
47                                 throw new NotImplementedException ("Count != 1");
48
49                         IEnumerator enumerator = values.GetEnumerator ();
50                         enumerator.MoveNext ();
51                         LocalInfo li = (LocalInfo)enumerator.Current;
52
53                         VarExpr var = li.Type as VarExpr;
54                         if (var != null) {
55                                 li.Type = var.ResolveLValue (ec, from, var.Location);
56                                 if (li.Type == null)
57                                         return null;
58                                 li.VariableType = li.Type.Type;
59                         }
60
61                         Expression e = query.BuildQueryClause (ec, this, from, li);
62                         return e.Resolve (ec);
63                 }
64
65                 public override void Emit (EmitContext ec)
66                 {
67                         throw new NotSupportedException ();
68                 }
69         }
70
71         public abstract class ALinqExpression : Expression
72         {
73                 // Dictionary of method name -> MethodGroupExpr
74                 static Hashtable methods = new Hashtable ();
75                 static Type enumerable_class;
76
77                 protected abstract string MethodName { get; }
78
79                 // TODO: Linq methods are context specific
80                 protected MethodGroupExpr MethodGroup {
81                         get {
82                                 MethodGroupExpr method_group = (MethodGroupExpr)methods [MethodName];
83                                 if (method_group != null)
84                                         return method_group;
85
86                                 if (enumerable_class == null)
87                                         enumerable_class = TypeManager.CoreLookupType ("System.Linq", "Enumerable");
88
89                                 MemberList ml = TypeManager.FindMembers (enumerable_class,
90                                         MemberTypes.Method, BindingFlags.Static | BindingFlags.Public,
91                                         Type.FilterName, MethodName);
92
93                                 method_group = new MethodGroupExpr (ArrayList.Adapter (ml), loc);
94                                 methods.Add (MethodName, method_group);
95                                 return method_group;
96                         }
97                 }
98         }
99
100         public abstract class AQueryClause : ALinqExpression
101         {
102                 public AQueryClause Next;
103                 protected Expression expr;
104
105                 protected AQueryClause (Expression expr, Location loc)
106                 {
107                         this.expr = expr;
108                         this.loc = loc;
109                 }
110
111                 public override Expression DoResolve (EmitContext ec)
112                 {
113                         return expr.DoResolve (ec);
114                 }
115
116                 public override void Emit (EmitContext ec)
117                 {
118                         throw new NotSupportedException ();
119                 }
120
121                 public Expression BuildQueryClause (EmitContext ec, QueryExpression top, Expression from, LocalInfo li)
122                 {
123                         // TODO: An anonymous method is not enough to infer implicitly typed arguments,
124                         // we need lambda expression here
125                         Parameters parameters = new Parameters (new Parameter (li.Type, li.Name, Parameter.Modifier.NONE, null, loc));
126                         AnonymousMethodExpression ame = new AnonymousMethodExpression (
127                                 null, null, top.Host,
128                                 parameters,
129                                 top.Block, loc);
130                         ame.Block = new ToplevelBlock (parameters, loc);
131                         ame.Block.AddStatement (new Return (expr, loc));
132
133                         expr = new Invocation (MethodGroup, CreateArguments (ame, from));
134                         if (Next != null)
135                                 return Next.BuildQueryClause (ec, top, this, li);
136
137                         return expr;
138                 }
139                                                
140                 protected virtual ArrayList CreateArguments (AnonymousMethodExpression ame, Expression from)
141                 {
142                         ArrayList args = new ArrayList (2);
143                         args.Add (new Argument (from));
144                         args.Add (new Argument (ame));
145                         return args;
146                 }
147         }
148
149         public class Cast : ALinqExpression
150         {
151                 readonly Expression expr;
152                 readonly Expression cast_type;
153
154                 public Cast (Expression type, Expression expr, Location loc)
155                 {
156                         this.cast_type = type;
157                         this.expr = expr;
158                         this.loc = loc;
159                 }
160
161                 protected override string MethodName {
162                         get { return "Cast"; }
163                 }
164
165                 public override Expression DoResolve (EmitContext ec)
166                 {
167                         TypeArguments type_arguments = new TypeArguments (loc, cast_type);
168                         Expression cast = MethodGroup.ResolveGeneric (ec, type_arguments);
169
170                         ArrayList args = new ArrayList (1);
171                         args.Add (new Argument (expr));
172                         return new Invocation (cast, args).DoResolve (ec);
173                 }
174
175                 public override void Emit (EmitContext ec)
176                 {
177                         throw new NotSupportedException ();
178                 }
179         }
180
181         public class GroupBy : AQueryClause
182         {
183                 readonly Expression element_selector;
184                 
185                 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
186                         : base (keySelector, loc)
187                 {
188                         this.element_selector = elementSelector;
189                 }
190
191                 protected override ArrayList CreateArguments (AnonymousMethodExpression ame, Expression from)
192                 {
193                         ArrayList args = base.CreateArguments (ame, from);
194                         
195                         // A query can be optimized when selector is not group by specific
196                         if (!element_selector.Equals (from)) {
197                                 AnonymousMethodExpression am_element = new AnonymousMethodExpression (
198                                         null, null, ame.Host, ame.Parameters, ame.Container, loc);
199                                 am_element.Block = new ToplevelBlock (ame.Parameters, loc);
200                                 am_element.Block.AddStatement (new Return (element_selector, loc));
201                                 
202                                 args.Add (new Argument (am_element));
203                         }
204                         return args;
205                 }
206
207                 protected override string MethodName {
208                         get { return "GroupBy"; }
209                 }
210         }
211
212         public class Select : AQueryClause
213         {
214                 public Select (Expression expr, Location loc)
215                         : base (expr, loc)
216                 {
217                 }
218
219                 protected override string MethodName {
220                         get { return "Select"; }
221                 }
222         }
223
224         public class Where : AQueryClause
225         {
226                 public Where (Expression expr, Location loc)
227                         : base (expr, loc)
228                 {
229                 }
230
231                 protected override string MethodName {
232                         get { return "Where"; }
233                 }
234         }
235 }