Skip to content

Commit 838b74e

Browse files
committed
Textual tree formatter #38
1 parent 63b0c81 commit 838b74e

9 files changed

Lines changed: 2334 additions & 27 deletions

File tree

Shared/Globals.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class FormatterNames {
1313
public const string FactoryMethods = "Factory methods";
1414
public const string ObjectNotation = "Object notation";
1515
public const string DebugView = "DebugView";
16-
public const string ToStringName = "ToString";
16+
public const string TextualTree = "Textual tree";
1717
}
1818

1919
public static class Globals {
@@ -40,7 +40,7 @@ static Globals() {
4040
stringFormats = methods["Format"].ToHashSet();
4141
}
4242

43-
public static List<Type> NodeTypes = new List<Type>() {
43+
public readonly static List<Type> NodeTypes = new List<Type>() {
4444
typeof(Expression),
4545
typeof(MemberBinding),
4646
typeof(ElementInit),
@@ -49,9 +49,9 @@ static Globals() {
4949
typeof(LabelTarget)
5050
};
5151

52-
public static List<Type> PropertyTypes = NodeTypes.Select(x => typeof(IEnumerable<>).MakeGenericType(x)).ToList();
52+
public readonly static List<Type> PropertyTypes = NodeTypes.Select(x => typeof(IEnumerable<>).MakeGenericType(x)).ToList();
5353

54-
public static Dictionary<ExpressionType, string> BinaryUnaryMethods = new Dictionary<ExpressionType, string>() {
54+
public readonly static Dictionary<ExpressionType, string> BinaryUnaryMethods = new Dictionary<ExpressionType, string>() {
5555
{ Add, "Add" },
5656
{ AddAssign, "AddAssign" },
5757
{ AddAssignChecked, "AddAssignChecked" },
@@ -139,5 +139,21 @@ static Globals() {
139139
{ Unbox, "Unbox" },
140140
};
141141

142+
public readonly static List<(Type type, string[] propertyNames)> PreferredPropertyOrders = new List<(Type, string[])> {
143+
(typeof(LambdaExpression), new [] {"Parameters", "Body" } ),
144+
(typeof(BinaryExpression), new [] {"Left", "Right", "Conversion"}),
145+
(typeof(BlockExpression), new [] { "Variables", "Expressions"}),
146+
(typeof(CatchBlock), new [] { "Variable", "Filter", "Body"}),
147+
(typeof(ConditionalExpression), new [] { "Test", "IfTrue", "IfFalse"}),
148+
(typeof(IndexExpression), new [] { "Object", "Arguments" }),
149+
(typeof(InvocationExpression), new [] {"Arguments", "Expression"}),
150+
(typeof(ListInitExpression), new [] {"NewExpression", "Initializers"}),
151+
(typeof(MemberInitExpression), new [] {"NewExpression", "Bindings"}),
152+
(typeof(MethodCallExpression), new [] {"Object", "Arguments"}),
153+
(typeof(SwitchCase), new [] {"TestValues", "Body"}),
154+
(typeof(SwitchExpression), new [] {"SwitchValue", "Cases", "DefaultBody"}),
155+
(typeof(TryExpression), new [] {"Body", "Handlers", "Finally", "Fault"}),
156+
(typeof(DynamicExpression), new [] {"Binder", "Arguments"})
157+
};
142158
}
143159
}

Shared/ObjectNotationFormatter.cs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,13 @@
1414

1515
namespace ExpressionToString {
1616
public class ObjectNotationFormatter : WriterBase {
17-
public ObjectNotationFormatter(object o, string language) : base(o, ResolveLanguage(language)) { }
17+
public ObjectNotationFormatter(object o, string language) : base(o, language) { }
1818

19-
public ObjectNotationFormatter(object o, string language, out Dictionary<string, (int start, int length)> pathSpans) : base(o, ResolveLanguage(language), out pathSpans) { }
19+
public ObjectNotationFormatter(object o, string language, out Dictionary<string, (int start, int length)> pathSpans) : base(o, language, out pathSpans) { }
2020

2121
// TODO represent parameters using variables, except for first usage where variable is defined
2222
// TODO if a given type always has the same node type, don't include
2323

24-
private static List<(Type, string[])> preferredPropertyOrders = new List<(Type, string[])> {
25-
(typeof(LambdaExpression), new [] {"Parameters", "Body" } ),
26-
(typeof(BinaryExpression), new [] {"Left", "Right", "Conversion"}),
27-
(typeof(BlockExpression), new [] { "Variables", "Expressions"}),
28-
(typeof(CatchBlock), new [] { "Variable", "Filter", "Body"}),
29-
(typeof(ConditionalExpression), new [] { "Test", "IfTrue", "IfFalse"}),
30-
(typeof(IndexExpression), new [] { "Object", "Arguments" }),
31-
(typeof(InvocationExpression), new [] {"Arguments", "Expression"}),
32-
(typeof(ListInitExpression), new [] {"NewExpression", "Initializers"}),
33-
(typeof(MemberInitExpression), new [] {"NewExpression", "Bindings"}),
34-
(typeof(MethodCallExpression), new [] {"Object", "Arguments"}),
35-
(typeof(SwitchCase), new [] {"TestValues", "Body"}),
36-
(typeof(SwitchExpression), new [] {"SwitchValue", "Cases", "DefaultBody"}),
37-
(typeof(TryExpression), new [] {"Body", "Handlers", "Finally", "Fault"}),
38-
(typeof(DynamicExpression), new [] {"Binder", "Arguments"})
39-
};
40-
4124
private static HashSet<Type> hideNodeType = new HashSet<Type>() {
4225
typeof(BlockExpression),
4326
typeof(ConditionalExpression),
@@ -63,7 +46,7 @@ public ObjectNotationFormatter(object o, string language, out Dictionary<string,
6346

6447
private void WriteObjectCreation(object o) {
6548
var type = writeNew(o);
66-
var preferredOrder = preferredPropertyOrders.FirstOrDefault(x => x.Item1.IsAssignableFrom(o.GetType())).Item2;
49+
var preferredOrder = PreferredPropertyOrders.FirstOrDefault(x => x.type.IsAssignableFrom(o.GetType())).propertyNames;
6750
var properties = type.GetProperties().Where(x => {
6851
if (x.Name.In("CanReduce", "TailCall", "CanReduce", "IsLifted", "IsLiftedToNull", "ArgumentCount")) { return false; }
6952
if (x.Name == "NodeType" && hideNodeType.Contains(type)) { return false; }

Shared/Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
<Compile Include="$(MSBuildThisFileDirectory)CSharpMultilineBlockTypes.cs" />
1414
<Compile Include="$(MSBuildThisFileDirectory)FactoryMethodsFormatter.cs" />
1515
<Compile Include="$(MSBuildThisFileDirectory)ObjectNotationFormatter.cs" />
16+
<Compile Include="$(MSBuildThisFileDirectory)TextualTreeFormatter.cs" />
1617
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\BlockExpression.cs" />
1718
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\CallSiteBinder.cs" />
1819
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\IListT.cs" />
1920
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\Match.cs" />
2021
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\MethodInfo.cs" />
2122
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\ParameterInfo.cs" />
23+
<Compile Include="$(MSBuildThisFileDirectory)Util\Extensions\ValueTuple.cs" />
2224
<Compile Include="$(MSBuildThisFileDirectory)VBExpressionMetadata.cs" />
2325
<Compile Include="$(MSBuildThisFileDirectory)WriterBase.cs" />
2426
<Compile Include="$(MSBuildThisFileDirectory)CSharpCodeWriter.cs" />

Shared/TextualTreeFormatter.cs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using ExpressionToString.Util;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Dynamic;
5+
using System.Linq;
6+
using System.Linq.Expressions;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using static ExpressionToString.Util.Functions;
11+
using static ExpressionToString.Globals;
12+
using System.Collections;
13+
14+
namespace ExpressionToString {
15+
public class TextualTreeFormatter : WriterBase {
16+
public TextualTreeFormatter(object o, string language) : base(o, language) { }
17+
18+
public TextualTreeFormatter(object o, string language, out Dictionary<string, (int start, int length)> pathSpans) : base(o, language, out pathSpans) { }
19+
20+
private void WriteTextualNode(object o) {
21+
var nodeType = "";
22+
var typename = "";
23+
var name = "";
24+
object value = null;
25+
26+
switch (o) {
27+
case Expression expr:
28+
nodeType = expr.NodeType.ToString();
29+
typename = $"({expr.Type.FriendlyName(language)})";
30+
name = expr.Name();
31+
32+
switch (expr) {
33+
case ConstantExpression cexpr when !expr.Type.IsClosureClass():
34+
value = cexpr.Value;
35+
break;
36+
case Expression _ when expr.IsClosedVariable():
37+
case DefaultExpression _:
38+
value = expr.ExtractValue();
39+
break;
40+
}
41+
break;
42+
43+
case MemberBinding mbind:
44+
nodeType = mbind.BindingType.ToString();
45+
name = mbind.Member.Name;
46+
break;
47+
case CallSiteBinder binder:
48+
nodeType = binder.BinderType();
49+
break;
50+
default:
51+
nodeType = o.GetType().FriendlyName(language);
52+
break;
53+
}
54+
55+
string stringValue = "";
56+
if (value != null) { stringValue = "= " + StringValue(value, language); }
57+
58+
Write((nodeType, typename, name, stringValue).Where(x => !x.IsNullOrWhitespace()).Joined(" "));
59+
60+
var type = o.GetType();
61+
var preferredOrder = PreferredPropertyOrders.FirstOrDefault(x => x.type.IsAssignableFrom(o.GetType())).propertyNames;
62+
var childNodes = type.GetProperties()
63+
.Where(prp =>
64+
prp.PropertyType.InheritsFromOrImplementsAny(PropertyTypes) ||
65+
prp.PropertyType.InheritsFromOrImplementsAny(NodeTypes)
66+
)
67+
.OrderBy(x => {
68+
if (preferredOrder == null) { return -1; }
69+
return Array.IndexOf(preferredOrder, x.Name);
70+
})
71+
.ThenBy(x => x.Name)
72+
.SelectMany(prp => {
73+
if (prp.PropertyType.InheritsFromOrImplements<IEnumerable>()) {
74+
return (prp.GetValue(o) as IEnumerable).Cast<object>().Select((x, index) => (name: $"{prp.Name}[{index}]", value: x));
75+
} else {
76+
return new[] { (prp.Name, prp.GetValue(o)) };
77+
}
78+
})
79+
.Where(x => x.value != null)
80+
.ToList();
81+
82+
if (childNodes.Any()) {
83+
Indent();
84+
WriteEOL();
85+
childNodes.ForEach((node, index) => {
86+
if (index > 0) { WriteEOL(); }
87+
Write(node.name);
88+
Write(" -- ");
89+
WriteNode(node);
90+
});
91+
Dedent();
92+
}
93+
}
94+
95+
protected override void WriteBinary(BinaryExpression expr) => WriteTextualNode(expr);
96+
protected override void WriteUnary(UnaryExpression expr) => WriteTextualNode(expr);
97+
protected override void WriteLambda(LambdaExpression expr) => WriteTextualNode(expr);
98+
protected override void WriteParameter(ParameterExpression expr) => WriteTextualNode(expr);
99+
protected override void WriteConstant(ConstantExpression expr) => WriteTextualNode(expr);
100+
protected override void WriteMemberAccess(MemberExpression expr) => WriteTextualNode(expr);
101+
protected override void WriteNew(NewExpression expr) => WriteTextualNode(expr);
102+
protected override void WriteCall(MethodCallExpression expr) => WriteTextualNode(expr);
103+
protected override void WriteMemberInit(MemberInitExpression expr) => WriteTextualNode(expr);
104+
protected override void WriteListInit(ListInitExpression expr) => WriteTextualNode(expr);
105+
protected override void WriteNewArray(NewArrayExpression expr) => WriteTextualNode(expr);
106+
protected override void WriteConditional(ConditionalExpression expr, object metadata) => WriteTextualNode(expr);
107+
protected override void WriteDefault(DefaultExpression expr) => WriteTextualNode(expr);
108+
protected override void WriteTypeBinary(TypeBinaryExpression expr) => WriteTextualNode(expr);
109+
protected override void WriteInvocation(InvocationExpression expr) => WriteTextualNode(expr);
110+
protected override void WriteIndex(IndexExpression expr) => WriteTextualNode(expr);
111+
protected override void WriteBlock(BlockExpression expr, object metadata) => WriteTextualNode(expr);
112+
protected override void WriteSwitch(SwitchExpression expr) => WriteTextualNode(expr);
113+
protected override void WriteTry(TryExpression expr) => WriteTextualNode(expr);
114+
protected override void WriteLabel(LabelExpression expr) => WriteTextualNode(expr);
115+
protected override void WriteGoto(GotoExpression expr) => WriteTextualNode(expr);
116+
protected override void WriteLoop(LoopExpression expr) => WriteTextualNode(expr);
117+
protected override void WriteRuntimeVariables(RuntimeVariablesExpression expr) => WriteTextualNode(expr);
118+
protected override void WriteDebugInfo(DebugInfoExpression expr) => WriteTextualNode(expr);
119+
protected override void WriteElementInit(ElementInit elementInit) => WriteTextualNode(elementInit);
120+
protected override void WriteBinding(MemberBinding binding) => WriteTextualNode(binding);
121+
protected override void WriteSwitchCase(SwitchCase switchCase) => WriteTextualNode(switchCase);
122+
protected override void WriteCatchBlock(CatchBlock catchBlock) => WriteTextualNode(catchBlock);
123+
protected override void WriteLabelTarget(LabelTarget labelTarget) => WriteTextualNode(labelTarget);
124+
protected override void WriteDynamic(DynamicExpression expr) => WriteTextualNode(expr);
125+
protected override void WriteBinaryOperationBinder(BinaryOperationBinder binaryOperationBinder, IList<Expression> args) => throw new NotImplementedException();
126+
protected override void WriteConvertBinder(ConvertBinder convertBinder, IList<Expression> args) => throw new NotImplementedException();
127+
protected override void WriteCreateInstanceBinder(CreateInstanceBinder createInstanceBinder, IList<Expression> args) => throw new NotImplementedException();
128+
protected override void WriteDeleteIndexBinder(DeleteIndexBinder deleteIndexBinder, IList<Expression> args) => throw new NotImplementedException();
129+
protected override void WriteDeleteMemberBinder(DeleteMemberBinder deleteMemberBinder, IList<Expression> args) => throw new NotImplementedException();
130+
protected override void WriteGetIndexBinder(GetIndexBinder getIndexBinder, IList<Expression> args) => throw new NotImplementedException();
131+
protected override void WriteGetMemberBinder(GetMemberBinder getMemberBinder, IList<Expression> args) => throw new NotImplementedException();
132+
protected override void WriteInvokeBinder(InvokeBinder invokeBinder, IList<Expression> args) => throw new NotImplementedException();
133+
protected override void WriteInvokeMemberBinder(InvokeMemberBinder invokeMemberBinder, IList<Expression> args) => throw new NotImplementedException();
134+
protected override void WriteSetIndexBinder(SetIndexBinder setIndexBinder, IList<Expression> args) => throw new NotImplementedException();
135+
protected override void WriteSetMemberBinder(SetMemberBinder setMemberBinder, IList<Expression> args) => throw new NotImplementedException();
136+
protected override void WriteUnaryOperationBinder(UnaryOperationBinder unaryOperationBinder, IList<Expression> args) => throw new NotImplementedException();
137+
protected override void WriteParameterDeclarationImpl(ParameterExpression prm) => throw new NotImplementedException();
138+
}
139+
}

Shared/Util/Extensions/Expression.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,31 @@ public static bool IsEmpty(this Expression expr) =>
3333

3434
public static bool IsClosedVariable(this Expression expr) =>
3535
expr is MemberExpression mexpr && (mexpr.Expression?.Type.IsClosureClass() ?? false);
36+
37+
public static string Name(this Expression expr, string language = "C#") {
38+
string ret = "";
39+
string staticTypename = "";
40+
switch (expr) {
41+
case ParameterExpression pexpr:
42+
ret = pexpr.Name;
43+
break;
44+
case MemberExpression mexpr:
45+
if (mexpr.Expression == null) {
46+
staticTypename = mexpr.Member.DeclaringType.FriendlyName(language) + ".";
47+
}
48+
ret = staticTypename + mexpr.Member.Name;
49+
break;
50+
case MethodCallExpression callExpr:
51+
if (callExpr.Object == null) {
52+
staticTypename = callExpr.Method.DeclaringType.FriendlyName(language);
53+
}
54+
ret = staticTypename + callExpr.Method.Name;
55+
break;
56+
case LambdaExpression lambdaExpr:
57+
ret = lambdaExpr.Name;
58+
break;
59+
}
60+
return ret;
61+
}
3662
}
3763
}

Shared/Util/Extensions/IEnumerableTuple.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public static class IEnumerableTupleExtensions {
88
public static void AddRangeTo<T1, T2>(this IEnumerable<(T1, T2)> src, IDictionary<T1, T2> dict) => dict.AddRange(src);
99
public static IEnumerable<(T1, T2)> ForEachT<T1,T2>(this IEnumerable<(T1, T2)> src, Action<T1,T2> action) => src.ForEach(x => action(x.Item1, x.Item2));
1010
public static IEnumerable<(T1, T2)> ForEachT<T1, T2>(this IEnumerable<(T1, T2)> src, Action<T1, T2, int> action) => src.ForEach((x, index) => action(x.Item1, x.Item2, index));
11+
public static IEnumerable<(T1, T2, T3)> ForEachT<T1, T2, T3>(this IEnumerable<(T1, T2, T3)> src, Action<T1, T2, T3> action) => src.ForEach(x => action(x.Item1, x.Item2, x.Item3));
1112

1213
public static string Joined<T1, T2>(this IEnumerable<(T1, T2)> src, string delimiter, Func<T1, T2, string> selector) =>
1314
src.Joined(delimiter, x => selector(x.Item1, x.Item2));
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace ExpressionToString.Util {
8+
public static class ValueTupleExtensions {
9+
public static IEnumerable<T> Select<T>(this (T,T,T,T) src) {
10+
yield return src.Item1;
11+
yield return src.Item2;
12+
yield return src.Item3;
13+
yield return src.Item4;
14+
}
15+
16+
public static IEnumerable<T> Where<T>(this (T, T, T, T) src, Func<T, bool> selector) => src.Select().Where(selector);
17+
}
18+
}

0 commit comments

Comments
 (0)