diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 24f0346..7c64628 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -50,15 +50,14 @@ namespace Serein.Library Flipflop, /// /// 动作节点,可以异步等待 - /// 如果不显式的设置入参数据(例如文本、@Get取值表达式),就会默认使用该节点的运行时上一个节点的数据。 - /// 假如上一节点是某个对象,但入参需要的是对象中某个属性/字段,则建议使用取值表达式、表达式节点获取所需要的数据。 + /// 如果不显式的设置入参数据,就会默认使用该节点的运行时上一个节点的数据。 + /// 假如上一节点是某个对象,但入参需要的是对象中某个属性/字段,则建议使用表达式节点获取所需要的数据。 /// 关于@Get取值表达式的使用方法: /// public class UserInfo /// { - /// public string Name; // 取值表达式:@Get .Name - /// public string[] PhoneNums; // 获取第1项的取值表达式:@Get .PhoneNums[0] + /// public string Name; // 取值表达式:@Get .Name + /// public string[] PhoneNums; // 取值表达式:@Get .PhoneNums /// } - /// 取值表达式可以符合直觉的如此获取实例成员:@Get .Data.Array[2].Data...... /// 格式说明:@Get大小写不敏感,然后空一格,需要标记“.”,然后才是获取成员名称(成员名称大小写敏感)。 /// Action, diff --git a/Library/FlowBaseLibrary.cs b/Library/FlowBaseLibrary.cs index a470cd6..915f960 100644 --- a/Library/FlowBaseLibrary.cs +++ b/Library/FlowBaseLibrary.cs @@ -1,11 +1,6 @@ -using Serein.Library; -using Serein.Library.Api; -using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression; +using Serein.Library.Utils; using System; using System.Collections.Generic; -using System.Dynamic; -using System.Linq; using System.Text; namespace Serein.Library diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 7ce5491..eae280a 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -1,9 +1,6 @@ using Serein.Library.Api; using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; @@ -119,10 +116,6 @@ namespace Serein.Library public partial class ParameterDetails { - - - - /// /// 用于创建元数据 @@ -276,17 +269,8 @@ namespace Serein.Library } } - // 5. 表达式处理 - if (IsExplicitData && DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) - { - var lower = DataValue.ToLowerInvariant(); - if (lower.StartsWith("@get") || lower.StartsWith("@dtc") || lower.StartsWith("@data")) - { - inputParameter = SerinExpressionEvaluator.Evaluate(DataValue, inputParameter, out _); - } - } - // 6. 类型转换 + // 5. 类型转换 if (!DataType.IsValueType && inputParameter is null) throw new Exception($"[arg{Index}] 参数不能为null"); @@ -303,7 +287,10 @@ namespace Serein.Library throw new Exception($"[arg{Index}] 类型不匹配:目标类型为 {DataType},实际类型为 {actualType}"); } - /// + + + + /* /// /// 转为方法入参数据 /// /// @@ -347,7 +334,7 @@ namespace Serein.Library - /*#region “枚举-类型”转换器 + *//*#region “枚举-类型”转换器 if (ExplicitType is not null && ExplicitType.IsEnum && DataType != ExplicitType) { var resultEnum = Enum.Parse(ExplicitType, DataValue); @@ -359,7 +346,7 @@ namespace Serein.Library return value; } } - #endregion*/ + #endregion*//* // 需要获取预入参数据 object inputParameter; @@ -473,7 +460,7 @@ namespace Serein.Library throw new Exception($"[arg{Index}][{Name}][{DataType}]入参类型不符合,当前预入参类型为{inputParameterType}"); } - +*/ public override string ToString() { return $"[{this.Index}] {(string.IsNullOrWhiteSpace(this.Description) ? string.Empty : $"({this.Description})")}{this.Name} : {this.DataType?.FullName}"; diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 214d2b1..a9448bb 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -42,12 +42,15 @@ + + + diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs index 912367b..bbf09e5 100644 --- a/Library/Utils/SereinExpression/SereinConditionParser.cs +++ b/Library/Utils/SereinExpression/SereinConditionParser.cs @@ -63,6 +63,7 @@ namespace Serein.Library.Utils.SereinExpression /// /// 条件解析器(生成IL进行判断) /// 格式: data.[propertyName] [operator] [value] + /// 返回值:boolea /// public class SereinConditionParser { @@ -113,11 +114,6 @@ namespace Serein.Library.Utils.SereinExpression } - //bool ContainsArithmeticOperators(string expression) - //{ - // return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/'); - //} - } /// diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs index a4ca601..f312ff4 100644 --- a/NodeFlow/Env/LocalFlowEnvironment.cs +++ b/NodeFlow/Env/LocalFlowEnvironment.cs @@ -1,8 +1,6 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression; -using Serein.NodeFlow.Model.Library; using Serein.NodeFlow.Services; using Serein.NodeFlow.Tool; using System.Text; @@ -667,7 +665,7 @@ namespace Serein.NodeFlow.Env // "NodeModel.Path" if (TryGetNodeModel(nodeGuid, out var nodeModel)) { - SerinExpressionEvaluator.Evaluate($"@Set .{path} = {value}", nodeModel, out _); // 更改对应的数据 + //SerinExpressionEvaluator.Evaluate($"@Set .{path} = {value}", nodeModel, out _); // 更改对应的数据 } diff --git a/NodeFlow/Model/Node/SingleConditionNode.cs b/NodeFlow/Model/Node/SingleConditionNode.cs index ebe8952..9849f7d 100644 --- a/NodeFlow/Model/Node/SingleConditionNode.cs +++ b/NodeFlow/Model/Node/SingleConditionNode.cs @@ -1,9 +1,6 @@ using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils; -using Serein.Library.Utils.SereinExpression; -using System; -using System.ComponentModel; +using Serein.Script; using System.Dynamic; using System.Linq.Expressions; @@ -125,44 +122,41 @@ namespace Serein.NodeFlow.Model // 使用自动取参 var pd = MethodDetails.ParameterDetailss[INDEX_EXPRESSION]; var hasNode = context.Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode); - if (hasNode) + if (!hasNode) { - context.NextOrientation = ConnectionInvokeType.IsError; - return new FlowResult(this.Guid, context); - } - if (hasNode) - { - return new FlowResult(this.Guid, context); - } - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) - { - result = context.GetFlowData(argSourceNode.Guid).Value; // 使用自定义节点的参数 - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) - { - CancellationTokenSource cts = new CancellationTokenSource(); - var nodeResult = await argSourceNode.ExecutingAsync(context, cts.Token); - result = nodeResult.Value; - cts?.Cancel(); - cts?.Dispose(); + /*context.NextOrientation = ConnectionInvokeType.IsError; + return new FlowResult(this.Guid, context);*/ + parameter = null; } else { - result = context.TransmissionData(this.Guid).Value; // 条件节点透传上一节点的数据 + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + result = context.GetFlowData(argSourceNode.Guid).Value; // 使用自定义节点的参数 + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + CancellationTokenSource cts = new CancellationTokenSource(); + var nodeResult = await argSourceNode.ExecutingAsync(context, cts.Token); + result = nodeResult.Value; + cts?.Cancel(); + cts?.Dispose(); + } + else + { + result = context.TransmissionData(this.Guid).Value; // 条件节点透传上一节点的数据 + } + parameter = result; // 使用上一节点的参数 } - parameter = result; // 使用上一节点的参数 + } else { var exp = ExplicitData?.ToString(); - if (!string.IsNullOrEmpty(exp) && exp.StartsWith('@')) + if (!string.IsNullOrWhiteSpace(exp) && exp.StartsWith("@Get", StringComparison.OrdinalIgnoreCase)) { - parameter = result; // 表达式获取上一节点数据 - if (parameter is not null) - { - parameter = SerinExpressionEvaluator.Evaluate(exp, parameter, out _); - } + parameter = GetValueExpressionAsync(context, result, exp); } else { @@ -173,7 +167,8 @@ namespace Serein.NodeFlow.Model bool judgmentResult = false; try { - judgmentResult = SereinConditionParser.To(parameter, Expression); + judgmentResult = await ConditionExpressionAsync(context, parameter, Expression); + //judgmentResult = SereinConditionParser.To(parameter, Expression); context.NextOrientation = judgmentResult ? ConnectionInvokeType.IsSucceed : ConnectionInvokeType.IsFail; } catch (Exception ex) @@ -189,6 +184,95 @@ namespace Serein.NodeFlow.Model + + /// + /// 解析取值表达式 + /// + private SereinScript getValueScript; + private string getValueExpression; + private async Task GetValueExpressionAsync(IFlowContext flowContext, object? data, string expression) + { + var dataName = nameof(data); + if (!expression.Equals(getValueExpression)) + { + getValueExpression = expression; + getValueScript = new SereinScript(); + var dataType = data is null ? typeof(object) : data.GetType(); + if (expression[0..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase)) + { + expression = expression[4..]; + // 表达式默认包含 “.” + expression = $"return {dataName}{expression};"; + } + else + { + // 表达式默认包含 “.” + expression = $"return {expression};"; + } + var resultType = getValueScript.ParserScript(expression, new Dictionary + { + { dataName, dataType}, + }); + + } + + IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext); + scriptContext.SetVarValue(dataName, data); + + var result = await getValueScript.InterpreterAsync(scriptContext); + return result; + } + + + + /// + /// 解析条件 + /// + private SereinScript conditionScript; + private string conditionExpression; + + private async Task ConditionExpressionAsync(IFlowContext flowContext, object? data, string expression) + { + var dataName = nameof(data); + if (!expression.Equals(conditionExpression)) + { + conditionExpression = expression; + conditionScript = new SereinScript(); + var dataType = data is null ? typeof(object) : data.GetType(); + expression = expression.Trim(); + if (expression[0] == '.') + { + // 对象取值 + expression = $"return {dataName}{expression};"; + } + else + { + // 直接表达式 + expression = $"return {dataName}{expression};"; + } + _ = conditionScript.ParserScript(expression, new Dictionary + { + { dataName, dataType}, + }); + } + + IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext); + scriptContext.SetVarValue(dataName, data); + + var result = await conditionScript.InterpreterAsync(scriptContext); + if(result is bool @bool) + { + return @bool; + } + else + { + flowContext.NextOrientation = ConnectionInvokeType.IsError; + return false; + } + } + + + } diff --git a/NodeFlow/Model/Node/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs index 9151ca0..76507d7 100644 --- a/NodeFlow/Model/Node/SingleExpOpNode.cs +++ b/NodeFlow/Model/Node/SingleExpOpNode.cs @@ -1,6 +1,6 @@ using Serein.Library; using Serein.Library.Api; -using Serein.Library.Utils.SereinExpression; +using Serein.Script; using System.Dynamic; namespace Serein.NodeFlow.Model @@ -96,44 +96,40 @@ namespace Serein.NodeFlow.Model var pd = MethodDetails.ParameterDetailss[0]; var hasNode = context.Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode); - if (hasNode) + if (!hasNode) { - context.NextOrientation = ConnectionInvokeType.IsError; - return new FlowResult(this.Guid, context); + /*context.NextOrientation = ConnectionInvokeType.IsError; + return new FlowResult(this.Guid, context);*/ + parameter = null; } - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + else { - // 使用自定义节点的参数 - parameter = context.GetFlowData(argSourceNode.Guid).Value; - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) - { - // 立刻调用目标节点,然后使用其返回值 - var cts = new CancellationTokenSource(); - var result = await argSourceNode.ExecutingAsync(context, cts.Token); - cts?.Cancel(); - cts?.Dispose(); - parameter = result.Value; + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + // 使用自定义节点的参数 + parameter = context.GetFlowData(argSourceNode.Guid).Value; + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + // 立刻调用目标节点,然后使用其返回值 + var cts = new CancellationTokenSource(); + var result = await argSourceNode.ExecutingAsync(context, cts.Token); + cts?.Cancel(); + cts?.Dispose(); + parameter = result.Value; + } } + + /* else { // 条件节点透传上一节点的数据 parameter = context.TransmissionData(this.Guid); } - +*/ try { - var newData = SerinExpressionEvaluator.Evaluate(Expression, parameter, out bool isChange); - object? result = null; - if (isChange) - { - result = newData; - } - else - { - result = parameter; - } - + var result = await GetValueExpressionAsync(context, parameter, Expression); context.NextOrientation = ConnectionInvokeType.IsSucceed; return new FlowResult(this.Guid, context, result); } @@ -146,5 +142,45 @@ namespace Serein.NodeFlow.Model } + + + /// + /// 解析取值表达式 + /// + private SereinScript getValueScript; + private string getValueExpression; + private async Task GetValueExpressionAsync(IFlowContext flowContext, object? data, string expression) + { + var dataName = nameof(data); + if (!expression.Equals(getValueExpression)) + { + getValueExpression = expression; + getValueScript = new SereinScript(); + var dataType = data is null ? typeof(object) : data.GetType(); + if (expression[0..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase)) + { + expression = expression[4..]; + // 表达式默认包含 “.” + expression = $"return {dataName}{expression};"; + } + else + { + // 表达式默认包含 “.” + expression = $"return {expression};"; + } + var resultType = getValueScript .ParserScript(expression, new Dictionary + { + { dataName, dataType}, + }); + + } + + IScriptInvokeContext scriptContext = new ScriptInvokeContext(flowContext); + scriptContext.SetVarValue(dataName, data); + + var result = await getValueScript .InterpreterAsync(scriptContext); + return result; + } + } } diff --git a/README.md b/README.md index 5737a75..8805196 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,12 @@ https://space.bilibili.com/33526379 * 其它获取到全局数据的方式: 1. 表达式 : ~~~ - @Get #KeyName# // 使用##符号表达全局数据KeyName的标识符 + @Get global("DataName") // 使用global表达全局数据 DataName 的标识符 ~~~ 2. Script代码: ~~~~ - data = global("YOUR-NAME"); // 获取全局数据节点中的数据 + data = global("DataName"); // 获取全局数据节点中 DataName 的数据 ~~~~ - 3. C# 代码中(不建议): - ~~~ - SereinEnv.GetFlowGlobalData("KeyName"); // 获取全局数据 - SereinEnv.AddOrUpdateFlowGlobalData("KeyName", obj); // 设置/更新全局数据,不建议 - ~~~ ## 3. 从DLL生成控件的枚举值: * **Action - 动作** * 入参:自定义。如果入参类型为IFlowContext,会传入当前的上下文;如果入参类型为IFlowNode,会传入节点对应的实体Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 diff --git a/Serein.Script/ScriptInvokeContext.cs b/Serein.Script/ScriptInvokeContext.cs index 1e70195..ee62bb0 100644 --- a/Serein.Script/ScriptInvokeContext.cs +++ b/Serein.Script/ScriptInvokeContext.cs @@ -2,7 +2,7 @@ namespace Serein.Script { - public class ScriptInvokeContext : IScriptInvokeContext + public sealed class ScriptInvokeContext : IScriptInvokeContext { /// /// 脚本使用流程上下文 diff --git a/Workbench/Themes/ObjectViewerControl.xaml.cs b/Workbench/Themes/ObjectViewerControl.xaml.cs index 2edf486..e0ca417 100644 --- a/Workbench/Themes/ObjectViewerControl.xaml.cs +++ b/Workbench/Themes/ObjectViewerControl.xaml.cs @@ -1,27 +1,9 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using Serein.Library.Utils.SereinExpression; +using Serein.Library.Api; using Serein.Workbench.Tool; -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Markup.Primitives; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using System.Xml.Linq; using static Serein.Workbench.Themes.TypeViewerWindow; namespace Serein.Workbench.Themes diff --git a/Workbench/Views/BaseNodesView.xaml b/Workbench/Views/BaseNodesView.xaml index 3c0006a..b47227f 100644 --- a/Workbench/Views/BaseNodesView.xaml +++ b/Workbench/Views/BaseNodesView.xaml @@ -15,8 +15,8 @@ - - + +