Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/NodeDev.Blazor/Components/SourceViewer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,12 @@ else
try
{
var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
//var assemblyPath = Method.Graph.Project.Build(Core.BuildOptions.Debug with { OutputPath = temp });
Method.Graph.Project.BuildAndGetAssembly(Core.BuildOptions.Debug);
var assemblyPath = Method.Graph.Project.Build(Core.BuildOptions.Debug with { OutputPath = temp });
Creator = Method.Graph.Project.NodeClassTypeCreator;

try
{
Creator!.GetBodyAsCsAndMsilCode("", Method, out CodeCs, out CodeMsil);
Creator!.GetBodyAsCsAndMsilCode(assemblyPath, Method, out CodeCs, out CodeMsil);

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

Expand Down
43 changes: 11 additions & 32 deletions src/NodeDev.Core/Class/NodeClassTypeCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,16 @@ internal NodeClassTypeCreator(Project project, BuildOptions buildOptions)
public void CreateProjectClassesAndAssembly()
{
// TODO Remove this when the new System.Reflection.Emit is available in .NET 10
//if (TemporaryReflectionAssembly == null)
//{
// var assembly = System.Reflection.Assembly.GetExecutingAssembly();
// using var stream = assembly.GetManifestResourceStream("NodeDev.Core.Dependencies.System.Reflection.Emit.dll")!;
// var bytes = new byte[stream.Length];
// stream.ReadExactly(bytes);
// TemporaryReflectionAssembly = System.Reflection.Assembly.Load(bytes);
//}
//var persisted = Activator.CreateInstance(TemporaryReflectionAssembly.ExportedTypes.First(), new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), typeof(object).Assembly, null)!;
//Assembly = (AssemblyBuilder)persisted;

Assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), AssemblyBuilderAccess.RunAndCollect);
if (TemporaryReflectionAssembly == null)
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream("NodeDev.Core.Dependencies.System.Reflection.Emit.dll")!;
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);
TemporaryReflectionAssembly = System.Reflection.Assembly.Load(bytes);
}
var persisted = Activator.CreateInstance(TemporaryReflectionAssembly.ExportedTypes.First(), new AssemblyName("NodeProject_" + this.Project.Id.ToString().Replace('-', '_')), typeof(object).Assembly, null)!;
Assembly = (AssemblyBuilder)persisted;

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

cs = str.ToString();

