Skip to content

Commit ceeeded

Browse files
committed
Add TryCatchNode to represent try-catch operation
--- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/VisualNodeDev/NodeDev?shareId=XXXX-XXXX-XXXX-XXXX).
1 parent 024e6d8 commit ceeeded

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using NodeDev.Core.Connections;
2+
using NodeDev.Core.Types;
3+
using System.Linq.Expressions;
4+
5+
namespace NodeDev.Core.Nodes.Flow;
6+
7+
public class TryCatchNode : FlowNode
8+
{
9+
public override bool IsFlowNode => true;
10+
11+
public TryCatchNode(Graph graph, string? id = null) : base(graph, id)
12+
{
13+
Name = "TryCatch";
14+
15+
Inputs.Add(new("Exec", this, TypeFactory.ExecType));
16+
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+
}
22+
23+
public override string GetExecOutputPathId(string pathId, Connection execOutput)
24+
{
25+
return pathId + "-" + execOutput.Id;
26+
}
27+
28+
public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false;
29+
30+
public override bool DoesOutputPathAllowMerge(Connection execOutput) => true;
31+
32+
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
33+
{
34+
ArgumentNullException.ThrowIfNull(subChunks);
35+
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));
39+
40+
var exceptionVariable = Expression.Variable(typeof(Exception), "ex");
41+
info.LocalVariables[3] = exceptionVariable; // Make sure other pieces of code use the right variable for that exception
42+
43+
var catchClause = Expression.Catch(exceptionVariable, catchBlock);
44+
45+
return Expression.TryCatchFinally(tryBlock, finallyBlock, catchClause);
46+
}
47+
}

src/NodeDev.Tests/GraphExecutorTests.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,56 @@ public void TestProjectRun(SerializableBuildOptions options)
142142
output = graph.SelfClass.Project.Run(options, [-1, -2]);
143143
Assert.Equal(1, output);
144144
}
145-
}
145+
146+
// This test validates the TryCatchNode by simulating a scenario where an exception is thrown and caught.
147+
// The test sets up a graph with an entry node, a TryCatchNode, and return nodes for both try and catch blocks.
148+
// The try block attempts to parse an invalid integer string, which throws an exception.
149+
// The catch block returns 1, indicating that the exception was caught.
150+
// The test asserts that the output is 1, confirming that the exception was caught and handled correctly.
151+
[Theory]
152+
[MemberData(nameof(GetBuildOptions))]
153+
public void TestTryCatchNode(SerializableBuildOptions options)
154+
{
155+
var project = new Project(Guid.NewGuid());
156+
var nodeClass = new NodeClass("Program", "Test", project);
157+
project.Classes.Add(nodeClass);
158+
159+
var graph = new Graph();
160+
var method = new NodeClassMethod(nodeClass, "Main", nodeClass.TypeFactory.Get<int>(), graph);
161+
method.IsStatic = true;
162+
nodeClass.Methods.Add(method);
163+
graph.SelfMethod = nodeClass.Methods.First();
164+
165+
var entryNode = new Core.Nodes.Flow.EntryNode(graph);
166+
var tryCatchNode = new Core.Nodes.Flow.TryCatchNode(graph);
167+
168+
graph.AddNode(entryNode, false);
169+
graph.AddNode(tryCatchNode, false);
170+
171+
graph.Connect(entryNode.Outputs[0], tryCatchNode.Inputs[0], false);
172+
173+
var catchReturnNode = new Core.Nodes.Flow.ReturnNode(graph);
174+
catchReturnNode.Inputs.Add(new("Result", catchReturnNode, nodeClass.TypeFactory.Get<int>()));
175+
catchReturnNode.Inputs[1].UpdateTextboxText("1");
176+
graph.AddNode(catchReturnNode, false);
177+
graph.Connect(tryCatchNode.Outputs[1], catchReturnNode.Inputs[0], false);
178+
179+
var parseNode = new Core.Nodes.MethodCall(graph);
180+
parseNode.SetMethodTarget(typeof(int).GetMethod("Parse", new[] { typeof(string) })!);
181+
parseNode.Inputs[0].UpdateTextboxText("invalid");
182+
graph.AddNode(parseNode, false);
183+
graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false);
184+
185+
var tryReturnNode = new Core.Nodes.Flow.ReturnNode(graph);
186+
tryReturnNode.Inputs.Add(new("Result", tryReturnNode, nodeClass.TypeFactory.Get<int>()));
187+
tryReturnNode.Inputs[1].UpdateTextboxText("0");
188+
graph.AddNode(tryReturnNode, false);
189+
// 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
190+
graph.Connect(parseNode.Outputs[0], tryReturnNode.Inputs[0], false);
191+
graph.Connect(tryCatchNode.Outputs[2], tryReturnNode.Inputs[0], false);
192+
193+
var output = graph.Project.Run(options, Array.Empty<object>());
194+
195+
Assert.Equal(1, output);
196+
}
197+
}

0 commit comments

Comments
 (0)