zspitz / expressiontreevisualizer Goto Github PK
View Code? Open in Web Editor NEWDebugging visualizer for expression trees
License: MIT License
Debugging visualizer for expression trees
License: MIT License
When .NET Standard includes the ITuple interface, we should use that.
ITuple also has the benefit of supporting more than 8 elements, which is currently not supported.
(Continuation of #36.)
Type | Extension method exists |
---|---|
Expression | Y |
MemberBinding | Y |
CatchBlock | Y |
ElementInit | Y |
IArgumentProvider | |
IDynamicExpression | |
LabelTarget | |
SwitchCase | Y |
This could be useful as an interactive display of expressions.
Have a text box with an expression, parse with Roslyn, and display using the control.
For example:
Constant(new [] { 1, 2, 3 })
should render as:
new [] { 1, 2, 3 }
instead of:
#Int32[]
Even arrays of unrenderable values could be rendered:
new [] { #Random, #Random, #Random }
If all the elements have the same type as the array use implicit array type. Otherwise, use the explicit array type:
new object [] { #Random, #Random, #Random }
e.g. ConstantExpression whose value is an Expression, or MemberAccessExpression.
The value of the expression can be extracted using ExtractValue.
For ConstantExpression, we could add a node for the Value property. Other expressions would need a virtual (Value)
node.
We'd have to make sure that there are no ParameterExpression
s within the expression we're evaluating.
Provide documentation in tooltip for:
Source of truth of current selection is the tree -- selection of the tree node selects the text in the source code, and (when appropriate) selects the relevant end node.
Selecting either the source code or the end node will reflect back in the tree.
Only when ConstantExpression
Currently, value tuple cannot be written in an expression
Copy icon on hover for every node
path to selected node when pasted in Watch window / immediate window
child expressions need to be strongly typed to the appropriate expression type
(((expr as LambdaExpression).Body as BinaryExpression).Left as UnaryExpression).Operand
Will require #2
For now:
Within mapper:
maintain a list of tuples - closure id, name, type
in visualizer:
display an expander for each closure id; each expander will have a list of names and types
enable copying expression to extract current value in Watch window (wrap in LambdaExpression and invoke)
Any exceptions that only affect the source code generation will show up in the source code as a new line with some details about the exception.
----- NotImplementedException - NodeType: AddAssign, Expression object type: SimpleBinaryExpression ()
But if there is an exception while generating the VisualizerData
, then we should display instead of the regular UI, a special exception UI, with 1) the DebugView of the expression, 2) the ToString using the factory methods formatter (#38) and 3) a link to create an issue on GitHub with these details filled in.
Currently using https://unclassified.software/en/source/multiselecttreeview, and I also looked at https://github.com/cmyksvoll/MultiSelectTreeView
Required features:
Nice to have:
Two use cases:
Allow the user to click on a URL from the visualizer UI, and autofill an issue.
If the method is marked with ExtensionAttribute, then render as a method call on the first argument.
Unless Roslyn does this automatically?
Two uses:
The recommended way to install a custom visualizer is simply to copy the DLL into the appropriate Visualizers folder. This can be either the Visual Studio install path, or within My Documents.
It's possible to use a VSIX for this purpose -- enclose the visualizer DLL in the VSIX package; and each time the application loads, check if the visualizer has been installed from the package; and if not, copy the file. This is not a really good solution, because every visualizer developer has to reimplement it for their VSIX.
I think a better solution would be to create an extension to manage visualizers. On load, it would go over each VSIX package and find visualizers (indicate the visualizer in the package somehow? in the package metadata? Checking all the DLLs for a reference to Microsoft.Debugger.Visualizer.DLL doesn't make sense). Then it would check the target folder (whether this particular visualizer should prefer VS-level installation if possible, or per-user installation might be part of the metadata of the package), and if the visualizer isn't in the target folder, or an older version is in the target folder, it would copy over the visualizer.
A natural part of this extension could be a way to manage visualizers:
The information could be extracted from the DLLs, or from .natvis
files.
Then, individual visualizer authors could include the visualizer in a VSIX with appropriate metadata, list the above extension as a dependency, and everything else would happen automatically.
Type | C# | VB |
---|---|---|
BinaryExpression | Y | Y |
BlockExpression | Y | Y |
ConditionalExpression - ternary | Y | Y |
ConditionalExpression - block | Y | Y |
ConstantExpression - literal | Y | Y |
ConstantExpression - non-literal | Y | Y |
DebugInfoExpression | ||
DefaultExpression | Y | Y |
DynamicExpression | ||
GotoExpression | ||
IndexExpression | Y | Y |
InvocationExpression | Y | Y |
LabelExpression | ||
LambdaExpression | Y | Y |
ListInitExpression | Y | Y |
LoopExpression | ||
MemberExpression - instance members | Y | Y |
MemberExpression - static members | Y | Y |
MemberExpression - closed variables | Y | Y |
MemberInitExpression | Y | Y |
MethodCallExpression - instance methods | Y | Y |
MethodCallExpression - static methods | Y | Y |
MethodCallExpression - extension methods | Y | Y |
MethodCallExpression - String.Concat | Y | Y |
NewArrayExpression - NewArrayInit | Y | Y |
NewArrayExpression - NewArrayBounds | Y | Y |
NewExpression | Y | Y |
NewExpression - anonymous types | Y | Y |
ParameterExpression | Y | Y |
RuntimeVariablesExpression | ||
SwitchExpression | Y | Y |
TryExpression | ||
TypeBinaryExpression | Y | Y |
UnaryExpression | Y | Y |
Type | C# | VB |
---|---|---|
MemberAssignment | Y | Y |
MemberListBinding | Y | Y |
MemberMemberBinding | Y | Y |
Type | Base type | C# | VB |
---|---|---|---|
CatchBlock | Object | ||
ElementInit | Object | Y | Y |
IArgumentProvider | |||
IDynamicExpression | |||
LabelTarget | Object | ||
SwitchCase | Object | Y | Y |
Making the visualizer available as a VSIX package is problematic; see #44.
For example, the Solution Explorer is a multiselect treeview, which could be used instead of a third party component.
It adds nothing for the user, only for development.
This involves three classes: an abstract
base class with methods for writing to a StringBuilder
based on a given expression or other part (such as MemberBinding
or ElementInit
), and language-specific implementations for C# and VB.NET,
Object.ReferenceEquals
returns true
when comparing both ParameterExpression
objects that refer to the same parameter.
We were originally using an Dictionary<object, (int start, int length)>
to hold the span corresponding to the Expression
or other part of the expression tree. ParameterExpression
instances will only exist in the dictionary once, even though they may be used multiple times in the expression tree; the ParameterExpression
would return the same (start, length)
anywhere it was used in the expression tree.
The current workaround is to using a Dictionary<object, List<(int start, int length)>
; only for ParameterExpression
is there more than one item in the list. And we're figuring out which (start, length)
is the right one, by passing in the parent (start, length)
and finding the (start, length)
that fall's within the parent's (start, length)
.
But this falls down if the same ParameterExpression
is set as two of the direct children of the node.
A solution to this would be to define the order of properties traversed in the formatter for a given node, and to follow that order when generating the visualizer data; thus we could know "this is the first instance of ParameterExpression
as a child of the current node; this is the second` and match them up to the appropriate properties. (I prefer this because it means we're using a simpler data structure.)
Some other possible solutions:
Body.Call.Argument1
. This assumes the formatter is walking the expression tree just like the visualizer data generation is; probably a valid assumption, but we'd have to verify it.Dictionary<string, (start, length)>
, and instead of passing the parent (start, length)
, we could pass in the parent's property which is the path to the current node -- Dictionary<string, (start, length)>
.Shows the calls to the factory methods needed to construct the expression
Variant for C# and for VB.NET
Assumes using
/Imports
System.Linq.Expressions.Expression
() => (((null).i) * ((null).j)) <= (25)
Func'1
Integer
or int
instead of Int32
ExpressionNodeData
instances by EndNodeData
; this is what the grids should displayThis will require multi-selection on treeview.
For some types, display the value, as = 5
Examples: string, int, boolean etc.
ExpressionNodeData should have a property -- string ConstantValue
When opening a certain expression in the visualizer, it returns a System.Exception
.
Before calling FillPlaceholders
, the expression looks like this:
After, the left of the OrElse
looks like this:
and the right looks like this:
This is the .ToString
of the complete expression:
(LikeString(Param_0.Email, value(Machshevet.Core.Filtering+_Closure$__1-3).$VB$Local_phrase1, Text) OrElse LikeString(Param_0.Email, value(Machshevet.Core.Filtering+_Closure$__1-3).$VB$Local_kbtext1, Text))
and the DebugView
:
.Call Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString(
$var1.Email,
.Constant<Machshevet.Core.Filtering+_Closure$__1-3>(Machshevet.Core.Filtering+_Closure$__1-3).$VB$Local_phrase1,
.Constant<Microsoft.VisualBasic.CompareMethod>(Text)) || .Call Microsoft.VisualBasic.CompilerServices.LikeOperator.LikeString(
$var1.Email,
.Constant<Machshevet.Core.Filtering+_Closure$__1-3>(Machshevet.Core.Filtering+_Closure$__1-3).$VB$Local_kbtext1,
.Constant<Microsoft.VisualBasic.CompareMethod>(Text))
I saw a compiler generated expression where the closure class instance was itself an instance of another closure class:
LambdaExpression
- Body: MemberAccessExpression
- Expression: MemberAccessExpression - <instance of closure class>
- Constant: <instance of second closure class>
The code formatters have been fixed to treat members of an expression whose type is a closure classes as simple variables, even when the instance of said member is itself only the member of another closure class.
Nevertheless, I am unable to reproduce in tests, so I am leaving this open for now.
We probably have to keep a Dictionary<ParameterExpression, string>
and if it doesn't exist, add to the dictionary, together with an autogenerated name -- $var0
, $var1
etc.
See #27
Presumably if #2 can be done, so can this.
When creating an expression with an interpolated string:
int i = 5;
Expression<Func<string>> expr = () => $"{i}";
the compiler generates the expression using a call to String.Format
and a composite format string; in other words, like the following expression:
expr = () => string.Format("{0}", i);
Therefore, when rendering the expression in C# or VB.NET, it would be better to render such calls (to String.Format
where the first parameter is a ConstantExpression
containing a string) as interpolated strings.
Currently, the visualizer is only registered for Expression
. Allow registering for other types used in expression trees:
Type | Visualizer enabled |
---|---|
Expression | Y |
MemberBinding | Y |
CatchBlock | |
ElementInit | Y |
IArgumentProvider | |
IDynamicExpression | |
LabelTarget | |
SwitchCase |
This will require another attribute call for each type, and that the VisualizerData
constructor properly be able to handle each of these types take an object
instead of a more specific type (until and if C# implements intersection types), taking an optional VisualizerDataOptions
as well.
See also #41
Path from parent:
Node type:
Type:
Name:
String value:
This will enable the visualizer DLL to be independent of any others.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.