Merge branch 'cecil-light'
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Compiler / ExpressionQuoter.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Apache License, Version 2.0, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Apache License, Version 2.0.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15
16 #if CLR2
17 using Microsoft.Scripting.Ast;
18 using Microsoft.Scripting.Ast.Compiler;
19 #else
20 using System.Linq.Expressions;
21 using System.Linq.Expressions.Compiler;
22 #endif
23
24 using System.Collections.Generic;
25 using System.ComponentModel;
26 using System.Diagnostics;
27 using System.Dynamic.Utils;
28
29 namespace System.Runtime.CompilerServices {
30     public partial class RuntimeOps {
31         /// <summary>
32         /// Quotes the provided expression tree.
33         /// </summary>
34         /// <param name="expression">The expression to quote.</param>
35         /// <param name="hoistedLocals">The hoisted local state provided by the compiler.</param>
36         /// <param name="locals">The actual hoisted local values.</param>
37         /// <returns>The quoted expression.</returns>
38         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]
39         public static Expression Quote(Expression expression, object hoistedLocals, object[] locals) {
40             Debug.Assert(hoistedLocals != null && locals != null);
41             var quoter = new ExpressionQuoter((HoistedLocals)hoistedLocals, locals);
42             return quoter.Visit(expression);
43         }
44
45         /// <summary>
46         /// Combines two runtime variable lists and returns a new list.
47         /// </summary>
48         /// <param name="first">The first list.</param>
49         /// <param name="second">The second list.</param>
50         /// <param name="indexes">The index array indicating which list to get variables from.</param>
51         /// <returns>The merged runtime variables.</returns>
52         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]
53         public static IRuntimeVariables MergeRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) {
54             return new MergedRuntimeVariables(first, second, indexes);
55         }
56
57         // Modifies a quoted Expression instance by changing hoisted variables and
58         // parameters into hoisted local references. The variable's StrongBox is
59         // burned as a constant, and all hoisted variables/parameters are rewritten
60         // as indexing expressions.
61         //
62         // The behavior of Quote is indended to be like C# and VB expression quoting
63         private sealed class ExpressionQuoter : ExpressionVisitor {
64             private readonly HoistedLocals _scope;
65             private readonly object[] _locals;
66
67             // A stack of variables that are defined in nested scopes. We search
68             // this first when resolving a variable in case a nested scope shadows
69             // one of our variable instances.
70             private readonly Stack<Set<ParameterExpression>> _shadowedVars = new Stack<Set<ParameterExpression>>();
71
72             internal ExpressionQuoter(HoistedLocals scope, object[] locals) {
73                 _scope = scope;
74                 _locals = locals;
75             }
76
77             protected internal override Expression VisitLambda<T>(Expression<T> node) {
78                 _shadowedVars.Push(new Set<ParameterExpression>(node.Parameters));
79                 Expression b = Visit(node.Body);
80                 _shadowedVars.Pop();
81                 if (b == node.Body) {
82                     return node;
83                 }
84                 return Expression.Lambda<T>(b, node.Name, node.TailCall, node.Parameters);
85             }
86
87             protected internal override Expression VisitBlock(BlockExpression node) {
88                 if (node.Variables.Count > 0) {
89                     _shadowedVars.Push(new Set<ParameterExpression>(node.Variables));
90                 }
91                 var b = Visit(node.Expressions);
92                 if (node.Variables.Count > 0) {
93                     _shadowedVars.Pop();
94                 }
95                 if (b == node.Expressions) {
96                     return node;
97                 }
98                 return Expression.Block(node.Variables, b);
99             }
100
101             protected override CatchBlock VisitCatchBlock(CatchBlock node) {
102                 if (node.Variable != null) {
103                     _shadowedVars.Push(new Set<ParameterExpression>(new[] { node.Variable }));
104                 }
105                 Expression b = Visit(node.Body);
106                 Expression f = Visit(node.Filter);
107                 if (node.Variable != null) {
108                     _shadowedVars.Pop();
109                 }
110                 if (b == node.Body && f == node.Filter) {
111                     return node;
112                 }
113                 return Expression.MakeCatchBlock(node.Test, node.Variable, b, f);
114             }
115
116             protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) {
117                 int count = node.Variables.Count;
118                 var boxes = new List<IStrongBox>();
119                 var vars = new List<ParameterExpression>();
120                 var indexes = new int[count];
121                 for (int i = 0; i < count; i++) {
122                     IStrongBox box = GetBox(node.Variables[i]);
123                     if (box == null) {
124                         indexes[i] = vars.Count;
125                         vars.Add(node.Variables[i]);
126                     } else {
127                         indexes[i] = -1 - boxes.Count;
128                         boxes.Add(box);
129                     }
130                 }
131
132                 // No variables were rewritten. Just return the original node
133                 if (boxes.Count == 0) {
134                     return node;
135                 }
136
137                 var boxesConst = Expression.Constant(new RuntimeVariables(boxes.ToArray()), typeof(IRuntimeVariables));
138                 // All of them were rewritten. Just return the array as a constant
139                 if (vars.Count == 0) {
140                     return boxesConst;
141                 }
142
143                 // Otherwise, we need to return an object that merges them
144                 return Expression.Call(
145                     typeof(RuntimeOps).GetMethod("MergeRuntimeVariables"),
146                     Expression.RuntimeVariables(new TrueReadOnlyCollection<ParameterExpression>(vars.ToArray())),
147                     boxesConst,
148                     Expression.Constant(indexes)
149                 );
150             }
151
152             protected internal override Expression VisitParameter(ParameterExpression node) {
153                 IStrongBox box = GetBox(node);
154                 if (box == null) {
155                     return node;
156                 }
157                 return Expression.Field(Expression.Constant(box), "Value");
158             }
159
160             private IStrongBox GetBox(ParameterExpression variable) {
161                 // Skip variables that are shadowed by a nested scope/lambda
162                 foreach (Set<ParameterExpression> hidden in _shadowedVars) {
163                     if (hidden.Contains(variable)) {
164                         return null;
165                     }
166                 }
167
168                 HoistedLocals scope = _scope;
169                 object[] locals = _locals;
170                 while (true) {
171                     int hoistIndex;
172                     if (scope.Indexes.TryGetValue(variable, out hoistIndex)) {
173                         return (IStrongBox)locals[hoistIndex];
174                     }
175                     scope = scope.Parent;
176                     if (scope == null) {
177                         break;
178                     }
179                     locals = HoistedLocals.GetParent(locals);
180                 }
181
182                 // Unbound variable: an error should've been thrown already
183                 // from VariableBinder
184                 throw ContractUtils.Unreachable;
185             }
186         }
187
188         private sealed class RuntimeVariables : IRuntimeVariables {
189             private readonly IStrongBox[] _boxes;
190
191             internal RuntimeVariables(IStrongBox[] boxes) {
192                 _boxes = boxes;
193             }
194
195             int IRuntimeVariables.Count {
196                 get { return _boxes.Length; }
197             }
198
199             object IRuntimeVariables.this[int index] {
200                 get {
201                     return _boxes[index].Value;
202                 }
203                 set {
204                     _boxes[index].Value = value;
205                 }
206             }
207         }
208
209         /// <summary>
210         /// Provides a list of variables, supporing read/write of the values
211         /// Exposed via RuntimeVariablesExpression
212         /// </summary>
213         private sealed class MergedRuntimeVariables : IRuntimeVariables {
214             private readonly IRuntimeVariables _first;
215             private readonly IRuntimeVariables _second;
216
217             // For reach item, the index into the first or second list
218             // Positive values mean the first array, negative means the second
219             private readonly int[] _indexes;
220
221             internal MergedRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) {
222                 _first = first;
223                 _second = second;
224                 _indexes = indexes;
225             }
226
227             public int Count {
228                 get { return _indexes.Length; }
229             }
230
231             public object this[int index] {
232                 get {
233                     index = _indexes[index];
234                     return (index >= 0) ? _first[index] : _second[-1 - index];
235                 }
236                 set {
237                     index = _indexes[index];
238                     if (index >= 0) {
239                         _first[index] = value;
240                     } else {
241                         _second[-1 - index] = value;
242                     }
243                 }
244             }
245         }
246     }
247 }