Skip to content

Instantly share code, notes, and snippets.

@daniel-chenery
Last active August 8, 2023 17:35
Show Gist options
  • Save daniel-chenery/2f1ca39ef183fba787abfa6b01351e13 to your computer and use it in GitHub Desktop.
Save daniel-chenery/2f1ca39ef183fba787abfa6b01351e13 to your computer and use it in GitHub Desktop.
Expression Explainer
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
internal class Program
{
static void Main()
{
var defaultError = "Invalid string";
var translator = new ExpressionTranslator();
Expression<Func<string, string>> expression = (string str) => str.Length > 0 ? "String has content!" : defaultError;
Console.WriteLine(translator.Explain(expression));
}
}
internal class ExpressionTranslator : ExpressionVisitor
{
private readonly StringBuilder _builder;
public ExpressionTranslator()
{
_builder = new StringBuilder();
}
public string Explain(Expression expression)
{
_builder.Clear();
Visit(expression);
return _builder.ToString();
}
protected override Expression VisitParameter(ParameterExpression node)
{
_builder.Append(node.Name);
_builder.Space();
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
_builder.Append("selecting ");
Visit(node.Expression);
_builder.TrimSpace();
if (node.Member is FieldInfo fieldInfo && fieldInfo.IsStatic || node.Member is PropertyInfo propertyInfo && propertyInfo.GetGetMethod()?.IsStatic == true)
{
_builder.Append(node.Member.ReflectedType?.Name);
}
_builder.Append('.');
_builder.Append(node.Member.Name);
_builder.Space();
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_builder.Append($"{node.Name ?? "Lambda"} is invoked ");
if (node.Parameters.Count > 0)
{
_builder.Append("with ");
VisitArguments(node.Parameters);
}
Visit(node.Body);
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
_builder.Append("calling ");
_builder.Append('(');
if (node.Method.IsStatic)
{
_builder.Append(node.Method.ReflectedType.Name);
}
else
{
Visit(node.Object);
}
_builder.TrimSpace();
_builder.Append('.');
_builder.Append(node.Method.Name);
_builder.Space();
if (node.Arguments.Count > 0)
{
_builder.Append("with ");
VisitArguments(node.Arguments);
}
_builder.AppendFormat("returning type of '{0}'", node.Method.ReturnType.Name);
_builder.Space();
_builder.TrimSpace();
_builder.Append(')');
_builder.Space();
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
_builder.AppendFormat("{0}a constant value of '{1}'", _builder.Length == 0 ? "Is " : string.Empty, node.Value);
_builder.Space();
return node;
}
protected override Expression VisitBinary(BinaryExpression node)
{
Visit(node.Left);
_builder.AppendFormat("performing '{0}'", node.NodeType);
_builder.Space();
Visit(node.Right);
return node;
}
protected override Expression VisitUnary(UnaryExpression node)
{
_builder.AppendFormat("performing '{0}' to '{1}'", node.NodeType, node.Type.Name);
_builder.Space();
_builder.Append('(');
Visit(node.Operand);
_builder.Length--;
_builder.Append(')');
_builder.Space();
return node;
}
private void VisitArguments(IReadOnlyCollection<Expression> arguments)
{
if (arguments.Count > 0)
{
foreach (var argument in arguments)
{
Visit(argument);
_builder.AppendFormat("(of type '{0}')", argument.Type.Name);
_builder.Append(" and ");
}
_builder.Length -= 4;
}
}
protected override Expression VisitConditional(ConditionalExpression node)
{
_builder.AppendFormat("checking {0}", node.NodeType);
_builder.Space();
Visit(node.Test);
_builder.Append("then ");
Visit(node.IfTrue);
_builder.Append("otherwise ");
Visit(node.IfFalse);
return node;
}
protected override Expression VisitNew(NewExpression node)
{
_builder.Append("creating ");
_builder.Append(node.Type.Name);
_builder.Space();
if (node.Arguments.Count > 0)
{
_builder.Append("with ");
VisitArguments(node.Arguments);
}
return node;
}
public override Expression Visit(Expression node)
{
if (node == null)
{
_builder.Space();
return node;
}
return base.Visit(node);
}
}
internal static class Extensions
{
public static StringBuilder Space(this StringBuilder stringBuilder) => stringBuilder.Append(' ');
public static StringBuilder TrimSpace(this StringBuilder stringBuilder)
{
if (stringBuilder[stringBuilder.Length - 1] == ' ')
{
stringBuilder.Length--;
}
return stringBuilder;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment