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
5 years ago
|
#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
|