暂时实现了简陋的脚本AST分析解释,后面再绑定到控件上

This commit is contained in:
fengjiayi
2024-12-20 23:39:29 +08:00
parent 114e81424b
commit ef119e11e3
52 changed files with 3175 additions and 261 deletions

View File

@@ -66,6 +66,7 @@ namespace Serein.NodeFlow.Env
NodeMVVMManagement.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点
NodeMVVMManagement.RegisterModel(NodeControlType.ConditionRegion, typeof(CompositeConditionNode)); // 条件区域
NodeMVVMManagement.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点
NodeMVVMManagement.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点
#endregion
}
@@ -401,9 +402,13 @@ namespace Serein.NodeFlow.Env
IOC.Reset(); // 开始运行时清空ioc中注册的实例
IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName, this);
IOC.Register<IScriptFlowApi, ScriptFlowApi>(); // 注册脚本接口
IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName, this); // 注册流程实例
if (this.UIContextOperation is not null)
{
// 注册封装好的UI线程上下文
IOC.CustomRegisterInstance(typeof(UIContextOperation).FullName, this.UIContextOperation, false);
}
await flowStarter.RunAsync(this, nodes, autoRegisterTypes, initMethods, loadMethods, exitMethods);

View File

@@ -90,6 +90,7 @@ namespace Serein.NodeFlow.Env
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleScriptNode)}" => NodeControlType.Script, // 数据节点
_ => NodeControlType.None,
};

View File

@@ -56,19 +56,6 @@ namespace Serein.NodeFlow
#endif
await startNode.StartFlowAsync(context); // 开始运行时从选定节点开始运行
context.Exit();
/*
foreach (var node in NodeModels.Values)
{
if (node is not null)
{
node.ReleaseFlowData(); // 退出时释放对象
}
}
*/
}
@@ -254,6 +241,7 @@ namespace Serein.NodeFlow
try
{
//await TestScript(env);
await startNode.StartFlowAsync(Context); // 开始运行时从起始节点开始运行
if (flipflopNodes.Count > 0)
@@ -293,6 +281,39 @@ namespace Serein.NodeFlow
#endregion
}
#if false
public async Task TestScript(IFlowEnvironment environment)
{
SingleScriptNode singleScriptNode = new SingleScriptNode(environment);
string script =
"""
//let argData1 = flow.GetArgIndex(0); // 通过索引的方式,获取当前节点入参第一个参数
//let argData2 = flow.GetArgName("name"); // 通过名称的方式,获取当前节点入参的第二个参数
//let nodeData = flow.GetFlowData(); // 获取上一个节点的数据
//let state = flow.GetGlobalData("key name"); // 获取全局数据
//let result1 = flow.CallNode("node guid",); // 立即调用某个节点,获取数据
//let result2 = flow.CallFunc();
class User{
int ID;
string Name;
}
let user = new User();
user.ID = 12345;
user.Name = "张三";
return user;
""";
singleScriptNode.Script = script;
singleScriptNode.LoadScript();
var result = await singleScriptNode.ExecutingAsync(new DynamicContext(environment));
SereinEnv.WriteLine(InfoType.INFO, result?.ToString());
}
#endif
private ConcurrentDictionary<SingleFlipflopNode, CancellationTokenSource> dictGlobalFlipflop = [];
/// <summary>

View File

