using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Lantean.QBTMud.Filter { internal static class ExpressionModifier { internal static Expression> Modify(this Expression firstExpression, Expression secondExpression) { var bodyIdentifier = new ExpressionBodyIdentifier(); var body = bodyIdentifier.Identify(firstExpression); var parameterIdentifier = new ExpressionParameterIdentifier(); var parameter = (ParameterExpression)parameterIdentifier.Identify(firstExpression); var body2 = bodyIdentifier.Identify(secondExpression); var parameter2 = (ParameterExpression)parameterIdentifier.Identify(secondExpression); var treeModifier = new ExpressionReplacer(parameter2, body); return Expression.Lambda>(treeModifier.Visit(body2), parameter); } internal static Expression ReplaceBinary(this Expression exp, ExpressionType from, ExpressionType to) { var binaryReplacer = new BinaryReplacer(from, to); return binaryReplacer.Visit(exp); } public static Expression> GenerateBinary(this Expression expression, ExpressionType binaryOperation, object? value) { var bodyIdentifier = new ExpressionBodyIdentifier(); var body = bodyIdentifier.Identify(expression); var parameterIdentifier = new ExpressionParameterIdentifier(); var parameter = (ParameterExpression)parameterIdentifier.Identify(expression); BinaryExpression? binaryExpression; if (Nullable.GetUnderlyingType(body.Type) is not null) { // property type is nullable... binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type)); } else { if (value is null) { // We can short circuit here because the value to be compared is null and the property type is not nullable. return x => true; } binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type)); } return Expression.Lambda>(binaryExpression, parameter); } public static Expression> ChangeExpressionReturnType(this Expression expression) { var bodyIdentifier = new ExpressionBodyIdentifier(); var body = bodyIdentifier.Identify(expression); var parameterIdentifier = new ExpressionParameterIdentifier(); var parameter = (ParameterExpression)parameterIdentifier.Identify(expression); if (body.Type is U) { // Expression already has the right type. return Expression.Lambda>(body, parameter); } // Change parameter. var converted = Expression.Convert(body, typeof(U)); return Expression.Lambda>(converted, parameter); } public static (Expression>, Type) CreatePropertySelector(string propertyName) { var type = typeof(T); var propertyInfo = type.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); if (propertyInfo is null) { throw new InvalidOperationException($"Unable to match property {propertyName} for {type.Name}"); } var parameterExpression = Expression.Parameter(type); var propertyExpression = Expression.Property(parameterExpression, propertyInfo); var convertExpression = Expression.Convert(propertyExpression, typeof(object)); return (Expression.Lambda>(convertExpression, parameterExpression), propertyInfo.PropertyType); } } internal class ExpressionReplacer : ExpressionVisitor { private readonly Expression _from; private readonly Expression _to; public ExpressionReplacer(Expression from, Expression to) { _from = from; _to = to; } [return: NotNullIfNotNull(nameof(node))] public override Expression? Visit(Expression? node) { if (node == _from) { return _to; } return base.Visit(node); } } internal class ExpressionBodyIdentifier : ExpressionVisitor { public Expression Identify(Expression node) { return base.Visit(node); } protected override Expression VisitLambda(Expression node) { return node.Body; } } internal class ExpressionParameterIdentifier : ExpressionVisitor { public Expression Identify(Expression node) { return base.Visit(node); } protected override Expression VisitLambda(Expression node) { return node.Parameters[0]; } } internal class BinaryReplacer : ExpressionVisitor { private readonly ExpressionType _from; private readonly ExpressionType _to; public BinaryReplacer(ExpressionType from, ExpressionType to) { _from = from; _to = to; } protected override Expression VisitBinary(BinaryExpression node) { if (node.NodeType == _from) { return Expression.MakeBinary(_to, node.Left, node.Right); } return base.VisitBinary(node); } } }