Skip to content

Commit 8cb452e

Browse files
committed
Fix Graph resolution bug
1 parent 5524431 commit 8cb452e

File tree

9 files changed

+131
-67
lines changed

9 files changed

+131
-67
lines changed

src/NodeDev.Blazor/Components/SourceViewer.razor

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,13 @@ else
9595
try
9696
{
9797
var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
98-
var assemblyPath = Method.Graph.Project.Build(Core.BuildOptions.Debug with { OutputPath = temp });
98+
//var assemblyPath = Method.Graph.Project.Build(Core.BuildOptions.Debug with { OutputPath = temp });
99+
Method.Graph.Project.BuildAndGetAssembly(Core.BuildOptions.Debug);
99100
Creator = Method.Graph.Project.NodeClassTypeCreator;
100101

101102
try
102103
{
103-
Creator!.GetBodyAsCsAndMsilCode(assemblyPath, Method, out CodeCs, out CodeMsil);
104+
Creator!.GetBodyAsCsAndMsilCode("", Method, out CodeCs, out CodeMsil);
104105

105106
CodeCs = $"// Pseudo code for debugging.{System.Environment.NewLine}// This is not the actual code executed, we execute IL directly!{System.Environment.NewLine}{CodeCs}";
106107

@@ -116,16 +117,11 @@ else
116117

117118
CodeCs += $"{System.Environment.NewLine}*/";
118119
}
119-
catch (Exception ex)
120-
{
121-
CodeCs = $"/* Error during code generation: {System.Environment.NewLine}{ex}{System.Environment.NewLine}*/";
122-
CodeMsil = "";
123-
}
124120
}
125-
catch (Exception)
121+
catch (Exception ex)
126122
{
127-
Creator = null;
128-
return;
123+
CodeCs = $"/* Error during code generation: {System.Environment.NewLine}{ex}{System.Environment.NewLine}*/";
124+
CodeMsil = "";
129125
}
130126
}
131127
}

src/NodeDev.Core/Class/NodeClassTypeCreator.cs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Dis2Msil;
66
using System.Text;
77
using System.Runtime.Loader;
8+
using System.Linq.Expressions;
89

910
namespace NodeDev.Core.Class;
1011

@@ -36,17 +37,18 @@ internal NodeClassTypeCreator(Project project, BuildOptions buildOptions)
3637
public void CreateProjectClassesAndAssembly()
3738
{
3839
// TODO Remove this when the new System.Reflection.Emit is available in .NET 10
39-
if (TemporaryReflectionAssembly == null)
40-
{
41-
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
42-
using var stream = assembly.GetManifestResourceStream("NodeDev.Core.Dependencies.System.Reflection.Emit.dll")!;
43-
var bytes = new byte[stream.Length];
44-
stream.ReadExactly(bytes);
45-
TemporaryReflectionAssembly = System.Reflection.Assembly.Load(bytes);
46-
}
47-
var persisted = Activator.CreateInstance(TemporaryReflectionAssembly.ExportedTypes.First(), new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), typeof(object).Assembly, null)!;
48-
Assembly = (AssemblyBuilder)persisted;
49-
40+
//if (TemporaryReflectionAssembly == null)
41+
//{
42+
// var assembly = System.Reflection.Assembly.GetExecutingAssembly();
43+
// using var stream = assembly.GetManifestResourceStream("NodeDev.Core.Dependencies.System.Reflection.Emit.dll")!;
44+
// var bytes = new byte[stream.Length];
45+
// stream.ReadExactly(bytes);
46+
// TemporaryReflectionAssembly = System.Reflection.Assembly.Load(bytes);
47+
//}
48+
//var persisted = Activator.CreateInstance(TemporaryReflectionAssembly.ExportedTypes.First(), new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), typeof(object).Assembly, null)!;
49+
//Assembly = (AssemblyBuilder)persisted;
50+
51+
Assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), AssemblyBuilderAccess.RunAndCollect);
5052