@@ -0,0 +1,143 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Script;
using Serein.Script.Node;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Serein.NodeFlow.Model
{
[NodeProperty(ValuePath = NodeValuePath.Node)]
public partial class SingleScriptNode : NodeModelBase
{
[PropertyInfo(IsNotification = true)]
private string _script;
}
/// <summary>
/// 流程脚本节点
/// </summary>
public partial class SingleScriptNode : NodeModelBase
{
private IScriptFlowApi ScriptFlowApi { get; }
private ASTNode mainNode;
/// <summary>
/// 构建流程脚本节点
/// </summary>
/// <param name="environment"></param>
public SingleScriptNode(IFlowEnvironment environment):base(environment)
{
//ScriptFlowApi = environment.IOC.Get<ScriptFlowApi>();
ScriptFlowApi = new ScriptFlowApi(environment, this);
MethodInfo? method = this.GetType().GetMethod(nameof(GetFlowApi));
if (method != null)
{
SereinScriptInterpreter.AddFunction(nameof(GetFlowApi), method, () => this); // 挂载获取流程接口
}
// 挂载静态方法
var tempMethods = typeof(BaseFunc).GetMethods().Where(method =>
!(method.Name.Equals("GetHashCode")
|| method.Name.Equals("Equals")
|| method.Name.Equals("ToString")
|| method.Name.Equals("GetType")
)).Select(method => (method.Name, method)).ToArray();
foreach ((string name, MethodInfo method) item in tempMethods)
{
SereinScriptInterpreter.AddFunction(item.name, item.method); // 加载基础方法
}
}
/// <summary>
/// 加载脚本代码
/// </summary>
public void LoadScript()
{
try
{
mainNode = new SereinScriptParser(Script).Parse();
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.ERROR, ex.ToString());
}
}
/// <summary>
/// 执行脚本
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<object?> ExecutingAsync(IDynamicContext context)
{
mainNode ??= new SereinScriptParser(Script).Parse();
SereinScriptInterpreter scriptInterpreter = new SereinScriptInterpreter();
var result = await scriptInterpreter.InterpretAsync(mainNode); // 从入口节点执行
scriptInterpreter.ResetVar();
return result;
}
public IScriptFlowApi GetFlowApi()
{
return ScriptFlowApi;
}
private static class BaseFunc
{
public static Type TypeOf(object type)
{
return type.GetType();
}
public static void Print(object value)
{
SereinEnv.WriteLine(InfoType.INFO, value?.ToString());
}
#region
public static int ToInt(object value)
{
return int.Parse(value.ToString());
}
public static double ToDouble(object value)
{
return double.Parse(value.ToString());
}
public static bool ToBool(object value)
{
return bool.Parse(value.ToString());
}
#endregion
public static async Task Delay(object value)
{
if (value is int @int)
{
Console.WriteLine($"等待{@int}ms");
await Task.Delay(@int);
}
else if (value is TimeSpan timeSpan)
{
Console.WriteLine($"等待{timeSpan}");
await Task.Delay(timeSpan);
}
}
}
}
}

66
NodeFlow/ScriptFlowApi.cs Normal file
View File

@@ -0,0 +1,66 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow
{
/// <summary>
/// 脚本代码中关于流程运行的API
/// </summary>
public class ScriptFlowApi : IScriptFlowApi
{
/// <summary>
/// 流程环境
/// </summary>
public IFlowEnvironment Env { get; private set; }
/// <summary>
/// 对应的节点
/// </summary>
public NodeModelBase NodeModel { get; private set; }
IDynamicContext IScriptFlowApi.Context { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
/// <summary>
/// 创建流程脚本接口
/// </summary>
/// <param name="environment">运行环境</param>
/// <param name="nodeModel">节点</param>
public ScriptFlowApi(IFlowEnvironment environment, NodeModelBase nodeModel)
{
Env = environment;
NodeModel = nodeModel;
}
Task<object> IScriptFlowApi.CallNode(string nodeGuid)
{
throw new NotImplementedException();
}
object IScriptFlowApi.GetDataOfParams(int index)
{
throw new NotImplementedException();
}
object IScriptFlowApi.GetDataOfParams(string name)
{
throw new NotImplementedException();
}
object IScriptFlowApi.GetFlowData()
{
throw new NotImplementedException();
}
object IScriptFlowApi.GetGlobalData(string keyName)
{
throw new NotImplementedException();
}
}
}

View File

@@ -67,6 +67,7 @@
<ProjectReference Include="..\Library.Core\Serein.Library.Core.csproj" />
<ProjectReference Include="..\Library.Framework\Serein.Library.Framework.csproj" />
<ProjectReference Include="..\Library\Serein.Library.csproj" />
<ProjectReference Include="..\Serein.Script\Serein.Script.csproj" />
</ItemGroup>
</Project>