You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
9.9 KiB
274 lines
9.9 KiB
#if NET20 || NET30 || !NET_4_6 |
|
|
|
// Copyright (c) Microsoft. All rights reserved. |
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information. |
|
|
|
using System.Collections.Generic; |
|
using System.ComponentModel; |
|
using System.Diagnostics; |
|
using System.Dynamic.Utils; |
|
using System.Linq.Expressions.Reimplement; |
|
using System.Linq.Expressions.Reimplement.Compiler; |
|
|
|
namespace System.Runtime.CompilerServices |
|
{ |
|
public partial class RuntimeOps |
|
{ |
|
/// <summary> |
|
/// Quotes the provided expression tree. |
|
/// </summary> |
|
/// <param name="expression">The expression to quote.</param> |
|
/// <param name="hoistedLocals">The hoisted local state provided by the compiler.</param> |
|
/// <param name="locals">The actual hoisted local values.</param> |
|
/// <returns>The quoted expression.</returns> |
|
[Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] |
|
public static Expression Quote(Expression expression, object hoistedLocals, object[] locals) |
|
{ |
|
Debug.Assert(hoistedLocals != null && locals != null); |
|
var quoter = new ExpressionQuoter((HoistedLocals)hoistedLocals, locals); |
|
return quoter.Visit(expression); |
|
} |
|
|
|
/// <summary> |
|
/// Combines two runtime variable lists and returns a new list. |
|
/// </summary> |
|
/// <param name="first">The first list.</param> |
|
/// <param name="second">The second list.</param> |
|
/// <param name="indexes">The index array indicating which list to get variables from.</param> |
|
/// <returns>The merged runtime variables.</returns> |
|
[Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] |
|
public static IRuntimeVariables MergeRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) |
|
{ |
|
return new MergedRuntimeVariables(first, second, indexes); |
|
} |
|
|
|
// Modifies a quoted Expression instance by changing hoisted variables and |
|
// parameters into hoisted local references. The variable's StrongBox is |
|
// burned as a constant, and all hoisted variables/parameters are rewritten |
|
// as indexing expressions. |
|
// |
|
// The behavior of Quote is indended to be like C# and VB expression quoting |
|
private sealed class ExpressionQuoter : ExpressionVisitor |
|
{ |
|
private readonly HoistedLocals _scope; |
|
private readonly object[] _locals; |
|
|
|
// A stack of variables that are defined in nested scopes. We search |
|
// this first when resolving a variable in case a nested scope shadows |
|
// one of our variable instances. |
|
private readonly Stack<Set<ParameterExpression>> _shadowedVars = new Stack<Set<ParameterExpression>>(); |
|
|
|
internal ExpressionQuoter(HoistedLocals scope, object[] locals) |
|
{ |
|
_scope = scope; |
|
_locals = locals; |
|
} |
|
|
|
protected internal override Expression VisitLambda<T>(Expression<T> node) |
|
{ |
|
_shadowedVars.Push(new Set<ParameterExpression>(node.Parameters)); |
|
var b = Visit(node.Body); |
|
_shadowedVars.Pop(); |
|
if (b == node.Body) |
|
{ |
|
return node; |
|
} |
|
return Expression.Lambda<T>(b, node.Name, node.TailCall, node.Parameters); |
|
} |
|
|
|
protected internal override Expression VisitBlock(BlockExpression node) |
|
{ |
|
if (node.Variables.Count > 0) |
|
{ |
|
_shadowedVars.Push(new Set<ParameterExpression>(node.Variables)); |
|
} |
|
var b = Visit(node.Expressions); |
|
if (node.Variables.Count > 0) |
|
{ |
|
_shadowedVars.Pop(); |
|
} |
|
if (b == node.Expressions) |
|
{ |
|
return node; |
|
} |
|
return Expression.Block(node.Variables, b); |
|
} |
|
|
|
protected override CatchBlock VisitCatchBlock(CatchBlock node) |
|
{ |
|
if (node.Variable != null) |
|
{ |
|
_shadowedVars.Push(new Set<ParameterExpression>(new[] { node.Variable })); |
|
} |
|
var b = Visit(node.Body); |
|
var f = Visit(node.Filter); |
|
if (node.Variable != null) |
|
{ |
|
_shadowedVars.Pop(); |
|
} |
|
if (b == node.Body && f == node.Filter) |
|
{ |
|
return node; |
|
} |
|
return Expression.MakeCatchBlock(node.Test, node.Variable, b, f); |
|
} |
|
|
|
protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) |
|
{ |
|
var count = node.Variables.Count; |
|
var boxes = new List<IStrongBox>(); |
|
var vars = new List<ParameterExpression>(); |
|
var indexes = new int[count]; |
|
for (var i = 0; i < count; i++) |
|
{ |
|
var box = GetBox(node.Variables[i]); |
|
if (box == null) |
|
{ |
|
indexes[i] = vars.Count; |
|
vars.Add(node.Variables[i]); |
|
} |
|
else |
|
{ |
|
indexes[i] = -1 - boxes.Count; |
|
boxes.Add(box); |
|
} |
|
} |
|
|
|
// No variables were rewritten. Just return the original node |
|
if (boxes.Count == 0) |
|
{ |
|
return node; |
|
} |
|
|
|
var boxesConst = Expression.Constant(new RuntimeVariables(boxes.ToArray()), typeof(IRuntimeVariables)); |
|
// All of them were rewritten. Just return the array as a constant |
|
if (vars.Count == 0) |
|
{ |
|
return boxesConst; |
|
} |
|
|
|
// Otherwise, we need to return an object that merges them |
|
return Expression.Call( |
|
typeof(RuntimeOps).GetMethod("MergeRuntimeVariables"), |
|
Expression.RuntimeVariables(new TrueReadOnlyCollection<ParameterExpression>(vars.ToArray())), |
|
boxesConst, |
|
Expression.Constant(indexes) |
|
); |
|
} |
|
|
|
protected internal override Expression VisitParameter(ParameterExpression node) |
|
{ |
|
var box = GetBox(node); |
|
if (box == null) |
|
{ |
|
return node; |
|
} |
|
return Expression.Field(Expression.Constant(box), "Value"); |
|
} |
|
|
|
private IStrongBox GetBox(ParameterExpression variable) |
|
{ |
|
// Skip variables that are shadowed by a nested scope/lambda |
|
foreach (var hidden in _shadowedVars) |
|
{ |
|
if (hidden.Contains(variable)) |
|
{ |
|
return null; |
|
} |
|
} |
|
|
|
var scope = _scope; |
|
var locals = _locals; |
|
while (true) |
|
{ |
|
int hoistIndex; |
|
if (scope.Indexes.TryGetValue(variable, out hoistIndex)) |
|
{ |
|
return (IStrongBox)locals[hoistIndex]; |
|
} |
|
scope = scope.Parent; |
|
if (scope == null) |
|
{ |
|
break; |
|
} |
|
locals = HoistedLocals.GetParent(locals); |
|
} |
|
|
|
// Unbound variable: an error should've been thrown already |
|
// from VariableBinder |
|
throw ContractUtils.Unreachable; |
|
} |
|
} |
|
|
|
private sealed class RuntimeVariables : IRuntimeVariables |
|
{ |
|
private readonly IStrongBox[] _boxes; |
|
|
|
internal RuntimeVariables(IStrongBox[] boxes) |
|
{ |
|
_boxes = boxes; |
|
} |
|
|
|
int IRuntimeVariables.Count |
|
{ |
|
get { return _boxes.Length; } |
|
} |
|
|
|
object IRuntimeVariables.this[int index] |
|
{ |
|
get { return _boxes[index].Value; } |
|
|
|
set { _boxes[index].Value = value; } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Provides a list of variables, supporing read/write of the values |
|
/// Exposed via RuntimeVariablesExpression |
|
/// </summary> |
|
private sealed class MergedRuntimeVariables : IRuntimeVariables |
|
{ |
|
private readonly IRuntimeVariables _first; |
|
private readonly IRuntimeVariables _second; |
|
|
|
// For reach item, the index into the first or second list |
|
// Positive values mean the first array, negative means the second |
|
private readonly int[] _indexes; |
|
|
|
internal MergedRuntimeVariables(IRuntimeVariables first, IRuntimeVariables second, int[] indexes) |
|
{ |
|
_first = first; |
|
_second = second; |
|
_indexes = indexes; |
|
} |
|
|
|
public int Count |
|
{ |
|
get { return _indexes.Length; } |
|
} |
|
|
|
public object this[int index] |
|
{ |
|
get |
|
{ |
|
index = _indexes[index]; |
|
return (index >= 0) ? _first[index] : _second[-1 - index]; |
|
} |
|
set |
|
{ |
|
index = _indexes[index]; |
|
if (index >= 0) |
|
{ |
|
_first[index] = value; |
|
} |
|
else |
|
{ |
|
_second[-1 - index] = value; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |