using System.Linq.Expressions; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; namespace AyCode.Blazor.Components.Services.ExpressionHelpers; /// /// Expression visitor that serializes an Expression tree to AcExpressionNode DTO. /// Handles all common expression types recursively. /// public class AcExpressionSerializerVisitor : ExpressionVisitor { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter() } }; private readonly Dictionary _parameterIndexes = new(); private int _nextParameterIndex; // Stack to collect converted nodes private readonly Stack _nodeStack = new(); /// /// Converts an Expression to an AcExpressionNode DTO. /// public AcExpressionNode Convert(Expression expression) { _nodeStack.Clear(); _parameterIndexes.Clear(); _nextParameterIndex = 0; VisitAndConvert(expression); return _nodeStack.Count != 1 ? throw new InvalidOperationException($"Expected 1 node on stack, found {_nodeStack.Count}") : _nodeStack.Pop(); } /// /// Serializes an Expression to JSON string. /// public string ToJson(Expression expression) { var node = Convert(expression); return JsonSerializer.Serialize(node, JsonOptions); } private void VisitAndConvert(Expression expression) { Visit(expression); } protected override Expression VisitBinary(BinaryExpression node) { VisitAndConvert(node.Left); var left = _nodeStack.Pop(); VisitAndConvert(node.Right); var right = _nodeStack.Pop(); _nodeStack.Push(new AcExpressionNode { NodeType = node.NodeType, TypeName = node.Type.FullName, Left = left, Right = right }); return node; } protected override Expression VisitUnary(UnaryExpression node) { VisitAndConvert(node.Operand); var operand = _nodeStack.Pop(); _nodeStack.Push(new AcExpressionNode { NodeType = node.NodeType, TypeName = node.Type.FullName, Operand = operand }); return node; } protected override Expression VisitLambda(Expression node) { // Register parameters with indexes var parameters = new List(); foreach (var param in node.Parameters) { var index = _nextParameterIndex++; _parameterIndexes[param] = index; parameters.Add(new ParameterNode { Name = param.Name ?? $"p{index}", TypeName = param.Type.FullName ?? param.Type.Name, Index = index }); } VisitAndConvert(node.Body); var body = _nodeStack.Pop(); _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Lambda, TypeName = node.Type.FullName, Body = body, Parameters = parameters }); return node; } protected override Expression VisitParameter(ParameterExpression node) { var index = _parameterIndexes.GetValueOrDefault(node, -1); _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Parameter, TypeName = node.Type.FullName, ParameterName = node.Name, ParameterIndex = index }); return node; } protected override Expression VisitMember(MemberExpression node) { // Check if this is a closure variable access (captured variable) if (IsClosureAccess(node)) { var value = EvaluateClosureValue(node); _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Constant, TypeName = node.Type.FullName, Value = SerializeValue(value) }); return node; } AcExpressionNode? objectNode = null; if (node.Expression != null) { VisitAndConvert(node.Expression); objectNode = _nodeStack.Pop(); } _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.MemberAccess, TypeName = node.Type.FullName, MemberName = node.Member.Name, Object = objectNode, DeclaringType = node.Member.DeclaringType?.FullName }); return node; } protected override Expression VisitConstant(ConstantExpression node) { _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Constant, TypeName = node.Type.FullName, Value = SerializeValue(node.Value) }); return node; } protected override Expression VisitMethodCall(MethodCallExpression node) { AcExpressionNode? objectNode = null; if (node.Object != null) { VisitAndConvert(node.Object); objectNode = _nodeStack.Pop(); } var arguments = new List(); foreach (var arg in node.Arguments) { VisitAndConvert(arg); arguments.Add(_nodeStack.Pop()); } var genericArgs = node.Method.IsGenericMethod ? node.Method.GetGenericArguments().Select(t => t.FullName ?? t.Name).ToList() : null; _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Call, TypeName = node.Type.FullName, MethodName = node.Method.Name, Object = objectNode, Arguments = arguments, DeclaringType = node.Method.DeclaringType?.FullName, GenericArguments = genericArgs }); return node; } protected override Expression VisitConditional(ConditionalExpression node) { VisitAndConvert(node.Test); var test = _nodeStack.Pop(); VisitAndConvert(node.IfTrue); var ifTrue = _nodeStack.Pop(); VisitAndConvert(node.IfFalse); var ifFalse = _nodeStack.Pop(); _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Conditional, TypeName = node.Type.FullName, Test = test, IfTrue = ifTrue, IfFalse = ifFalse }); return node; } protected override Expression VisitNew(NewExpression node) { var arguments = new List(); foreach (var arg in node.Arguments) { VisitAndConvert(arg); arguments.Add(_nodeStack.Pop()); } _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.New, TypeName = node.Type.FullName, ConstructorArguments = arguments }); return node; } protected override Expression VisitMemberInit(MemberInitExpression node) { var arguments = new List(); foreach (var arg in node.NewExpression.Arguments) { VisitAndConvert(arg); arguments.Add(_nodeStack.Pop()); } _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.MemberInit, TypeName = node.Type.FullName, ConstructorArguments = arguments, MemberBindings = node.Bindings.Select(ConvertMemberBinding).ToList() }); return node; } protected override Expression VisitNewArray(NewArrayExpression node) { var elements = new List(); foreach (var expr in node.Expressions) { VisitAndConvert(expr); elements.Add(_nodeStack.Pop()); } _nodeStack.Push(new AcExpressionNode { NodeType = node.NodeType, TypeName = node.Type.FullName, Elements = elements }); return node; } protected override Expression VisitTypeBinary(TypeBinaryExpression node) { VisitAndConvert(node.Expression); var operand = _nodeStack.Pop(); _nodeStack.Push(new AcExpressionNode { NodeType = node.NodeType, TypeName = node.TypeOperand.FullName, Operand = operand }); return node; } protected override Expression VisitInvocation(InvocationExpression node) { VisitAndConvert(node.Expression); var objectNode = _nodeStack.Pop(); var arguments = new List(); foreach (var arg in node.Arguments) { VisitAndConvert(arg); arguments.Add(_nodeStack.Pop()); } _nodeStack.Push(new AcExpressionNode { NodeType = ExpressionType.Invoke, TypeName = node.Type.FullName, Object = objectNode, Arguments = arguments }); return node; } #region Helper Methods private MemberBindingNode ConvertMemberBinding(MemberBinding binding) { return binding switch { MemberAssignment assignment => ConvertMemberAssignment(assignment), MemberMemberBinding memberBinding => new MemberBindingNode { MemberName = memberBinding.Member.Name, BindingType = MemberBindingType.MemberBinding, Bindings = memberBinding.Bindings.Select(ConvertMemberBinding).ToList() }, MemberListBinding listBinding => new MemberBindingNode { MemberName = listBinding.Member.Name, BindingType = MemberBindingType.ListBinding, Initializers = listBinding.Initializers .Select(i => i.Arguments.Select(ConvertArgument).ToList()) .ToList() }, _ => throw new NotSupportedException($"Member binding type '{binding.BindingType}' is not supported.") }; } private MemberBindingNode ConvertMemberAssignment(MemberAssignment assignment) { VisitAndConvert(assignment.Expression); var expr = _nodeStack.Pop(); return new MemberBindingNode { MemberName = assignment.Member.Name, BindingType = MemberBindingType.Assignment, Expression = expr }; } private AcExpressionNode ConvertArgument(Expression expression) { VisitAndConvert(expression); return _nodeStack.Pop(); } private static bool IsClosureAccess(MemberExpression node) { return node.Expression switch { ConstantExpression => true, MemberExpression nested => IsClosureAccess(nested), _ => false }; } private static object? EvaluateClosureValue(MemberExpression node) { var objectStack = new Stack(); Expression? current = node; while (current is MemberExpression me) { objectStack.Push(me); current = me.Expression; } if (current is not ConstantExpression constant) throw new InvalidOperationException("Expected constant at root of closure access."); object? value = constant.Value; while (objectStack.Count > 0) { var me = objectStack.Pop(); value = me.Member switch { FieldInfo fi => fi.GetValue(value), PropertyInfo pi => pi.GetValue(value), _ => throw new InvalidOperationException($"Unsupported member type: {me.Member.GetType()}") }; } return value; } private static string? SerializeValue(object? value) { if (value == null) return null; // Handle IQueryable source - serialize as placeholder if (value is IQueryable) return null; return JsonSerializer.Serialize(value, JsonOptions); } #endregion }