/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Microsoft Public License. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Microsoft Public License, please send an email to
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Microsoft Public License.
*
* You must not remove this notice, or any other, from this software.
*
*
* ***************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Dynamic.Utils;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
#if CLR2
namespace Microsoft.Scripting.Ast{
#else
namespace System.Linq.Expressions {
#endif
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
internal sealed class DebugViewWriter : ExpressionVisitor {
[Flags]
private enum Flow {
None,
Space,
NewLine,
Break = 0x8000 // newline if column > MaxColumn
};
private const int Tab = 4;
private const int MaxColumn = 120;
private TextWriter _out;
private int _column;
private Stack<int> _stack = new Stack<int>();
private int _delta;
private Flow _flow;
// All the unique lambda expressions in the ET, will be used for displaying all
// the lambda definitions.
private Queue<LambdaExpression> _lambdas;
// Associate every unique anonymous LambdaExpression in the tree with an integer.
// The id is used to create a name for the anonymous lambda.
//
private Dictionary<LambdaExpression, int> _lambdaIds;
// Associate every unique anonymous parameter or variable in the tree with an integer.
// The id is used to create a name for the anonymous parameter or variable.
//
private Dictionary<ParameterExpression, int> _paramIds;
// Associate every unique anonymous LabelTarget in the tree with an integer.
// The id is used to create a name for the anonymous LabelTarget.
//
private Dictionary<LabelTarget, int> _labelIds;
private DebugViewWriter(TextWriter file) {
_out = file;
}
private int Base {
get {
return _stack.Count > 0 ? _stack.Peek() : 0;
}
}
private int Delta {
get { return _delta; }
}
private int Depth {
get { return Base + Delta; }
}
private void Indent() {
_delta += Tab;
}
private void Dedent() {
_delta -= Tab;
}
private void NewLine() {
_flow = Flow.NewLine;
}
private static int GetId<T>(T e, ref Dictionary<T, int> ids) {
if (ids == null) {
ids = new Dictionary<T, int>();
ids.Add(e, 1);
return 1;
} else {
int id;
if (!ids.TryGetValue(e, out id)) {
// e is met the first time
id = ids.Count + 1;
ids.Add(e, id);
}
return id;
}
}
private int GetLambdaId(LambdaExpression le) {
Debug.Assert(String.IsNullOrEmpty(le.Name));
return GetId(le, ref _lambdaIds);
}
private int GetParamId(ParameterExpression p) {
Debug.Assert(String.IsNullOrEmpty(p.Name));
return GetId(p, ref _paramIds);
}
private int GetLabelTargetId(LabelTarget target) {
Debug.Assert(String.IsNullOrEmpty(target.Name));
return GetId(target, ref _labelIds);
}
/// <summary>
/// Write out the given AST
/// </summary>
internal static void WriteTo(Expression node, TextWriter writer) {
Debug.Assert(node != null);
Debug.Assert(writer != null);
new DebugViewWriter(writer).WriteTo(node);
}
private void WriteTo(Expression node) {
var lambda = node as LambdaExpression;
if (lambda != null) {
WriteLambda(lambda);
} else {
Visit(node);
Debug.Assert(_stack.Count == 0);
}
//
// Output all lambda expression definitions.
// in the order of their appearances in the tree.
//
while (_lambdas != null && _lambdas.Count > 0) {
WriteLine();
WriteLine();
WriteLambda(_lambdas.Dequeue());
}
}
#region The printing code
private void Out(string s) {
Out(Flow.None, s, Flow.None);
}
private void Out(Flow before, string s) {
Out(before, s, Flow.None);
}
private void Out(string s, Flow after) {
Out(Flow.None, s, after);
}
private void Out(Flow before, string s, Flow after) {
switch (GetFlow(before)) {
case Flow.None:
break;
case Flow.Space:
Write(" ");
break;
case Flow.NewLine:
WriteLine();
Write(new String(' ', Depth));
break;
}
Write(s);
_flow = after;
}
private void WriteLine() {
_out.WriteLine();
_column = 0;
}
private void Write(string s) {
_out.Write(s);
_column += s.Length;
}
private Flow GetFlow(Flow flow) {
Flow last;
last = CheckBreak(_flow);
flow = CheckBreak(flow);
// Get the biggest flow that is requested None < Space < NewLine
return (Flow)System.Math.Max((int)last, (int)flow);
}
private Flow CheckBreak(Flow flow) {
if ((flow & Flow.Break) != 0) {
if (_column > (MaxColumn + Depth)) {
flow = Flow.NewLine;
} else {
flow &= ~Flow.Break;
}
}
return flow;
}
#endregion
#region The AST Output
// More proper would be to make this a virtual method on Action
private static string FormatBinder(CallSiteBinder binder) {
ConvertBinder convert;
GetMemberBinder getMember;
SetMemberBinder setMember;
DeleteMemberBinder deleteMember;
GetIndexBinder getIndex;
SetIndexBinder setIndex;
DeleteIndexBinder deleteIndex;
InvokeMemberBinder call;
InvokeBinder invoke;
CreateInstanceBinder create;
UnaryOperationBinder unary;
BinaryOperationBinder binary;
if ((convert = binder as ConvertBinder) != null) {
return "Convert " + convert.Type.ToString();
} else if ((getMember = binder as GetMemberBinder) != null) {
return "GetMember " + getMember.Name;
} else if ((setMember = binder as SetMemberBinder) != null) {
return "SetMember " + setMember.Name;
} else if ((deleteMember = binder as DeleteMemberBinder) != null) {
return "DeleteMember " + deleteMember.Name;
} else if ((getIndex = binder as GetIndexBinder) != null) {
return "GetIndex";
} else if ((setIndex = binder as SetIndexBinder) != null) {
return "SetIndex";
} else if ((deleteIndex = binder as DeleteIndexBinder) != null) {
return "DeleteIndex";
} else if ((call = binder as InvokeMemberBinder) != null) {
return "Call " + call.Name;
} else if ((invoke = binder as InvokeBinder) != null) {
return "Invoke";
} else if ((create = binder as CreateInstanceBinder) != null) {
return "Create";
} else if ((unary = binder as UnaryOperationBinder) != null) {
return "UnaryOperation " + unary.Operation;
} else if ((binary = binder as BinaryOperationBinder) != null) {
return "BinaryOperation " + binary.Operation;
} else {
return binder.ToString();
}
}
private void VisitExpressions<T>(char open, IList<T> expressions) where T : Expression {
VisitExpressions<T>(open, ',', expressions);
}
private void VisitExpressions<T>(char open, char separator, IList<T> expressions) where T : Expression {
VisitExpressions(open, separator, expressions, e => Visit(e));
}
private void VisitDeclarations(IList<ParameterExpression> expressions) {
VisitExpressions('(', ',', expressions, variable =>
{
Out(variable.Type.ToString());
if (variable.IsByRef) {
Out("&");
}
Out(" ");
VisitParameter(variable);
});
}
private void VisitExpressions<T>(char open, char separator, IList<T> expressions, Action<T> visit) {
Out(open.ToString());
if (expressions != null) {
Indent();
bool isFirst = true;
foreach (T e in expressions) {
if (isFirst) {
if (open == '{' || expressions.Count > 1) {
NewLine();
}
isFirst = false;
} else {
Out(separator.ToString(), Flow.NewLine);
}
visit(e);
}
Dedent();
}
char close;
switch (open) {
case '(': close = ')'; break;
case '{': close = '}'; break;
case '[': close = ']'; break;
case '<': close = '>'; break;
default: throw ContractUtils.Unreachable;
}
if (open == '{') {
NewLine();
}
Out(close.ToString(), Flow.Break);
}
protected internal override Expression VisitDynamic(DynamicExpression node) {
Out(".Dynamic", Flow.Space);
Out(FormatBinder(node.Binder));
VisitExpressions('(', node.Arguments);
return node;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
protected internal override Expression VisitBinary(BinaryExpression node) {
if (node.NodeType == ExpressionType.ArrayIndex) {
ParenthesizedVisit(node, node.Left);
Out("[");
Visit(node.Right);
Out("]");
} else {
bool parenthesizeLeft = NeedsParentheses(node, node.Left);
bool parenthesizeRight = NeedsParentheses(node, node.Right);
string op;
bool isChecked = false;
Flow beforeOp = Flow.Space;
switch (node.NodeType) {
case ExpressionType.Assign: op = "="; break;
case ExpressionType.Equal: op = "=="; break;
case ExpressionType.NotEqual: op = "!="; break;
case ExpressionType.AndAlso: op = "&&"; beforeOp = Flow.Break | Flow.Space; break;
case ExpressionType.OrElse: op = "||"; beforeOp = Flow.Break | Flow.Space; 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 = "+="; isChecked = true; break;
case ExpressionType.AddChecked: op = "+"; isChecked = true; break;
case ExpressionType.Subtract: op = "-"; break;
case ExpressionType.SubtractAssign: op = "-="; break;
case ExpressionType.SubtractAssignChecked: op = "-="; isChecked = true; break;
case ExpressionType.SubtractChecked: op = "-"; isChecked = true; 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 = "*="; isChecked = true; break;
case ExpressionType.MultiplyChecked: op = "*"; isChecked = true; 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 = "&"; break;
case ExpressionType.AndAssign: op = "&="; break;
case ExpressionType.Or: op = "|"; break;
case ExpressionType.OrAssign: 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();
}
if (parenthesizeLeft) {
Out("(", Flow.None);
}
Visit(node.Left);
if (parenthesizeLeft) {
Out(Flow.None, ")", Flow.Break);
}
// prepend # to the operator to represent checked op
if (isChecked) {
op = String.Format(
CultureInfo.CurrentCulture,
"#{0}",
op
);
}
Out(beforeOp, op, Flow.Space | Flow.Break);
if (parenthesizeRight) {
Out("(", Flow.None);
}
Visit(node.Right);
if (parenthesizeRight) {
Out(Flow.None, ")", Flow.Break);
}
}
return node;
}
protected internal override Expression VisitParameter(ParameterExpression node) {
// Have '$' for the DebugView of ParameterExpressions
Out("$");
if (String.IsNullOrEmpty(node.Name)) {
// If no name if provided, generate a name as $var1, $var2.
// No guarantee for not having name conflicts with user provided variable names.
//
int id = GetParamId(node);
Out("var" + id);
} else {
Out(GetDisplayName(node.Name));
}
return node;
}
protected internal override Expression VisitLambda<T>(Expression<T> node) {
Out(
String.Format(CultureInfo.CurrentCulture,
"{0} {1}<{2}>",
".Lambda",
GetLambdaName(node),
node.Type.ToString()
)
);
if (_lambdas == null) {
_lambdas = new Queue<LambdaExpression>();
}
// N^2 performance, for keeping the order of the lambdas.
if (!_lambdas.Contains(node)) {
_lambdas.Enqueue(node);
}
return node;
}
private static bool IsSimpleExpression(Expression node) {
var binary = node as BinaryExpression;
if (binary != null) {
return !(binary.Left is BinaryExpression || binary.Right is BinaryExpression);
}
return false;
}
protected internal override Expression VisitConditional(ConditionalExpression node) {
if (IsSimpleExpression(node.Test)) {
Out(".If (");
Visit(node.Test);
Out(") {", Flow.NewLine);
} else {
Out(".If (", Flow.NewLine);
Indent();
Visit(node.Test);
Dedent();
Out(Flow.NewLine, ") {", Flow.NewLine);
}
Indent();
Visit(node.IfTrue);
Dedent();
Out(Flow.NewLine, "} .Else {", Flow.NewLine);
Indent();
Visit(node.IfFalse);
Dedent();
Out(Flow.NewLine, "}");
return node;
}
protected internal override Expression VisitConstant(ConstantExpression node) {
object value = node.Value;
if (value == null) {
Out("null");
} else if ((value is string) && node.Type == typeof(string)) {
Out(String.Format(
CultureInfo.CurrentCulture,
"\"{0}\"",
value));
} else if ((value is char) && node.Type == typeof(char)) {
Out(String.Format(
CultureInfo.CurrentCulture,
"'{0}'",
value));
} else if ((value is int) && node.Type == typeof(int)
|| (value is bool) && node.Type == typeof(bool)) {
Out(value.ToString());
} else {
string suffix = GetConstantValueSuffix(node.Type);
if (suffix != null) {
Out(value.ToString());
Out(suffix);
} else {
Out(String.Format(
CultureInfo.CurrentCulture,
".Constant<{0}>({1})",
node.Type.ToString(),
value));
}
}
return node;
}
private static string GetConstantValueSuffix(Type type) {
if (type == typeof(UInt32)) {
return "U";
}
if (type == typeof(Int64)) {
return "L";
}
if (type == typeof(UInt64)) {
return "UL";
}
if (type == typeof(Double)) {
return "D";
}
if (type == typeof(Single)) {
return "F";
}
if (type == typeof(Decimal)) {
return "M";
}
return null;
}
protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) {
Out(".RuntimeVariables");
VisitExpressions('(', node.Variables);
return node;
}
// Prints ".instanceField" or "declaringType.staticField"
private void OutMember(Expression node, Expression instance, MemberInfo member) {
if (instance != null) {
ParenthesizedVisit(node, instance);
Out("." + member.Name);
} else {
// For static members, include the type name
Out(member.DeclaringType.ToString() + "." + member.Name);
}
}
protected internal override Expression VisitMember(MemberExpression node) {
OutMember(node, node.Expression, node.Member);
return node;
}
protected internal override Expression VisitInvocation(InvocationExpression node) {
Out(".Invoke ");
ParenthesizedVisit(node, node.Expression);
VisitExpressions('(', node.Arguments);
return node;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
private static bool NeedsParentheses(Expression parent, Expression child) {
Debug.Assert(parent != null);
if (child == null) {
return false;
}
// Some nodes always have parentheses because of how they are
// displayed, for example: ".Unbox(obj.Foo)"
switch (parent.NodeType) {
case ExpressionType.Increment:
case ExpressionType.Decrement:
case ExpressionType.IsTrue:
case ExpressionType.IsFalse:
case ExpressionType.Unbox:
return true;
}
int childOpPrec = GetOperatorPrecedence(child);
int parentOpPrec = GetOperatorPrecedence(parent);
if (childOpPrec == parentOpPrec) {
// When parent op and child op has the same precedence,
// we want to be a little conservative to have more clarity.
// Parentheses are not needed if
// 1) Both ops are &&, ||, &, |, or ^, all of them are the only
// op that has the precedence.
// 2) Parent op is + or *, e.g. x + (y - z) can be simplified to
// x + y - z.
// 3) Parent op is -, / or %, and the child is the left operand.
// In this case, if left and right operand are the same, we don't
// remove parenthesis, e.g. (x + y) - (x + y)
//
switch (parent.NodeType) {
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.ExclusiveOr:
// Since these ops are the only ones on their precedence,
// the child op must be the same.
Debug.Assert(child.NodeType == parent.NodeType);
// We remove the parenthesis, e.g. x && y && z
return false;
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
return false;
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
BinaryExpression binary = parent as BinaryExpression;
Debug.Assert(binary != null);
// Need to have parenthesis for the right operand.
return child == binary.Right;
}
return true;
}
// Special case: negate of a constant needs parentheses, to
// disambiguate it from a negative constant.
if (child != null && child.NodeType == ExpressionType.Constant &&
(parent.NodeType == ExpressionType.Negate || parent.NodeType == ExpressionType.NegateChecked)) {
return true;
}
// If the parent op has higher precedence, need parentheses for the child.
return childOpPrec < parentOpPrec;
}
// the greater the higher
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
private static int GetOperatorPrecedence(Expression node) {
// Roughly matches C# operator precedence, with some additional
// operators. Also things which are not binary/unary expressions,
// such as conditional and type testing, don't use this mechanism.
switch (node.NodeType) {
// Assignment
case ExpressionType.Assign:
case ExpressionType.ExclusiveOrAssign:
case ExpressionType.AddAssign:
case ExpressionType.AddAssignChecked:
case ExpressionType.SubtractAssign:
case ExpressionType.SubtractAssignChecked:
case ExpressionType.DivideAssign:
case ExpressionType.ModuloAssign:
case ExpressionType.MultiplyAssign:
case ExpressionType.MultiplyAssignChecked:
case ExpressionType.LeftShiftAssign:
case ExpressionType.RightShiftAssign:
case ExpressionType.AndAssign:
case ExpressionType.OrAssign:
case ExpressionType.PowerAssign:
case ExpressionType.Coalesce:
return 1;
// Conditional (?:) would go here
// Conditional OR
case ExpressionType.OrElse:
return 2;
// Conditional AND
case ExpressionType.AndAlso:
return 3;
// Logical OR
case ExpressionType.Or:
return 4;
// Logical XOR
case ExpressionType.ExclusiveOr:
return 5;
// Logical AND
case ExpressionType.And:
return 6;
// Equality
case ExpressionType.Equal:
case ExpressionType.NotEqual:
return 7;
// Relational, type testing
case ExpressionType.GreaterThan:
case ExpressionType.LessThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThanOrEqual:
case ExpressionType.TypeAs:
case ExpressionType.TypeIs:
case ExpressionType.TypeEqual:
return 8;
// Shift
case ExpressionType.LeftShift:
case ExpressionType.RightShift:
return 9;
// Additive
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
return 10;
// Multiplicative
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
return 11;
// Unary
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.UnaryPlus:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.PreIncrementAssign:
case ExpressionType.PreDecrementAssign:
case ExpressionType.OnesComplement:
case ExpressionType.Increment:
case ExpressionType.Decrement:
case ExpressionType.IsTrue:
case ExpressionType.IsFalse:
case ExpressionType.Unbox:
case ExpressionType.Throw:
return 12;
// Power, which is not in C#
// But VB/Python/Ruby put it here, above unary.
case ExpressionType.Power:
return 13;
// Primary, which includes all other node types:
// member access, calls, indexing, new.
case ExpressionType.PostIncrementAssign:
case ExpressionType.PostDecrementAssign:
default:
return 14;
// These aren't expressions, so never need parentheses:
// constants, variables
case ExpressionType.Constant:
case ExpressionType.Parameter:
return 15;
}
}
private void ParenthesizedVisit(Expression parent, Expression nodeToVisit) {
if (NeedsParentheses(parent, nodeToVisit)) {
Out("(");
Visit(nodeToVisit);
Out(")");
} else {
Visit(nodeToVisit);
}
}
protected internal override Expression VisitMethodCall(MethodCallExpression node) {
Out(".Call ");
if (node.Object != null) {
ParenthesizedVisit(node, node.Object);
} else if (node.Method.DeclaringType != null) {
Out(node.Method.DeclaringType.ToString());
} else {
Out("<UnknownType>");
}
Out(".");
Out(node.Method.Name);
VisitExpressions('(', node.Arguments);
return node;
}
protected internal override Expression VisitNewArray(NewArrayExpression node) {
if (node.NodeType == ExpressionType.NewArrayBounds) {
// .NewArray MyType[expr1, expr2]
Out(".NewArray " + node.Type.GetElementType().ToString());
VisitExpressions('[', node.Expressions);
} else {
// .NewArray MyType {expr1, expr2}
Out(".NewArray " + node.Type.ToString(), Flow.Space);
VisitExpressions('{', node.Expressions);
}
return node;
}
protected internal override Expression VisitNew(NewExpression node) {
Out(".New " + node.Type.ToString());
VisitExpressions('(', node.Arguments);
return node;
}
protected override ElementInit VisitElementInit(ElementInit node) {
if (node.Arguments.Count == 1) {
Visit(node.Arguments[0]);
} else {
VisitExpressions('{', node.Arguments);
}
return node;
}
protected internal override Expression VisitListInit(ListInitExpression node) {
Visit(node.NewExpression);
VisitExpressions('{', ',', node.Initializers, e => VisitElementInit(e));
return node;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment) {
Out(assignment.Member.Name);
Out(Flow.Space, "=", Flow.Space);
Visit(assignment.Expression);
return assignment;
}
protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding) {
Out(binding.Member.Name);
Out(Flow.Space, "=", Flow.Space);
VisitExpressions('{', ',', binding.Initializers, e => VisitElementInit(e));
return binding;
}
protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) {
Out(binding.Member.Name);
Out(Flow.Space, "=", Flow.Space);
VisitExpressions('{', ',', binding.Bindings, e => VisitMemberBinding(e));
return binding;
}
protected internal override Expression VisitMemberInit(MemberInitExpression node) {
Visit(node.NewExpression);
VisitExpressions('{', ',', node.Bindings, e => VisitMemberBinding(e));
return node;
}
protected internal override Expression VisitTypeBinary(TypeBinaryExpression node) {
ParenthesizedVisit(node, node.Expression);
switch (node.NodeType) {
case ExpressionType.TypeIs:
Out(Flow.Space, ".Is", Flow.Space);
break;
case ExpressionType.TypeEqual:
Out(Flow.Space, ".TypeEqual", Flow.Space);
break;
}
Out(node.TypeOperand.ToString());
return node;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
protected internal override Expression VisitUnary(UnaryExpression node) {
bool parenthesize = NeedsParentheses(node, node.Operand);
switch (node.NodeType) {
case ExpressionType.Convert:
Out("(" + node.Type.ToString() + ")");
break;
case ExpressionType.ConvertChecked:
Out("#(" + node.Type.ToString() + ")");
break;
case ExpressionType.TypeAs:
break;
case ExpressionType.Not:
Out(node.Type == typeof(bool) ? "!" : "~");
break;
case ExpressionType.OnesComplement:
Out("~");
break;
case ExpressionType.Negate:
Out("-");
break;
case ExpressionType.NegateChecked:
Out("#-");
break;
case ExpressionType.UnaryPlus:
Out("+");
break;
case ExpressionType.ArrayLength:
break;
case ExpressionType.Quote:
Out("'");
break;
case ExpressionType.Throw:
if (node.Operand == null) {
Out(".Rethrow");
} else {
Out(".Throw", Flow.Space);
}
break;
case ExpressionType.IsFalse:
Out(".IsFalse");
break;
case ExpressionType.IsTrue:
Out(".IsTrue");
break;
case ExpressionType.Decrement:
Out(".Decrement");
break;
case ExpressionType.Increment:
Out(".Increment");
break;
case ExpressionType.PreDecrementAssign:
Out("--");
break;
case ExpressionType.PreIncrementAssign:
Out("++");
break;
case ExpressionType.Unbox:
Out(".Unbox");
break;
}
ParenthesizedVisit(node, node.Operand);
switch (node.NodeType) {
case ExpressionType.TypeAs:
Out(Flow.Space, ".As", Flow.Space | Flow.Break);
Out(node.Type.ToString());
break;
case ExpressionType.ArrayLength:
Out(".Length");
break;
case ExpressionType.PostDecrementAssign:
Out("--");
break;
case ExpressionType.PostIncrementAssign:
Out("++");
break;
}
return node;
}
protected internal override Expression VisitBlock(BlockExpression node) {
Out(".Block");
// Display <type> if the type of the BlockExpression is different from the
// last expression's type in the block.
if (node.Type != node.GetExpression(node.ExpressionCount - 1).Type) {
Out(String.Format(CultureInfo.CurrentCulture, "<{0}>", node.Type.ToString()));
}
VisitDeclarations(node.Variables);
Out(" ");
// Use ; to separate expressions in the block
VisitExpressions('{', ';', node.Expressions);
return node;
}
protected internal override Expression VisitDefault(DefaultExpression node) {
Out(".Default(" + node.Type.ToString() + ")");
return node;
}
protected internal override Expression VisitLabel(LabelExpression node) {
Out(".Label", Flow.NewLine);
Indent();
Visit(node.DefaultValue);
Dedent();
NewLine();
DumpLabel(node.Target);
return node;
}
protected internal override Expression VisitGoto(GotoExpression node) {
Out("." + node.Kind.ToString(), Flow.Space);
Out(GetLabelTargetName(node.Target), Flow.Space);
Out("{", Flow.Space);
Visit(node.Value);
Out(Flow.Space, "}");
return node;
}
protected internal override Expression VisitLoop(LoopExpression node) {
Out(".Loop", Flow.Space);
if (node.ContinueLabel != null) {
DumpLabel(node.ContinueLabel);
}
Out(" {", Flow.NewLine);
Indent();
Visit(node.Body);
Dedent();
Out(Flow.NewLine, "}");
if (node.BreakLabel != null) {
Out("", Flow.NewLine);
DumpLabel(node.BreakLabel);
}
return node;
}
protected override SwitchCase VisitSwitchCase(SwitchCase node) {
foreach (var test in node.TestValues) {
Out(".Case (");
Visit(test);
Out("):", Flow.NewLine);
}
Indent(); Indent();
Visit(node.Body);
Dedent(); Dedent();
NewLine();
return node;
}
protected internal override Expression VisitSwitch(SwitchExpression node) {
Out(".Switch ");
Out("(");
Visit(node.SwitchValue);
Out(") {", Flow.NewLine);
Visit(node.Cases, VisitSwitchCase);
if (node.DefaultBody != null) {
Out(".Default:", Flow.NewLine);
Indent(); Indent();
Visit(node.DefaultBody);
Dedent(); Dedent();
NewLine();
}
Out("}");
return node;
}
protected override CatchBlock VisitCatchBlock(CatchBlock node) {
Out(Flow.NewLine, "} .Catch (" + node.Test.ToString());
if (node.Variable != null) {
Out(Flow.Space, "");
VisitParameter(node.Variable);
}
if (node.Filter != null) {
Out(") .If (", Flow.Break);
Visit(node.Filter);
}
Out(") {", Flow.NewLine);
Indent();
Visit(node.Body);
Dedent();
return node;
}
protected internal override Expression VisitTry(TryExpression node) {
Out(".Try {", Flow.NewLine);
Indent();
Visit(node.Body);
Dedent();
Visit(node.Handlers, VisitCatchBlock);
if (node.Finally != null) {
Out(Flow.NewLine, "} .Finally {", Flow.NewLine);
Indent();
Visit(node.Finally);
Dedent();
} else if (node.Fault != null) {
Out(Flow.NewLine, "} .Fault {", Flow.NewLine);
Indent();
Visit(node.Fault);
Dedent();
}
Out(Flow.NewLine, "}");
return node;
}
protected internal override Expression VisitIndex(IndexExpression node) {
if (node.Indexer != null) {
OutMember(node, node.Object, node.Indexer);
} else {
ParenthesizedVisit(node, node.Object);
}
VisitExpressions('[', node.Arguments);
return node;
}
protected internal override Expression VisitExtension(Expression node) {
Out(String.Format(CultureInfo.CurrentCulture, ".Extension<{0}>", node.GetType().ToString()));
if (node.CanReduce) {
Out(Flow.Space, "{", Flow.NewLine);
Indent();
Visit(node.Reduce());
Dedent();
Out(Flow.NewLine, "}");
}
return node;
}
protected internal override Expression VisitDebugInfo(DebugInfoExpression node) {
Out(String.Format(
CultureInfo.CurrentCulture,
".DebugInfo({0}: {1}, {2} - {3}, {4})",
node.Document.FileName,
node.StartLine,
node.StartColumn,
node.EndLine,
node.EndColumn)
);
return node;
}
private void DumpLabel(LabelTarget target) {
Out(String.Format(CultureInfo.CurrentCulture, ".LabelTarget {0}:", GetLabelTargetName(target)));
}
private string GetLabelTargetName(LabelTarget target) {
if (string.IsNullOrEmpty(target.Name)) {
// Create the label target name as #Label1, #Label2, etc.
return String.Format(CultureInfo.CurrentCulture, "#Label{0}", GetLabelTargetId(target));
} else {
return GetDisplayName(target.Name);
}
}
private void WriteLambda(LambdaExpression lambda) {
Out(
String.Format(
CultureInfo.CurrentCulture,
".Lambda {0}<{1}>",
GetLambdaName(lambda),
lambda.Type.ToString())
);
VisitDeclarations(lambda.Parameters);
Out(Flow.Space, "{", Flow.NewLine);
Indent();
Visit(lambda.Body);
Dedent();
Out(Flow.NewLine, "}");
Debug.Assert(_stack.Count == 0);
}
private string GetLambdaName(LambdaExpression lambda) {
if (String.IsNullOrEmpty(lambda.Name)) {
return "#Lambda" + GetLambdaId(lambda);
}
return GetDisplayName(lambda.Name);
}
/// <summary>
/// Return true if the input string contains any whitespace character.
/// Otherwise false.
/// </summary>
private static bool ContainsWhiteSpace(string name) {
foreach (char c in name) {
if (Char.IsWhiteSpace(c)) {
return true;
}
}
return false;
}
private static string QuoteName(string name) {
return String.Format(CultureInfo.CurrentCulture, "'{0}'", name);
}
private static string GetDisplayName(string name) {
if (ContainsWhiteSpace(name)) {
// if name has whitespaces in it, quote it
return QuoteName(name);
} else {
return name;
}
}
#endregion
}
}
|