优化了脚本生成AST时的代码提示,增加了脚本运行时错误提示。

This commit is contained in:
fengjiayi
2025-07-09 21:49:26 +08:00
parent 4da8bf6b84
commit 70f674ca1b
32 changed files with 1218 additions and 266 deletions

View File

@@ -69,12 +69,12 @@ namespace Serein.Library.Api
/// <param name="argSourceType">决定了方法参数来源</param>
/// <param name="argIndex">设置第几个参数</param>
void ConnectArgSourceNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex);
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex);
/// <summary>
/// 移除两个节点之间的方法调用关系

View File

@@ -288,11 +288,11 @@ namespace Serein.Library.Api
/// <summary>
/// 连接类型
/// </summary>
public ConnectionInvokeType ConnectionInvokeType { get;}
public ConnectionInvokeType ConnectionInvokeType { get; } = ConnectionInvokeType.None;
/// <summary>
/// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系
/// </summary>
public ConnectChangeType ChangeType { get;}
public ConnectChangeType ChangeType { get;}
/// <summary>
/// 指示需要创建什么类型的连接线
/// </summary>
@@ -300,7 +300,7 @@ namespace Serein.Library.Api
/// <summary>
/// 节点对应的方法入参所需参数来源
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get;}
public ConnectionArgSourceType ConnectionArgSourceType { get;}
/// <summary>
/// 第几个参数
/// </summary>

View File

@@ -105,6 +105,11 @@ namespace Serein.Library
/// </summary>
public bool HasParamsArg => _paramsArgIndex >= 0;
/// <summary>
/// 是否为静态方法
/// </summary>
public bool IsStatic { get; set; }
/// <summary>
/// 新增可变参数
/// </summary>
@@ -119,10 +124,13 @@ namespace Serein.Library
return false;
}
var newPd = ParameterDetailss[index].CloneOfModel(this.NodeModel); // 复制出属于本身节点的参数描述
newPd.Index = ParameterDetailss.Length; // 更新索引
newPd.IsParams = true;
ParameterDetailss = ArrayHelper.AddToArray(ParameterDetailss, newPd); // 新增
return true;
}
@@ -140,6 +148,7 @@ namespace Serein.Library
parameterDetails.Index = ParameterDetailss.Length; // 更新索引
parameterDetails.IsParams = true;
ParameterDetailss = ArrayHelper.AddToArray(ParameterDetailss, parameterDetails); // 新增
return true;
}
@@ -299,6 +308,7 @@ namespace Serein.Library
ParamsArgIndex = this.ParamsArgIndex, // 拷贝
ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(), // 拷贝属于节点方法的新入参描述
IsAsync = this.IsAsync, // 拷贝
IsStatic = this.IsStatic, // 拷贝
};
return md;

View File

@@ -13,18 +13,17 @@ namespace Serein.Library
/// <summary>
/// 基础功能
/// </summary>
[DynamicFlow(Name ="[基础功能]")]
public static class SereinBaseFunction
{
[NodeAction(NodeType.Action, "对象透传")]
public static object SereinTransmissionObject(object value) => value;
[NodeAction(NodeType.Action, "键值对组装")]
public static Dictionary<string, object> SereinKvDataCollectionNode(string argName,
public static Dictionary<string, object> SereinKvDataCollection(string argName,
params object[] value)
{
var names = argName.Split(';');
var count = Math.Min(value.Length, names.Length);
var dict = new Dictionary<string, object>();
@@ -36,13 +35,13 @@ namespace Serein.Library
}
[NodeAction(NodeType.Action, "数组组装")]
public static object[] SereinListDataCollectionNode(params object[] value)
public static object[] SereinListDataCollection(params object[] value)
{
return value;
}
[NodeAction(NodeType.Action, "输出")]
public static object[] SereinConsoleNode(params object[] value)
public static object[] SereinConsole(params object[] value)
{
foreach (var item in value)
{
@@ -104,7 +103,7 @@ namespace Serein.Library
}
[NodeAction(NodeType.Action, "设置/更新全局数据")]
[NodeAction(NodeType.Action, "设置更新全局数据")]
public static object SereinAddOrUpdateFlowGlobalData(string name, object data)
{
SereinEnv.AddOrUpdateFlowGlobalData(name, data);

View File

@@ -16,6 +16,20 @@ namespace Serein.Library.Utils
// 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式)
static Dictionary<string, Type> typeCache = new Dictionary<string, Type>();
/// <summary>
/// 获取运行时创建过的类型
/// </summary>
/// <param name="className"></param>
/// <returns></returns>
public static Type GetCacheType(string className)
{
if(typeCache.TryGetValue(className, out var type))
{
return type;
}
return null;
}
public static object Resolve(IDictionary<string, object> properties, string typeName)
{
var obj = CreateObjectWithProperties(properties, typeName);

View File

@@ -101,8 +101,6 @@ namespace Serein.Library
#endregion
/// <summary>
/// 设置运行流程
/// </summary>
@@ -138,8 +136,35 @@ namespace Serein.Library
}
/// <summary>
/// 尝试在UI线程上触发事件
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public async static Task TriggerEvent(Action action)
{
if (environment is null)
{
action?.Invoke();
}
else
{
var uco = environment.UIContextOperation;
if (uco is null)
{
action?.Invoke();
}
else
{
await uco.InvokeAsync(() =>
{
action?.Invoke();
});
}
}
}
}

View File

@@ -272,6 +272,7 @@ namespace Serein.NodeFlow.Env
ToNodeGuid = toNodeGuid,
ConnectionInvokeType = connectionType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove,
JunctionOfConnectionType = JunctionOfConnectionType.Invoke,
};
flowOperationService.Execute(operation);
}
@@ -284,7 +285,8 @@ namespace Serein.NodeFlow.Env
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ArgIndex = argIndex,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove,
JunctionOfConnectionType = JunctionOfConnectionType.Arg,
};
flowOperationService.Execute(operation);
}
@@ -354,7 +356,8 @@ namespace Serein.NodeFlow.Env
}*/
canvasModel.StartNode = newStartNodeModel;
//newStartNode.IsStart = true;
_ = TriggerEvent(() =>
_ = SereinEnv.TriggerEvent(() =>
flowEnvironmentEvent.OnStartNodeChanged(
new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, newStartNodeModel.Guid)
));
@@ -651,20 +654,7 @@ namespace Serein.NodeFlow.Env
private async Task TriggerEvent(Action action)
{
if(UIContextOperation is null)
{
action?.Invoke();
}
else
{
await UIContextOperation.InvokeAsync(() =>
{
action?.Invoke();
});
}
}
private async Task TriggerEvent(Action action) => await SereinEnv.TriggerEvent(action);
}

View File

@@ -146,24 +146,28 @@ namespace Serein.NodeFlow.Model
partial void OnIsPublicChanged(bool oldValue, bool newValue)
{
var list = CanvasDetails.PublicNodes.ToList();
if (newValue)
_ = SereinEnv.TriggerEvent(() =>
{
// 公开节点
if (!CanvasDetails.PublicNodes.Contains(this))
if (newValue)
{
list.Add(this);
CanvasDetails.PublicNodes= list;
// 公开节点
if (!CanvasDetails.PublicNodes.Contains(this))
{
list.Add(this);
CanvasDetails.PublicNodes = list;
}
}
}
else
{
// 取消公开
if (CanvasDetails.PublicNodes.Contains(this))
else
{
list.Remove(this);
CanvasDetails.PublicNodes = list;
// 取消公开
if (CanvasDetails.PublicNodes.Contains(this))
{
list.Remove(this);
CanvasDetails.PublicNodes = list;
}
}
}
});
}

View File

@@ -132,19 +132,31 @@ namespace Serein.NodeFlow.Model
throw new Exception($"节点{this.Guid}不存在对应委托");
}
var instance = Env.IOC.Get(md.ActingInstanceType);
if (instance is null)
if (md.IsStatic)
{
Env.IOC.Register(md.ActingInstanceType).Build();
instance = Env.IOC.Get(md.ActingInstanceType);
object[] args = await this.GetParametersAsync(context, token);
var result = await dd.InvokeAsync(null, args);
var flowReslt = new FlowResult(this.Guid, context, result);
return flowReslt;
}
object[] args = await this.GetParametersAsync(context, token);
var result = await dd.InvokeAsync(instance, args);
var flowReslt = new FlowResult(this.Guid, context, result);
return flowReslt;
else
{
var instance = Env.IOC.Get(md.ActingInstanceType);
if (instance is null)
{
Env.IOC.Register(md.ActingInstanceType).Build();
instance = Env.IOC.Get(md.ActingInstanceType);
}
object[] args = await this.GetParametersAsync(context, token);
var result = await dd.InvokeAsync(instance, args);
var flowReslt = new FlowResult(this.Guid, context, result);
return flowReslt;
}
}
}

