Skip to content

Commit 17bafb6

Browse files
committed
Added visualisation of generated CS code and MSIL code
1 parent 2effd56 commit 17bafb6

File tree

9 files changed

+162
-109
lines changed

9 files changed

+162
-109
lines changed

.gitmodules

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
url = https://github.com/snakex64/Blazor.Diagrams.git
44
[submodule "src/Dis2Msil"]
55
path = src/Dis2Msil
6-
url = https://github.com/rhotav/Dis2Msil
6+
url = https://github.com/snakex64/Dis2Msil

src/NodeDev.Blazor/Components/SourceViewer.razor

+52-47
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,27 @@
66
@using BlazorMonaco.Editor
77
@using BlazorMonaco.Languages
88

9+
<style>
10+
.sourceViewer, .sourceViewer > .mud-tabs-panels {
11+
width: 100%;
12+
height: 100%
13+
}
14+
</style>
915

10-
@if (Method == null || Code == null)
16+
@if (Method == null || CodeCs == null || CodeMsil == null)
1117
{
1218
<MudText Typo="Typo.caption">Open a method to view its generated source code</MudText>
1319
}
1420
else
1521
{
16-
var id = Code.GetHashCode().ToString();
17-
<StandaloneCodeEditor @key="id" Id="@id" ConstructionOptions="EditorConstructionOptions" CssClass="wh100" />
22+
<MudTabs Class="sourceViewer">
23+
<MudTabPanel Text="Debug pseudo-CS" Class="wh100">
24+
<StandaloneCodeEditor @key="CodeCs.GetHashCode().ToString()" ConstructionOptions="@(x => EditorConstructionOptions(x, CodeCs, "csharp"))" CssClass="wh100" />
25+
</MudTabPanel>
26+
<MudTabPanel Text="Debug MSIL" Class="wh100">
27+
<StandaloneCodeEditor @key="CodeMsil.GetHashCode().ToString()" ConstructionOptions="@(x => EditorConstructionOptions(x, CodeMsil, "yaml"))" CssClass="wh100" />
28+
</MudTabPanel>
29+
</MudTabs>
1830
}
1931

2032
@code {
@@ -28,18 +40,23 @@ else
2840

2941
private IDisposable? GraphChangedSubscription { get; set; }
3042

31-
private string? Code { get; set; }
43+
private string? CodeMsil;
44+
private string? CodeCs;
3245

3346
private NodeClassTypeCreator? Creator;
3447

35-
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
48+
private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor, string code, string language)
3649
{
3750
return new()
3851
{
3952
ReadOnly = true,
40-
Value = Code,
41-
Language = "csharp",
42-
AutomaticLayout = true
53+
Value = code,
54+
Language = language,
55+
AutomaticLayout = true,
56+
Minimap = new()
57+
{
58+
Enabled = false
59+
}
4360
};
4461
}
4562

@@ -63,63 +80,51 @@ else
6380
}
6481

6582
PreviousMethod = Method;
66-
Code = null; // we don't want to leave the code from the previous method visible
83+
CodeCs = null; // we don't want to leave the code from the previous method visible
84+
CodeMsil = null;
6785

6886
if (Method != null)
69-
GetCode();
87+
OnGraphChanged(Method.Graph);
7088
}
7189
}
7290

