|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
namespace System.Linq.Expressions
{
internal sealed class ExpressionStringBuilder : ExpressionVisitor
{
private readonly StringBuilder _out;
// Associate every unique label or anonymous parameter in the tree with an integer.
// Labels are displayed as UnnamedLabel_#; parameters are displayed as Param_#.
private Dictionary<object, int>? _ids;
private ExpressionStringBuilder()
{
_out = new StringBuilder();
}
public override string ToString()
{
return _out.ToString();
}
private int GetLabelId(LabelTarget label) => GetId(label);
private int GetParamId(ParameterExpression p) => GetId(p);
private int GetId(object o)
{
_ids ??= new Dictionary<object, int>();
int id;
if (!_ids.TryGetValue(o, out id))
{
id = _ids.Count;
_ids.Add(o, id);
}
return id;
}
#region The printing code
private void Out(string? s)
{
_out.Append(s);
}
private void Out(char c)
{
_out.Append(c);
}
#endregion
#region Output an expression tree to a string
/// <summary>
/// Output a given expression tree to a string.
/// </summary>
internal static string ExpressionToString(Expression node)
{
Debug.Assert(node != null);
ExpressionStringBuilder esb = new ExpressionStringBuilder();
esb.Visit(node);
return esb.ToString();
}
internal static string CatchBlockToString(CatchBlock node)
{
Debug.Assert(node != null);
ExpressionStringBuilder esb = new ExpressionStringBuilder();
esb.VisitCatchBlock(node);
return esb.ToString();
}
internal static string SwitchCaseToString(SwitchCase node)
{
Debug.Assert(node != null);
ExpressionStringBuilder esb = new ExpressionStringBuilder();
esb.VisitSwitchCase(node);
return esb.ToString();
}
/// <summary>
/// Output a given member binding to a string.
/// </summary>
internal static string MemberBindingToString(MemberBinding node)
{
Debug.Assert(node != null);
ExpressionStringBuilder esb = new ExpressionStringBuilder();
esb.VisitMemberBinding(node);
return esb.ToString();
}
/// <summary>
/// Output a given ElementInit to a string.
/// </summary>
internal static string ElementInitBindingToString(ElementInit node)
{
Debug.Assert(node != null);
ExpressionStringBuilder esb = new ExpressionStringBuilder();
esb.VisitElementInit(node);
return esb.ToString();
}
private void VisitExpressions<T>(char open, ReadOnlyCollection<T> expressions, char close) where T : Expression
{
VisitExpressions(open, expressions, close, ", ");
}
private void VisitExpressions<T>(char open, ReadOnlyCollection<T> expressions, char close, string separator) where T : Expression
{
Out(open);
if (expressions != null)
{
bool isFirst = true;
foreach (T e in expressions)
{
if (isFirst)
{
isFirst = false;
}
else
{
Out(separator);
}
Visit(e);
}
}
Out(close);
}
protected internal override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.ArrayIndex)
{
Visit(node.Left);
Out('[');
Visit(node.Right);
Out(']');
}
else
{
string op;
switch (node.NodeType)
{
// AndAlso and OrElse were unintentionally changed in
// CLR 4. We changed them to "AndAlso" and "OrElse" to
// be 3.5 compatible, but it turns out 3.5 shipped with
// "&&" and "||". Oops.
case ExpressionType.AndAlso:
op = "AndAlso";
break;
case ExpressionType.OrElse:
op = "OrElse";
break;
case ExpressionType.Assign:
op = "=";
break;
case ExpressionType.Equal:
op = "==";
break;
case ExpressionType.NotEqual:
op = "!=";
break;
case ExpressionType.GreaterThan:
op = ">";
break;
case ExpressionType.LessThan:
op = "<";
break;
case ExpressionType.GreaterThanOrEqual:
op = ">=";
break;
case ExpressionType.LessThanOrEqual:
op = "<=";
break;
case ExpressionType.Add:
case ExpressionType.AddChecked:
op = "+";
break;
case ExpressionType.AddAssign:
case ExpressionType.AddAssignChecked:
op = "+=";
break;
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
op = "-";
break;
case ExpressionType.SubtractAssign:
case ExpressionType.SubtractAssignChecked:
op = "-=";
break;
case ExpressionType.Divide:
op = "/";
break;
case ExpressionType.DivideAssign:
op = "/=";
break;
case ExpressionType.Modulo:
op = "%";
break;
case ExpressionType.ModuloAssign:
op = "%=";
break;
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
op = "*";
break;
case ExpressionType.MultiplyAssign:
case ExpressionType.MultiplyAssignChecked:
op = "*=";
break;
case ExpressionType.LeftShift:
op = "<<";
break;
case ExpressionType.LeftShiftAssign:
op = "<<=";
break;
case ExpressionType.RightShift:
op = ">>";
break;
case ExpressionType.RightShiftAssign:
op = ">>=";
break;
case ExpressionType.And:
op = IsBool(node) ? "And" : "&";
break;
case ExpressionType.AndAssign:
op = IsBool(node) ? "&&=" : "&=";
break;
case ExpressionType.Or:
op = IsBool(node) ? "Or" : "|";
break;
case ExpressionType.OrAssign:
op = IsBool(node) ? "||=" : "|=";
break;
case ExpressionType.ExclusiveOr:
op = "^";
break;
case ExpressionType.ExclusiveOrAssign:
op = "^=";
break;
case ExpressionType.Power:
op = "**";
break; // This was changed in .NET Core from ^ to **
case ExpressionType.PowerAssign:
op = "**=";
break;
case ExpressionType.Coalesce:
op = "??";
break;
default:
throw new InvalidOperationException();
}
Out('(');
Visit(node.Left);
Out(' ');
Out(op);
Out(' ');
Visit(node.Right);
Out(')');
}
return node;
}
protected internal override Expression VisitParameter(ParameterExpression node)
{
if (node.IsByRef)
{
Out("ref ");
}
string? name = node.Name;
if (string.IsNullOrEmpty(name))
{
Out("Param_" + GetParamId(node));
}
else
{
Out(name);
}
return node;
}
protected internal override Expression VisitLambda<T>(Expression<T> node)
{
if (node.ParameterCount == 1)
{
// p => body
Visit(node.GetParameter(0));
}
else
{
// (p1, p2, ..., pn) => body
Out('(');
string sep = ", ";
for (int i = 0, n = node.ParameterCount; i < n; i++)
{
if (i > 0)
{
Out(sep);
}
Visit(node.GetParameter(i));
}
Out(')');
}
Out(" => ");
Visit(node.Body);
return node;
}
protected internal override Expression VisitListInit(ListInitExpression node)
{
Visit(node.NewExpression);
Out(" {");
for (int i = 0, n = node.Initializers.Count; i < n; i++)
{
if (i > 0)
{
Out(", ");
}
VisitElementInit(node.Initializers[i]);
}
Out('}');
return node;
}
protected internal override Expression VisitConditional(ConditionalExpression node)
{
Out("IIF(");
Visit(node.Test);
Out(", ");
Visit(node.IfTrue);
Out(", ");
Visit(node.IfFalse);
Out(')');
return node;
}
protected internal override Expression VisitConstant(ConstantExpression node)
{
if (node.Value != null)
{
string? sValue = node.Value.ToString();
if (node.Value is string)
{
Out('\"');
Out(sValue);
Out('\"');
}
else if (sValue == node.Value.GetType().ToString())
{
Out("value(");
Out(sValue);
Out(')');
}
else
{
Out(sValue);
}
}
else
{
Out("null");
}
return node;
}
protected internal override Expression VisitDebugInfo(DebugInfoExpression node)
{
Out($"<DebugInfo({node.Document.FileName}: {node.StartLine}, {node.StartColumn}, {node.EndLine}, {node.EndColumn})>");
return node;
}
protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
{
VisitExpressions('(', node.Variables, ')');
return node;
}
// Prints ".instanceField" or "declaringType.staticField"
private void OutMember(Expression? instance, MemberInfo member)
{
if (instance != null)
{
Visit(instance);
}
else
{
// For static members, include the type name
Out(member.DeclaringType!.Name);
}
Out('.');
Out(member.Name);
}
protected internal override Expression VisitMember(MemberExpression node)
{
OutMember(node.Expression, node.Member);
return node;
}
protected internal override Expression VisitMemberInit(MemberInitExpression node)
{
if (node.NewExpression.ArgumentCount == 0 &&
node.NewExpression.Type.Name.Contains('<'))
{
// anonymous type constructor
Out("new");
}
else
{
Visit(node.NewExpression);
}
Out(" {");
for (int i = 0, n = node.Bindings.Count; i < n; i++)
{
MemberBinding b = node.Bindings[i];
if (i > 0)
{
Out(", ");
}
VisitMemberBinding(b);
}
Out('}');
return node;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
{
Out(assignment.Member.Name);
Out(" = ");
Visit(assignment.Expression);
return assignment;
}
protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding)
{
Out(binding.Member.Name);
Out(" = {");
for (int i = 0, n = binding.Initializers.Count; i < n; i++)
{
if (i > 0)
{
Out(", ");
}
VisitElementInit(binding.Initializers[i]);
}
Out('}');
return binding;
}
protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)
{
Out(binding.Member.Name);
Out(" = {");
for (int i = 0, n = binding.Bindings.Count; i < n; i++)
{
if (i > 0)
{
Out(", ");
}
VisitMemberBinding(binding.Bindings[i]);
}
Out('}');
return binding;
}
protected override ElementInit VisitElementInit(ElementInit initializer)
{
Out(initializer.AddMethod.ToString());
string sep = ", ";
Out('(');
for (int i = 0, n = initializer.ArgumentCount; i < n; i++)
{
if (i > 0)
{
Out(sep);
}
Visit(initializer.GetArgument(i));
}
Out(')');
return initializer;
}
protected internal override Expression VisitInvocation(InvocationExpression node)
{
Out("Invoke(");
Visit(node.Expression);
string sep = ", ";
for (int i = 0, n = node.ArgumentCount; i < n; i++)
{
Out(sep);
Visit(node.GetArgument(i));
}
Out(')');
return node;
}
protected internal override Expression VisitMethodCall(MethodCallExpression node)
{
int start = 0;
Expression? ob = node.Object;
if (node.Method.GetCustomAttribute(typeof(ExtensionAttribute)) != null)
{
start = 1;
ob = node.GetArgument(0);
}
if (ob != null)
{
Visit(ob);
Out('.');
}
Out(node.Method.Name);
Out('(');
for (int i = start, n = node.ArgumentCount; i < n; i++)
{
if (i > start)
Out(", ");
Visit(node.GetArgument(i));
}
Out(')');
return node;
}
protected internal override Expression VisitNewArray(NewArrayExpression node)
{
switch (node.NodeType)
{
case ExpressionType.NewArrayBounds:
// new MyType[](expr1, expr2)
Out("new ");
Out(node.Type.ToString());
VisitExpressions('(', node.Expressions, ')');
break;
case ExpressionType.NewArrayInit:
// new [] {expr1, expr2}
Out("new [] ");
VisitExpressions('{', node.Expressions, '}');
break;
}
return node;
}
protected internal override Expression VisitNew(NewExpression node)
{
Out("new ");
Out(node.Type.Name);
Out('(');
ReadOnlyCollection<MemberInfo>? members = node.Members;
for (int i = 0; i < node.ArgumentCount; i++)
{
if (i > 0)
{
Out(", ");
}
if (members != null)
{
string name = members[i].Name;
Out(name);
Out(" = ");
}
Visit(node.GetArgument(i));
}
Out(')');
return node;
}
protected internal override Expression VisitTypeBinary(TypeBinaryExpression node)
{
Out('(');
Visit(node.Expression);
switch (node.NodeType)
{
case ExpressionType.TypeIs:
Out(" Is ");
break;
case ExpressionType.TypeEqual:
Out(" TypeEqual ");
break;
}
Out(node.TypeOperand.Name);
Out(')');
return node;
}
protected internal override Expression VisitUnary(UnaryExpression node)
{
switch (node.NodeType)
{
case ExpressionType.Negate:
case ExpressionType.NegateChecked: Out('-'); break;
case ExpressionType.Not: Out("Not("); break;
case ExpressionType.IsFalse: Out("IsFalse("); break;
case ExpressionType.IsTrue: Out("IsTrue("); break;
case ExpressionType.OnesComplement: Out("~("); break;
case ExpressionType.ArrayLength: Out("ArrayLength("); break;
case ExpressionType.Convert: Out("Convert("); break;
case ExpressionType.ConvertChecked: Out("ConvertChecked("); break;
case ExpressionType.Throw: Out("throw("); break;
case ExpressionType.TypeAs: Out('('); break;
case ExpressionType.UnaryPlus: Out('+'); break;
case ExpressionType.Unbox: Out("Unbox("); break;
case ExpressionType.Increment: Out("Increment("); break;
case ExpressionType.Decrement: Out("Decrement("); break;
case ExpressionType.PreIncrementAssign: Out("++"); break;
case ExpressionType.PreDecrementAssign: Out("--"); break;
case ExpressionType.Quote:
case ExpressionType.PostIncrementAssign:
case ExpressionType.PostDecrementAssign:
break;
default:
throw new InvalidOperationException();
}
Visit(node.Operand);
switch (node.NodeType)
{
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.UnaryPlus:
case ExpressionType.PreDecrementAssign:
case ExpressionType.PreIncrementAssign:
case ExpressionType.Quote:
break;
case ExpressionType.TypeAs:
Out(" As ");
Out(node.Type.Name);
Out(')'); break;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
Out(", ");
Out(node.Type.Name);
Out(')'); break; // These were changed in .NET Core to add the type name
case ExpressionType.PostIncrementAssign: Out("++"); break;
case ExpressionType.PostDecrementAssign: Out("--"); break;
default: Out(')'); break;
}
return node;
}
protected internal override Expression VisitBlock(BlockExpression node)
{
Out('{');
foreach (ParameterExpression v in node.Variables)
{
Out("var ");
Visit(v);
Out(';');
}
Out(" ... }");
return node;
}
protected internal override Expression VisitDefault(DefaultExpression node)
{
Out("default(");
Out(node.Type.Name);
Out(')');
return node;
}
protected internal override Expression VisitLabel(LabelExpression node)
{
Out("{ ... } ");
DumpLabel(node.Target);
Out(':');
return node;
}
protected internal override Expression VisitGoto(GotoExpression node)
{
string op = node.Kind switch
{
GotoExpressionKind.Goto => "goto",
GotoExpressionKind.Break => "break",
GotoExpressionKind.Continue => "continue",
GotoExpressionKind.Return => "return",
_ => throw new InvalidOperationException(),
};
Out(op);
Out(' ');
DumpLabel(node.Target);
if (node.Value != null)
{
Out(" (");
Visit(node.Value);
Out(")");
}
return node;
}
protected internal override Expression VisitLoop(LoopExpression node)
{
Out("loop { ... }");
return node;
}
protected override SwitchCase VisitSwitchCase(SwitchCase node)
{
Out("case ");
VisitExpressions('(', node.TestValues, ')');
Out(": ...");
return node;
}
protected internal override Expression VisitSwitch(SwitchExpression node)
{
Out("switch ");
Out('(');
Visit(node.SwitchValue);
Out(") { ... }");
return node;
}
protected override CatchBlock VisitCatchBlock(CatchBlock node)
{
Out("catch (");
Out(node.Test.Name);
if (!string.IsNullOrEmpty(node.Variable?.Name))
{
Out(' ');
Out(node.Variable.Name);
}
Out(") { ... }");
return node;
}
protected internal override Expression VisitTry(TryExpression node)
{
Out("try { ... }");
return node;
}
protected internal override Expression VisitIndex(IndexExpression node)
{
if (node.Object != null)
{
Visit(node.Object);
}
else
{
Debug.Assert(node.Indexer != null);
Out(node.Indexer.DeclaringType!.Name);
}
if (node.Indexer != null)
{
Out('.');
Out(node.Indexer.Name);
}
Out('[');
for (int i = 0, n = node.ArgumentCount; i < n; i++)
{
if (i > 0)
Out(", ");
Visit(node.GetArgument(i));
}
Out(']');
return node;
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
Justification = "The 'ToString' method cannot be trimmed on any Expression type because we are calling Expression.ToString() in this method.")]
protected internal override Expression VisitExtension(Expression node)
{
// Prefer an overridden ToString, if available.
MethodInfo toString = node.GetType().GetMethod("ToString", Type.EmptyTypes)!;
if (toString.DeclaringType != typeof(Expression) && !toString.IsStatic)
{
Out(node.ToString());
return node;
}
Out('[');
// For 3.5 subclasses, print the NodeType.
// For Extension nodes, print the class name.
Out(node.NodeType == ExpressionType.Extension ? node.GetType().FullName : node.NodeType.ToString());
Out(']');
return node;
}
private void DumpLabel(LabelTarget target)
{
if (!string.IsNullOrEmpty(target.Name))
{
Out(target.Name);
}
else
{
int labelId = GetLabelId(target);
Out("UnnamedLabel_" + labelId);
}
}
private static bool IsBool(Expression node)
{
return node.Type == typeof(bool) || node.Type == typeof(bool?);
}
#endregion
}
}
|