View File

@@ -317,7 +317,8 @@ namespace Serein.NodeFlow.Model
_ => base.ExecutingAsync(context, token)
});
// 对于目标节点的后续节点如果入参参数来源指定为它目标节点就需要从上下文中根据它的Guid获取流程数据
context.AddOrUpdateFlowData(TargetNode.Guid, flowData);
if (IsShareParam)
{
// 设置运行时上一节点
@@ -325,7 +326,6 @@ namespace Serein.NodeFlow.Model
// 此处代码与SereinFlow.Library.FlowNode.ParameterDetails
// ToMethodArgData()方法中判断流程接口节点分支逻辑耦合
// 不要轻易修改
context.AddOrUpdateFlowData(TargetNode.Guid, flowData);
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
if (this.SuccessorNodes[ctType] == null) continue;

View File

@@ -158,13 +158,14 @@ namespace Serein.NodeFlow.Model
varNames.Add(pd.Name);
}
StringBuilder sb = new StringBuilder();
var sb = new StringBuilder();
foreach (var pd in MethodDetails.ParameterDetailss)
{
sb.AppendLine($"let {pd.Name};"); // 提前声明这些变量
}
sb.Append(Script);
var p = new SereinScriptParser(sb.ToString());
var script = sb.ToString();
var p = new SereinScriptParser(script);
//var p = new SereinScriptParser(Script);
mainNode = p.Parse(); // 开始解析

View File

@@ -5,6 +5,8 @@ using Serein.NodeFlow.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using static Serein.Library.Api.NodeConnectChangeEventArgs;
@@ -163,11 +165,51 @@ namespace Serein.NodeFlow.Model.Operation
return false;
}
var isOverwriting = false;
ConnectionInvokeType overwritingCt = ConnectionInvokeType.None;
var isPass = false;
#region
bool checkTypeState = true;
List<ParameterDetails> toPds = new List<ParameterDetails>();
if (ToNode.MethodDetails.ParameterDetailss.Length > 0)
{
var fromNoeReturnType = fromNode.MethodDetails.ReturnType;
if (fromNoeReturnType != null
&& fromNoeReturnType != typeof(object)
&& fromNoeReturnType != typeof(void)
&& fromNoeReturnType != typeof(Unit))
{
var toNodePds = toNode.MethodDetails.ParameterDetailss;
foreach (ParameterDetails toNodePd in toNodePds)
{
if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点
&& toNodePd.DataType.IsAssignableFrom(fromNoeReturnType)) // 返回值与目标入参相同(或可转换为目标入参)
{
toPds.Add(toNodePd);
}
}
if (toPds.Count == 0)
{
var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接
checkTypeState = any;
}
else
{
checkTypeState = true; // 类型检查初步通过
}
}
}
if (!checkTypeState) // 类型检查不通过
{
SereinEnv.WriteLine(InfoType.ERROR, "创建失败,目标节点没有合适的入参接收返回值");
return false;
}
#endregion
#region
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
@@ -237,7 +279,27 @@ namespace Serein.NodeFlow.Model.Operation
NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接
));
});
/* foreach (var toPd in toPds)
{
string canvasGuid = CanvasGuid;
string fromNodeGuid = fromNode.Guid;
string toNodeGuid = toNode.Guid;
JunctionType fromNodeJunctionType = JunctionType.ReturnData;
JunctionType toNodeJunctionType = JunctionType.ArgData;
ConnectionArgSourceType argSourceType = ConnectionArgSourceType.GetOtherNodeData;
int argIndex = toPd.Index;
// 调用创建连线接口
flowEnvironment.FlowEdit.ConnectArgSourceNode(canvasGuid,
fromNodeGuid,
toNodeGuid,
fromNodeJunctionType,
toNodeJunctionType,
argSourceType,
argIndex);
}*/
// Invoke
// GetResult
return true;
@@ -407,7 +469,9 @@ namespace Serein.NodeFlow.Model.Operation
/// <param name="index"></param>
private async Task<bool> RemoveArgConnection()
{
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null;
var type = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType;
FromNode.NeedResultNodes[type].Remove(ToNode);
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = string.Empty;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值

View File

@@ -102,23 +102,7 @@ namespace Serein.NodeFlow.Model.Operation
public abstract void ToInfo();
protected async Task TriggerEvent(Action action)
{
/* if (OperatingSystem.IsWindows())
{
}*/
if (uiContextOperation is null)
{
action?.Invoke();
}
else
{
await uiContextOperation.InvokeAsync(() =>
{
action?.Invoke();
});
}
}
protected async Task TriggerEvent(Action action) => await SereinEnv.TriggerEvent(action);
}

View File

@@ -9,6 +9,7 @@ using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Serein.NodeFlow.Model.Operation
{
@@ -58,13 +59,17 @@ namespace Serein.NodeFlow.Model.Operation
// 还需要记录移除的事件参数,用以撤销恢复
#region
foreach (var item in flowNode.PreviousNodes)
// 检查该节点的前继节点,然后从这些前继节点中移除与该节点的连接关系
var previousNodes = flowNode.PreviousNodes.Where(kvp => kvp.Value.Count > 0).ToDictionary();
foreach (var item in previousNodes)
{
var connectionType = item.Key; // 连接类型
var previousNodes = item.Value; // 对应类型的父节点集合
foreach (IFlowNode previousNode in previousNodes)
var nodes = item.Value.ToArray(); // 对应类型的父节点集合
foreach (IFlowNode previousNode in nodes)
{
flowNode.PreviousNodes[connectionType].Remove(previousNode);
previousNode.SuccessorNodes[connectionType].Remove(flowNode);
var e = new NodeConnectChangeEventArgs(
CanvasGuid, // 画布
@@ -74,13 +79,11 @@ namespace Serein.NodeFlow.Model.Operation
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(e);
});
}
}
// 检查该节点的后续节点,然后从这些后续节点中移除与该节点的连接关系
var successorNodes = flowNode.SuccessorNodes.Where(kvp => kvp.Value.Count > 0).ToDictionary();
if (flowNode.ControlType == NodeControlType.FlowCall)
{
// 根据流程接口节点目前的设计,暂未支持能连接下一个节点
@@ -88,14 +91,15 @@ namespace Serein.NodeFlow.Model.Operation
else
{
// 遍历所有后继节点,从那些后继节点中的前置节点集合中移除该节点
foreach (var item in flowNode.SuccessorNodes)
foreach (var item in successorNodes)
{
var connectionType = item.Key; // 方法调用连接类型
var successorNodes = item.Value; // 对应类型的父节点集合
foreach (IFlowNode successorNode in successorNodes)
var nodes = item.Value.ToArray(); // 对应类型的父节点集合
foreach (IFlowNode successorNode in nodes)
{
successorNode.SuccessorNodes[connectionType].Remove(flowNode);
successorNode.PreviousNodes[connectionType].Remove(flowNode);
flowNode.SuccessorNodes[connectionType].Remove(successorNode);
var e = new NodeConnectChangeEventArgs(
CanvasGuid, // 画布
flowNode.Guid, // 被移除的节点Guid
@@ -104,10 +108,7 @@ namespace Serein.NodeFlow.Model.Operation
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(e);
});
}
}
}
@@ -120,7 +121,7 @@ namespace Serein.NodeFlow.Model.Operation
foreach (var item in flowNode.NeedResultNodes)
{
var connectionType = item.Key; // 参数来源连接类型
var argNodes = item.Value; // 对应类型的入参需求节点集合
var argNodes = item.Value.ToArray(); // 对应类型的入参需求节点集合
foreach (var argNode in argNodes)
{
var md = argNode.MethodDetails;
@@ -140,10 +141,6 @@ namespace Serein.NodeFlow.Model.Operation
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(e);
});
}
}
}
@@ -152,7 +149,7 @@ namespace Serein.NodeFlow.Model.Operation
if (flowNode.MethodDetails?.ParameterDetailss != null)
{
var pds = flowNode.MethodDetails.ParameterDetailss;
var pds = flowNode.MethodDetails.ParameterDetailss.ToArray();
foreach (var pd in pds)
{
if (string.IsNullOrWhiteSpace(pd.ArgDataSourceNodeGuid)) continue;
@@ -168,10 +165,6 @@ namespace Serein.NodeFlow.Model.Operation
pd.ArgDataSourceType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeConnectChanged(e);
});
}
}
}
@@ -179,28 +172,55 @@ namespace Serein.NodeFlow.Model.Operation
#endregion
flowModelService.RemoveNodeModel(flowNode); // 从记录中移除
//flowNode.Remove(); // 调用节点的移除方法
//flowNode.Remove(); // 调用节点的移除方法
if(flowEnvironment.UIContextOperation is null)
// 存在UI上下文操作当前运行环境极有可能运行在有UI线程的平台上
// 为了避免直接修改 ObservableCollection 集合导致异常产生故而使用UI线程上下文操作运行
NodeConnectChangeEventArgs[] es = EventArgs.ToArray();
await TriggerEvent(() =>
{
/*flowCanvasDetails.Nodes.Remove(flowNode);
flowCanvasDetails.OnPropertyChanged(nameof(FlowCanvasDetails.Nodes));
if (flowNode.IsPublic)
{
flowCanvasDetails.PublicNodes.Remove(flowNode);
flowCanvasDetails.OnPropertyChanged(nameof(FlowCanvasDetails.PublicNodes));
}*/
// 手动赋值刷新UI显示
var lsit = flowCanvasDetails.Nodes.ToList();
lsit.Remove(flowNode);
flowCanvasDetails.Nodes = lsit;
if (flowNode.IsPublic)
{
var publicNodes = flowCanvasDetails.PublicNodes.ToList();
publicNodes.Remove(flowNode);
flowCanvasDetails.PublicNodes = publicNodes;
}
foreach (var e in es)
{
flowEnvironmentEvent.OnNodeConnectChanged(e); // 触发事件
}
flowEnvironmentEvent.OnNodeRemoved(new NodeRemoveEventArgs(CanvasGuid, NodeGuid));
});
/*if (flowEnvironment.UIContextOperation is null)
{
flowCanvasDetails?.Nodes.Remove(flowNode);
}
else
{
// 存在UI上下文操作当前运行环境极有可能运行在有UI线程的平台上
// 为了避免直接修改 ObservableCollection 集合导致异常产生故而使用UI线程上下文操作运行
await TriggerEvent(() =>
{
var lsit = flowCanvasDetails.Nodes.ToList();
lsit.Remove(flowNode);
flowCanvasDetails.Nodes = lsit;
});
}
}*/
await TriggerEvent(() =>
/* await TriggerEvent(() =>
{
flowEnvironmentEvent.OnNodeRemoved(new NodeRemoveEventArgs(CanvasGuid, NodeGuid));
});
});*/
return true;
}

