重写脚本解释器的实现,提高其可读性。

This commit is contained in:
fengjiayi
2025-07-13 17:34:03 +08:00
parent 6141d2c1c1
commit 01ab905155
42 changed files with 1747 additions and 715 deletions

View File

@@ -85,43 +85,47 @@ namespace Serein.NodeFlow.Model
TargetNodeGuid = nodeGuid;
}
/// <summary>
/// 目标节点发生改变
/// </summary>
/// <param name="value"></param>
partial void OnTargetNodeGuidChanged(string value)
{
if (string.IsNullOrEmpty(value) || !Env.TryGetNodeModel(value, out var targetNode))
{
// 取消设置接口节点
TargetNode.PropertyChanged -= TargetNode_PropertyChanged;
TargetNode.PropertyChanged -= TargetNode_PropertyChanged; // 不再监听目标属通知
TargetNode = null;
this.ApiGlobalName = "";
this.MethodDetails = new MethodDetails();
}
else
{
//if (this.MethodDetails.ActingInstanceType.FullName.Equals())
TargetNode = targetNode;
if (!this.IsShareParam
&& CacheMethodDetails is not null
&& TargetNode.MethodDetails is not null
&& TargetNode.MethodDetails.AssemblyName == CacheMethodDetails.AssemblyName
&& TargetNode.MethodDetails.MethodName == CacheMethodDetails.MethodName)
if (!this.IsShareParam // 不共享参数
&& TargetNode.MethodDetails is not null // 目标节点有方法描述
&& CacheMethodDetails is not null // 存在缓存
&& TargetNode.MethodDetails.AssemblyName == CacheMethodDetails.AssemblyName // 与缓存中一致
&& TargetNode.MethodDetails.MethodName == CacheMethodDetails.MethodName) // 与缓存中一致
{
this.MethodDetails = CacheMethodDetails;
this.ApiGlobalName = GetApiInvokeName(this);
this.MethodDetails = CacheMethodDetails; // 直接使用缓存
this.ApiGlobalName = GetApiInvokeName(this); // 生成新的接口名称
}
else
{
if (TargetNode.MethodDetails is not null)
if (TargetNode.MethodDetails is not null) // // 目标节点有方法描述
{
CacheMethodDetails = TargetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份
TargetNode.PropertyChanged += TargetNode_PropertyChanged;
this.MethodDetails = CacheMethodDetails;
this.ApiGlobalName = GetApiInvokeName(this);
CacheMethodDetails = TargetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份到缓存中
TargetNode.PropertyChanged += TargetNode_PropertyChanged; // 监听目标属性通知“IsPublic”属性
this.MethodDetails = CacheMethodDetails; // 设置流程接口节点的方法描述为目标节点的方法描述(共享参数更改)
this.ApiGlobalName = GetApiInvokeName(this); // 生成新的接口名称
}
}
}
OnPropertyChanged(nameof(MethodDetails));
OnPropertyChanged(nameof(MethodDetails)); // 通知控件MethodDetails属性发生改变
}
partial void OnIsShareParamChanged(bool value)
@@ -132,6 +136,7 @@ namespace Serein.NodeFlow.Model
}
if (value)
{
// 不再与目标节点共享参数,而是拷贝新的实体,缓存起来,自己单独使用
CacheMethodDetails = TargetNode.MethodDetails.CloneOfNode(this);
this.MethodDetails = CacheMethodDetails;
}
@@ -140,13 +145,18 @@ namespace Serein.NodeFlow.Model
if(TargetNode.ControlType == NodeControlType.Script)
{
// 脚本节点入参需不可编辑入参数量、入参名称
// 限制脚本节点对于入参数量、入参名称的修改
foreach (var item in CacheMethodDetails.ParameterDetailss)
{
item.IsParams = false;
}
}
this.MethodDetails = CacheMethodDetails;
// 与目标节点共享参数
if(CacheMethodDetails is null)
{
CacheMethodDetails = TargetNode.MethodDetails; // 防御性代码,几乎不可能触发
}
this.MethodDetails = CacheMethodDetails;
}
OnPropertyChanged(nameof(MethodDetails));

