1. 新增了脚本节点之间连接时,入参类型伴随来源节点的返回类型改变而改变。

2. 重新优化了Script项目脚本生成代码的缩进排版
3. 修复了Script中对于Double字面量错误的使用了Float解析的bug
This commit is contained in:
fengjiayi
2025-07-31 15:45:02 +08:00
parent 827a9242ae
commit 5f6a58168a
10 changed files with 273 additions and 183 deletions

View File

@@ -27,13 +27,15 @@ namespace Serein.Library
RunState = RunState.Running;
}
private string _guid = global::System.Guid.NewGuid().ToString();
/// <summary>
/// 是否记录流程调用信息
/// </summary>
public bool IsRecordInvokeInfo { get; set; } = true;
string IFlowContext.Guid => _guid;
/// <summary>
/// 标识流程的Guid
/// </summary>
public string Guid { get; private set; } = global::System.Guid.NewGuid().ToString();
/// <summary>
/// 运行环境
@@ -97,7 +99,7 @@ namespace Serein.Library
FlowInvokeInfo flowInvokeInfo = new FlowInvokeInfo
{
ContextGuid = this._guid,
ContextGuid = this.Guid,
Id = id,
PreviousNodeGuid = previousNode?.Guid,
Method = theNode.MethodDetails?.MethodName,
@@ -258,7 +260,7 @@ namespace Serein.Library
NextOrientation = ConnectionInvokeType.None;
RunState = RunState.Running;
flowInvokeInfos.Clear();
_guid = global::System.Guid.NewGuid().ToString();
Guid = global::System.Guid.NewGuid().ToString();
}
/// <summary>

View File

@@ -69,6 +69,7 @@ namespace Serein.NodeFlow.Model.Nodes
};
this.MethodDetails.ParameterDetailss = [.. pd];
this.MethodDetails.ReturnType = typeof(object);
}
/// <summary>
@@ -103,14 +104,12 @@ namespace Serein.NodeFlow.Model.Nodes
{
if(token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
object? parameter = null;// context.TransmissionData(this); // 表达式节点使用上一节点数据
object? parameter = null;// 表达式节点使用上一节点数据
var pd = MethodDetails.ParameterDetailss[0];
var hasNode = context.Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode);
if (!hasNode)
{
/*context.NextOrientation = ConnectionInvokeType.IsError;
return new FlowResult(this.Guid, context);*/
parameter = null;
}
else
@@ -130,14 +129,6 @@ namespace Serein.NodeFlow.Model.Nodes
parameter = result.Value;
}
}
/*
else
{
// 条件节点透传上一节点的数据
parameter = context.TransmissionData(this.Guid);
}
*/
try
{
var result = await GetValueExpressionAsync(context, parameter, Expression);
@@ -184,6 +175,7 @@ namespace Serein.NodeFlow.Model.Nodes
{ dataName, dataType},
});
}
IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext);

View File