73-
private void GetCode()
91+
private void OnGraphChanged(Graph graph)
7492
{
7593
ArgumentNullException.ThrowIfNull(Method);
7694

77-
if (Creator == null)
78-
OnGraphChanged(Method.Graph);
79-
if (Creator == null) // We were probably unable to compile the project
80-
return;
81-
8295
try
8396
{
84-
Code = Creator.GetBodyAsCsCode(Method);
97+
Creator = Method.Graph.Project.CreateNodeClassTypeCreator(new(Core.Nodes.BuildExpressionOptions.Release, true));
98+
Creator.CreateProjectClassesAndAssembly();
8599

86-
Code = $"// Pseudo code for debugging.{System.Environment.NewLine}// This is not the actual code executed, NodeDev generates IL directly!{System.Environment.NewLine}{Code}";
87-
88-
StateHasChanged();
89-
}
90-
catch (BuildError buildError)
91-
{
92-
Code = $"/* Error during code generation of node {buildError.Node.Name}: {System.Environment.NewLine}{buildError.Message}{System.Environment.NewLine}";
100+
try
101+
{
102+
Creator.GetBodyAsCsAndMsilCode(Method, out CodeCs, out CodeMsil);
93103

94-
if(buildError.InnerException != null)
95-
Code += $"{System.Environment.NewLine}Inner exception:{System.Environment.NewLine}{buildError.InnerException}";
104+
CodeCs = $"// Pseudo code for debugging.{System.Environment.NewLine}// This is not the actual code executed, we execute IL directly!{System.Environment.NewLine}{CodeCs}";
96105

97-
Code += $"{System.Environment.NewLine}*/";
98-
}
99-
catch(Exception ex)
100-
{
101-
Code = $"/* Error during code generation: {System.Environment.NewLine}{ex}{System.Environment.NewLine}*/";
102-
}
103-
}
106+
StateHasChanged();
107+
}
108+
catch (BuildError buildError)
109+
{
110+
CodeCs = $"/* Error during code generation of node {buildError.Node.Name}: {System.Environment.NewLine}{buildError.Message}{System.Environment.NewLine}";
111+
CodeMsil = "";
104112

105-
private void OnGraphChanged(Graph graph)
106-
{
107-
ArgumentNullException.ThrowIfNull(Method);
113+
if (buildError.InnerException != null)
114+
CodeCs += $"{System.Environment.NewLine}Inner exception:{System.Environment.NewLine}{buildError.InnerException}";
108115

109-
if (Creator == null)
110-
{
111-
try
112-
{
113-
Creator = Method.Graph.Project.CreateNodeClassTypeCreator(new(Core.Nodes.BuildExpressionOptions.Release, true));
114-
Creator.CreateProjectClassesAndAssembly();
116+
CodeCs += $"{System.Environment.NewLine}*/";
115117
}
116-
catch (Exception)
118+
catch (Exception ex)
117119
{
118-
Creator = null;
119-
return;
120+
CodeCs = $"/* Error during code generation: {System.Environment.NewLine}{ex}{System.Environment.NewLine}*/";
121+
CodeMsil = "";
120122
}
121123
}
122-
123-
GetCode();
124+
catch (Exception)
125+
{
126+
Creator = null;
127+
return;
128+
}
124129
}
125130
}

src/NodeDev.Blazor/Index.razor

+59-47
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<MudDialogProvider />
55
<MudSnackbarProvider />
66

7-
<MudLayout Style="height: 100%; width: 100%">
7+
<MudLayout Style="height: 100vh; width: 100%; overflow: hidden">
88
<MudAppBar Elevation="1">
99
<MudButton OnClick="Save" Class="ml-3">Save</MudButton>
1010
<MudButton OnClick="Add" Class="ml-3">Add node</MudButton>
@@ -13,63 +13,60 @@
1313
<MudSpacer />
1414
<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
1515
</MudAppBar>
16-
<MudMainContent Style="width: 100%; height: 100%">
16+
<MudMainContent Style="width: 100%; height: 100%; overflow-y: hidden">
1717

18-
<MudExtensions.MudSplitter EnableSlide="true" Sensitivity="0.01" @bind-Dimension="GraphPercentage" OnDoubleClicked="@(_ => GraphPercentage = 100)" Class="h100 overflow-hidden" Style="overflow-y: hidden">
18+
<MudExtensions.MudSplitter EnableSlide="true" Sensitivity="0.01" @bind-Dimension="ProjectExplorerGraphPercentage" Class="wh100 overflow-hidden">
1919
<StartContent>
20-
<MudStack Row="true" Class="wh100 pa-1 relative">
21-
22-
23-
<MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 h100" KeepPanelsAlive="true" Style="width: 25%; height: 100%">
24-
<MudTabPanel Text="Project">
25-
<ProjectExplorer Project="Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer>
26-
</MudTabPanel>
27-
<MudTabPanel Text="Class">
28-
@if (SelectedClass != null)
29-
{
30-
<ClassExplorer @key="SelectedClass" Class="SelectedClass" SelectedMethodChanged="OpenMethod"></ClassExplorer>
31-
}
32-
</MudTabPanel>
33-
</MudTabs>
34-
35-
@* The 'relative' div is used to control the "Open/Close" icon for the debugger console panel as well as the source viewer open icon *@
36-
<div class="w100 relative flex-1">
20+
<MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 wh100" KeepPanelsAlive="true" Class="wh100">
21+
<MudTabPanel Text="Project">
22+
<ProjectExplorer Project="Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer>
23+
</MudTabPanel>
24+
<MudTabPanel Text="Class">
25+
@if (SelectedClass != null)
26+
{
27+
<ClassExplorer @key="SelectedClass" Class="SelectedClass" SelectedMethodChanged="OpenMethod"></ClassExplorer>
28+
}
29+
</MudTabPanel>
30+
</MudTabs>
31+
</StartContent>
3732