View File

@@ -2,7 +2,7 @@
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Script;
using Serein.Script.Node;
using Serein.Script.Node.FlowControl;
using System;
using System.Collections.Generic;
using System.Dynamic;
@@ -35,7 +35,7 @@ namespace Serein.NodeFlow.Model
private IScriptFlowApi ScriptFlowApi;
private ProgramNode programNode;
private SereinScriptInterpreter ScriptInterpreter;
private readonly SereinScriptInterpreter scriptInterpreter;
private bool IsScriptChanged = false;
/// <summary>
@@ -45,7 +45,7 @@ namespace Serein.NodeFlow.Model
public SingleScriptNode(IFlowEnvironment environment):base(environment)
{
ScriptFlowApi = new ScriptFlowApi(environment, this);
ScriptInterpreter = new SereinScriptInterpreter();
scriptInterpreter = new SereinScriptInterpreter();
}
static SingleScriptNode()
@@ -79,11 +79,11 @@ namespace Serein.NodeFlow.Model
/// </summary>
public override void OnCreating()
{
MethodInfo? method = this.GetType().GetMethod(nameof(GetFlowApi));
/* MethodInfo? method = this.GetType().GetMethod(nameof(GetFlowApi));
if (method != null)
{
ScriptInterpreter.AddFunction(nameof(GetFlowApi), method, () => this); // 挂载获取流程接口
}
}*/
var md = MethodDetails;
var pd = md.ParameterDetailss ??= new ParameterDetails[1];
@@ -103,8 +103,8 @@ namespace Serein.NodeFlow.Model
Items = null,
IsParams = true,
//Description = "脚本节点入参"
};
md.ReturnType = typeof(object); // 默认返回 object
}
@@ -157,19 +157,24 @@ namespace Serein.NodeFlow.Model
varNames.Add(pd.Name);
}
var sb = new StringBuilder();
/*var sb = new StringBuilder();
foreach (var pd in MethodDetails.ParameterDetailss)
{
sb.AppendLine($"let {pd.Name};"); // 提前声明这些变量
}
sb.Append(Script);
var script = sb.ToString();
var p = new SereinScriptParser(script);
//var p = new SereinScriptParser(Script);
programNode = p.Parse(); // 开始解析
var typeAnalysis = new SereinScriptTypeAnalysis();
ScriptInterpreter.SetTypeAnalysis(typeAnalysis);
typeAnalysis.AnalysisProgramNode(programNode);
var script = sb.ToString();*/
var parser = new SereinScriptParser(); // 准备解析器
var typeAnalysis = new SereinScriptTypeAnalysis(); // 准备分析器
programNode = parser.Parse(Script); // 开始解析获取程序主节点
var dict = MethodDetails.ParameterDetailss.ToDictionary(pd => pd.Name, pd => pd.DataType);
typeAnalysis.NodeSymbolInfos.Clear(); // 清空符号表
typeAnalysis.LoadSymbol(dict); // 提前加载脚本节点定义的符号
typeAnalysis.AnalysisProgramNode(programNode); // 分析节点类型
var returnType = typeAnalysis.NodeSymbolInfos[programNode]; // 获取返回类型
MethodDetails.ReturnType = returnType;
//scriptInterpreter.SetTypeAnalysis(typeAnalysis); // 设置类型分析器
}
catch (Exception ex)
@@ -240,17 +245,17 @@ namespace Serein.NodeFlow.Model
if (token.IsCancellationRequested) return null;
var result = await ScriptInterpreter.InterpretAsync(scriptContext, programNode); // 从入口节点执行
var result = await scriptInterpreter.InterpretAsync(scriptContext, programNode); // 从入口节点执行
envEvent.FlowRunComplete -= onFlowStop;
return new FlowResult(this.Guid, context, result);
}
#region
public IScriptFlowApi GetFlowApi()
/*public IScriptFlowApi GetFlowApi()
{
return ScriptFlowApi;
}
}*/
private static class ScriptBaseFunc
{

View File

@@ -375,13 +375,28 @@ namespace Serein.NodeFlow.Model.Operation
$"{Environment.NewLine}目标节点:{ToNode.Guid}");
return false;
}*/
if (FromNode.MethodDetails.ReturnType == typeof(void))
{
SereinEnv.WriteLine(InfoType.WARN, $"连接失败,节点参数入参不允许接收 void 返回值");
return false;
}
var toNodeArgSourceGuid = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid; // 目标节点对应参数可能已经有其它连接
var toNodeArgSourceType = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType;
if (FromNode.Guid == toNodeArgSourceGuid
&& toNodeArgSourceType == ConnectionArgSourceType)
if (false && string.IsNullOrWhiteSpace(toNodeArgSourceGuid) && flowModelService.ContainsNodeModel(toNodeArgSourceGuid))
{
if (FromNode.NeedResultNodes[type].Contains(ToNode))
SereinEnv.WriteLine(InfoType.WARN, $"连接失败,节点参数入参不允许接收多个节点返回值");
return false;
}
// 判断是否建立过连接关系
if (FromNode.Guid == toNodeArgSourceGuid && toNodeArgSourceType == ConnectionArgSourceType)
{
if (FromNode.NeedResultNodes[type].Contains(ToNode)) // 如果来源节点被该节点获取过,则创建链接
{
SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系" +
$"起始节点:{FromNode.Guid}" +
@@ -390,14 +405,18 @@ namespace Serein.NodeFlow.Model.Operation
$"参数类型:{ConnectionArgSourceType}");
return false;
}
var fromNodeGuid = FromNode.Guid;
var toNodeGuid = ToNode.Guid;
// 目标节点需要参数,但却没有被依赖的记录,则添加依赖记录并出现连接
FromNode.NeedResultNodes[type].Add(ToNode);
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid, // 从哪个节点开始
ToNode.Guid, // 连接到那个节点
fromNodeGuid, // 从哪个节点开始
toNodeGuid, // 连接到那个节点
ArgIndex, // 连接线的样式类型
JunctionOfConnectionType.Arg,
ConnectionArgSourceType,
@@ -409,8 +428,8 @@ namespace Serein.NodeFlow.Model.Operation
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid, // 从哪个节点开始
ToNode.Guid, // 连接到那个节点
fromNodeGuid, // 从哪个节点开始
toNodeGuid, // 连接到那个节点
ArgIndex, // 连接线的样式类型
JunctionOfConnectionType.Arg,
ConnectionArgSourceType,
@@ -421,18 +440,20 @@ namespace Serein.NodeFlow.Model.Operation
return true;
}
if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) // 更改关系获取
if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) // 参数入参节点已有来源,更改节点参数来源
{
var fromNodeGuid = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid;
var toNodeGuid = ToNode.Guid;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值
FromNode.NeedResultNodes[type].Remove(ToNode);
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid,
ToNode.Guid,
fromNodeGuid,
toNodeGuid,
ArgIndex,
JunctionOfConnectionType.Arg,
ConnectionArgSourceType.GetPreviousNodeData,
@@ -440,8 +461,15 @@ namespace Serein.NodeFlow.Model.Operation
});
}
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = FromNode.Guid; // 设置
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = FromNode.Guid;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType;
FromNode.NeedResultNodes[type].Add(ToNode);
if (ToNode.ControlType == NodeControlType.Script)
{
// 脚本节点入参确定/改变来源时,更改对应的入参数据类型
ToNode.MethodDetails.ParameterDetailss[ArgIndex].DataType = FromNode.MethodDetails.ReturnType;
}
await TriggerEvent(() =>
{
@@ -469,6 +497,7 @@ namespace Serein.NodeFlow.Model.Operation
/// <param name="index"></param>
private async Task<bool> RemoveArgConnection()
{
if (ToNode.MethodDetails.ParameterDetailss is null) return false;
var type = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType;
FromNode.NeedResultNodes[type].Remove(ToNode);
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = string.Empty;