5153
// https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder?view=net-7.0
5254
//var persisted = new PersistedAssemblyBuilder(new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), typeof(object).Assembly);
@@ -148,7 +150,8 @@ public void GetBodyAsCsAndMsilCode(string assemblyPath, NodeClassMethod method,
148150

149151
cs = str.ToString();
150152

151-
var assembly = context.LoadFromAssemblyPath(assemblyPath);
153+
//var assembly = context.LoadFromAssemblyPath(assemblyPath);
154+
var assembly = Assembly;
152155
var type = assembly.GetType(HiddenName(method.Class.Name));
153156
if(type == null)
154157
{
@@ -180,6 +183,24 @@ private void GenerateHiddenMethodBody(NodeClassMethod method, MethodBuilder meth
180183
// Generate the expression tree for the method
181184
var expression = method.Graph.BuildExpression(Options.BuildExpressionOptions);
182185

186+
187+
var returnLabelTarget = Expression.Label(typeof(int), "ReturnLabel");
188+
189+
var tryBody = Expression.Call(typeof(Console).GetMethod("Clear")!);
190+
var catchBody = Expression.Return(returnLabelTarget, Expression.Constant(10));
191+
192+
var tryCatch = Expression.TryCatch(tryBody, Expression.Catch(typeof(Exception), catchBody));
193+
194+
var afterCatch = Expression.Block(
195+
Expression.Call(typeof(Console).GetMethod("Clear")!),
196+
Expression.Return(returnLabelTarget, Expression.Constant(5)),
197+
Expression.Label(returnLabelTarget, Expression.Constant(0)));
198+
199+
var body = Expression.Block(tryCatch, afterCatch);
200+
201+
expression = Expression.Lambda(body);
202+
203+
var v = expression.CompileFast();
183204
var ilGenerator = methodBuilder.GetILGenerator();
184205
var result = expression.CompileFastToIL(ilGenerator, CompilerFlags.ThrowOnNotSupportedExpression);
185206

src/NodeDev.Core/Graph.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ internal record class NodePathChunks(Connection OutputStartPoint, List<NodePathC
7373
{
7474
internal bool ContainOutput(Connection output)
7575
{
76+
if (OutputStartPoint == output)
77+
return true;
78+
7679
foreach (var part in Chunks)
7780
{
7881
if (part.ContainOutput(output))
@@ -107,8 +110,33 @@ internal NodePathChunks GetChunks(Connection execOutput, bool allowDeadEnd)
107110
{
108111
if (currentInput.Parent.Outputs.Count(x => x.Type.IsExec) <= 1) // we can keep adding to the straight path. It's either a dead end or the path keeps going
109112
{
110-
// find the next output node to follow
111-
var output = currentInput.Parent.Outputs.FirstOrDefault(x => x.Type.IsExec && x != execOutput);
113+
if (currentInput.Connections.Count != 1)
114+
{
115+
// We reached a merging point. We need to validate if this merging point was already validated by the last chunk. It is possible that we are in a junction like so :
116+
// if(...)
117+
// {
118+
// if(...)
119+
// {
120+
// ...
121+
// }
122+
// else
123+
// {
124+
// ...
125+
// }
126+
// }
127+
// else
128+
// {
129+
// ...
130+
// }
131+
// ... <- we are here
132+
// If we are indeed in this scenario, everything from the first "if" should be in the "chunks"
133+
// We can check any merge point of the last chunk. They should all either be the same of null
134+
if(chunks.LastOrDefault()?.SubChunk?.Values?.FirstOrDefault(x => x.InputMergePoint != null)?.InputMergePoint != currentInput)
135+
return new NodePathChunks(execOutput, chunks, currentInput, null); // we reached a merging point
136+
}
137+
138+
// find the next output node to follow
139+
var output = currentInput.Parent.Outputs.FirstOrDefault(x => x.Type.IsExec && x != execOutput);
112140
var nextInput = output?.Connections.FirstOrDefault(); // get the next input, there's either 1 or none
113141

114142
if (nextInput == null)
@@ -280,7 +308,7 @@ public LambdaExpression BuildExpression(BuildExpressionOptions options)
280308
.Distinct() // lots of inputs use the same variable as another node's output, make sure we only declare them once
281309
.Except(info.MethodParametersExpression.Values); // Remove the method parameters as they are declared later and not here
282310

283-
var expressionBlock = Expression.Block(localVariables, expressions.Append(Expression.Label(returnLabelTarget, Expression.Default(returnLabelTarget.Type))));
311+
var expressionBlock = Expression.Block(localVariables, expressions.Append(returnLabel));
284312

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

src/NodeDev.Core/NodeDev.Core.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="FastExpressionCompiler" Version="4.2.1" />
12-
<PackageReference Include="NuGet.Versioning" Version="6.11.0" />
11+
<PackageReference Include="FastExpressionCompiler" Version="4.2.2" />
12+
<PackageReference Include="NuGet.Versioning" Version="6.11.1" />
1313
<PackageReference Include="System.Reactive" Version="6.0.1" />
1414
</ItemGroup>
1515

src/NodeDev.Core/Nodes/Flow/Branch.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePa
3535
var ifTrue = Graph.BuildExpression(subChunks[Outputs[0]], info);
3636
var ifFalse = Graph.BuildExpression(subChunks[Outputs[1]], info);
3737

38-
var ifThenElse = Expression.IfThenElse(info.LocalVariables[Inputs[1]], Expression.Block(ifTrue), Expression.Block(ifFalse));
39-
40-
return ifThenElse;
38+
if(ifTrue.Length == 0 && ifFalse.Length == 0)
39+
throw new InvalidOperationException("Branch node must have at least a 'IfTrue' of 'IfFalse' statement.");
40+
else if(ifTrue.Length == 0) // NOT the operation, instead of "if(condition){} else { ...}" we'll have if(!condition) { ... }
41+
return Expression.IfThen(Expression.Not(info.LocalVariables[Inputs[1]]), Expression.Block(ifFalse));
42+
else if(ifFalse.Length == 0)
43+
return Expression.IfThen(info.LocalVariables[Inputs[1]], Expression.Block(ifTrue));
44+
else
45+
return Expression.IfThenElse(info.LocalVariables[Inputs[1]], Expression.Block(ifTrue), Expression.Block(ifFalse));
4146
}
4247
}

src/NodeDev.Core/Nodes/Flow/ReturnNode.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public ReturnNode(Graph graph, string? id = null) : base(graph, id)
1616
Name = "Return";
1717

1818
Inputs.Add(new("Exec", this, TypeFactory.ExecType));
19+
20+
Refresh();
1921
}
2022

2123
private bool HasReturnValue => Graph.SelfMethod.ReturnType != TypeFactory.Void;

src/NodeDev.Core/Nodes/Flow/TryCatchNode.cs

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,50 @@ namespace NodeDev.Core.Nodes.Flow;
66

77
public class TryCatchNode : FlowNode
88
{
9-
public override bool IsFlowNode => true;
9+
public override bool IsFlowNode => true;
1010

11-
public TryCatchNode(Graph graph, string? id = null) : base(graph, id)
12-
{
13-
Name = "TryCatch";
11+
public TryCatchNode(Graph graph, string? id = null) : base(graph, id)
12+
{
13+
Name = "TryCatch";
1414

15-
Inputs.Add(new("Exec", this, TypeFactory.ExecType));
15+
Inputs.Add(new("Exec", this, TypeFactory.ExecType));
1616

17-
Outputs.Add(new("Try", this, TypeFactory.ExecType));
18-
Outputs.Add(new("Catch", this, TypeFactory.ExecType));
19-
Outputs.Add(new("Finally", this, TypeFactory.ExecType));
20-
Outputs.Add(new("Exception", this, TypeFactory.Get<Exception>(), linkedExec: Outputs[1]));
21-
}
17+
Outputs.Add(new("Try", this, TypeFactory.ExecType));
18+
Outputs.Add(new("Catch", this, TypeFactory.ExecType));
19+
Outputs.Add(new("Finally", this, TypeFactory.ExecType));
20+
Outputs.Add(new("Exception", this, new UndefinedGenericType("T"), linkedExec: Outputs[1]));
21+
}
2222

23-
public override string GetExecOutputPathId(string pathId, Connection execOutput)
24-
{
25-
return pathId + "-" + execOutput.Id;
26-
}
23+
public override string GetExecOutputPathId(string pathId, Connection execOutput)
24+
{
25+
return pathId + "-" + execOutput.Id;
26+
}
2727

28-
public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false;
28+
// We're allowed to have 'nothing' in the blocks, ex an empty "finally" or an empty "catch"
29+
public override bool DoesOutputPathAllowDeadEnd(Connection execOutput)
30+
{
31+
return execOutput.Connections.Count == 0;
32+
}
2933

30-
public override bool DoesOutputPathAllowMerge(Connection execOutput) => true;
34+
/// <summary>
35+
/// We allow merging back together at the end. Technically all paths could continue, such as :
36+
/// Try { } catch( Exception ex) {} finally { } ...
37+
/// </summary>
38+
public override bool DoesOutputPathAllowMerge(Connection execOutput) => true;
3139

32-
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
33-
{
34-
ArgumentNullException.ThrowIfNull(subChunks);
40+
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
41+
{
42+
ArgumentNullException.ThrowIfNull(subChunks);
3543

36-
var tryBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[0]], info));
37-
var catchBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[1]], info));
38-
var finallyBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[2]], info));
44+
var tryBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[0]], info));
45+
var catchBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[1]], info));
46+
var finallyBlock = Expression.Block(Graph.BuildExpression(subChunks[Outputs[2]], info));
3947