@@ -49,6 +49,7 @@ namespace Serein.NodeFlow.Model.Nodes
public SingleScriptNode(IFlowEnvironment environment) : base(environment)
{
sereinScript = new SereinScript();
}
static SingleScriptNode()
@@ -106,6 +107,38 @@ namespace Serein.NodeFlow.Model.Nodes
}
/// <summary>
/// 更新节点返回类型
/// </summary>
/// <param name="newType"></param>
private void UploadNodeReturnType(Type newType)
{
MethodDetails.ReturnType = newType;
foreach (var ct in NodeStaticConfig.ConnectionArgSourceTypes)
{
var newResultNodes = NeedResultNodes[ct].ToArray();
foreach (var node in newResultNodes)
{
if(node is SingleScriptNode scriptNode)
{
var pds = scriptNode.MethodDetails.ParameterDetailss;
foreach (var pd in pds)
{
if (pd.ArgDataSourceType == ct &&
pd.ArgDataSourceNodeGuid == this.Guid)
{
pd.DataType = newType; // 更新参数类型
}
}
//scriptNode.ReloadScript(); // 重新加载目标脚本节点
}
}
}
}
/// <summary>
/// 保存项目时保存脚本代码、方法入参类型、返回值类型
/// </summary>
@@ -181,56 +214,75 @@ namespace Serein.NodeFlow.Model.Nodes
}
private object reloadLockObj = new object();
/// <summary>
/// 重新加载脚本代码
/// </summary>
public bool ReloadScript()
{
lock (reloadLockObj)
{
if (!CheckRepeatParamter())
return false;
var argTypes = GetParamterTypeInfo();
try
{
HashSet<string> varNames = new HashSet<string>();
foreach (var pd in MethodDetails.ParameterDetailss)
{
if (varNames.Contains(pd.Name))
{
throw new Exception($"脚本节点重复的变量名称:{pd.Name} - {Guid}");
}
varNames.Add(pd.Name);
}
var argTypes = MethodDetails.ParameterDetailss
.Select(pd =>
{
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
{
var Type = pd.DataType;
return (pd.Name, Type);
}
if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) &&
node.MethodDetails?.ReturnType is not null)
{
pd.DataType = node.MethodDetails.ReturnType;
var Type = node.MethodDetails.ReturnType;
return (pd.Name, Type);
}
return default;
})
.Where(x => x != default)
.ToDictionary(x => x.Name, x => x.Type); // 准备预定义类型
var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点
MethodDetails.ReturnType = returnType;
return true;
UploadNodeReturnType(returnType);
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.WARN, ex.Message);
return false; // 解析失败
}
return true;
}
}
/// <summary>
/// 检查脚本参数是否有重复的名称
/// </summary>
/// <returns></returns>
private bool CheckRepeatParamter()
{
bool isSueccess = true;
HashSet<string> varNames = new HashSet<string>();
foreach (var pd in MethodDetails.ParameterDetailss)
{
if (varNames.Contains(pd.Name))
{
SereinEnv.WriteLine(InfoType.ERROR, $"脚本节点重复的变量名称:{pd.Name} - {Guid}");
isSueccess = false;
}
varNames.Add(pd.Name);
}
return isSueccess;
}
/// <summary>
/// 获取参数类型信息
/// </summary>
/// <returns></returns>
private Dictionary<string, Type> GetParamterTypeInfo()
{
Dictionary<string, Type> argTypes = [];
var pds = MethodDetails.ParameterDetailss.ToArray();
foreach (var pd in pds)
{
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
{
argTypes[pd.Name] = pd.DataType;
}
if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) &&
node.MethodDetails?.ReturnType is not null)
{
pd.DataType = node.MethodDetails.ReturnType;
var Type = node.MethodDetails.ReturnType;
argTypes[pd.Name] = Type;
}
}
return argTypes;
}
/// <summary>
@@ -246,39 +298,11 @@ namespace Serein.NodeFlow.Model.Nodes
methodName = $"FlowMethod_{tmp}";
}
HashSet<string> varNames = new HashSet<string>();
foreach (var pd in MethodDetails.ParameterDetailss)
{
if (varNames.Contains(pd.Name))
{
throw new Exception($"脚本节点重复的变量名称:{pd.Name} - {Guid}");
}
varNames.Add(pd.Name);
}
var argTypes = MethodDetails.ParameterDetailss
.Select(pd =>
{
if(pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
{
var Type = pd.DataType;
return (pd.Name, Type);
}
if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) &&
node.MethodDetails?.ReturnType is not null)
{
pd.DataType = node.MethodDetails.ReturnType;
var Type = node.MethodDetails.ReturnType;
return (pd.Name, Type);
}
return default;
})
.Where(x => x != default)
.ToDictionary(x => x.Name, x => x.Type); // 准备预定义类型
if (!CheckRepeatParamter())
throw new Exception($"脚本节点入参重复");
var argTypes = GetParamterTypeInfo();
var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点
MethodDetails.ReturnType = returnType;
UploadNodeReturnType(returnType);
var scriptMethodInfo = sereinScript.ConvertCSharpCode(methodName, argTypes);
return scriptMethodInfo;
}
@@ -297,6 +321,13 @@ namespace Serein.NodeFlow.Model.Nodes
/// <returns></returns>
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
{
if (IsScriptChanged)
{
if (!ReloadScript())
{
return FlowResult.Fail(this.Guid, context, "脚本解析失败,请检查脚本代码");
}
}
var result = await ExecutingAsync(this, context, token);
return result;
}
@@ -311,6 +342,14 @@ namespace Serein.NodeFlow.Model.Nodes
public async Task<FlowResult> ExecutingAsync(NodeModelBase flowCallNode, IFlowContext context, CancellationToken token)
{
if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消");
if (IsScriptChanged)
{
if (!ReloadScript())
{
return FlowResult.Fail(this.Guid, context, "脚本解析失败,请检查脚本代码");
}
}
var @params = await flowCallNode.GetParametersAsync(context, token);
IScriptInvokeContext scriptContext = new ScriptInvokeContext(context);

View File

@@ -189,6 +189,7 @@ namespace Serein.NodeFlow.Model.Operations
return false;
}
if (ToNode.ControlType is not (NodeControlType.GlobalData or NodeControlType.ExpCondition or NodeControlType.ExpOp))
{
@@ -199,6 +200,7 @@ namespace Serein.NodeFlow.Model.Operations
}
if (ToNode.MethodDetails.ParameterDetailss.Length > 0)
{
var fromNoeReturnType = fromNode.MethodDetails.ReturnType;
if (fromNoeReturnType != null
&& fromNoeReturnType != typeof(object)
@@ -206,6 +208,12 @@ namespace Serein.NodeFlow.Model.Operations
&& fromNoeReturnType != typeof(Unit))
{
var toNodePds = toNode.MethodDetails.ParameterDetailss;
if (toNodePds.Any(pd=>pd.IsExplicitData))
{
checkTypeState = true; // 目标节点使用了显式的入参,无需关心参数是否匹配
}
else
{
foreach (ParameterDetails toNodePd in toNodePds)
{
if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点
@@ -217,6 +225,7 @@ namespace Serein.NodeFlow.Model.Operations
}
if (toPds.Count == 0)
{
var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接
checkTypeState = any;
}
@@ -225,6 +234,8 @@ namespace Serein.NodeFlow.Model.Operations
checkTypeState = true; // 类型检查初步通过
}
}
}
}
}

