You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1446 lines
43 KiB
1446 lines
43 KiB
#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.Dynamic.Utils; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Reflection; |
|
|
|
namespace System.Linq.Expressions.Reimplement |
|
{ |
|
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 readonly TextWriter _out; |
|
private int _column; |
|
|
|
private readonly 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> |
|
{ |
|
{ 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); |
|
} |
|
|
|
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) |
|
{ |
|
var last = CheckBreak(_flow); |
|
flow = CheckBreak(flow); |
|
|
|
// Get the biggest flow that is requested None < Space < NewLine |
|
return (Flow)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 The printing code |
|
|
|
#region The AST Output |
|
|
|
private void VisitExpressions<T>(char open, IList<T> expressions) where T : Expression |
|
{ |
|
VisitExpressions(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) |
|
{ |
|
// NOTICE this method has no null check |
|
Out(open.ToString()); |
|
|
|
if (expressions != null) |
|
{ |
|
Indent(); |
|
var isFirst = true; |
|
foreach (var 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 VisitBinary(BinaryExpression node) |
|
{ |
|
if (node.NodeType == ExpressionType.ArrayIndex) |
|
{ |
|
ParenthesizedVisit(node, node.Left); |
|
Out("["); |
|
Visit(node.Right); |
|
Out("]"); |
|
} |
|
else |
|
{ |
|
var parenthesizeLeft = NeedsParentheses(node, node.Left); |
|
var parenthesizeRight = NeedsParentheses(node, node.Right); |
|
|
|
string op; |
|
var isChecked = false; |
|
var 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. |
|
// |
|
var id = GetParamId(node); |
|
Out("var" + id); |
|
} |
|
else |
|
{ |
|
Out(GetDisplayName(node.Name)); |
|
} |
|
return node; |
|
} |
|
|
|
protected internal override Expression VisitLambda(LambdaExpression 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) |
|
{ |
|
var 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 |
|
{ |
|
var 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(uint)) |
|
{ |
|
return "U"; |
|
} |
|
if (type == typeof(long)) |
|
{ |
|
return "L"; |
|
} |
|
if (type == typeof(ulong)) |
|
{ |
|
return "UL"; |
|
} |
|
if (type == typeof(double)) |
|
{ |
|
return "D"; |
|
} |
|
if (type == typeof(float)) |
|
{ |
|
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 + "." + 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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
var childOpPrec = GetOperatorPrecedence(child); |
|
var 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: |
|
var 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 |
|
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()); |
|
VisitExpressions('[', node.Expressions); |
|
} |
|
else |
|
{ |
|
// .NewArray MyType {expr1, expr2} |
|
Out(".NewArray " + node.Type, Flow.Space); |
|
VisitExpressions('{', node.Expressions); |
|
} |
|
return node; |
|
} |
|
|
|
protected internal override Expression VisitNew(NewExpression node) |
|
{ |
|
Out(".New " + node.Type); |
|
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 node) |
|
{ |
|
Out(node.Member.Name); |
|
Out(Flow.Space, "=", Flow.Space); |
|
Visit(node.Expression); |
|
return node; |
|
} |
|
|
|
protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) |
|
{ |
|
Out(node.Member.Name); |
|
Out(Flow.Space, "=", Flow.Space); |
|
VisitExpressions('{', ',', node.Initializers, e => VisitElementInit(e)); |
|
return node; |
|
} |
|
|
|
protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) |
|
{ |
|
Out(node.Member.Name); |
|
Out(Flow.Space, "=", Flow.Space); |
|
VisitExpressions('{', ',', node.Bindings, e => VisitMemberBinding(e)); |
|
return node; |
|
} |
|
|
|
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; |
|
} |
|
|
|
protected internal override Expression VisitUnary(UnaryExpression node) |
|
{ |
|
switch (node.NodeType) |
|
{ |
|
case ExpressionType.Convert: |
|
Out("(" + node.Type + ")"); |
|
break; |
|
|
|
case ExpressionType.ConvertChecked: |
|
Out("#(" + node.Type + ")"); |
|
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 + ")"); |
|
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); |
|
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); |
|
} |
|
|
|
private static bool ContainsWhiteSpace(string name) |
|
{ |
|
foreach (var 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 The AST Output |
|
} |
|
} |
|
|
|
#endif |