//var assembly = context.LoadFromAssemblyPath(assemblyPath);
var assembly = Assembly;
var assembly = context.LoadFromAssemblyPath(assemblyPath);
var type = assembly.GetType(HiddenName(method.Class.Name));
if(type == null)
{
Expand Down Expand Up @@ -183,24 +180,6 @@ private void GenerateHiddenMethodBody(NodeClassMethod method, MethodBuilder meth
// Generate the expression tree for the method
var expression = method.Graph.BuildExpression(Options.BuildExpressionOptions);


var returnLabelTarget = Expression.Label(typeof(int), "ReturnLabel");

var tryBody = Expression.Call(typeof(Console).GetMethod("Clear")!);
var catchBody = Expression.Return(returnLabelTarget, Expression.Constant(10));

var tryCatch = Expression.TryCatch(tryBody, Expression.Catch(typeof(Exception), catchBody));

var afterCatch = Expression.Block(
Expression.Call(typeof(Console).GetMethod("Clear")!),
Expression.Return(returnLabelTarget, Expression.Constant(5)),
Expression.Label(returnLabelTarget, Expression.Constant(0)));

var body = Expression.Block(tryCatch, afterCatch);

expression = Expression.Lambda(body);

var v = expression.CompileFast();
var ilGenerator = methodBuilder.GetILGenerator();
var result = expression.CompileFastToIL(ilGenerator, CompilerFlags.ThrowOnNotSupportedExpression);

Expand Down
29 changes: 29 additions & 0 deletions src/NodeDev.Core/Nodes/DeclareVariableNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NodeDev.Core.Connections;
using NodeDev.Core.Types;
using System.Linq.Expressions;

namespace NodeDev.Core.Nodes;

public class DeclareVariableNode : NormalFlowNode
{
public DeclareVariableNode(Graph graph, string? id = null) : base(graph, id)
{
Name = "Declare Variable";

var t = new UndefinedGenericType("T");
Outputs.Add(new Connection("Variable", this, t));
Inputs.Add(new Connection("InitialValue", this, t));
}

public override string TitleColor => "blue";

internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
{
return Expression.Assign(info.LocalVariables[Outputs[1]], info.LocalVariables[Inputs[1]]);
}

internal override void BuildInlineExpression(BuildExpressionInfo info)
{
throw new NotImplementedException();
}
}
22 changes: 22 additions & 0 deletions src/NodeDev.Core/Nodes/SetVariableValueNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NodeDev.Core.Connections;
using NodeDev.Core.Types;
using System.Linq.Expressions;

namespace NodeDev.Core.Nodes;

public class SetVariableValueNode : NormalFlowNode
{
public SetVariableValueNode(Graph graph, string? id = null) : base(graph, id)
{
Name = "Set Variable";

var type = new UndefinedGenericType("T");
Inputs.Add(new Connection("Variable", this, type));
Inputs.Add(new Connection("Value", this, type));
}

internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
{
return Expression.Assign(info.LocalVariables[Inputs[1]], info.LocalVariables[Inputs[2]]);
}
}
135 changes: 118 additions & 17 deletions src/NodeDev.Tests/GraphExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ public static T Run<T>(Project project, BuildOptions buildOptions, params object
finally
{
// Clean up
Directory.Delete(buildOptions.OutputPath, true);
if(Directory.Exists(buildOptions.OutputPath))
Directory.Delete(buildOptions.OutputPath, true);
}
}

Expand Down Expand Up @@ -317,33 +318,133 @@ public void TestTryCatchNode(SerializableBuildOptions options)
graph.AddNode(entryNode, false);
graph.AddNode(tryCatchNode, false);

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

var catchReturnNode = new ReturnNode(graph);
catchReturnNode.Inputs.Add(new("Result", catchReturnNode, nodeClass.TypeFactory.Get<int>()));
catchReturnNode.Inputs[1].UpdateTextboxText("1");
graph.AddNode(catchReturnNode, false);
graph.Connect(tryCatchNode.Outputs[1], catchReturnNode.Inputs[0], false);
graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[0], tryCatchNode.Inputs[0], false);

var parseNode = new MethodCall(graph);
var returnNode = new ReturnNode(graph);
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);
graph.AddNode(returnNode, false);

// Create the catch block body
var catchVariableNode = new SetVariableValueNode(graph);
catchVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
catchVariableNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
catchVariableNode.Inputs[2].UpdateTextboxText("2");
graph.AddNode(catchVariableNode, false);
graph.Connect(tryCatchNode.Outputs[1], catchVariableNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[1], catchVariableNode.Inputs[1], false);
graph.Connect(catchVariableNode.Outputs[0], returnNode.Inputs[0], false);

// Create the try block body
var parseNode = new MethodCall(graph);
parseNode.SetMethodTarget(new RealMethodInfo(nodeClass.TypeFactory, typeof(int).GetMethod("Parse", new[] { typeof(string) })!, nodeClass.TypeFactory.Get<int>()));
parseNode.Inputs[0].UpdateTextboxText("invalid");
parseNode.Inputs[1].UpdateTextboxText("invalid");
graph.AddNode(parseNode, false);
graph.Connect(tryCatchNode.Outputs[0], parseNode.Inputs[0], false);