View File

@@ -29,21 +29,21 @@ namespace Serein.Script.Node
}
/// <summary>
/// int 整数型字面量
/// long 整数型字面量
/// </summary>
public class NumberLongNode(long vlaue) : NumberNode<long>(vlaue)
{
}
/// <summary>
/// int 整数型字面量
/// float 字面量
/// </summary>
public class NumberFloatNode(float vlaue) : NumberNode<float>(vlaue)
{
}
/// <summary>
/// int 整数型字面量
/// double 字面量
/// </summary>
public class NumberDoubleNode(double vlaue) : NumberNode<double>(vlaue)
{

View File

@@ -321,11 +321,11 @@ namespace Serein.Script
async Task<object?> InterpreterFunctionCallNodeAsync(IScriptInvokeContext context, FunctionCallNode functionCallNode)
{
// 获取流程上下文
if (context.FlowContext != null && functionCallNode.FunctionName.Equals("getFlowApi", StringComparison.OrdinalIgnoreCase))
if (context.FlowContext != null && functionCallNode.FunctionName.Equals("getFlowContext", StringComparison.OrdinalIgnoreCase))
{
return context.FlowContext;
}
else if (functionCallNode.FunctionName.Equals("getScriptApi", StringComparison.OrdinalIgnoreCase))
else if (functionCallNode.FunctionName.Equals("getScriptContext", StringComparison.OrdinalIgnoreCase))
{
return context;
}

View File

@@ -1087,7 +1087,7 @@ namespace Serein.Script
}
else if (_currentToken.Type == TokenType.NumberDouble)
{
var value = float.Parse(_currentToken.Value);
var value = double.Parse(_currentToken.Value);
NextToken(); // 消耗 double 浮点数
return new NumberDoubleNode(value).SetTokenInfo(factorToken);
}

View File

@@ -51,6 +51,13 @@ namespace Serein.Script
_codeBuilder.Append(code);
}
private void CompleteCurrentStatement()
{
_codeBuilder.Append($";{Environment.NewLine}");
}
private string Tab => new string (' ', _indentLevel* 4);
private void Indent() => _indentLevel++;
private void Unindent() => _indentLevel--;
@@ -119,7 +126,10 @@ namespace Serein.Script
foreach (var stmt in programNode.Statements)
{
ConvertCode(stmt);
Append(";");
if (stmt is not (IfNode or WhileNode))
{
CompleteCurrentStatement();
}
}
if (_symbolInfos[programNode] == typeof(void))
{
@@ -167,16 +177,16 @@ namespace Serein.Script
case ReturnNode returnNode: // 程序退出节点
void ConvertCodeOfReturnNode(ReturnNode returnNode)
{
Append(Tab);
if (returnNode.Value is not null)
{
Append($"return ");
ConvertCode(returnNode.Value);
Append(";");
AppendLine(string.Empty);
}
else
{
AppendLine("return defult;");
AppendLine("return defult");
}
}
ConvertCodeOfReturnNode(returnNode);
@@ -212,41 +222,22 @@ namespace Serein.Script
{
var varName = identifierNode.Name;
Append(varName);
/*if (_local.TryGetValue(varName, out var type))
{
// 定义过,需要使用变量
Append(varName);
}
else
{
if (_symbolInfos.TryGetValue(identifierNode, out type))
{
// 不存在,定义变量
Append($"global::{type.FullName} {varName}");
_local[varName] = type;
//Append(varName);
}
else
{
throw new Exception($"加载符号表时,无法匹配 IdentifierNode 节点的类型。 name {varName}");
}
}*/
}
ConvertCodeOfIdentifierNode(identifierNode);
break;
case IfNode ifNode: // if语句结构
void ConvertCodeOfIfNode(IfNode ifNOde)
{
AppendLine("");
Append($"if(");
Append($"{Tab}if(");
ConvertCode(ifNOde.Condition); // 解析条件
Append($" == true)");
Append($" == true){Environment.NewLine}");
AppendLine("{");
Indent();
foreach (var item in ifNOde.TrueBranch)
{
ConvertCode(item);
AppendLine(string.Empty);
CompleteCurrentStatement();
}
Unindent();
AppendLine("}");
@@ -256,7 +247,7 @@ namespace Serein.Script
foreach (var item in ifNOde.FalseBranch)
{
ConvertCode(item);
AppendLine(string.Empty);
CompleteCurrentStatement();
}
Unindent();
AppendLine("}");
@@ -275,8 +266,7 @@ namespace Serein.Script
foreach (var item in whileNode.Body)
{
ConvertCode(item);
Append(";");
AppendLine(string.Empty);
CompleteCurrentStatement();
}
Unindent();
AppendLine("}");
@@ -286,6 +276,8 @@ namespace Serein.Script
case AssignmentNode assignmentNode: // 变量赋值语句
void ConvertCodeOfAssignmentNode(AssignmentNode assignmentNode)
{
Append(Tab);
ConvertCode(assignmentNode.Target);
Append(" = ");
if (assignmentNode.Value is null)
@@ -296,8 +288,6 @@ namespace Serein.Script
{
ConvertCode(assignmentNode.Value);
}
Append(";");
AppendLine(string.Empty);
}
ConvertCodeOfAssignmentNode(assignmentNode);
break;
@@ -318,7 +308,6 @@ namespace Serein.Script
ConvertCode(collectionAssignmentNode.Collection);
Append(" = ");
ConvertCode(collectionAssignmentNode.Value);
Append(";");
AppendLine(string.Empty);
}
ConvertCodeOfCollectionAssignmentNode(collectionAssignmentNode);
@@ -333,6 +322,25 @@ namespace Serein.Script
}
ConvertCodeOfCollectionIndexNode(collectionIndexNode);
break;
case ArrayDefintionNode arrayDefintionNode:
void ConvertCodeOfArrayDefintionNode(ArrayDefintionNode arrayDefintionNode)
{
var arrType = this._symbolInfos[arrayDefintionNode];
var tab = new string(' ', (_indentLevel + 1) * 4);
Append($"new global::{arrType.FullName}{{{Environment.NewLine}");
for (int index = 0; index < arrayDefintionNode.Elements.Count; index++)
{
ASTNode? e = arrayDefintionNode.Elements[index];
Append(tab + " ");
ConvertCode(e);
if (index < arrayDefintionNode.Elements.Count - 1)
Append($", {Environment.NewLine}");
}
Append($"{Environment.NewLine}{tab}}}");
}
ConvertCodeOfArrayDefintionNode(arrayDefintionNode);
break;
case ClassTypeDefinitionNode classTypeDefinitionNode: // 类型定义
void ConvertCodeOfClassTypeDefinitionNode(ClassTypeDefinitionNode classTypeDefinitionNode)
{
@@ -417,8 +425,6 @@ namespace Serein.Script
ConvertCode(memberAssignmentNode.Object);
Append($".{memberAssignmentNode.MemberName} = ");
ConvertCode(memberAssignmentNode.Value);
Append($";");
AppendLine(string.Empty);
}
ConvertCodeOfMemberAssignmentNode(memberAssignmentNode);
break;
@@ -495,6 +501,9 @@ namespace Serein.Script
}
}
}

