#if NET20 || NET30 || !NET_4_6 // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using LinqInternal.Collections.ThreadSafe; using LinqInternal.Core; namespace System.Linq.Expressions.Reimplement { internal sealed class ExpressionStringBuilder : ExpressionVisitor { private readonly StringBuilder _out; // Associate every unique label or anonymous parameter in the tree with an integer. // The label is displayed as Label_#. private Dictionary _ids; private ExpressionStringBuilder() { _out = new StringBuilder(); } public override string ToString() { return _out.ToString(); } private void AddLabel(LabelTarget label) { if (_ids == null) { _ids = new Dictionary { { label, 0 } }; } else { if (!_ids.ContainsKey(label)) { _ids.Add(label, _ids.Count); } } } private int GetLabelId(LabelTarget label) { if (_ids == null) { _ids = new Dictionary(); AddLabel(label); return 0; } else { int id; if (!_ids.TryGetValue(label, out id)) { //label is met the first time id = _ids.Count; AddLabel(label); } return id; } } private void AddParam(ParameterExpression p) { if (_ids == null) { _ids = new Dictionary { { _ids, 0 } }; } else { if (!_ids.ContainsKey(p)) { _ids.Add(p, _ids.Count); } } } private int GetParamId(ParameterExpression p) { if (_ids == null) { _ids = new Dictionary(); AddParam(p); return 0; } else { int id; if (!_ids.TryGetValue(p, out id)) { // p is met the first time id = _ids.Count; AddParam(p); } return id; } } #region The printing code private void Out(string s) { _out.Append(s); } private void Out(char c) { _out.Append(c); } #endregion The printing code #region Output an expresstion tree to a string internal static string ExpressionToString(Expression node) { Debug.Assert(node != null); var esb = new ExpressionStringBuilder(); esb.Visit(node); return esb.ToString(); } internal static string CatchBlockToString(CatchBlock node) { Debug.Assert(node != null); var esb = new ExpressionStringBuilder(); esb.VisitCatchBlock(node); return esb.ToString(); } internal static string SwitchCaseToString(SwitchCase node) { Debug.Assert(node != null); var esb = new ExpressionStringBuilder(); esb.VisitSwitchCase(node); return esb.ToString(); } internal static string MemberBindingToString(MemberBinding node) { Debug.Assert(node != null); var esb = new ExpressionStringBuilder(); esb.VisitMemberBinding(node); return esb.ToString(); } internal static string ElementInitBindingToString(ElementInit node) { Debug.Assert(node != null); var esb = new ExpressionStringBuilder(); esb.VisitElementInit(node); return esb.ToString(); } private void VisitExpressions(char open, IList expressions, char close) where T : Expression { VisitExpressions(open, expressions, close, ", "); } private void VisitExpressions(char open, IList expressions, char close, string seperator) where T : Expression { Out(open); if (expressions != null) { var isFirst = true; foreach (var e in expressions) { if (isFirst) { isFirst = false; } else { Out(seperator); } 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: op = "+"; break; case ExpressionType.AddAssign: op = "+="; break; case ExpressionType.AddAssignChecked: op = "+="; break; case ExpressionType.AddChecked: op = "+"; break; case ExpressionType.Subtract: op = "-"; break; case ExpressionType.SubtractAssign: op = "-="; break; case ExpressionType.SubtractAssignChecked: op = "-="; break; case ExpressionType.SubtractChecked: 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: op = "*"; break; case ExpressionType.MultiplyAssign: op = "*="; break; case ExpressionType.MultiplyAssignChecked: op = "*="; break; case ExpressionType.MultiplyChecked: 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: if (node.Type == typeof(bool) || node.Type == typeof(bool?)) { op = "And"; } else { op = "&"; } break; case ExpressionType.AndAssign: if (node.Type == typeof(bool) || node.Type == typeof(bool?)) { op = "&&="; } else { op = "&="; } break; case ExpressionType.Or: if (node.Type == typeof(bool) || node.Type == typeof(bool?)) { op = "Or"; } else { op = "|"; } break; case ExpressionType.OrAssign: if (node.Type == typeof(bool) || node.Type == typeof(bool?)) { op = "||="; } else { op = "|="; } break; case ExpressionType.ExclusiveOr: op = "^"; break; case ExpressionType.ExclusiveOrAssign: op = "^="; break; case ExpressionType.Power: op = "^"; break; 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 "); } var name = node.Name; if (string.IsNullOrEmpty(name)) { Out("Param_" + GetParamId(node)); } else { Out(name); } return node; } protected internal override Expression VisitLambda(LambdaExpression node) { if (node.Parameters.Count == 1) { // p => body Visit(node.Parameters[0]); } else { // (p1, p2, ..., pn) => body VisitExpressions('(', node.Parameters, ')'); } Out(" => "); Visit(node.Body); return node; } protected internal override Expression VisitListInit(ListInitExpression node) { Visit(node.NewExpression); Out(" {"); var n = node.Initializers.Count; for (var i = 0; i < n; i++) { if (i > 0) { Out(", "); } Out(node.Initializers[i].ToString()); } 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) { var 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) { var s = string.Format( CultureInfo.CurrentCulture, "", node.Document.FileName, node.StartLine, node.StartColumn, node.EndLine, node.EndColumn ); Out(s); 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); Out("." + member.Name); } else { // For static members, include the type name Out(member.DeclaringType.Name + "." + 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.Arguments.Count == 0 && node.NewExpression.Type.Name.Contains("<")) { // anonymous type constructor Out("new"); } else { Visit(node.NewExpression); } Out(" {"); var n = node.Bindings.Count; for (var i = 0; i < n; i++) { var b = node.Bindings[i]; if (i > 0) { Out(", "); } VisitMemberBinding(b); } Out("}"); return node; } protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) { Out(node.Member.Name); Out(" = "); Visit(node.Expression); return node; } protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) { Out(node.Member.Name); Out(" = {"); var n = node.Initializers.Count; for (var i = 0; i < n; i++) { if (i > 0) { Out(", "); } VisitElementInit(node.Initializers[i]); } Out("}"); return node; } protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) { Out(node.Member.Name); Out(" = {"); var n = node.Bindings.Count; for (var i = 0; i < n; i++) { if (i > 0) { Out(", "); } VisitMemberBinding(node.Bindings[i]); } Out("}"); return node; } protected override ElementInit VisitElementInit(ElementInit node) { Out(node.AddMethod.ToString()); const string sep = ", "; VisitExpressions('(', node.Arguments, ')', sep); return node; } protected internal override Expression VisitInvocation(InvocationExpression node) { Out("Invoke("); Visit(node.Expression); const string sep = ", "; var n = node.Arguments.Count; for (var i = 0; i < n; i++) { Out(sep); Visit(node.Arguments[i]); } Out(")"); return node; } protected internal override Expression VisitMethodCall(MethodCallExpression node) { var start = 0; var ob = node.Object; if (node.Method.HasAttribute()) { start = 1; ob = node.Arguments[0]; } if (ob != null) { Visit(ob); Out("."); } Out(node.Method.Name); Out("("); var n = node.Arguments.Count; for (var i = start; i < n; i++) { if (i > start) { Out(", "); } Visit(node.Arguments[i]); } Out(")"); return node; } protected internal override Expression VisitNewArray(NewArrayExpression node) { switch (node.NodeType) { case ExpressionType.NewArrayBounds: // new MyType[](expr1, expr2) Out("new " + node.Type); 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 " + node.Type.Name); Out("("); var members = node.Members; for (var i = 0; i < node.Arguments.Count; i++) { if (i > 0) { Out(", "); } if (members != null) { var name = members[i].Name; Out(name); Out(" = "); } Visit(node.Arguments[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.TypeAs: Out("("); break; case ExpressionType.Not: Out("Not("); break; case ExpressionType.Negate: case ExpressionType.NegateChecked: Out("-"); break; case ExpressionType.UnaryPlus: Out("+"); break; case ExpressionType.Quote: break; case ExpressionType.Throw: Out("throw("); 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.OnesComplement: Out("~("); break; default: Out(node.NodeType.ToString()); Out("("); break; } 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.PostIncrementAssign: Out("++"); break; case ExpressionType.PostDecrementAssign: Out("--"); break; default: Out(")"); break; } return node; } protected internal override Expression VisitBlock(BlockExpression node) { Out("{"); foreach (var 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) { Out(node.Kind.ToString().ToLower()); 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 (" + node.Test.Name); if (node.Variable != null) { 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); } VisitExpressions('[', node.Arguments, ']'); return node; } protected internal override Expression VisitExtension(Expression node) { // Prefer an overridden ToString, if available. var toString = node.GetType().GetMethod("ToString", ArrayReservoir.EmptyArray); 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. if (node.NodeType == ExpressionType.Extension) { Out(node.GetType().FullName); } else { Out(node.NodeType.ToString()); } Out("]"); return node; } private void DumpLabel(LabelTarget target) { if (!string.IsNullOrEmpty(target.Name)) { Out(target.Name); } else { var labelId = GetLabelId(target); Out("UnamedLabel_" + labelId); } } #endregion Output an expresstion tree to a string } } #endif