Skip to content

Commit e115af6

Browse files
authored
Add local variable declaration and set (#53)
* Add local variable declaration and set Fixes #14
1 parent 8cb452e commit e115af6

File tree

5 files changed

+182
-52
lines changed

5 files changed

+182
-52
lines changed

src/NodeDev.Blazor/Components/SourceViewer.razor

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,12 @@ 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 });
99-
Method.Graph.Project.BuildAndGetAssembly(Core.BuildOptions.Debug);
98+
var assemblyPath = Method.Graph.Project.Build(Core.BuildOptions.Debug with { OutputPath = temp });
10099
Creator = Method.Graph.Project.NodeClassTypeCreator;
101100

102101
try
103102
{
104-
Creator!.GetBodyAsCsAndMsilCode("", Method, out CodeCs, out CodeMsil);
103+
Creator!.GetBodyAsCsAndMsilCode(assemblyPath, Method, out CodeCs, out CodeMsil);
105104

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

src/NodeDev.Core/Class/NodeClassTypeCreator.cs

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,16 @@ internal NodeClassTypeCreator(Project project, BuildOptions buildOptions)
3737
public void CreateProjectClassesAndAssembly()
3838
{
3939
// TODO Remove this when the new System.Reflection.Emit is available in .NET 10
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);
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;
5250

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

151149
cs = str.ToString();
152150

153-
//var assembly = context.LoadFromAssemblyPath(assemblyPath);
154-
var assembly = Assembly;
151+
var assembly = context.LoadFromAssemblyPath(assemblyPath);
155152
var type = assembly.GetType(HiddenName(method.Class.Name));
156153
if(type == null)
157154
{
@@ -183,24 +180,6 @@ private void GenerateHiddenMethodBody(NodeClassMethod method, MethodBuilder meth
183180
// Generate the expression tree for the method
184181
var expression = method.Graph.BuildExpression(Options.BuildExpressionOptions);
185182

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();
204183
var ilGenerator = methodBuilder.GetILGenerator();
205184
var result = expression.CompileFastToIL(ilGenerator, CompilerFlags.ThrowOnNotSupportedExpression);
206185

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using NodeDev.Core.Connections;
2+
using NodeDev.Core.Types;
3+
using System.Linq.Expressions;
4+
5+
namespace NodeDev.Core.Nodes;
6+
7+
public class DeclareVariableNode : NormalFlowNode
8+
{
9+
public DeclareVariableNode(Graph graph, string? id = null) : base(graph, id)
10+
{
11+
Name = "Declare Variable";
12+
13+
var t = new UndefinedGenericType("T");
14+
Outputs.Add(new Connection("Variable", this, t));
15+
Inputs.Add(new Connection("InitialValue", this, t));
16+
}
17+
18+
public override string TitleColor => "blue";
19+
20+
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
21+
{
22+
return Expression.Assign(info.LocalVariables[Outputs[1]], info.LocalVariables[Inputs[1]]);
23+
}
24+
25+
internal override void BuildInlineExpression(BuildExpressionInfo info)
26+
{
27+
throw new NotImplementedException();
28+
}
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using NodeDev.Core.Connections;
2+
using NodeDev.Core.Types;
3+
using System.Linq.Expressions;
4+
5+
namespace NodeDev.Core.Nodes;
6+
7+
public class SetVariableValueNode : NormalFlowNode
8+
{
9+
public SetVariableValueNode(Graph graph, string? id = null) : base(graph, id)
10+
{
11+
Name = "Set Variable";
12+
13+
var type = new UndefinedGenericType("T");
14+
Inputs.Add(new Connection("Variable", this, type));
15+
Inputs.Add(new Connection("Value", this, type));
16+
}
17+
18+
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
19+
{
20+
return Expression.Assign(info.LocalVariables[Inputs[1]], info.LocalVariables[Inputs[2]]);
21+
}
22+
}

src/NodeDev.Tests/GraphExecutorTests.cs

Lines changed: 118 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ public static T Run<T>(Project project, BuildOptions buildOptions, params object
190190
finally
191191
{
192192
// Clean up
193-
Directory.Delete(buildOptions.OutputPath, true);
193+
if(Directory.Exists(buildOptions.OutputPath))
194+
Directory.Delete(buildOptions.OutputPath, true);
194195
}
195196
}
196197

@@ -317,33 +318,133 @@ public void TestTryCatchNode(SerializableBuildOptions options)
317318
graph.AddNode(entryNode, false);
318319
graph.AddNode(tryCatchNode, false);
319320

320-
graph.Connect(entryNode.Outputs[0], tryCatchNode.Inputs[0], false);
321+
// Create local variable
322+
var declareVariableNode = new DeclareVariableNode(graph);
323+
declareVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Initial value
324+
declareVariableNode.Outputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
325+
graph.AddNode(declareVariableNode, false);
321326

322-
var catchReturnNode = new ReturnNode(graph);
323-
catchReturnNode.Inputs.Add(new("Result", catchReturnNode, nodeClass.TypeFactory.Get<int>()));
324-
catchReturnNode.Inputs[1].UpdateTextboxText("1");
325-
graph.AddNode(catchReturnNode, false);
326-
graph.Connect(tryCatchNode.Outputs[1], catchReturnNode.Inputs[0], false);
327+
graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
328+
graph.Connect(declareVariableNode.Outputs[0], tryCatchNode.Inputs[0], false);
327329

328-
var parseNode = new MethodCall(graph);
330+
var returnNode = new ReturnNode(graph);
331+
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);
332+
graph.AddNode(returnNode, false);
333+
334+
// Create the catch block body
335+
var catchVariableNode = new SetVariableValueNode(graph);
336+
catchVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
337+
catchVariableNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
338+
catchVariableNode.Inputs[2].UpdateTextboxText("2");
339+
graph.AddNode(catchVariableNode, false);
340+
graph.Connect(tryCatchNode.Outputs[1], catchVariableNode.Inputs[0], false);
341+
graph.Connect(declareVariableNode.Outputs[1], catchVariableNode.Inputs[1], false);
342+
graph.Connect(catchVariableNode.Outputs[0], returnNode.Inputs[0], false);
343+
344+
// Create the try block body
345+
var parseNode = new MethodCall(graph);
329346
parseNode.SetMethodTarget(new RealMethodInfo(nodeClass.TypeFactory, typeof(int).GetMethod("Parse", new[] { typeof(string) })!, nodeClass.TypeFactory.Get<int>()));
330-
parseNode.Inputs[0].UpdateTextboxText("invalid");
347+
parseNode.Inputs[1].UpdateTextboxText("invalid");
331348
graph.AddNode(parseNode, false);
332349
graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false);
333350

334-
var tryReturnNode = new ReturnNode(graph);
335-
tryReturnNode.Inputs.Add(new("Result", tryReturnNode, nodeClass.TypeFactory.Get<int>()));
336-
tryReturnNode.Inputs[1].UpdateTextboxText("0");
337-
graph.AddNode(tryReturnNode, false);
338-
// 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
339-
graph.Connect(parseNode.Outputs[0], tryReturnNode.Inputs[0], false);
340-
graph.Connect(tryCatchNode.Outputs[2], tryReturnNode.Inputs[0], false);
351+
var tryVariableNode = new SetVariableValueNode(graph);
352+
tryVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
353+
tryVariableNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
354+
tryVariableNode.Inputs[2].UpdateTextboxText("1");
355+
graph.AddNode(tryVariableNode, false);
341356

357+
graph.Connect(parseNode.Outputs[0], tryVariableNode.Inputs[0], false);
358+
graph.Connect(declareVariableNode.Outputs[1], tryVariableNode.Inputs[1], false);
359+
graph.Connect(tryVariableNode.Outputs[0], returnNode.Inputs[0], false);
342360

343361
CreateStaticMainWithConversion(nodeClass, method);
344362

345363
var output = Run<int>(project, options);
346364

347-
Assert.Equal(1, output);
365+
Assert.Equal(2, output);
348366
}
367+
368+
[Theory]
369+
[MemberData(nameof(GetBuildOptions))]
370+
public void TestDeclareAndSetVariable(SerializableBuildOptions options)
371+
{
372+
var project = new Project(Guid.NewGuid());
373+
var nodeClass = new NodeClass("Program", "Test", project);
374+
project.Classes.Add(nodeClass);
375+
376+
var graph = new Graph();
377+
var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get<int>(), graph, true);
378+
nodeClass.Methods.Add(method);
379+
graph.SelfMethod = method;
380+
381+
method.Parameters.Add(new("A", nodeClass.TypeFactory.Get<int>(), method));
382+
383+
var entryNode = new EntryNode(graph);
384+
graph.AddNode(entryNode, false);
385+
386+
var declareVariableNode = new DeclareVariableNode(graph);
387+
declareVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Initial value
388+
declareVariableNode.Outputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
389+
graph.AddNode(declareVariableNode, false);
390+
391+
var setVariableValueNode = new SetVariableValueNode(graph);
392+
setVariableValueNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
393+
setVariableValueNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
394+
graph.AddNode(setVariableValueNode, false);
395+
396+
var returnNode = new ReturnNode(graph);
397+
graph.AddNode(returnNode, false);
398+
399+
graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
400+
graph.Connect(declareVariableNode.Outputs[0], setVariableValueNode.Inputs[0], false);
401+
graph.Connect(declareVariableNode.Outputs[1], setVariableValueNode.Inputs[1], false);
402+
graph.Connect(entryNode.Outputs[1], setVariableValueNode.Inputs[2], false);
403+
graph.Connect(setVariableValueNode.Outputs[0], returnNode.Inputs[0], false);
404+
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);
405+
406+
CreateStaticMainWithConversion(nodeClass, method);
407+
408+
var output = Run<int>(graph.Project, options, [5]);
409+
410+
Assert.Equal(5, output);
411+
}
412+
413+
[Theory]
414+
[MemberData(nameof(GetBuildOptions))]
415+
public void TestDeclareVariableDefaultValue(SerializableBuildOptions options)
416+
{
417+
var project = new Project(Guid.NewGuid());
418+
var nodeClass = new NodeClass("Program", "Test", project);
419+
project.Classes.Add(nodeClass);
420+
421+
var graph = new Graph();
422+
var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get<int>(), graph, true);
423+
nodeClass.Methods.Add(method);
424+
graph.SelfMethod = method;
425+
426+
method.Parameters.Add(new("A", nodeClass.TypeFactory.Get<int>(), method));
427+
428+
var entryNode = new EntryNode(graph);
429+
graph.AddNode(entryNode, false);
430+
431+
var declareVariableNode = new DeclareVariableNode(graph);
432+
declareVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true);
433+
declareVariableNode.Outputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true);
434+
graph.AddNode(declareVariableNode, false);
435+
436+
var returnNode = new ReturnNode(graph);
437+
graph.AddNode(returnNode, false);
438+
439+
graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
440+
graph.Connect(entryNode.Outputs[1], declareVariableNode.Inputs[1], false);
441+
graph.Connect(declareVariableNode.Outputs[0], returnNode.Inputs[0], false);
442+
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);
443+
444+
CreateStaticMainWithConversion(nodeClass, method);
445+
446+
var output = Run<int>(graph.Project, options, [5]);
447+
448+
Assert.Equal(5, output);
449+
}
349450
}

0 commit comments

Comments
 (0)