View File

@@ -1,4 +1,5 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Script.Node;
using Serein.Script.Node.FlowControl;
@@ -104,12 +105,22 @@ namespace Serein.Script
return typeof(void);
case ReturnNode returnNode: // 程序退出节点
Type AnalysisReturnNode(ReturnNode returnNode)
{
if(returnNode.Value is null)
{
var resultType = typeof(void);
NodeSymbolInfos[returnNode] = resultType;
return resultType;
}
else
{
var resultType = Analysis(returnNode.Value);
NodeSymbolInfos[returnNode.Value] = resultType;
NodeSymbolInfos[returnNode] = resultType;
return resultType;
}
}
return AnalysisReturnNode(returnNode);
case NullNode nullNode: // null
NodeSymbolInfos[nullNode] = typeof(object);
@@ -406,9 +417,25 @@ namespace Serein.Script
{
var objectType = Analysis(memberFunctionCallNode.Object);
var types = memberFunctionCallNode.Arguments.Select(arg => Analysis(arg)).ToArray();
var methodInfo = objectType.GetMethod(memberFunctionCallNode.FunctionName, types);
if (methodInfo is null)
{
var t = objectType.GetMethods().Where(m => m.Name.Equals(memberFunctionCallNode.FunctionName)).ToArray();
if(t.Length > 0)
{
var content = string.Join($";{Environment.NewLine}", t.Select(m => m.ToString()));
throw new Exception($"类型 {objectType} 没有指定的重载方法 " +
$"{memberFunctionCallNode.FunctionName}({string.Join(",", types.Select(t => t.Name))})" +
$"但存在其它重载方法:{content}");
}
else
{
throw new Exception($"类型 {objectType} 没有方法 {memberFunctionCallNode.FunctionName}");
}
}
for (int index = 0; index < memberFunctionCallNode.Arguments.Count; index++)
{
ASTNode argNode = memberFunctionCallNode.Arguments[index];
@@ -428,6 +455,16 @@ namespace Serein.Script
case FunctionCallNode functionCallNode: // 外部挂载的函数调用
Type AnalysisFunctionCallNode(FunctionCallNode functionCallNode)
{
// 获取流程上下文
if (functionCallNode.FunctionName.Equals("getFlowContext", StringComparison.OrdinalIgnoreCase))
{
return typeof(IFlowContext);
}
else if (functionCallNode.FunctionName.Equals("getScriptContext", StringComparison.OrdinalIgnoreCase))
{
return typeof(IScriptInvokeContext);
}
if (!SereinScript.FunctionInfos.TryGetValue(functionCallNode.FunctionName, out var methodInfo))
{
throw new Exception($"脚本没有挂载方法 {functionCallNode.FunctionName}");

View File

@@ -1281,7 +1281,7 @@ namespace Serein.Workbench.Views
private ContextMenu ConfiguerSelectionRectangle()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", (s, e) =>
/*contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", (s, e) =>
{
if (selectNodeControls.Count > 0)
{
@@ -1296,7 +1296,7 @@ namespace Serein.Workbench.Views
}
}
SelectionRectangle.Visibility = Visibility.Collapsed;
}));
}));*/
return contextMenu;
// nodeControl.ContextMenu = contextMenu;
}