var tryReturnNode = new ReturnNode(graph);
tryReturnNode.Inputs.Add(new("Result", tryReturnNode, nodeClass.TypeFactory.Get<int>()));
tryReturnNode.Inputs[1].UpdateTextboxText("0");
graph.AddNode(tryReturnNode, false);
// 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
graph.Connect(parseNode.Outputs[0], tryReturnNode.Inputs[0], false);
graph.Connect(tryCatchNode.Outputs[2], tryReturnNode.Inputs[0], false);
var tryVariableNode = new SetVariableValueNode(graph);
tryVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
tryVariableNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
tryVariableNode.Inputs[2].UpdateTextboxText("1");
graph.AddNode(tryVariableNode, false);

graph.Connect(parseNode.Outputs[0], tryVariableNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[1], tryVariableNode.Inputs[1], false);
graph.Connect(tryVariableNode.Outputs[0], returnNode.Inputs[0], false);

CreateStaticMainWithConversion(nodeClass, method);

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

Assert.Equal(1, output);
Assert.Equal(2, output);
}

[Theory]
[MemberData(nameof(GetBuildOptions))]
public void TestDeclareAndSetVariable(SerializableBuildOptions options)
{
var project = new Project(Guid.NewGuid());
var nodeClass = new NodeClass("Program", "Test", project);
project.Classes.Add(nodeClass);

var graph = new Graph();
var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get<int>(), graph, true);
nodeClass.Methods.Add(method);
graph.SelfMethod = method;

method.Parameters.Add(new("A", nodeClass.TypeFactory.Get<int>(), method));

var entryNode = new EntryNode(graph);
graph.AddNode(entryNode, false);

var declareVariableNode = new DeclareVariableNode(graph);
declareVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Initial value
declareVariableNode.Outputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
graph.AddNode(declareVariableNode, false);

var setVariableValueNode = new SetVariableValueNode(graph);
setVariableValueNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Variable
setVariableValueNode.Inputs[2].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true); // Value
graph.AddNode(setVariableValueNode, false);

var returnNode = new ReturnNode(graph);
graph.AddNode(returnNode, false);

graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[0], setVariableValueNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[1], setVariableValueNode.Inputs[1], false);
graph.Connect(entryNode.Outputs[1], setVariableValueNode.Inputs[2], false);
graph.Connect(setVariableValueNode.Outputs[0], returnNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);

CreateStaticMainWithConversion(nodeClass, method);

var output = Run<int>(graph.Project, options, [5]);

Assert.Equal(5, output);
}

[Theory]
[MemberData(nameof(GetBuildOptions))]
public void TestDeclareVariableDefaultValue(SerializableBuildOptions options)
{
var project = new Project(Guid.NewGuid());
var nodeClass = new NodeClass("Program", "Test", project);
project.Classes.Add(nodeClass);

var graph = new Graph();
var method = new NodeClassMethod(nodeClass, "MainInternal", nodeClass.TypeFactory.Get<int>(), graph, true);
nodeClass.Methods.Add(method);
graph.SelfMethod = method;

method.Parameters.Add(new("A", nodeClass.TypeFactory.Get<int>(), method));

var entryNode = new EntryNode(graph);
graph.AddNode(entryNode, false);

var declareVariableNode = new DeclareVariableNode(graph);
declareVariableNode.Inputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true);
declareVariableNode.Outputs[1].UpdateTypeAndTextboxVisibility(nodeClass.TypeFactory.Get<int>(), true);
graph.AddNode(declareVariableNode, false);

var returnNode = new ReturnNode(graph);
graph.AddNode(returnNode, false);

graph.Connect(entryNode.Outputs[0], declareVariableNode.Inputs[0], false);
graph.Connect(entryNode.Outputs[1], declareVariableNode.Inputs[1], false);
graph.Connect(declareVariableNode.Outputs[0], returnNode.Inputs[0], false);
graph.Connect(declareVariableNode.Outputs[1], returnNode.Inputs[1], false);

CreateStaticMainWithConversion(nodeClass, method);

var output = Run<int>(graph.Project, options, [5]);

Assert.Equal(5, output);
}
}