View File

@@ -255,7 +255,7 @@ namespace Serein.NodeFlow.Services
}
else if (pd.DataType.IsAssignableFrom(otherNodeReturnType))
{
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {flowContext}.{nameof(IDynamicContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据");
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IDynamicContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据");
}
else
{
@@ -1058,7 +1058,7 @@ namespace Serein.NodeFlow.Services
sb.AppendCode(3, $"{{");
sb.AppendCode(4, $"throw new ArgumentNullException($\"{{(flowResult.Value is null ? \"返回数据为 null\" : $\"返回数据与需求类型不匹配,当前返回类型为[{{flowResult.Value.GetType().FullName}}。\")}}\");");
sb.AppendCode(3, $"}}");
sb.AppendCode(3, $"return {flowResult};");
//sb.AppendCode(3, $"return {flowResult};");
sb.AppendCode(2, $"}}");
return sb.ToString();
// throw new ArgumentNullException($"类型转换失败,{(flowResult.Value is null ? "返回数据为 null" : $"返回数据与需求类型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}。")}");

View File

@@ -65,6 +65,7 @@ public static class NodeMethodDetailsHelper
Type? returnType;
bool isAsync = IsGenericTask(methodInfo.ReturnType, out var taskResult);
bool isStatic = methodInfo.IsStatic;
if (attribute.MethodDynamicType == Library.NodeType.UI)
@@ -162,6 +163,7 @@ public static class NodeMethodDetailsHelper
// 如果存在可变参数,取最后一个元素的下标,否则为-1
ParamsArgIndex = hasParamsArg ? explicitDataOfParameters.Length - 1 : -1,
IsAsync = isAsync,
IsStatic = isStatic,
};
//var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var methodDelegate);// 返回值

View File

@@ -251,7 +251,7 @@ namespace Serein.Library.NodeGenerator
sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); ");
sb.AppendLine(" } ");
sb.AppendLine(" protected void OnPropertyChanged(string propertyName) => ");
sb.AppendLine(" public void OnPropertyChanged(string propertyName) => ");
sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); ");
sb.AppendLine(" ");
sb.AppendLine(" ");

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Script
{
public static class BinaryOperationEvaluator
{
public static object EvaluateValue(object left, string op, object right)
{
if (op == null) throw new ArgumentNullException(nameof(op));
// 特判字符串拼接
if (op == "+" && (left is string || right is string))
{
return (left?.ToString() ?? "") + (right?.ToString() ?? "");
}
// 支持 null 运算(可按需扩展)
if (left == null || right == null)
{
throw new InvalidOperationException($"无法对 null 执行操作:{op}");
}
// 尝试统一类型(浮点优先)
var leftType = left.GetType();
var rightType = right.GetType();
Type resultType = GetWiderType(leftType, rightType);
dynamic leftValue = Convert.ChangeType(left, resultType);
dynamic rightValue = Convert.ChangeType(right, resultType);
return op switch
{
"+" => leftValue + rightValue,
"-" => leftValue - rightValue,
"*" => leftValue * rightValue,
"/" => rightValue == 0 ? throw new DivideByZeroException() : leftValue / rightValue,
">" => leftValue > rightValue,
"<" => leftValue < rightValue,
">=" => leftValue >= rightValue,
"<=" => leftValue <= rightValue,
"==" => Equals(leftValue, rightValue),
"!=" => !Equals(leftValue, rightValue),
_ => throw new NotImplementedException($"未实现的操作符: {op}")
};
}
/// <summary>
/// 推导两个类型的“最通用类型”(类型提升)
/// </summary>
private static Type GetWiderType(Type t1, Type t2)
{
if (t1 == typeof(string) || t2 == typeof(string)) return typeof(string);
if (t1 == typeof(decimal) || t2 == typeof(decimal)) return typeof(decimal);
if (t1 == typeof(double) || t2 == typeof(double)) return typeof(double);
if (t1 == typeof(float) || t2 == typeof(float)) return typeof(float);
if (t1 == typeof(ulong) || t2 == typeof(ulong)) return typeof(ulong);
if (t1 == typeof(long) || t2 == typeof(long)) return typeof(long);
if (t1 == typeof(uint) || t2 == typeof(uint)) return typeof(uint);
if (t1 == typeof(int) || t2 == typeof(int)) return typeof(int);
// fallback
return typeof(object);
}
public static Type EvaluateType(Type leftType, string op, Type rightType)
{
if (leftType == null || rightType == null)
throw new ArgumentNullException("操作数类型不能为 null");
// 字符串拼接
if (op == "+" && (leftType == typeof(string) || rightType == typeof(string)))
return typeof(string);
// 比较操作总是返回 bool
if (op is ">" or "<" or ">=" or "<=" or "==" or "!=")
return typeof(bool);
// 数值类型推导
if (IsNumeric(leftType) && IsNumeric(rightType))
{
return PromoteNumericType(leftType, rightType);
}
// 逻辑操作
if (op is "&&" or "||")
{
if (leftType == typeof(bool) && rightType == typeof(bool))
return typeof(bool);
throw new InvalidOperationException($"逻辑操作 '{op}' 仅适用于 bool 类型");
}
throw new NotImplementedException($"不支持操作符 '{op}' 对 {leftType.Name} 和 {rightType.Name} 进行类型推导");
}
private static bool IsNumeric(Type type)
{
return type == typeof(byte) || type == typeof(sbyte) ||
type == typeof(short) || type == typeof(ushort) ||
type == typeof(int) || type == typeof(uint) ||
type == typeof(long) || type == typeof(ulong) ||
type == typeof(float) || type == typeof(double) ||
type == typeof(decimal);
}
// 提升到更高精度类型
private static Type[] types = new[]
{
typeof(decimal),
typeof(double),
typeof(float),
typeof(ulong),
typeof(long),
typeof(uint),
typeof(int),
typeof(ushort),
typeof(short),
typeof(byte),
typeof(sbyte)
};
private static Type PromoteNumericType(Type left, Type right)
{
var ranks = types;
foreach (var type in ranks)
{
if (type == left || type == right)
return type;
}
return typeof(object); // fallback
}
}
}

