|
|
@ -0,0 +1,386 @@ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using System.Text; |
|
|
|
using System.Linq.Expressions; |
|
|
|
using System.Reflection; |
|
|
|
|
|
|
|
namespace Net3_Proxy |
|
|
|
{ |
|
|
|
public static class ExpressionEx |
|
|
|
{ |
|
|
|
internal static ExpressionType ExprT(this ExpressionTypeEx ex) |
|
|
|
=> (ExpressionType)ex; |
|
|
|
internal static ExpressionTypeEx ExprTEx(this ExpressionType ex) |
|
|
|
=> (ExpressionTypeEx)ex; |
|
|
|
|
|
|
|
private static void Validate(Type type, bool allowByRef) |
|
|
|
{ |
|
|
|
if (type == null) |
|
|
|
throw new ArgumentNullException(nameof(type)); |
|
|
|
TypeUtils.ValidateType(type, nameof(type), allowByRef, false); |
|
|
|
if (type == typeof(void)) |
|
|
|
throw new ArgumentException("Argument cannot be of type void", nameof(type)); |
|
|
|
} |
|
|
|
|
|
|
|
private static void RequiresCanWrite(Expression expression, string paramName) |
|
|
|
{ |
|
|
|
if (expression == null) |
|
|
|
{ |
|
|
|
throw new ArgumentNullException(paramName); |
|
|
|
} |
|
|
|
ExpressionType nodeType = expression.NodeType; |
|
|
|
if (nodeType != ExpressionType.MemberAccess) |
|
|
|
{ |
|
|
|
if (nodeType == ExpressionType.Parameter) |
|
|
|
return; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
MemberInfo member = ((MemberExpression)expression).Member; |
|
|
|
if (member is PropertyInfo propertyInfo) |
|
|
|
{ |
|
|
|
if (propertyInfo.CanWrite) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
FieldInfo fieldInfo = (FieldInfo)member; |
|
|
|
if (!fieldInfo.IsInitOnly && !fieldInfo.IsLiteral) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
throw new ArgumentException("Expression must be writeable", paramName); |
|
|
|
} |
|
|
|
|
|
|
|
public static void RequiresCanRead(Expression expression, string paramName, int idx = -1) |
|
|
|
{ |
|
|
|
if (expression == null) |
|
|
|
throw new ArgumentNullException(TypeUtils.GetParamName(paramName, idx)); |
|
|
|
ExpressionType nodeType = expression.NodeType; |
|
|
|
if (nodeType == ExpressionType.MemberAccess) |
|
|
|
{ |
|
|
|
if (((MemberExpression)expression).Member is PropertyInfo propertyInfo && !propertyInfo.CanRead) |
|
|
|
{ |
|
|
|
throw new ArgumentException("Expression must be readable", TypeUtils.GetParamName(paramName, idx)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public static VariableExpression Variable(Type type, string name = null) |
|
|
|
{ |
|
|
|
Validate(type, false); |
|
|
|
return new VariableExpression(type, name); |
|
|
|
} |
|
|
|
|
|
|
|
public static AssignExpression Assign(Expression left, Expression right) |
|
|
|
{ |
|
|
|
RequiresCanWrite(left, nameof(left)); |
|
|
|
RequiresCanRead(right, nameof(right)); |
|
|
|
TypeUtils.ValidateType(left.Type, nameof(left), true, true); |
|
|
|
TypeUtils.ValidateType(right.Type, nameof(right), true, true); |
|
|
|
if (!TypeUtils.AreReferenceAssignable(left.Type, right.Type)) |
|
|
|
throw new ArgumentException($"Expression of type '{left.Type}' cannot be used for assignment to type '{right.Type}'"); |
|
|
|
if (left.NodeType.ExprTEx() != ExpressionTypeEx.Variable) |
|
|
|
throw new NotSupportedException("Non-variable left hand operands to assignment is currently not supported"); |
|
|
|
return new AssignExpression(left, right); |
|
|
|
} |
|
|
|
|
|
|
|
public static Expression Block(IEnumerable<VariableExpression> vars, params Expression[] body) => Block(vars, body); |
|
|
|
public static Expression Block(IEnumerable<VariableExpression> vars, IEnumerable<Expression> body) |
|
|
|
{ // this is probably terrible performance-wise when executing, but i'm not giving up BlockExpression damnit!
|
|
|
|
var varSet = new HashSet<VariableExpression>(vars); |
|
|
|
var bodyArr = body.ToArray(); |
|
|
|
var finalExprList = new Stack<(List<Expression> list, BlockParseStackInfo info)>(); |
|
|
|
var remaining = bodyArr.Length; |
|
|
|
|
|
|
|
var varParams = new Dictionary<VariableExpression, ParameterExpression>(varSet.Count); |
|
|
|
|
|
|
|
finalExprList.Push((new List<Expression>(remaining), default(BlockParseStackInfo))); |
|
|
|
|
|
|
|
while (remaining > 0) |
|
|
|
{ |
|
|
|
var (list, info) = finalExprList.Pop(); |
|
|
|
var targetExpr = bodyArr[bodyArr.Length - remaining--]; |
|
|
|
|
|
|
|
if (targetExpr.NodeType.ExprTEx() == ExpressionTypeEx.Assign) |
|
|
|
{ |
|
|
|
var assign = (AssignExpression)targetExpr; |
|
|
|
var left = (VariableExpression)assign.Left; |
|
|
|
var right = assign.Right; |
|
|
|
|
|
|
|
var param = Expression.Parameter(left.Type, left.Name); |
|
|
|
|
|
|
|
finalExprList.Push((list, info)); |
|
|
|
finalExprList.Push((new List<Expression>(remaining), new BlockParseStackInfo |
|
|
|
{ |
|
|
|
ValueExpr = BlockVisitReplaceVariables(right, varParams), Param = param |
|
|
|
})); |
|
|
|
|
|
|
|
varParams.Add(left, param); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
list.Add(BlockVisitReplaceVariables(targetExpr, varParams)); |
|
|
|
finalExprList.Push((list, info)); |
|
|
|
} |
|
|
|
|
|
|
|
var funcType = typeof(Func<>); |
|
|
|
Expression topExpr = null; |
|
|
|
|
|
|
|
while (finalExprList.Count > 0) |
|
|
|
{ |
|
|
|
var (list, info) = finalExprList.Pop(); |
|
|
|
// there is an optimization opportunity for consecutive assignments, but it needs to make sure they don't depend on each other
|
|
|
|
if (topExpr != null) list.Add(topExpr); |
|
|
|
|
|
|
|
|
|
|
|
Expression last = null; |
|
|
|
Type lastType = null; |
|
|
|
var rest = new List<Expression<Action>>(list.Count); |
|
|
|
|
|
|
|
for (int i = 0; i < list.Count; i++) |
|
|
|
{ |
|
|
|
var expr = list[i]; |
|
|
|
if (i + 1 == list.Count) |
|
|
|
{ |
|
|
|
var ty = expr.Type; |
|
|
|
if (ty == typeof(void)) |
|
|
|
rest.Add(Expression.Lambda<Action>(expr)); |
|
|
|
else |
|
|
|
{ |
|
|
|
lastType = ty; |
|
|
|
var func = funcType.MakeGenericType(ty); |
|
|
|
last = Expression.Lambda(func, expr); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
rest.Add(Expression.Lambda<Action>(expr)); |
|
|
|
} |
|
|
|
|
|
|
|
Expression topBody; |
|
|
|
if (lastType != null) |
|
|
|
{ |
|
|
|
var execSeq = ExecuteSequenceTyped.MakeGenericMethod(lastType); |
|
|
|
topBody = Expression.Call(null, execSeq, last, Expression.NewArrayInit(typeof(Action), rest.Cast<Expression>())); |
|
|
|
} |
|
|
|
else |
|
|
|
topBody = Expression.Call(null, ExecuteSequenceVoid, Expression.NewArrayInit(typeof(Action), rest.Cast<Expression>())); |
|
|
|
|
|
|
|
if (info.Param != null && info.ValueExpr != null) |
|
|
|
topExpr = Expression.Invoke(Expression.Lambda(topBody, info.Param), info.ValueExpr); |
|
|
|
else |
|
|
|
topExpr = topBody; |
|
|
|
} |
|
|
|
|
|
|
|
return topExpr; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* { |
|
|
|
* Console.WriteLine("ho"); |
|
|
|
* int i = 3; |
|
|
|
* Console.WriteLine(i); |
|
|
|
* i // return last
|
|
|
|
* } |
|
|
|
* |
|
|
|
* ExecuteSequence<int>( |
|
|
|
* () => (i => |
|
|
|
* ExecuteSequence<int>( |
|
|
|
* () => i, |
|
|
|
* () => Console.WriteLine(i) |
|
|
|
* ) |
|
|
|
* )(3), |
|
|
|
* () => Console.WriteLine("ho") |
|
|
|
* ) |
|
|
|
*/ |
|
|
|
|
|
|
|
private struct BlockParseStackInfo |
|
|
|
{ |
|
|
|
public Expression ValueExpr; |
|
|
|
public ParameterExpression Param; |
|
|
|
} |
|
|
|
|
|
|
|
private static Expression BlockVisitReplaceVariables(Expression expr, Dictionary<VariableExpression, ParameterExpression> mapping) |
|
|
|
{ |
|
|
|
var binary = expr as BinaryExpression; |
|
|
|
var unary = expr as UnaryExpression; |
|
|
|
switch (expr.NodeType) |
|
|
|
{ |
|
|
|
case ExpressionType.Add: |
|
|
|
return Expression.Add(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.AddChecked: |
|
|
|
return Expression.AddChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.And: |
|
|
|
return Expression.And(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.AndAlso: |
|
|
|
return Expression.AndAlso(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.ArrayIndex: |
|
|
|
return Expression.ArrayIndex(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Coalesce: |
|
|
|
return Expression.Coalesce(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Divide: |
|
|
|
return Expression.Divide(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Equal: |
|
|
|
return Expression.Equal(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.ExclusiveOr: |
|
|
|
return Expression.ExclusiveOr(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.GreaterThan: |
|
|
|
return Expression.GreaterThan(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.GreaterThanOrEqual: |
|
|
|
return Expression.GreaterThanOrEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.LeftShift: |
|
|
|
return Expression.LeftShift(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.LessThan: |
|
|
|
return Expression.LessThan(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.LessThanOrEqual: |
|
|
|
return Expression.LessThanOrEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Modulo: |
|
|
|
return Expression.Modulo(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Multiply: |
|
|
|
return Expression.Multiply(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.MultiplyChecked: |
|
|
|
return Expression.MultiplyChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.NotEqual: |
|
|
|
return Expression.NotEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Or: |
|
|
|
return Expression.Or(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.OrElse: |
|
|
|
return Expression.OrElse(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Power: |
|
|
|
return Expression.Power(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.RightShift: |
|
|
|
return Expression.RightShift(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.Subtract: |
|
|
|
return Expression.Subtract(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.SubtractChecked: |
|
|
|
return Expression.SubtractChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); |
|
|
|
case ExpressionType.ArrayLength: |
|
|
|
return Expression.ArrayLength(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.Negate: |
|
|
|
return Expression.Negate(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.UnaryPlus: |
|
|
|
return Expression.UnaryPlus(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.NegateChecked: |
|
|
|
return Expression.NegateChecked(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.Not: |
|
|
|
return Expression.Not(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.TypeAs: |
|
|
|
return Expression.TypeAs(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type); |
|
|
|
case ExpressionType.Call: |
|
|
|
var callExpr = expr as MethodCallExpression; |
|
|
|
return Expression.Call(BlockVisitReplaceVariables(callExpr.Object, mapping), |
|
|
|
callExpr.Method, callExpr.Arguments.Select(a => BlockVisitReplaceVariables(a, mapping))); |
|
|
|
case ExpressionType.Conditional: |
|
|
|
var condExpr = expr as ConditionalExpression; |
|
|
|
return Expression.Condition(BlockVisitReplaceVariables(condExpr.Test, mapping), |
|
|
|
BlockVisitReplaceVariables(condExpr.IfTrue, mapping), BlockVisitReplaceVariables(condExpr.IfFalse, mapping)); |
|
|
|
case ExpressionType.Constant: return expr; // constants should be unchanged
|
|
|
|
case ExpressionType.Convert: |
|
|
|
return Expression.Convert(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type, unary.Method); |
|
|
|
case ExpressionType.ConvertChecked: |
|
|
|
return Expression.ConvertChecked(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type, unary.Method); |
|
|
|
case ExpressionType.Invoke: |
|
|
|
var invokeExpr = expr as InvocationExpression; |
|
|
|
return Expression.Invoke(BlockVisitReplaceVariables(invokeExpr.Expression, mapping), |
|
|
|
invokeExpr.Arguments.Select(e => BlockVisitReplaceVariables(e, mapping))); |
|
|
|
case ExpressionType.Lambda: |
|
|
|
var lambdaExpr = expr as LambdaExpression; |
|
|
|
return Expression.Lambda(lambdaExpr.Type, BlockVisitReplaceVariables(lambdaExpr.Body, mapping), lambdaExpr.Parameters); |
|
|
|
case ExpressionType.ListInit: |
|
|
|
var listInitExpr = expr as ListInitExpression; |
|
|
|
return Expression.ListInit((NewExpression)BlockVisitReplaceVariables(listInitExpr.NewExpression, mapping), |
|
|
|
listInitExpr.Initializers.Select(i => i.AddMethod).First(), |
|
|
|
listInitExpr.Initializers.SelectMany(i => i.Arguments) |
|
|
|
.Select(e => BlockVisitReplaceVariables(e, mapping))); |
|
|
|
case ExpressionType.MemberAccess: |
|
|
|
var memberExpr = expr as MemberExpression; |
|
|
|
return Expression.MakeMemberAccess(BlockVisitReplaceVariables(memberExpr.Expression, mapping), memberExpr.Member); |
|
|
|
case ExpressionType.MemberInit: |
|
|
|
var memberInitExpr = expr as MemberInitExpression; |
|
|
|
return Expression.MemberInit((NewExpression)BlockVisitReplaceVariables(memberInitExpr.NewExpression, mapping), |
|
|
|
memberInitExpr.Bindings); |
|
|
|
case ExpressionType.New: |
|
|
|
var newExpr = expr as NewExpression; |
|
|
|
return Expression.New(newExpr.Constructor, |
|
|
|
newExpr.Arguments.Select(e => BlockVisitReplaceVariables(e, mapping)), |
|
|
|
newExpr.Members); |
|
|
|
case ExpressionType.NewArrayInit: |
|
|
|
var newArrayInitExpr = expr as NewArrayExpression; |
|
|
|
return Expression.NewArrayInit(newArrayInitExpr.Type, |
|
|
|
newArrayInitExpr.Expressions.Select(e => BlockVisitReplaceVariables(e, mapping))); |
|
|
|
case ExpressionType.NewArrayBounds: |
|
|
|
var newArrayBoundsExpr = expr as NewArrayExpression; |
|
|
|
return Expression.NewArrayBounds(newArrayBoundsExpr.Type, |
|
|
|
newArrayBoundsExpr.Expressions.Select(e => BlockVisitReplaceVariables(e, mapping))); |
|
|
|
case ExpressionType.Parameter: return expr; // like constant
|
|
|
|
case ExpressionType.Quote: |
|
|
|
return Expression.Quote(BlockVisitReplaceVariables(unary.Operand, mapping)); |
|
|
|
case ExpressionType.TypeIs: |
|
|
|
var typeIsExpr = expr as TypeBinaryExpression; |
|
|
|
return Expression.TypeIs(BlockVisitReplaceVariables(typeIsExpr.Expression, mapping), typeIsExpr.TypeOperand); |
|
|
|
default: |
|
|
|
switch (expr.NodeType.ExprTEx()) |
|
|
|
{ |
|
|
|
case ExpressionTypeEx.Variable: |
|
|
|
var varExpr = expr as VariableExpression; |
|
|
|
if (mapping.TryGetValue(varExpr, out var paramExpr)) |
|
|
|
return paramExpr; |
|
|
|
else |
|
|
|
return varExpr; // not in scope in the current context, might be later
|
|
|
|
case ExpressionTypeEx.Assign: |
|
|
|
throw new InvalidOperationException("Assign expression must appear directly inside a block expression"); |
|
|
|
default: |
|
|
|
throw new ArgumentException($"Unhandled expression type '{expr.NodeType}'"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static readonly MethodInfo ExecuteSequenceVoid = typeof(ExpressionEx).GetMethod(nameof(ExecuteSequence), |
|
|
|
BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Action[]) }, null); |
|
|
|
private static void ExecuteSequence(params Action[] exec) |
|
|
|
{ |
|
|
|
foreach (var act in exec) act(); |
|
|
|
} |
|
|
|
private static readonly MethodInfo ExecuteSequenceTyped = typeof(ExpressionEx).GetMethod(nameof(ExecuteSequence), |
|
|
|
BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Func<>), typeof(Action[]) }, null); |
|
|
|
private static T ExecuteSequence<T>(Func<T> last, params Action[] first) |
|
|
|
{ |
|
|
|
ExecuteSequence(first); |
|
|
|
return last(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
internal enum ExpressionTypeEx |
|
|
|
{ |
|
|
|
Variable = 46, |
|
|
|
Assign = 47 |
|
|
|
} |
|
|
|
|
|
|
|
public class VariableExpression : Expression, IEquatable<VariableExpression> |
|
|
|
{ |
|
|
|
public string Name { get; } |
|
|
|
|
|
|
|
internal VariableExpression(Type varType, string name) : base(ExpressionTypeEx.Variable.ExprT(), varType) |
|
|
|
=> Name = name; |
|
|
|
|
|
|
|
public bool Equals(VariableExpression other) |
|
|
|
=> Name == other.Name && Type == other.Type; |
|
|
|
} |
|
|
|
|
|
|
|
public class AssignExpression : Expression |
|
|
|
{ |
|
|
|
public Expression Left { get; } |
|
|
|
public Expression Right { get; } |
|
|
|
|
|
|
|
internal AssignExpression(Expression left, Expression right) : base(ExpressionTypeEx.Assign.ExprT(), left.Type) |
|
|
|
{ |
|
|
|
Left = left; |
|
|
|
Right = right; |
|
|
|
} |
|
|
|
} |
|
|
|
} |