38-
<MudStack Row="false" Class="wh100">
33+
<EndContent>
34+
<MudExtensions.MudSplitter Class="wh100 pa-1 relative overflow-y-hidden" @bind-Dimension="SourceViewerGraphPercentage" OnDoubleClicked="@(_ => SourceViewerGraphPercentage = 100)">
35+
<StartContent>
36+
@* The 'relative' div is used to control the "Open/Close" icon for the debugger console panel as well as the source viewer open icon *@
37+
<div class="wh100 relative flex-1 overflow-y-hidden">
3938

40-
<DebuggedPathView OpenMethod="OpenMethodFromDebuggedPath" />
39+
<MudStack Row="false" Class="wh100">
4140

42-
<CascadingValue Value="this" IsFixed="true">
43-
<MudTabs @ref="Tabs" Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 wh100" KeepPanelsAlive="true" Class="wh100" @bind-ActivePanelIndex="ActivePanelIndex">
44-
@foreach (var method in OpenedMethods)
45-
{
46-
<MudTabPanel ID="method" @key="method" Text="@method.Name">
47-
<GraphCanvas @key="method.Graph" Graph="method.Graph"></GraphCanvas>
48-
</MudTabPanel>
49-
}
50-
</MudTabs>
51-
</CascadingValue>
41+
<DebuggedPathView OpenMethod="OpenMethodFromDebuggedPath" />
5242

53-
<DebuggerConsolePanel Project="Project" />
43+
<CascadingValue Value="this" IsFixed="true">
44+
<MudTabs @ref="Tabs" Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 wh100" KeepPanelsAlive="true" Class="wh100" @bind-ActivePanelIndex="ActivePanelIndex">
45+
@foreach (var method in OpenedMethods)
46+
{
47+
<MudTabPanel ID="method" @key="method" Text="@method.Name">
48+
<GraphCanvas @key="method.Graph" Graph="method.Graph"></GraphCanvas>
49+
</MudTabPanel>
50+
}
51+
</MudTabs>
52+
</CascadingValue>
5453

55-
</MudStack>
54+
<DebuggerConsolePanel Project="Project" />
5655

57-
</div>
56+
</MudStack>
5857

59-
<div class="h100 absolute d-flex" style="right: 0px">
60-
@{
61-
var close = GraphPercentage <= 75;
62-
var icon = close ? Icons.Material.Filled.ArrowRight : Icons.Material.Filled.ArrowLeft;
63-
}
64-
<MudIconButton Icon="@icon" OnClick="@(() => GraphPercentage = close ? 100 : 75)" Style="margin-top: auto; margin-bottom: auto" />
65-
</div>
58+
</div>
6659

67-
</MudStack>
60+
<div class="h100 absolute d-flex" style="right: 0px">
61+
<MudIconButton Icon="@(SourceViewerGraphPercentage <= 75 ? Icons.Material.Filled.ArrowRight : Icons.Material.Filled.ArrowLeft)" OnClick="SwitchSourceViewerGraph" Style="margin-top: auto; margin-bottom: auto" />
62+
</div>
6863

69-
</StartContent>
64+
</StartContent>
7065

71-
<EndContent>
72-
<SourceViewer Method="@(OpenedMethods.Count > 0 ? OpenedMethods[ActivePanelIndex] : null)" IsVisible="@(GraphPercentage != 100)" />
66+
<EndContent>
67+
<SourceViewer Method="@(OpenedMethods.Count > 0 ? OpenedMethods[ActivePanelIndex] : null)" IsVisible="@(SourceViewerGraphPercentage != 100)" />
68+
</EndContent>
69+
</MudExtensions.MudSplitter>
7370
</EndContent>
7471

7572
</MudExtensions.MudSplitter>
@@ -88,7 +85,9 @@
8885

8986
private int ActivePanelIndex = 0;
9087

91-
private double GraphPercentage = 100;
88+
private double SourceViewerGraphPercentage = 100;
89+
private double SourceOpenedGraphPercentage = 75;
90+
private double ProjectExplorerGraphPercentage = 18;
9291

9392
protected override void OnInitialized()
9493
{
@@ -100,6 +99,19 @@
10099
DebuggedPathService.ChangeProject(Project);
101100
}
102101