View File

@@ -11,7 +11,14 @@ namespace Serein.Script.Node
/// </summary>
public class MemberAccessNode : ASTNode
{
/// <summary>
/// 对象token
/// </summary>
public ASTNode Object { get; }
/// <summary>
/// 成员名称
/// </summary>
public string MemberName { get; }
public MemberAccessNode(ASTNode obj, string memberName)

View File

@@ -11,8 +11,17 @@ namespace Serein.Script.Node
/// </summary>
public class MemberAssignmentNode : ASTNode
{
/// <summary>
/// 作用的对象
/// </summary>
public ASTNode Object { get; }
/// <summary>
/// 被赋值的成员(属性/字段)名称
/// </summary>
public string MemberName { get; }
/// <summary>
/// 值来源
/// </summary>
public ASTNode Value { get; }
public MemberAssignmentNode(ASTNode obj, string memberName, ASTNode value)

View File

@@ -11,8 +11,19 @@ namespace Serein.Script.Node
/// </summary>
public class MemberFunctionCallNode : ASTNode
{
/// <summary>
/// 需要被调用的对象
/// </summary>
public ASTNode Object { get; }
/// <summary>
/// 被调用的方法名称
/// </summary>
public string FunctionName { get; }
/// <summary>
/// 方法参数
/// </summary>
public List<ASTNode> Arguments { get; }
public MemberFunctionCallNode(ASTNode @object, string functionName, List<ASTNode> arguments)

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Script.Node
{
/// <summary>
/// 数值型节点
/// </summary>
public abstract class NumberNode<T> : ASTNode where T : struct, IComparable<T>
{
public T Value { get; }
public NumberNode(T value) => Value = value;
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberIntNode(int vlaue) : NumberNode<int>(vlaue)
{
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberLongNode(long vlaue) : NumberNode<long>(vlaue)
{
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberFloatNode(float vlaue) : NumberNode<float>(vlaue)
{
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberDoubleNode(double vlaue) : NumberNode<double>(vlaue)
{
}
/*/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberIntNode : ASTNode
{
public int Value { get; }
public NumberIntNode(int value) => Value = value;
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberLongNode : ASTNode
{
public long Value { get; }
public NumberLongNode(long value) => Value = value;
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberFloatNode : ASTNode
{
public float Value { get; }
public NumberFloatNode(float value) => Value = value;
}
/// <summary>
/// int 整数型字面量
/// </summary>
public class NumberDoubleNode : ASTNode
{
public double Value { get; }
public NumberDoubleNode(double value) => Value = value;
}*/
}

View File

@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Script.Node
{
/// <summary>
/// 整数型字面量
/// </summary>
public class NumberNode : ASTNode
{
public int Value { get; }
public NumberNode(int value) => Value = value;
}
}

View File

@@ -30,6 +30,9 @@ namespace Serein.Script
/// </summary>
public interface IScriptInvokeContext
{
/// <summary>
/// 脚本运行的流程上下文,包含了流程上下文和变量等信息
/// </summary>
IDynamicContext FlowContext { get; }
/// <summary>
@@ -64,7 +67,6 @@ namespace Serein.Script
void OnExit();
}
public class ScriptInvokeContext : IScriptInvokeContext
{
public ScriptInvokeContext(IDynamicContext dynamicContext)
@@ -78,7 +80,10 @@ namespace Serein.Script
/// 定义的变量
/// </summary>
private Dictionary<string, object> _variables = new Dictionary<string, object>();
/// <summary>
/// 取消令牌源,用于控制脚本的执行
/// </summary>
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
/// <summary>
@@ -109,8 +114,6 @@ namespace Serein.Script
}
void IScriptInvokeContext.OnExit()
{
// 清理脚本中加载的非托管资源
@@ -134,7 +137,9 @@ namespace Serein.Script
}
/// <summary>
/// 脚本解释器,负责解析和执行 Serein 脚本
/// </summary>
public class SereinScriptInterpreter
{
@@ -220,23 +225,34 @@ namespace Serein.Script
private async Task<object?> ExecutionProgramNodeAsync(IScriptInvokeContext context, ProgramNode programNode)
{
// 加载变量
// 遍历 ProgramNode 中的所有语句并执行它们
foreach (var statement in programNode.Statements)
ASTNode statement = null;
try
{
// 直接退出
if (statement is ReturnNode returnNode) // 遇到 Return 语句 提前退出
{
return await EvaluateAsync(context, statement);
}
else
{
await InterpretAsync(context, statement);
}
}
return null;
// 遍历 ProgramNode 中的所有语句并执行它们
for (int index = 0; index < programNode.Statements.Count; index++)
{
statement = programNode.Statements[index];
// 直接退出
if (statement is ReturnNode returnNode) // 遇到 Return 语句 提前退出
{
return await EvaluateAsync(context, statement);
}
else
{
await InterpretAsync(context, statement);
}
}
return null;
}
catch (Exception ex )
{
if(statement is not null)
{
SereinEnv.WriteLine(InfoType.ERROR, $"脚本异常发生在[行{statement.Row}]{ex.Message}{Environment.NewLine}\t{statement.Code}");
}
throw;
}
}
/// <summary>
@@ -385,6 +401,13 @@ namespace Serein.Script
}
/// <summary>
/// 解释操作
/// </summary>
/// <param name="context"></param>
/// <param name="node"></param>
/// <returns></returns>
/// <exception cref="SereinSciptException"></exception>
public async Task<object?> InterpretAsync(IScriptInvokeContext context, ASTNode node)
{
if(node == null)
@@ -407,10 +430,10 @@ namespace Serein.Script
case MemberAssignmentNode memberAssignmentNode: // 设置对象属性
await SetMemberValue(context, memberAssignmentNode);
break;
case MemberFunctionCallNode memberFunctionCallNode:
case MemberFunctionCallNode memberFunctionCallNode: // 对象方法调用
return await CallMemberFunction(context, memberFunctionCallNode);
case IfNode ifNode: // 执行 if...else... 语句块
await ExecutionIfNodeAsync(context, ifNode);
await ExecutionIfNodeAsync(context, ifNode);
break;
case WhileNode whileNode: // 循环语句块
await ExectutionWhileNodeAsync(context, whileNode);
@@ -425,7 +448,13 @@ namespace Serein.Script
return null;
}
/// <summary>
/// 评估
/// </summary>
/// <param name="context"></param>
/// <param name="node"></param>
/// <returns></returns>
/// <exception cref="SereinSciptException"></exception>
private async Task<object?> EvaluateAsync(IScriptInvokeContext context, ASTNode node)
{
if(node == null)
@@ -438,8 +467,14 @@ namespace Serein.Script
return null;
case BooleanNode booleanNode:
return booleanNode.Value; // 返回数值
case NumberNode numberNode:
return numberNode.Value; // 返回数
case NumberIntNode numberNode:
return numberNode.Value; // 返回 int 整型
case NumberLongNode numberNode:
return numberNode.Value; // 返回 long 整型数
case NumberFloatNode numberNode:
return numberNode.Value; // 返回 float 浮点型
case NumberDoubleNode numberNode:
return numberNode.Value; // 返回 double 浮点型
case StringNode stringNode:
return stringNode.Value; // 返回字符串值
case CharNode charNode:
@@ -451,17 +486,11 @@ namespace Serein.Script
case BinaryOperationNode binOpNode:
// 递归计算二元操作
var left = await EvaluateAsync(context, binOpNode.Left);
//if (left == null )
//{
// throw new SereinSciptException(binOpNode.Left, $"左值尝试使用 null");
//}
//if (left == null ) throw new SereinSciptException(binOpNode.Left, $"左值尝试使用 null");
var right = await EvaluateAsync(context, binOpNode.Right);
//if (right == null)
//{
// throw new SereinSciptException(binOpNode.Right, "右值尝试使用计算 null");
//}
//if (right == null) throw new SereinSciptException(binOpNode.Right, "右值尝试使用计算 null");
return EvaluateBinaryOperation(left, binOpNode.Operator, right);
case ObjectInstantiationNode objectInstantiationNode:
case ObjectInstantiationNode objectInstantiationNode: // 对象实例化
if (_classDefinition.TryGetValue(objectInstantiationNode.TypeName,out var type ))
{
object?[] args = new object[objectInstantiationNode.Arguments.Count];
@@ -493,6 +522,8 @@ namespace Serein.Script
return await GetCollectionValue(context, collectionIndexNode);
case ReturnNode returnNode: // 返回内容
return await EvaluateAsync(context, returnNode.Value); // 直接返回响应的内容
//case ObjectInstantiationNode objectInstantiationNode: // 返回内容
default:
throw new SereinSciptException(node, $"解释器 EvaluateAsync() 未实现{node}节点行为");
}
@@ -500,8 +531,7 @@ namespace Serein.Script
private object EvaluateBinaryOperation(object left, string op, object right)
{
return BinaryOperationEvaluator.EvaluateValue(left, op, right);
// 根据运算符执行不同的运算
switch (op)

View File

@@ -1,9 +1,13 @@
using Newtonsoft.Json.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using static System.Net.Mime.MediaTypeNames;
namespace Serein.Script
{
/// <summary>
/// Serein脚本词法分析器的Token类型
/// </summary>
internal enum TokenType
{
/// <summary>
@@ -19,9 +23,21 @@ namespace Serein.Script
/// </summary>
Boolean,
/// <summary>
/// 数
/// int 整
/// </summary>
Number,
NumberInt,
/// <summary>
/// long 整数
/// </summary>
NumberLong,
/// <summary>
/// float 浮点数
/// </summary>
NumberFloat,
/// <summary>
/// double 浮点数
/// </summary>
NumberDouble,
/// <summary>
/// 字符串
/// </summary>
@@ -91,6 +107,9 @@ namespace Serein.Script
EOF
}
/// <summary>
/// Serein脚本词法分析器的Token结构体
/// </summary>
internal ref struct Token
{
public TokenType Type { get; }
@@ -108,18 +127,26 @@ namespace Serein.Script
}
}
/// <summary>
/// Serein脚本词法分析器
/// </summary>
internal ref struct SereinScriptLexer
{
private readonly ReadOnlySpan<char> _input;
private int _index;
private int _row ;
private int coreRangeStartIndex = 0;
/// <summary>
/// 关键字,防止声明为变量
/// </summary>
private string[] _keywords = [
"let",
"func",
"if",
"else",
"func",
"if",
"else",
"return",
"while",
"new",
@@ -136,8 +163,10 @@ namespace Serein.Script
internal Token PeekToken()
{
int currentIndex = _index; // 保存当前索引
var currentRow = _row; // 保存当前行数
Token nextToken = NextToken(); // 获取下一个 token
_index = currentIndex; // 恢复索引到当前位置
_row = currentRow; // 恢复到当前行数
return nextToken; // 返回下一个 token
}
@@ -156,8 +185,7 @@ namespace Serein.Script
}
if (_index >= _input.Length) return new Token(TokenType.EOF, string.Empty);
if (_index >= _input.Length) return new Token(TokenType.EOF, string.Empty); // 程序结束
char currentChar = _input[_index];
@@ -230,12 +258,77 @@ namespace Serein.Script
// 识别数字
if (char.IsDigit(currentChar))
{
var start = _index;
while (_index < _input.Length && char.IsDigit(_input[_index]))
_index++;
var value = _input.Slice(start, _index - start).ToString();
_index = start; // 回退索引,索引必须只能在 CreateToken 方法内更新
return CreateToken(TokenType.Number, value);
#region
if (char.IsDigit(currentChar))
{
var start = _index;
bool hasDot = false;
bool hasSuffix = false;
while (_index < _input.Length)
{
var ch = _input[_index];
if (char.IsDigit(ch))
{
_index++;
}
else if (ch == '.' && !hasDot)
{
hasDot = true;
_index++;
}
else if (ch is 'f' or 'F' or 'd' or 'D' or 'l' or 'L')
{
hasSuffix = true;
_index++;
break; // 后缀后应结束
}
else
{
break;
}
}
var raw = _input.Slice(start, _index - start).ToString();
_index = start; // 回退索引,仅 CreateToken 负责推进
TokenType type;
// 判断类型
if (hasDot)
{
if (raw.EndsWith("f", StringComparison.OrdinalIgnoreCase))
type = TokenType.NumberFloat;
else if (raw.EndsWith("d", StringComparison.OrdinalIgnoreCase))
type = TokenType.NumberDouble;
else
type = TokenType.NumberDouble; // 默认小数为 double
}
else
{
if (raw.EndsWith("l", StringComparison.OrdinalIgnoreCase))
type = TokenType.NumberLong;
else
{
// 自动根据位数判断 int 或 long
if (long.TryParse(raw, out var val))
{
if (val >= int.MinValue && val <= int.MaxValue)
type = TokenType.NumberInt;
else
type = TokenType.NumberLong;
}
else
{
type = TokenType.NumberLong; // 超出 long 会出错,默认成 long
}
}
}
return CreateToken(type, raw);
}
#endregion
}
// 识别标识符(变量名、关键字)
@@ -303,6 +396,12 @@ namespace Serein.Script
throw new Exception("Unexpected character: " + currentChar);
}
/// <summary>
/// 创建一个新的Token实例
/// </summary>
/// <param name="tokenType"></param>
/// <param name="value"></param>
/// <returns></returns>
private Token CreateToken(TokenType tokenType, string value)
{
var code = GetLine(_row).ToString();
@@ -314,6 +413,7 @@ namespace Serein.Script
Code = code,
};
_index += value.Length;
return token;
}
@@ -396,6 +496,16 @@ namespace Serein.Script
}
public int GetIndex()
{
return _index;
}
public string GetCoreContent(int index)
{
ReadOnlySpan<char> text = _input;
var content = text.Slice(index, _index - index); // 返回从start到当前位置的行文本
return content.ToString();
}
}

View File

@@ -5,9 +5,14 @@ using Serein.Script.Node;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace Serein.Script
{
/// <summary>
/// SereinScriptParser 用于解析 Serein 脚本语言的语法。
/// </summary>
public ref struct SereinScriptParser
{
private SereinScriptLexer _lexer;
@@ -20,6 +25,11 @@ namespace Serein.Script
_currentToken = _lexer.NextToken();
}
/// <summary>
/// 解析脚本并返回 AST抽象语法树根节点。
/// </summary>
/// <returns></returns>
public ASTNode Parse()
{
return Program();
@@ -29,40 +39,67 @@ namespace Serein.Script
private List<ASTNode> Statements { get; } = new List<ASTNode>();
/// <summary>
/// 解析整个程序直到遇到文件结尾EOF为止。
/// </summary>
/// <returns></returns>
private ASTNode Program()
{
Statements.Clear();
while (_currentToken.Type != TokenType.EOF)
{
var astNode = Statement();
var astNode = Statement(); // 解析单个语句
if (astNode == null)
{
continue;
}
Statements.Add(astNode);
//if (astNode is ClassTypeDefinitionNode)
//{
// statements = [astNode, ..statements]; // 类型定义置顶
//}
//else
//{
// statements.Add(astNode);
//}
Statements.Add(astNode); // 将解析得到的 AST 节点添加到语句列表中
}
return new ProgramNode(Statements).SetTokenInfo(_currentToken);
var programNode = new ProgramNode(Statements);
programNode.SetTokenInfo(_currentToken); // 程序节点,包含所有解析的语句列表
SereinScriptTypeAnalysis typeAnalysis = new SereinScriptTypeAnalysis(programNode);
return programNode;
/*if (astNode is ClassTypeDefinitionNode)
{
statements = [astNode, ..statements]; // 类型定义置顶
}
else
{
statements.Add(astNode);
}*/
}
/// <summary>
/// 解析单个语句。
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode Statement()
{
// 处理其他语句(如表达式语句等)
while (_currentToken.Type == TokenType.Semicolon)
{
_currentToken = _lexer.NextToken();
}
if(_currentToken.Type == TokenType.EOF)
{
return null;
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "let")
{
// 处理 let 变量赋值语句
return ParseLetAssignment();
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "class")
{
// 加载类定义
return ParseClassDefinition(); // 加载类,如果已经加载过,则忽略
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "new")
@@ -70,39 +107,44 @@ namespace Serein.Script
var _peekToken = _lexer.PeekToken();
if (_peekToken.Type == TokenType.Keyword && _peekToken.Value == "class")
{
// 重新加载类定义
return ParseClassDefinition(); // 重新加载类
}
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "if")
{
// 处理 if 语句
return ParseIf();
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "while")
{
// 处理 while 循环语句
return ParseWhile();
}
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "return")
{
// 处理 return 语句
return ParseReturn();
}
if (_currentToken.Type == TokenType.Identifier)
{
// 处理标识符,可能是函数调用、变量赋值或对象成员访问等行为
return ParseIdentifier();
}
if (_currentToken.Type == TokenType.Null)
{
// 处理 null 语句
return Expression();
}
// 处理其他语句(如表达式语句等)
if (_currentToken.Type == TokenType.Semicolon)
/*if (_currentToken.Type == TokenType.Semicolon)
{
_currentToken = _lexer.NextToken();
return null; // 表示空语句
}
}*/
throw new Exception("Unexpected statement: " + _currentToken.Value.ToString());
}
@@ -126,12 +168,13 @@ namespace Serein.Script
else if (_tempToken.Type == TokenType.Dot)
{
// 对象成员的获取
return ParseMemberAccessOrAssignment();
return ParseMemberAccessOrAssignment();
}
else if (_tempToken.Type == TokenType.SquareBracketsLeft)
{
// 数组 index; 字典 key obj.Member[xxx];
return ParseCollectionIndex();
}
else
{
@@ -197,7 +240,6 @@ namespace Serein.Script
}
throw new Exception($"Expected '{_currentToken.Value}' after variable name");
}
@@ -209,33 +251,55 @@ namespace Serein.Script
/// <exception cref="Exception"></exception>
private ASTNode ParseLetAssignment()
{
_currentToken = _lexer.NextToken(); // Consume "let"
string variable = _currentToken.Value.ToString(); // 变量名称
_currentToken = _lexer.NextToken(); // Consume identifier
ASTNode value;
AssignmentNode assignmentNode;
if (_currentToken.Type == TokenType.Semicolon)
{
// 定义一个变量,初始值为 null
value = new NullNode();
assignmentNode = new AssignmentNode(variable, value); // 生成node
assignmentNode.SetTokenInfo(_currentToken); // 设置token信息
}
else
{
// 如果定义了变量,后面紧跟着操作符,且操作符不是“=”话视为异常
// let value = obj;
if (_currentToken.Type != TokenType.Operator || _currentToken.Value != "=")
throw new Exception("Expected '=' after variable name");
_currentToken = _lexer.NextToken();
value = Expression();
_currentToken = _lexer.NextToken(); // Consume semicolon
_currentToken = _lexer.NextToken(); // 消耗操作符(“=”)
var nodeToken = _currentToken;
value = Expression(); // 解析获取赋值表达式
assignmentNode = new AssignmentNode(variable, value); // 生成node
assignmentNode.SetTokenInfo(nodeToken); // 设置token信息
_currentToken = _lexer.NextToken(); // 消耗分号
}
return new AssignmentNode(variable, value).SetTokenInfo(_currentToken);
return assignmentNode;
}
/// <summary>
/// 解析类定义
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode ParseClassDefinition()
{
bool isOverlay = false;
var sb = new StringBuilder(); // 收集代码信息
/*
* 有两种定义类型的方式:
* 1. class MyClass{}
* 2. new class MyClass{}
* 解析执行时第二种方式定义的类会顶掉其他地方创建的“MyClass”同名类型
*/
var coreStartRangeIndex = _lexer.GetIndex() - "class".Length; // 从“class”开始锚定代码范围
bool isOverlay = false; // 指示是否覆盖缓存中创建过的同名其它类型
if (_currentToken.Value == "new")
{
isOverlay = true; // 重新加载类
@@ -248,15 +312,19 @@ namespace Serein.Script
throw new Exception("Expected '{' after class definition");
var classFields = new Dictionary<string, Type>();
_currentToken = _lexer.NextToken(); // 消耗括号
while (_currentToken.Type != TokenType.BraceRight)
{
var fieldType = _currentToken.Value.ToString().ToTypeOfString(); // 获取定义的类名
_currentToken = _lexer.NextToken();
// 获取类字段定义
var fieldType = _currentToken.Value.ToString().ToTypeOfString(); // 获取字段的类型
_currentToken = _lexer.NextToken(); // 消耗类型
var fieldName = _currentToken.Value.ToString(); // 获取定义的类名
_currentToken = _lexer.NextToken();
classFields.Add(fieldName,fieldType);
if (_currentToken.Type == TokenType.Semicolon && _lexer.PeekToken().Type == TokenType.BraceRight)
_currentToken = _lexer.NextToken(); // 消耗字段名称
classFields.Add(fieldName,fieldType); // 添加字段
if (_currentToken.Type == TokenType.Semicolon
&& _lexer.PeekToken().Type == TokenType.BraceRight)
{
// 如果遇到分号、大括号,退出字段定义。
break;
}
else
@@ -265,37 +333,50 @@ namespace Serein.Script
}
}
_currentToken = _lexer.NextToken();
_currentToken = _lexer.NextToken();
return new ClassTypeDefinitionNode(classFields, className, isOverlay).SetTokenInfo(_currentToken);
_currentToken = _lexer.NextToken(); // 消耗类型定义 } 括号
var typeDefinitionCode = _lexer.GetCoreContent(coreStartRangeIndex); // 收集类型定义的代码。在Statement方法中开始收集的
var node = new ClassTypeDefinitionNode(classFields, className, isOverlay);
_currentToken.Code = typeDefinitionCode;
node.SetTokenInfo(_currentToken);
// _currentToken = _lexer.NextToken();
_currentToken = _lexer.NextToken();
return node;
}
/// <summary>
/// 解析对象实例化行为
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public ASTNode ParseObjectInstantiation()
{
_currentToken = _lexer.NextToken(); // Consume "new"
string typeName = _currentToken.Value.ToString(); // Get type name
_currentToken = _lexer.NextToken(); // 消耗 new 关键字
string typeName = _currentToken.Value.ToString(); // 获取类型名称
_currentToken = _lexer.NextToken();
if (_currentToken.Type != TokenType.ParenthesisLeft)
throw new Exception("Expected '(' after function name");
_currentToken = _lexer.NextToken(); // consume "("
_currentToken = _lexer.NextToken(); // 消耗 "("
var arguments = new List<ASTNode>();
while (_currentToken.Type != TokenType.ParenthesisRight)
{
arguments.Add(Expression());
arguments.Add(Expression()); // 获取参数表达式
if (_currentToken.Type == TokenType.Comma)
{
_currentToken = _lexer.NextToken(); // consume ","
}
}
_currentToken = _lexer.NextToken(); // consume ")"
_currentToken = _lexer.NextToken(); // 消耗 ")"
return new ObjectInstantiationNode(typeName, arguments).SetTokenInfo(_currentToken);
}
/// <summary>
/// 解析集合索引行为(数组或字典)
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public ASTNode ParseCollectionIndex()
{
var identifierNode = new IdentifierNode(_currentToken.Value.ToString()).SetTokenInfo(_currentToken);
@@ -375,10 +456,15 @@ namespace Serein.Script
}
/// <summary>
/// 解析成员函数调用行为
/// </summary>
/// <param name="targetNode"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode ParseMemberFunctionCall(ASTNode targetNode)
{
string functionName = _currentToken.Value.ToString();
string functionName = _currentToken.Value.ToString(); // 函数名称
_currentToken = _lexer.NextToken(); // consume identifier
if (_currentToken.Type != TokenType.ParenthesisLeft)
@@ -389,9 +475,10 @@ namespace Serein.Script
var arguments = new List<ASTNode>();
while (_currentToken.Type != TokenType.ParenthesisRight)
{
// 获取参数表达式
var arg = Expression();
_currentToken = _lexer.NextToken(); // consume arg
arguments.Add(arg);
arguments.Add(arg); // 添加到参数列表
if (_currentToken.Type == TokenType.Comma)
{
_currentToken = _lexer.NextToken(); // consume ","
@@ -408,10 +495,15 @@ namespace Serein.Script
}
/// <summary>
/// 解析函数调用行为
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode ParseFunctionCall()
{
string functionName = _currentToken.Value.ToString();
_currentToken = _lexer.NextToken(); // consume identifier
_currentToken = _lexer.NextToken(); // consume identifier
if (_currentToken.Type != TokenType.ParenthesisLeft)
throw new Exception("Expected '(' after function name");
@@ -422,7 +514,7 @@ namespace Serein.Script
bool isBreak = false;
while (_currentToken.Type != TokenType.ParenthesisRight)
{
var arg = Expression();
var arg = Expression(); // 获取参数表达式
_currentToken = _lexer.NextToken(); // consume arg
arguments.Add(arg);
if (_currentToken.Type == TokenType.Comma)
@@ -456,18 +548,28 @@ namespace Serein.Script
}
/// <summary>
/// 解析 return 语句。
/// </summary>
/// <returns></returns>
public ASTNode ParseReturn()
{
_currentToken = _lexer.NextToken();
if(_currentToken.Type == TokenType.Semicolon)
{
return new ReturnNode().SetTokenInfo(_currentToken);
return new ReturnNode().SetTokenInfo(_currentToken); // 返回空的 ReturnNode
}
var resultValue = Expression();
_currentToken = _lexer.NextToken();
return new ReturnNode(resultValue).SetTokenInfo(_currentToken);
var resultValue = Expression(); // 获取返回值表达式
_currentToken = _lexer.NextToken();
return new ReturnNode(resultValue).SetTokenInfo(_currentToken);
}
/// <summary>
/// 解析 if 语句。
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode ParseIf()
{
_currentToken = _lexer.NextToken(); // Consume "if"
@@ -487,10 +589,10 @@ namespace Serein.Script
List<ASTNode> falseBranch = new List<ASTNode>();
while (_currentToken.Type != TokenType.BraceRight && _currentToken.Type != TokenType.EOF)
{
var astNode = Statement();
var astNode = Statement(); // 解析 if 分支中的语句
if (astNode != null)
{
trueBranch.Add(astNode);
trueBranch.Add(astNode); // 将 if 分支的语句添加到 trueBranch 中
}
}
// 确保匹配右大括号 }
@@ -505,10 +607,10 @@ namespace Serein.Script
_currentToken = _lexer.NextToken(); // Consume "{"
while (_currentToken.Type != TokenType.BraceRight && _currentToken.Type != TokenType.EOF)
{
var astNode = Statement();
var astNode = Statement(); // 解析 else 分支中的语句
if (astNode != null)
{
falseBranch.Add(astNode);
falseBranch.Add(astNode); // 将 else 分支的语句添加到 falseBranch 中
}
}
// 确保匹配右大括号 }
@@ -523,6 +625,10 @@ namespace Serein.Script
return new IfNode(condition, trueBranch, falseBranch).SetTokenInfo(_currentToken);
}
/// <summary>
/// 解析 while 循环语句。
/// </summary>
/// <returns></returns>
private ASTNode ParseWhile()
{
_currentToken = _lexer.NextToken(); // Consume "while"
@@ -533,13 +639,16 @@ namespace Serein.Script
List<ASTNode> body = new List<ASTNode>();
while (_currentToken.Type != TokenType.BraceRight)
{
body.Add(Statement());
body.Add(Statement()); // 解析循环体中的语句
}
_currentToken = _lexer.NextToken(); // Consume "}"
return new WhileNode(condition, body).SetTokenInfo(_currentToken);
}
/// <summary>
/// 解析表达式。
/// </summary>
/// <returns></returns>
private ASTNode Expression()
{
ASTNode left = Term();
@@ -555,6 +664,11 @@ namespace Serein.Script
return left;
}
/// <summary>
/// 解析项Term用于处理加减乘除等运算符。
/// </summary>
/// <returns></returns>
private ASTNode Term()
{
ASTNode left = Factor();
@@ -571,6 +685,11 @@ namespace Serein.Script
return left;
}
/// <summary>
/// 解析因子Factor用于处理基本的字面量、标识符、括号表达式等。
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private ASTNode Factor()
{
#region
@@ -588,8 +707,9 @@ namespace Serein.Script
if (_currentToken.Type == TokenType.String)
{
var text = _currentToken.Value;
var node = new StringNode(text).SetTokenInfo(_currentToken);
_currentToken = _lexer.NextToken(); // 消耗字符串
return new StringNode(text).SetTokenInfo(_currentToken);
return node;
}
if (_currentToken.Type == TokenType.Char)
{
@@ -599,6 +719,7 @@ namespace Serein.Script
}
if( _currentToken.Type == TokenType.InterpolatedString)
{
// 暂未实现插值字符串
// 可能是插值字符串;
// let context = $"a{A}b{B}c";
// let context = "a" + A + "b" + B + c;
@@ -607,11 +728,32 @@ namespace Serein.Script
}
}
if (_currentToken.Type == TokenType.Number)
if (_currentToken.Type == TokenType.NumberInt)
{
var value = int.Parse(_currentToken.Value);
_currentToken = _lexer.NextToken(); // 消耗 int 整型
return new NumberIntNode(value).SetTokenInfo(_currentToken);
}
if (_currentToken.Type == TokenType.NumberLong)
{
var value = long.Parse(_currentToken.Value);
_currentToken = _lexer.NextToken(); // 消耗
return new NumberLongNode(value).SetTokenInfo(_currentToken);
}
if (_currentToken.Type == TokenType.NumberFloat)
{
var value = float.Parse(_currentToken.Value);
_currentToken = _lexer.NextToken(); // 消耗数字
return new NumberNode(value).SetTokenInfo(_currentToken);
return new NumberFloatNode(value).SetTokenInfo(_currentToken);
}
if (_currentToken.Type == TokenType.NumberDouble)
{
var value = float.Parse(_currentToken.Value);
_currentToken = _lexer.NextToken(); // 消耗数字
return new NumberDoubleNode(value).SetTokenInfo(_currentToken);
}
#endregion
@@ -629,6 +771,7 @@ namespace Serein.Script
// 创建对象
if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "new")
{
// 可能是对象实例化
return ParseObjectInstantiation();
}
@@ -640,13 +783,15 @@ namespace Serein.Script
// 该标识符是方法调用
if (_identifierPeekToken.Type == TokenType.ParenthesisLeft)
{
// 可能是函数调用
return ParseFunctionCall();
}
// 需要从该标识符调用另一个标识符
if (_identifierPeekToken.Type == TokenType.Dot)
{
return ParseMemberAccessOrAssignment();
// 可能是成员访问或成员赋值
return ParseMemberAccessOrAssignment(); // 二元操作中获取对象成员
}

View File

@@ -0,0 +1,173 @@
using Serein.Library.Utils;
using Serein.Script.Node;
using Serein.Script.Symbol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Serein.Script
{
public class SereinScriptTypeAnalysis
{
private Dictionary<string, SymbolInfo> SymbolInfos = new Dictionary<string, SymbolInfo>();
public SereinScriptTypeAnalysis(ProgramNode programNode)
{
SymbolInfos.Clear(); // 清空符号表
foreach (ASTNode astNode in programNode.Statements)
{
var type = Trace(astNode);
if (type is null) continue;
var info = Analyse(astNode, type);
if(info != null)
{
SymbolInfos[info.Name] = info;
}
/*if(astNode is AssignmentNode assignmentNode)
{
var name = assignmentNode.Variable;
var node = assignmentNode.Value;
var type = Analyse(node);
if(type is null)
{
continue;
}
var symbolInfo = new SymbolInfo
{
Type = type,
Node = node,
Name = name,
};
SymbolInfos[name] = symbolInfo;
}*/
}
}
/// <summary>
/// 追踪类型
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private Type Trace(ASTNode node)
{
if (node == null)
{
return null;
}
switch (node)
{
case NullNode nullNode: // 返回null
return typeof(object);
case BooleanNode booleanNode: // 返回布尔
return typeof(bool);
case NumberIntNode numberNode: // 数值
return typeof(int);
case StringNode stringNode: // 字符串
return typeof(string);
case CharNode charNode: // char
return typeof(char);
case IdentifierNode identifierNode: // 定义变量
return typeof(object);
case AssignmentNode assignmentNode: // 赋值行为
var type = Trace(assignmentNode.Value);
return type;
//throw new SereinSciptException(identifierNode, "尝试使用值为null的变量");
//throw new SereinSciptException(identifierNode, "尝试使用未声明的变量");
case BinaryOperationNode binOpNode: // 递归计算二元操作
var leftType = Trace(binOpNode.Left);
var op = binOpNode.Operator;
var rightType = Trace(binOpNode.Right);
var resultType = BinaryOperationEvaluator.EvaluateType(leftType, op, rightType);
return resultType;
case ClassTypeDefinitionNode classTypeDefinitionNode:
var definitionType = DynamicObjectHelper.CreateTypeWithProperties(classTypeDefinitionNode.Fields, classTypeDefinitionNode.ClassName, true);
return definitionType;
case ObjectInstantiationNode objectInstantiationNode: // 创建对象
var typeName = objectInstantiationNode.TypeName;
var objectType = Type.GetType(typeName);
objectType ??= DynamicObjectHelper.GetCacheType(typeName);
return objectType;
case FunctionCallNode callNode: // 调用方法
return null;
case MemberFunctionCallNode memberFunctionCallNode: // 对象方法调用
return null;
case MemberAccessNode memberAccessNode: // 对象成员访问
var memberType = memberAccessNode.MemberName;
return null;
case CollectionIndexNode collectionIndexNode:
case ReturnNode returnNode: // 返回内容
default:
break;
//throw new SereinSciptException(node, $"解释器 EvaluateAsync() 未实现{node}节点行为");
}
return null;
}
private SymbolInfo Analyse(ASTNode node, Type type)
{
if (node == null)
{
return null;
}
switch (node)
{
case IdentifierNode identifierNode: // 定义变量
return new SymbolInfo
{
Name = identifierNode.Name,
Node = node,
Type = type,
};
case AssignmentNode assignmentNode: // 赋值行为
return new SymbolInfo
{
Name = assignmentNode.Variable,
Node = node,
Type = type,
};
case BinaryOperationNode binOpNode: // 递归计算二元操作
//case ClassTypeDefinitionNode classTypeDefinitionNode
case ObjectInstantiationNode objectInstantiationNode: // 创建对象
case FunctionCallNode callNode: // 调用方法
case MemberFunctionCallNode memberFunctionCallNode: // 对象方法调用
case MemberAccessNode memberAccessNode: // 对象成员访问
case CollectionIndexNode collectionIndexNode:
case ReturnNode returnNode: // 返回内容
default:
break;
//throw new SereinSciptException(node, $"解释器 EvaluateAsync() 未实现{node}节点行为");
}
return null;
}
/*
case NullNode nullNode: // 返回null
case BooleanNode booleanNode: // 返回布尔
case NumberIntNode numberNode: // 数值
case StringNode stringNode: // 字符串
case CharNode charNode: // char
case IdentifierNode identifierNode: // 定义变量
case AssignmentNode assignmentNode: // 赋值行为
case BinaryOperationNode binOpNode: // 递归计算二元操作
case ObjectInstantiationNode objectInstantiationNode: // 创建对象
case FunctionCallNode callNode: // 调用方法
case MemberFunctionCallNode memberFunctionCallNode: // 对象方法调用
case MemberAccessNode memberAccessNode: // 对象成员访问
case CollectionIndexNode collectionIndexNode:
case ReturnNode returnNode: // 返回内容
default:
break;
*/
}
}

View File

@@ -0,0 +1,37 @@
using Serein.Script.Node;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Script.Symbol
{
public enum SymbolType
{
Identifier,
FunctionReturn,
}
/// <summary>
/// 符号信息
/// </summary>
internal class SymbolInfo
{
/// <summary>
/// 符号名称
/// </summary>
public string Name;
/// <summary>
/// 对应类型
/// </summary>
public Type Type;
/// <summary>
/// 节点
/// </summary>
public ASTNode Node;
}
}

View File

@@ -101,7 +101,7 @@ namespace Serein.Workbench.Node.View
public void RemoveConnection(ConnectionControl connection)
{
connectionControls.Remove(connection);
connection.Remote();
connection.Remove(); // 主动删除连接
}
/// <summary>
@@ -111,7 +111,7 @@ namespace Serein.Workbench.Node.View
{
foreach (var connection in this.connectionControls)
{
connection.Remote();
connection.Remove(); // 主动删除连接
}
}

View File

@@ -1,5 +1,6 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Api;
using Serein.Workbench.Extension;
using Serein.Workbench.Tool;
using System;
@@ -142,7 +143,7 @@ namespace Serein.Workbench.Node.View
/// <summary>
/// 连接线
/// </summary>
private ConnectionLineShape BezierLine;
public ConnectionLineShape BezierLine { get;private set; }
@@ -225,17 +226,39 @@ namespace Serein.Workbench.Node.View
private void ConfigureLineContextMenu()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除连线", (s, e) => Remote()));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("移除该连接关系", (s, e) => Remove()));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("于父节点调用顺序中置顶", (s, e) => Topping()));
BezierLine.ContextMenu = contextMenu;
}
/// <summary>
/// 从画布删除
/// </summary>
public void RemoveOnCanvas()
{
Canvas.Children.Remove(BezierLine);
}
/// <summary>
/// 删除该连线
/// </summary>
public void Remote()
public void Remove()
{
/*string startGuid = Start.MyNode.Guid;
string endGuid = End.MyNode.Guid;
if (flowEventSerice is null) flowEventSerice = App.GetService<IFlowEEForwardingService>();
NodeConnectChangeHandler handler = null;
handler = (e) =>
{
if(e.ConnectionInvokeType == InvokeType && e.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove)
{
}
flowEventSerice.NodeConnectChanged -= handler;
};
flowEventSerice.NodeConnectChanged += handler;*/
//
Canvas.Children.Remove(BezierLine);
var env = Start.MyNode.Env;
var canvasGuid = Start.MyNode.CanvasDetails.Guid;
@@ -247,7 +270,7 @@ namespace Serein.Workbench.Node.View
}
else if (jct == JunctionOfConnectionType.Arg)
{
env.FlowEdit.RemoveArgSourceConnect(canvasGuid,Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ;
env.FlowEdit.RemoveArgSourceConnect(canvasGuid, Start.MyNode.Guid, End.MyNode.Guid, ArgIndex);
}
}

View File

@@ -50,6 +50,11 @@ namespace Serein.Workbench.Services
/// </summary>
public Action<MethodDetailsInfo> OnViewMethodDetailsInfoChanged { get; set; }
/// <summary>
/// FlowCanvasView 监听,需要移除连接线(控件)
/// </summary>
public Action<NodeConnectChangeEventArgs> OnRemoveConnectionLine { get; set; }
#endregion
#region
@@ -244,8 +249,11 @@ namespace Serein.Workbench.Services
_ => null
};
/*if(e.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove)
{
OnRemoveConnectionLine.Invoke(e); // 删除连线
}*/
action?.Invoke();
return;
}
@@ -408,7 +416,6 @@ namespace Serein.Workbench.Services
}
#endregion
/// <summary>

View File

@@ -42,6 +42,7 @@ using Clipboard = System.Windows.Clipboard;
using TextDataFormat = System.Windows.TextDataFormat;
using System.Windows.Media.Animation;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Services;
namespace Serein.Workbench.Views
{
@@ -151,6 +152,7 @@ namespace Serein.Workbench.Views
}
/// <summary>
/// 设置绑定
/// </summary>
@@ -175,9 +177,52 @@ namespace Serein.Workbench.Views
private void InitEvent()
{
keyEventService.OnKeyDown += KeyEventService_OnKeyDown;
//flowNodeService.OnRemoveConnectionLine += FlowNodeService_OnRemoveConnectionLine;
flowEEForwardingService.NodeLocated += FlowEEForwardingService_OnNodeLocated;
}
private void FlowNodeService_OnRemoveConnectionLine(NodeConnectChangeEventArgs e)
{
if(e.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create || e.CanvasGuid != this.Guid)
{
return;
}
var connectionControl = Connections.FirstOrDefault(c =>
{
if (c.Start.MyNode.Guid != e.FromNodeGuid
|| c.End.MyNode.Guid != e.ToNodeGuid)
{
return false; // 不是当前连接
}
var jct1 = c.Start.JunctionType.ToConnectyionType();
var jct2 = c.End.JunctionType.ToConnectyionType();
if (e.JunctionOfConnectionType == JunctionOfConnectionType.Invoke)
{
if (jct1 == JunctionOfConnectionType.Invoke
&& jct2 == JunctionOfConnectionType.Invoke)
{
return true; // 是当前连接
}
}
else
{
if (c.ArgIndex == e.ArgIndex
&& jct1 == JunctionOfConnectionType.Arg
&& jct2 == JunctionOfConnectionType.Arg)
{
return true; // 是当前连接
}
}
return true;
});
if(connectionControl is null)
{
return;
}
connectionControl.RemoveOnCanvas(); // 移除连接线
}
/// <summary>
/// 节点需要定位
/// </summary>
@@ -224,6 +269,7 @@ namespace Serein.Workbench.Views
nodeControl.RenderTransform = translateTransform;
ElasticAnimation(nodeControl, translateTransform, 6, 0.5, 0.5);
}
/// <summary>
/// 控件抖动
/// 来源https://www.cnblogs.com/RedSky/p/17705411.html
@@ -257,7 +303,6 @@ namespace Serein.Workbench.Views
};
}
/// <summary>
/// 加载完成后刷新显示
/// </summary>
@@ -267,8 +312,6 @@ namespace Serein.Workbench.Views
RefreshAllLine();
}
/// <summary>
/// 当前画布创建了节点
/// </summary>
@@ -1490,9 +1533,9 @@ namespace Serein.Workbench.Views
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.FlowEdit.SetStartNode(canvasGuid, nodeGuid)));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", async (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", (s, e) =>
{
flowEnvironment.FlowEdit.RemoveNode(canvasGuid, nodeGuid);
flowNodeService.RemoteNode(nodeControl);
}));
#region -