40-
var exceptionVariable = Expression.Variable(typeof(Exception), "ex");
41-
info.LocalVariables[Outputs[3]] = exceptionVariable; // Make sure other pieces of code use the right variable for that exception
48+
//var exceptionVariable = Expression.Variable(Outputs[3].Type.MakeRealType(), "ex");
49+
//info.LocalVariables[Outputs[3]] = exceptionVariable; // Make sure other pieces of code use the right variable for that exception
4250

43-
var catchClause = Expression.Catch(exceptionVariable, catchBlock);
51+
var catchClause = Expression.Catch(Outputs[3].Type.MakeRealType(), catchBlock);
4452

45-
return Expression.TryCatchFinally(tryBlock, finallyBlock, catchClause);
46-
}
53+
return Expression.Block(Expression.TryCatchFinally(tryBlock, Outputs[2].Connections.Count == 0 ? null : finallyBlock, catchClause));
54+
}
4755
}

src/NodeDev.Core/Project.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ public static Project CreateNewDefaultProject(out NodeClassMethod main)
8181

8282
var programClass = new NodeClass("Program", "NewProject", project);
8383

84-
main = new NodeClassMethod(programClass, "Main", project.TypeFactory.Get(typeof(void), null), new Graph());
85-
main.IsStatic = true;
84+
main = new NodeClassMethod(programClass, "Main", project.TypeFactory.Get<int>(), new Graph(), true);
8685