102+
private void SwitchSourceViewerGraph()
103+
{
104+
if (SourceViewerGraphPercentage <= 75)
105+
{
106+
SourceOpenedGraphPercentage = SourceViewerGraphPercentage;
107+
SourceViewerGraphPercentage = 100;
108+
}
109+
else
110+
{
111+
SourceViewerGraphPercentage = SourceOpenedGraphPercentage;
112+
}
113+
}
114+
103115
public void OpenMethodFromDebuggedPath(Core.Class.NodeClassMethod? method)
104116
{
105117
if (method == null)

src/NodeDev.Core/Class/NodeClassTypeCreator.cs

+27-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
using System.Reflection;
33
using NodeDev.Core.Types;
44
using FastExpressionCompiler;
5-
using NodeDev.Core.Nodes;
6-
using System.Linq.Expressions;
5+
using Dis2Msil;
76

87
namespace NodeDev.Core.Class;
98

@@ -98,17 +97,39 @@ public void CreateProjectClassesAndAssembly()
9897
generatedType.HiddenType.CreateType();
9998
}
10099
}
101-
102100
}
103101

104-
public string GetBodyAsCsCode(NodeClassMethod method)
102+
public void GetBodyAsCsAndMsilCode(NodeClassMethod method, out string cs, out string msil)
105103
{
106104
if (!IsPreBuilt)
107105
throw new Exception($"Unable to get method's body as CS code. Assembly must be built before");
108106

107+
var generated = GeneratedTypes[method.DeclaringType];
108+
var builder = generated.Methods[method];
109+
109110
var expression = method.Graph.BuildExpression(Options.BuildExpressionOptions);
110111

111-
return expression.ToCSharpString();
112+
var generator = builder.GetILGenerator();
113+
expression.CompileFastToIL(generator, CompilerFlags.ThrowOnNotSupportedExpression);
114+
115+
cs = expression.ToCSharpString();
116+
117+
dynamic type = generated.HiddenType.CreateType();
118+
for(int i = 0; i < type.DeclaredMethods.Length; ++i)
119+
{
120+
if (type.DeclaredMethods[i].Name == method.Name)
121+
{
122+
var methodInfo = (MethodInfo)type.DeclaredMethods[i];
123+
var bytes = methodInfo.GetMethodBody()!.GetILAsByteArray();
124+
var reader = new MethodBodyReader(builder.Module, bytes!);
125+
var code = reader.GetBodyCode();
126+
127+
msil = code;
128+
return;
129+
}
130+
}
131+
132+
msil = "method not found";
112133
}
113134

114135
private void GenerateHiddenMethodBody(NodeClassMethod method, MethodBuilder methodBuilder)
@@ -137,7 +158,7 @@ private static void GenerateRealMethodAndEmptyHiddenMethod(NodeClassMethod metho
137158
hiddenParameterTypes = hiddenParameterTypes.Prepend(generatedType.Type).ToArray();
138159

139160
// create the hidden method
140-
var hiddenMethod = generatedType.HiddenType.DefineMethod(method.Name, MethodAttributes.Assembly | MethodAttributes.Static, CallingConventions.Standard, returnType, hiddenParameterTypes);
161+
var hiddenMethod = generatedType.HiddenType.DefineMethod(method.Name, MethodAttributes.Static, CallingConventions.Standard, returnType, hiddenParameterTypes);
141162
generatedType.Methods[method] = hiddenMethod;
142163

143164
// Create the real method that calls the hidden method

src/NodeDev.Core/Graph.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,7 @@ public LambdaExpression BuildExpression(BuildExpressionOptions options)
280280
.Distinct() // lots of inputs use the same variable as another node's output, make sure we only declare them once
281281
.Except(info.MethodParametersExpression.Values); // Remove the method parameters as they are declared later and not here
282282

283-
IEnumerable<Expression> allExpressions = expressions;
284-
if (SelfMethod.HasReturnValue)
285-
allExpressions = allExpressions.Append(Expression.Label(returnLabelTarget, Expression.Default(returnLabelTarget.Type)));
286-
287-
var expressionBlock = Expression.Block(localVariables, allExpressions);
283+
var expressionBlock = Expression.Block(localVariables, expressions.Append(Expression.Label(returnLabelTarget, Expression.Default(returnLabelTarget.Type))));
288284

289285
var parameters = SelfMethod.IsStatic ? info.MethodParametersExpression.Values : info.MethodParametersExpression.Values.Prepend(info.ThisExpression!);
290286
var lambdaExpression = Expression.Lambda(expressionBlock, parameters);

src/NodeDev.Core/NodeDev.Core.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@
1111
<PackageReference Include="System.Reactive" Version="6.0.1" />
1212
</ItemGroup>
1313

14+
<ItemGroup>
15+
<ProjectReference Include="..\Dis2Msil\Dis2Msil\Dis2Msil.csproj" />
16+
</ItemGroup>
17+
1418
</Project>

0 commit comments

Comments
 (0)