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.
 
 
 
 

389 lines
17 KiB

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);
if (list.Count == 0)
list.Add(info.Param);
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;
}
}
}