8786
var entry = new EntryNode(main.Graph);
8887
var returnNode = new ReturnNode(main.Graph);
88+
returnNode.Inputs[1].UpdateTextboxText("0");
8989
main.Graph.AddNode(entry, false);
9090
main.Graph.AddNode(returnNode, false);
9191
programClass.Methods.Add(main);
@@ -165,7 +165,7 @@ private static string GetNetCoreVersion()
165165
return "";
166166
}
167167

168-
private AssemblyBuilder BuildAndGetAssembly(BuildOptions buildOptions)
168+
public AssemblyBuilder BuildAndGetAssembly(BuildOptions buildOptions)
169169
{
170170
CreateNodeClassTypeCreator(buildOptions);
171171

src/NodeDev.Tests/GraphExecutorTests.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,40 +305,44 @@ public void TestTryCatchNode(SerializableBuildOptions options)
305305
project.Classes.Add(nodeClass);
306306

307307
var graph = new Graph();
308-
var method = new NodeClassMethod(nodeClass, "Main", nodeClass.TypeFactory.Get<int>(), graph);
308+
var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get<int>(), graph);
309309
method.IsStatic = true;
310310
nodeClass.Methods.Add(method);
311311
graph.SelfMethod = nodeClass.Methods.First();
312312

313-
var entryNode = new Core.Nodes.Flow.EntryNode(graph);
314-
var tryCatchNode = new Core.Nodes.Flow.TryCatchNode(graph);
313+
var entryNode = new EntryNode(graph);
314+
var tryCatchNode = new TryCatchNode(graph);
315+
tryCatchNode.Outputs[3].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<Exception>(), overrideInitialType: true);
315316

316317
graph.AddNode(entryNode, false);
317318
graph.AddNode(tryCatchNode, false);
318319

319320
graph.Connect(entryNode.Outputs[0], tryCatchNode.Inputs[0], false);
320321

321-
var catchReturnNode = new Core.Nodes.Flow.ReturnNode(graph);
322+
var catchReturnNode = new ReturnNode(graph);
322323
catchReturnNode.Inputs.Add(new("Result", catchReturnNode, nodeClass.TypeFactory.Get<int>()));
323324
catchReturnNode.Inputs[1].UpdateTextboxText("1");
324325
graph.AddNode(catchReturnNode, false);
325326
graph.Connect(tryCatchNode.Outputs[1], catchReturnNode.Inputs[0], false);
326327

327-
var parseNode = new Core.Nodes.MethodCall(graph);
328+
var parseNode = new MethodCall(graph);
328329
parseNode.SetMethodTarget(new RealMethodInfo(nodeClass.TypeFactory, typeof(int).GetMethod("Parse", new[] { typeof(string) })!, nodeClass.TypeFactory.Get<int>()));
329330
parseNode.Inputs[0].UpdateTextboxText("invalid");
330331
graph.AddNode(parseNode, false);
331332
graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false);
332333

333-
var tryReturnNode = new Core.Nodes.Flow.ReturnNode(graph);
334+
var tryReturnNode = new ReturnNode(graph);
334335
tryReturnNode.Inputs.Add(new("Result", tryReturnNode, nodeClass.TypeFactory.Get<int>()));
335336
tryReturnNode.Inputs[1].UpdateTextboxText("0");
336337
graph.AddNode(tryReturnNode, false);
337338
// Both the try and finally merge to the same "return", as there is nothing to actually put in the "finally" we can reuse the same return for simplicity
338339
graph.Connect(parseNode.Outputs[0], tryReturnNode.Inputs[0], false);
339340
graph.Connect(tryCatchNode.Outputs[2], tryReturnNode.Inputs[0], false);
340341

341-
var output = graph.Project.Run(options, Array.Empty<object>());
342+
343+
CreateStaticMainWithConversion(nodeClass, method);
344+
345+
var output = Run<int>(project, options);
342346

343347
Assert.Equal(1, output);
344348
}

0 commit comments

Comments
 (0)