diff --git a/Library/Api/IScriptFlowApi.cs b/Library/Api/IScriptFlowApi.cs new file mode 100644 index 0000000..e7539b2 --- /dev/null +++ b/Library/Api/IScriptFlowApi.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Api +{ + /// + /// 脚本代码中关于流程运行的API + /// + public interface IScriptFlowApi + { + /// + /// 当前流程 + /// + IFlowEnvironment Env { get; } + /// + /// 对应的节点 + /// + NodeModelBase NodeModel { get; } + + /// + /// 动态流程上下文 + /// + IDynamicContext Context { get; set; } + + /// + /// 根据索引从入参数据获取数据 + /// + /// + /// + object GetDataOfParams(int index); + /// + /// 根据入参名称从入参数据获取数据 + /// + /// + /// + object GetDataOfParams(string name); + /// + /// 获取全局数据 + /// + /// + /// + object GetGlobalData(string keyName); + /// + /// 获取流程当前传递的数据 + /// + /// + object GetFlowData(); + /// + /// 立即调用某个节点并获取其返回值 + /// + /// + /// + Task CallNode(string nodeGuid); + } +} diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 30872ad..44b472b 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -97,6 +97,10 @@ namespace Serein.Library /// 全局数据 /// GlobalData, + /// + /// 脚本节点 + /// + Script, } } diff --git a/Library/FlowNode/DelegateDetails.cs b/Library/FlowNode/DelegateDetails.cs index 6d3231c..88f2eff 100644 --- a/Library/FlowNode/DelegateDetails.cs +++ b/Library/FlowNode/DelegateDetails.cs @@ -22,22 +22,11 @@ namespace Serein.Library public DelegateDetails(MethodInfo methodInfo) { var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var emitDelegate); - _emitMethodType = emitMethodType; + _emitMethodInfo = emitMethodType; _emitDelegate = emitDelegate; } - /// - /// 记录Emit委托 - /// - /// - /// - public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate) - { - _emitMethodType = EmitMethodType; - _emitDelegate = EmitDelegate; - } - /*/// /// 更新委托方法 @@ -50,9 +39,13 @@ namespace Serein.Library _emitDelegate = EmitDelegate; }*/ - private Delegate _emitDelegate; - private EmitMethodType _emitMethodType; + private EmitMethodInfo _emitMethodInfo; + + /// + /// 该Emit委托的相应信息 + /// + public EmitMethodInfo EmitMethodInfo => _emitMethodInfo; ///// ///// 普通方法:Func<object,object[],object> @@ -65,6 +58,22 @@ namespace Serein.Library ///// //public EmitMethodType EmitMethodType { get => _emitMethodType; } + + + public async Task AutoInvokeAsync(object[] args) + { + if (_emitMethodInfo.IsStatic) + { + return await InvokeAsync(null, args); + } + else + { + var obj = Activator.CreateInstance(_emitMethodInfo.DeclaringType); + return await InvokeAsync(obj, args); + } + throw new Exception("Not static method"); + } + /// /// 使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。 /// @@ -77,16 +86,20 @@ namespace Serein.Library { args = Array.Empty(); } + if(_emitMethodInfo.IsStatic) + { + instance = null; + } object result = null; - if (_emitMethodType == EmitMethodType.HasResultTask && _emitDelegate is Func> hasResultTask) + if (_emitDelegate is Func> hasResultTask) { result = await hasResultTask(instance, args); } - else if (_emitMethodType == EmitMethodType.Task && _emitDelegate is Func task) + else if (_emitDelegate is Func task) { await task.Invoke(instance, args); } - else if (_emitMethodType == EmitMethodType.Func && _emitDelegate is Func func) + else if (_emitDelegate is Func func) { result = func.Invoke(instance, args); } diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index 441a5db..99d0749 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -69,14 +69,6 @@ namespace Serein.Library public abstract partial class NodeModelBase : IDynamicFlowNode { - /// - /// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法 - /// - public virtual void OnCreating() - { - - } - public NodeModelBase(IFlowEnvironment environment) { PreviousNodes = new Dictionary>(); diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 1a74be1..d296fd7 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -29,6 +29,15 @@ namespace Serein.Library public abstract partial class NodeModelBase : IDynamicFlowNode { #region 节点相关事件 + /// + /// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法 + /// + public virtual void OnCreating() + { + + } + + /// /// 保存自定义信息 @@ -55,6 +64,7 @@ namespace Serein.Library { } + /// /// 移除该节点 /// @@ -95,7 +105,6 @@ namespace Serein.Library this.Env = null; } - /// /// 输出方法参数信息 /// @@ -125,7 +134,6 @@ namespace Serein.Library } - /// /// 导出为节点信息 /// @@ -164,8 +172,6 @@ namespace Serein.Library return nodeInfo; } - - /// /// 从节点信息加载节点 /// @@ -244,6 +250,8 @@ namespace Serein.Library #region 节点方法的执行 + + /// /// 是否应该退出执行 /// diff --git a/Library/FlowNode/ScriptFlowApi.cs b/Library/FlowNode/ScriptFlowApi.cs new file mode 100644 index 0000000..4f5e686 --- /dev/null +++ b/Library/FlowNode/ScriptFlowApi.cs @@ -0,0 +1,56 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 脚本代码中关于流程运行的API + /// + public class ScriptFlowApi : IScriptFlowApi + { + /// + /// 流程环境 + /// + public IFlowEnvironment Env { get; private set; } + + /// + /// 创建流程脚本接口 + /// + /// + public ScriptFlowApi(IFlowEnvironment environment) + { + Env = environment; + } + + Task IScriptFlowApi.CallNode(string nodeGuid) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetDataOfParams(int index) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetDataOfParams(string name) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetFlowData() + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetGlobalData(string keyName) + { + throw new NotImplementedException(); + } + } + + +} diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs index c08c257..8fff928 100644 --- a/Library/NodeStaticConfig.cs +++ b/Library/NodeStaticConfig.cs @@ -12,11 +12,11 @@ namespace Serein.Library /// /// 全局触发器CTS /// - public const string FlipFlopCtsName = "<>.FlowFlipFlopCts"; + public const string FlipFlopCtsName = "$FlowFlipFlopCts"; /// /// 流程运行CTS /// - public const string FlowRungCtsName = "<>.FlowRungCtsName"; + public const string FlowRungCtsName = "$FlowRungCtsName"; /// diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 9d21c52..310bff3 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -32,6 +32,7 @@ + diff --git a/Library/Utils/DynamicObjectHelper.cs b/Library/Utils/DynamicObjectHelper.cs index 6a5229f..0261177 100644 --- a/Library/Utils/DynamicObjectHelper.cs +++ b/Library/Utils/DynamicObjectHelper.cs @@ -66,6 +66,95 @@ namespace Serein.Library.Utils } + public static Type CreateTypeWithProperties(IDictionary properties, string typeName) + { + // 如果类型已经缓存,直接返回缓存的类型 + if (typeCache.ContainsKey(typeName)) + { + return typeCache[typeName]; + } + + // 定义动态程序集和模块 + var assemblyName = new AssemblyName("DynamicAssembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); + + // 定义动态类型 + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); + + // 为每个属性名和值添加相应的属性到动态类型中 + foreach (var kvp in properties) + { + string propName = kvp.Key; + object propValue = kvp.Value; + Type propType; + + if (propValue is IList>) // 处理数组类型 + { + var nestedPropValue = (propValue as IList>)[0]; + var nestedType = CreateTypeWithProperties(nestedPropValue, $"{propName}Element"); + propType = nestedType.GetType().MakeArrayType(); // 创建数组类型 + } + else if (propValue is Dictionary nestedProperties) + { + // 如果值是嵌套的字典,递归创建嵌套类型 + propType = CreateTypeWithProperties(nestedProperties, $"{typeName}_{propName}").GetType(); + } + else if (propValue is Type type) + { + // 如果是普通类型,使用值的类型 + propType = type ?? typeof(object); + } + else + { + throw new Exception($"无法解析的类型:{propValue}"); + } + + // 定义私有字段和公共属性 + var fieldBuilder = typeBuilder.DefineField("_" + propName, propType, FieldAttributes.Private); + var propertyBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, propType, null); + + // 定义 getter 方法 + var getMethodBuilder = typeBuilder.DefineMethod( + "get_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + propType, + Type.EmptyTypes); + + var getIL = getMethodBuilder.GetILGenerator(); + getIL.Emit(OpCodes.Ldarg_0); + getIL.Emit(OpCodes.Ldfld, fieldBuilder); + getIL.Emit(OpCodes.Ret); + + // 定义 setter 方法 + var setMethodBuilder = typeBuilder.DefineMethod( + "set_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + null, + new Type[] { propType }); + + var setIL = setMethodBuilder.GetILGenerator(); + setIL.Emit(OpCodes.Ldarg_0); + setIL.Emit(OpCodes.Ldarg_1); + setIL.Emit(OpCodes.Stfld, fieldBuilder); + setIL.Emit(OpCodes.Ret); + + // 将 getter 和 setter 方法添加到属性 + propertyBuilder.SetGetMethod(getMethodBuilder); + propertyBuilder.SetSetMethod(setMethodBuilder); + } + + // 创建类型并缓存 + var dynamicType = typeBuilder.CreateType(); + typeCache[typeName] = dynamicType; + + // 创建对象实例 + return dynamicType; + } + + + + #region 动态创建对象并赋值 // 方法 1: 创建动态类型及其对象实例 public static object CreateObjectWithProperties(IDictionary properties, string typeName) @@ -298,6 +387,7 @@ namespace Serein.Library.Utils return false; } + #endregion } } diff --git a/Library/Utils/EmitHelper.cs b/Library/Utils/EmitHelper.cs index 0cf481a..8980b90 100644 --- a/Library/Utils/EmitHelper.cs +++ b/Library/Utils/EmitHelper.cs @@ -14,6 +14,20 @@ namespace Serein.Library.Utils /// public class EmitHelper { + + public class EmitMethodInfo + { + public Type DeclaringType { get; set; } + /// + /// 是异步方法 + /// + public bool IsTask { get; set; } + /// + /// 是静态的 + /// + public bool IsStatic { get; set; } + } + public enum EmitMethodType { /// @@ -28,6 +42,15 @@ namespace Serein.Library.Utils /// 有返回值的异步方法 /// HasResultTask, + + /// + /// 普通的方法。如果方法返回void时,将会返回null。 + /// + StaticFunc, + /// + /// 无返回值的异步方法 + /// + StaticTask, } public static bool IsGenericTask(Type returnType, out Type taskResult) @@ -60,17 +83,19 @@ namespace Serein.Library.Utils /// /// /// - public static EmitMethodType CreateDynamicMethod(MethodInfo methodInfo,out Delegate @delegate) + public static EmitMethodInfo CreateDynamicMethod(MethodInfo methodInfo,out Delegate @delegate) { + EmitMethodInfo emitMethodInfo = new EmitMethodInfo(); bool IsTask = IsGenericTask(methodInfo.ReturnType, out var taskGenericsType); bool IsTaskGenerics = taskGenericsType != null; DynamicMethod dynamicMethod; - + Type returnType; if (!IsTask) { // 普通方法 returnType = typeof(object); + } else { @@ -96,10 +121,19 @@ namespace Serein.Library.Utils var il = dynamicMethod.GetILGenerator(); - // 加载实例 (this) - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Castclass, methodInfo.DeclaringType); // 将 ISocketControlBase 转换为目标类类型 + // 判断是否为静态方法 + bool isStatic = methodInfo.IsStatic; + if (isStatic) + { + // 如果是静态方法,直接跳过实例(不加载Ldarg_0) + } + else + { + // 加载实例 (this) 对于非静态方法 + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, methodInfo.DeclaringType); // 将 ISocketControlBase 转换为目标类类型 + } // 加载方法参数 var methodParams = methodInfo.GetParameters(); for (int i = 0; i < methodParams.Length; i++) @@ -122,11 +156,17 @@ namespace Serein.Library.Utils il.Emit(OpCodes.Castclass, paramType); } - } - // 调用方法 - il.Emit(OpCodes.Callvirt, methodInfo); + // 调用方法:静态方法使用 Call,实例方法使用 Callvirt + if (isStatic) + { + il.Emit(OpCodes.Call, methodInfo); // 对于静态方法,使用 Call + } + else + { + il.Emit(OpCodes.Callvirt, methodInfo); // 对于实例方法,使用 Callvirt + } //// 处理返回值,如果没有返回值,则返回null if (methodInfo.ReturnType == typeof(void)) @@ -139,27 +179,28 @@ namespace Serein.Library.Utils } // 处理返回值,如果没有返回值,则返回null il.Emit(OpCodes.Ret); // 返回 - EmitMethodType emitMethodType; if (IsTask) { if (IsTaskGenerics) { - emitMethodType = EmitMethodType.HasResultTask; @delegate = dynamicMethod.CreateDelegate(typeof(Func>)); } else { - emitMethodType = EmitMethodType.Task; @delegate = dynamicMethod.CreateDelegate(typeof(Func)); } } else { - emitMethodType = EmitMethodType.Func; @delegate = dynamicMethod.CreateDelegate(typeof(Func)); } - return emitMethodType; + return new EmitMethodInfo + { + DeclaringType = methodInfo.DeclaringType, + IsTask = IsTask, + IsStatic = isStatic + }; } diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index 1c81829..a71bf4d 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -15,6 +15,8 @@ namespace Serein.Library.Utils /// public class SereinIOC/* : ISereinIOC*/ { + + /// /// 类型集合,暂放待实例化的类型,完成实例化之后移除 /// diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 819e6d3..aea4500 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -66,6 +66,7 @@ namespace Serein.NodeFlow.Env NodeMVVMManagement.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点 NodeMVVMManagement.RegisterModel(NodeControlType.ConditionRegion, typeof(CompositeConditionNode)); // 条件区域 NodeMVVMManagement.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点 + NodeMVVMManagement.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点 #endregion } @@ -401,9 +402,13 @@ namespace Serein.NodeFlow.Env IOC.Reset(); // 开始运行时清空ioc中注册的实例 - IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName, this); + IOC.Register(); // 注册脚本接口 + IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName, this); // 注册流程实例 if (this.UIContextOperation is not null) + { + // 注册封装好的UI线程上下文 IOC.CustomRegisterInstance(typeof(UIContextOperation).FullName, this.UIContextOperation, false); + } await flowStarter.RunAsync(this, nodes, autoRegisterTypes, initMethods, loadMethods, exitMethods); diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs index 283bf95..215505a 100644 --- a/NodeFlow/Env/FlowFunc.cs +++ b/NodeFlow/Env/FlowFunc.cs @@ -90,6 +90,7 @@ namespace Serein.NodeFlow.Env $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点 + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleScriptNode)}" => NodeControlType.Script, // 数据节点 _ => NodeControlType.None, }; diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 25f6e91..8d101a9 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -56,19 +56,6 @@ namespace Serein.NodeFlow #endif await startNode.StartFlowAsync(context); // 开始运行时从选定节点开始运行 context.Exit(); - -/* - - foreach (var node in NodeModels.Values) - { - if (node is not null) - { - node.ReleaseFlowData(); // 退出时释放对象 - } - } - - - */ } @@ -254,6 +241,7 @@ namespace Serein.NodeFlow try { + //await TestScript(env); await startNode.StartFlowAsync(Context); // 开始运行时从起始节点开始运行 if (flipflopNodes.Count > 0) @@ -293,6 +281,39 @@ namespace Serein.NodeFlow #endregion } + +#if false + + public async Task TestScript(IFlowEnvironment environment) + { + SingleScriptNode singleScriptNode = new SingleScriptNode(environment); + string script = + """ + + //let argData1 = flow.GetArgIndex(0); // 通过索引的方式,获取当前节点入参第一个参数 + //let argData2 = flow.GetArgName("name"); // 通过名称的方式,获取当前节点入参的第二个参数 + //let nodeData = flow.GetFlowData(); // 获取上一个节点的数据 + //let state = flow.GetGlobalData("key name"); // 获取全局数据 + + //let result1 = flow.CallNode("node guid",); // 立即调用某个节点,获取数据 + //let result2 = flow.CallFunc(); + + class User{ + int ID; + string Name; + } + let user = new User(); + user.ID = 12345; + user.Name = "张三"; + return user; + """; + singleScriptNode.Script = script; + singleScriptNode.LoadScript(); + var result = await singleScriptNode.ExecutingAsync(new DynamicContext(environment)); + SereinEnv.WriteLine(InfoType.INFO, result?.ToString()); + } +#endif + private ConcurrentDictionary dictGlobalFlipflop = []; /// diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/SingleScriptNode.cs new file mode 100644 index 0000000..0c9b04c --- /dev/null +++ b/NodeFlow/Model/SingleScriptNode.cs @@ -0,0 +1,143 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Script; +using Serein.Script.Node; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Serein.NodeFlow.Model +{ + [NodeProperty(ValuePath = NodeValuePath.Node)] + public partial class SingleScriptNode : NodeModelBase + { + [PropertyInfo(IsNotification = true)] + private string _script; + } + + /// + /// 流程脚本节点 + /// + public partial class SingleScriptNode : NodeModelBase + { + private IScriptFlowApi ScriptFlowApi { get; } + + private ASTNode mainNode; + + /// + /// 构建流程脚本节点 + /// + /// + public SingleScriptNode(IFlowEnvironment environment):base(environment) + { + //ScriptFlowApi = environment.IOC.Get(); + ScriptFlowApi = new ScriptFlowApi(environment, this); + + + MethodInfo? method = this.GetType().GetMethod(nameof(GetFlowApi)); + if (method != null) + { + SereinScriptInterpreter.AddFunction(nameof(GetFlowApi), method, () => this); // 挂载获取流程接口 + } + + // 挂载静态方法 + var tempMethods = typeof(BaseFunc).GetMethods().Where(method => + !(method.Name.Equals("GetHashCode") + || method.Name.Equals("Equals") + || method.Name.Equals("ToString") + || method.Name.Equals("GetType") + )).Select(method => (method.Name, method)).ToArray(); + + foreach ((string name, MethodInfo method) item in tempMethods) + { + SereinScriptInterpreter.AddFunction(item.name, item.method); // 加载基础方法 + } + } + + /// + /// 加载脚本代码 + /// + public void LoadScript() + { + try + { + mainNode = new SereinScriptParser(Script).Parse(); + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, ex.ToString()); + } + } + + /// + /// 执行脚本 + /// + /// + /// + public override async Task ExecutingAsync(IDynamicContext context) + { + + mainNode ??= new SereinScriptParser(Script).Parse(); + SereinScriptInterpreter scriptInterpreter = new SereinScriptInterpreter(); + var result = await scriptInterpreter.InterpretAsync(mainNode); // 从入口节点执行 + scriptInterpreter.ResetVar(); + return result; + } + + + public IScriptFlowApi GetFlowApi() + { + return ScriptFlowApi; + } + + private static class BaseFunc + { + public static Type TypeOf(object type) + { + return type.GetType(); + } + + + public static void Print(object value) + { + SereinEnv.WriteLine(InfoType.INFO, value?.ToString()); + } + + #region 数据转换 + public static int ToInt(object value) + { + return int.Parse(value.ToString()); + } + public static double ToDouble(object value) + { + return double.Parse(value.ToString()); + } + public static bool ToBool(object value) + { + return bool.Parse(value.ToString()); + } + #endregion + + public static async Task Delay(object value) + { + if (value is int @int) + { + Console.WriteLine($"等待{@int}ms"); + await Task.Delay(@int); + } + else if (value is TimeSpan timeSpan) + { + + Console.WriteLine($"等待{timeSpan}"); + await Task.Delay(timeSpan); + } + + } + } + } +} diff --git a/NodeFlow/ScriptFlowApi.cs b/NodeFlow/ScriptFlowApi.cs new file mode 100644 index 0000000..05c5754 --- /dev/null +++ b/NodeFlow/ScriptFlowApi.cs @@ -0,0 +1,66 @@ +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + /// + /// 脚本代码中关于流程运行的API + /// + public class ScriptFlowApi : IScriptFlowApi + { + /// + /// 流程环境 + /// + public IFlowEnvironment Env { get; private set; } + + /// + /// 对应的节点 + /// + public NodeModelBase NodeModel { get; private set; } + + IDynamicContext IScriptFlowApi.Context { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + /// + /// 创建流程脚本接口 + /// + /// 运行环境 + /// 节点 + public ScriptFlowApi(IFlowEnvironment environment, NodeModelBase nodeModel) + { + Env = environment; + NodeModel = nodeModel; + } + + Task IScriptFlowApi.CallNode(string nodeGuid) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetDataOfParams(int index) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetDataOfParams(string name) + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetFlowData() + { + throw new NotImplementedException(); + } + + object IScriptFlowApi.GetGlobalData(string keyName) + { + throw new NotImplementedException(); + } + } + + +} diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 0c55436..18f57d9 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -67,6 +67,7 @@ + diff --git a/Serein.Script/Node/ASTNode.cs b/Serein.Script/Node/ASTNode.cs new file mode 100644 index 0000000..c184bea --- /dev/null +++ b/Serein.Script/Node/ASTNode.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + public abstract class ASTNode + { + public string Code { get; private set; } + public int Row { get; private set; } + public int StartIndex { get; private set; } + public int Length { get; private set; } + + internal ASTNode SetTokenInfo(Token token) + { + Row = token.Row; + StartIndex = token.StartIndex; + Length = token.Length; + Code = token.Code; + return this; + } + } + +} diff --git a/Serein.Script/Node/AssignmentNode.cs b/Serein.Script/Node/AssignmentNode.cs new file mode 100644 index 0000000..a5f731b --- /dev/null +++ b/Serein.Script/Node/AssignmentNode.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + + /// + /// 变量节点 + /// + public class AssignmentNode : ASTNode + { + public string Variable { get; } + public ASTNode Value { get; } + public AssignmentNode(string variable, ASTNode value) => (Variable, Value) = (variable, value); + } + + +} diff --git a/Serein.Script/Node/BinaryOperationNode.cs b/Serein.Script/Node/BinaryOperationNode.cs new file mode 100644 index 0000000..3513de0 --- /dev/null +++ b/Serein.Script/Node/BinaryOperationNode.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 二元表达式节点 + /// + + public class BinaryOperationNode : ASTNode + { + public ASTNode Left { get; } + public string Operator { get; } + public ASTNode Right { get; } + + public BinaryOperationNode(ASTNode left, string op, ASTNode right) + { + Left = left; + Operator = op; + Right = right; + } + } +} diff --git a/Serein.Script/Node/BooleanNode.cs b/Serein.Script/Node/BooleanNode.cs new file mode 100644 index 0000000..1c6695a --- /dev/null +++ b/Serein.Script/Node/BooleanNode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 布尔字面量 + /// + public class BooleanNode : ASTNode + { + public bool Value { get; } + public BooleanNode(bool value) => Value = value; + } +} diff --git a/Serein.Script/Node/ClassTypeDefinitionNode.cs b/Serein.Script/Node/ClassTypeDefinitionNode.cs new file mode 100644 index 0000000..2affd7a --- /dev/null +++ b/Serein.Script/Node/ClassTypeDefinitionNode.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 动态类型定义 + /// + public class ClassTypeDefinitionNode : ASTNode + { + public string ClassName { get; } + public Dictionary Fields { get; } + + public ClassTypeDefinitionNode(Dictionary fields, string className) + { + this.Fields = fields; + this.ClassName = className; + } + } + +} diff --git a/Serein.Script/Node/CollectionIndexNode.cs b/Serein.Script/Node/CollectionIndexNode.cs new file mode 100644 index 0000000..5f7a592 --- /dev/null +++ b/Serein.Script/Node/CollectionIndexNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 集合索引获取 + /// + public class CollectionIndexNode : ASTNode + { + public ASTNode IndexValue { get; } + public CollectionIndexNode(ASTNode indexValue) + { + this.IndexValue = indexValue; + } + } +} diff --git a/Serein.Script/Node/FunctionCallNode.cs b/Serein.Script/Node/FunctionCallNode.cs new file mode 100644 index 0000000..f7ae335 --- /dev/null +++ b/Serein.Script/Node/FunctionCallNode.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 挂载函数调用 + /// + public class FunctionCallNode : ASTNode + { + public string FunctionName { get; } + public List Arguments { get; } + + public FunctionCallNode(string functionName, List arguments) + { + FunctionName = functionName; + Arguments = arguments; + } + } + +} diff --git a/Serein.Script/Node/IdentifierNode.cs b/Serein.Script/Node/IdentifierNode.cs new file mode 100644 index 0000000..f2db273 --- /dev/null +++ b/Serein.Script/Node/IdentifierNode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 标识符(变量) + /// + public class IdentifierNode : ASTNode + { + public string Name { get; } + public IdentifierNode(string name) => Name = name; + } +} diff --git a/Serein.Script/Node/IfNode.cs b/Serein.Script/Node/IfNode.cs new file mode 100644 index 0000000..bbf1b2e --- /dev/null +++ b/Serein.Script/Node/IfNode.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 条件节点 + /// + public class IfNode : ASTNode + { + public ASTNode Condition { get; } + public List TrueBranch { get; } + public List FalseBranch { get; } + public IfNode(ASTNode condition, List trueBranch, List falseBranch) + => (Condition, TrueBranch, FalseBranch) = (condition, trueBranch, falseBranch); + } + +} diff --git a/Serein.Script/Node/MemberAccessNode.cs b/Serein.Script/Node/MemberAccessNode.cs new file mode 100644 index 0000000..d889a3d --- /dev/null +++ b/Serein.Script/Node/MemberAccessNode.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 表示对象的成员访问 + /// + public class MemberAccessNode : ASTNode + { + public ASTNode Object { get; } + public string MemberName { get; } + + public MemberAccessNode(ASTNode obj, string memberName) + { + Object = obj; + MemberName = memberName; + } + } +} diff --git a/Serein.Script/Node/MemberAssignmentNode.cs b/Serein.Script/Node/MemberAssignmentNode.cs new file mode 100644 index 0000000..ffa643a --- /dev/null +++ b/Serein.Script/Node/MemberAssignmentNode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 表示对对象成员的赋值 + /// + public class MemberAssignmentNode : ASTNode + { + public ASTNode Object { get; } + public string MemberName { get; } + public ASTNode Value { get; } + + public MemberAssignmentNode(ASTNode obj, string memberName, ASTNode value) + { + Object = obj; + MemberName = memberName; + Value = value; + } + } +} diff --git a/Serein.Script/Node/MemberFunctionCallNode.cs b/Serein.Script/Node/MemberFunctionCallNode.cs new file mode 100644 index 0000000..4d030b1 --- /dev/null +++ b/Serein.Script/Node/MemberFunctionCallNode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 对象成员方法调用 + /// + public class MemberFunctionCallNode : ASTNode + { + public ASTNode Object { get; } + public string FunctionName { get; } + public List Arguments { get; } + + public MemberFunctionCallNode(ASTNode @object, string functionName, List arguments) + { + Object = @object; + FunctionName = functionName; + Arguments = arguments; + } + } +} diff --git a/Serein.Script/Node/NullNode.cs b/Serein.Script/Node/NullNode.cs new file mode 100644 index 0000000..9806e3b --- /dev/null +++ b/Serein.Script/Node/NullNode.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// Null节点 + /// + public class NullNode : ASTNode + { + } +} diff --git a/Serein.Script/Node/NumberNode.cs b/Serein.Script/Node/NumberNode.cs new file mode 100644 index 0000000..0f1ad07 --- /dev/null +++ b/Serein.Script/Node/NumberNode.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + + + /// + /// 整数型字面量 + /// + public class NumberNode : ASTNode + { + public int Value { get; } + public NumberNode(int value) => Value = value; + } + + +} diff --git a/Serein.Script/Node/ObjectInstantiationNode.cs b/Serein.Script/Node/ObjectInstantiationNode.cs new file mode 100644 index 0000000..6ed41fc --- /dev/null +++ b/Serein.Script/Node/ObjectInstantiationNode.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 类型创建 + /// + public class ObjectInstantiationNode : ASTNode + { + public string TypeName { get; } + public List Arguments { get; } + public ObjectInstantiationNode(string typeName, List arguments) + { + this.TypeName = typeName; + this.Arguments = arguments; + } + } + +} diff --git a/Serein.Script/Node/ProgramNode.cs b/Serein.Script/Node/ProgramNode.cs new file mode 100644 index 0000000..4624b6e --- /dev/null +++ b/Serein.Script/Node/ProgramNode.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 程序入口 + /// + public class ProgramNode : ASTNode + { + public List Statements { get; } + + public ProgramNode(List statements) + { + Statements = statements; + } + } + +} diff --git a/Serein.Script/Node/ReturnNode.cs b/Serein.Script/Node/ReturnNode.cs new file mode 100644 index 0000000..259af19 --- /dev/null +++ b/Serein.Script/Node/ReturnNode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 返回值 + /// + public class ReturnNode : ASTNode + { + public ASTNode Value { get; } + + public ReturnNode(ASTNode returnNode) + { + Value = returnNode; + } + public ReturnNode() + { + } + } + +} diff --git a/Serein.Script/Node/StringNode.cs b/Serein.Script/Node/StringNode.cs new file mode 100644 index 0000000..ebc140a --- /dev/null +++ b/Serein.Script/Node/StringNode.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 字符串字面量节点 + /// + public class StringNode : ASTNode + { + public string Value { get; } + + public StringNode(string input) + { + // 使用 StringBuilder 来构建输出 + StringBuilder output = new StringBuilder(input.Length); + + for (int i = 0; i < input.Length; i++) + { + if (i < input.Length - 1 && input[i] == '\\') // 找到反斜杠 + { + char nextChar = input[i + 1]; + + // 处理转义符 + switch (nextChar) + { + case 'r': + output.Append('\r'); + i++; // 跳过 'r' + break; + case 'n': + output.Append('\n'); + i++; // 跳过 'n' + break; + case 't': + output.Append('\t'); + i++; // 跳过 't' + break; + case '\\': // 字面量反斜杠 + output.Append('\\'); + i++; // 跳过第二个 '\\' + break; + default: + output.Append(input[i]); // 不是转义符,保留反斜杠 + break; + } + } + else + { + output.Append(input[i]); // 其他字符直接添加 + } + } + Value = output.ToString(); + } + } + + +} diff --git a/Serein.Script/Node/WhileNode.cs b/Serein.Script/Node/WhileNode.cs new file mode 100644 index 0000000..4788ba5 --- /dev/null +++ b/Serein.Script/Node/WhileNode.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.Node +{ + /// + /// 循环条件节点 + /// + public class WhileNode : ASTNode + { + public ASTNode Condition { get; } + public List Body { get; } + public WhileNode(ASTNode condition, List body) => (Condition, Body) = (condition, body); + } + +} diff --git a/Serein.Script/Serein.Script.csproj b/Serein.Script/Serein.Script.csproj new file mode 100644 index 0000000..a2fa3f7 --- /dev/null +++ b/Serein.Script/Serein.Script.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Serein.Script/SereinScriptInterpreter.cs b/Serein.Script/SereinScriptInterpreter.cs new file mode 100644 index 0000000..d03f2c4 --- /dev/null +++ b/Serein.Script/SereinScriptInterpreter.cs @@ -0,0 +1,532 @@ +using Newtonsoft.Json.Linq; +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Script.Node; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml.Linq; + +namespace Serein.Script +{ + public sealed class SereinSciptException : Exception + { + //public ASTNode Node { get; } + public override string Message { get; } + + public SereinSciptException(ASTNode node, string message) + { + //this.Node = node; + Message = $"异常信息 : {message} ,代码在第{node.Row}行: {node.Code.Trim()}"; + } + } + + + public class SereinScriptInterpreter + { + /// + /// 定义的变量 + /// + private Dictionary _variables = new Dictionary(); + + /// + /// 挂载的函数 + /// + private static Dictionary _functionTable = new Dictionary(); + + /// + /// 挂载的函数调用的对象(用于函数需要实例才能调用的场景) + /// + private static Dictionary> _callFuncOfGetObjects = new Dictionary>(); + + /// + /// 定义的类型 + /// + private static Dictionary _classDefinition = new Dictionary(); + + /// + /// 重置的变量 + /// + public void ResetVar() + { + foreach (var nodeObj in _variables.Values) + { + if (nodeObj is not null) + { + if (typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) && nodeObj is IDisposable disposable) + { + disposable?.Dispose(); + } + } + else + { + + } + } + _variables.Clear(); + } + + /// + /// 挂载函数 + /// + /// 函数名称 + /// 方法信息 + public static void AddFunction(string functionName, MethodInfo methodInfo, Func? callObj = null) + { + //if (!_functionTable.ContainsKey(functionName)) + //{ + // _functionTable[functionName] = new DelegateDetails(methodInfo); + //} + if(!methodInfo.IsStatic && callObj is null) + { + SereinEnv.WriteLine(InfoType.WARN, "函数挂载失败:试图挂载非静态的函数,但没有传入相应的获取实例的方法。"); + return; + } + + + if(!methodInfo.IsStatic && callObj is not null) + { + // 静态函数不需要 + _callFuncOfGetObjects.Add(functionName, callObj); + } + _functionTable[functionName] = new DelegateDetails(methodInfo); + } + + /// + /// 挂载类型 + /// + /// 函数名称 + /// 方法信息 + public static void AddClassType(Type type , string typeName = "") + { + if (string.IsNullOrEmpty(typeName)) + { + typeName = type.Name; + } + if (!_classDefinition.ContainsKey(typeName)) + { + _classDefinition[typeName] = type; + } + } + + + /// + /// 入口节点 + /// + /// + /// + private async Task ExecutionProgramNodeAsync(ProgramNode programNode) + { + // 遍历 ProgramNode 中的所有语句并执行它们 + foreach (var statement in programNode.Statements) + { + // 直接退出 + if (statement is ReturnNode returnNode) // 遇到 Return 语句 提前退出 + { + return await EvaluateAsync(statement); + } + else + { + await InterpretAsync(statement); + } + } + + return null; + } + + /// + /// 类型定义 + /// + /// + /// + private void ExecutionClassTypeDefinitionNode(ClassTypeDefinitionNode classTypeDefinitionNode) + { + if (_classDefinition.ContainsKey(classTypeDefinitionNode.ClassName)) + { + //SereinEnv.WriteLine(InfoType.WARN, $"异常信息 : 类型重复定义,代码在第{classTypeDefinitionNode.Row}行: {classTypeDefinitionNode.Code.Trim()}"); + return; + } + var type = DynamicObjectHelper.CreateTypeWithProperties(classTypeDefinitionNode.Fields, classTypeDefinitionNode.ClassName); + _classDefinition[classTypeDefinitionNode.ClassName] = type; // 定义对象 + } + + /// + /// IF...ELSE... 语句块 + /// + /// + /// + /// + private async Task ExecutionIfNodeAsync(IfNode ifNode) + { + var result = await EvaluateAsync(ifNode.Condition) ?? throw new SereinSciptException(ifNode, $"条件语句返回了 null"); + + if (result is not bool condition) + { + throw new SereinSciptException(ifNode, "条件语句返回值不为 bool 类型"); + } + + if (condition) + { + foreach (var trueNode in ifNode.TrueBranch) + { + await InterpretAsync(trueNode); + } + } + else + { + foreach (var falseNode in ifNode.FalseBranch) + { + await InterpretAsync(falseNode); + } + } + } + + /// + /// WHILE(){...} 语句块 + /// + /// + /// + /// + private async Task ExectutionWhileNodeAsync(WhileNode whileNode) + { + while (true) + { + var result = await EvaluateAsync(whileNode.Condition) ?? throw new SereinSciptException(whileNode, $"条件语句返回了 null"); + if (result is not bool condition) + { + throw new SereinSciptException(whileNode, $"条件语句返回值不为 bool 类型(当前返回值类型为 {result.GetType()})"); + } + if (!condition) + { + break; + } + foreach(var node in whileNode.Body) + { + await InterpretAsync(node); + } + } + } + + /// + /// 操作节点 + /// + /// + /// + private async Task ExecutionAssignmentNodeAsync(AssignmentNode assignmentNode) + { + var tmp = await EvaluateAsync(assignmentNode.Value); + _variables[assignmentNode.Variable] = tmp; + } + private async Task InterpretFunctionCallAsync(FunctionCallNode functionCallNode) + { + // 评估函数参数 + var arguments = new object?[functionCallNode.Arguments.Count]; + for (int i = 0; i < functionCallNode.Arguments.Count; i++) + { + ASTNode? arg = functionCallNode.Arguments[i]; + arguments[i] = await EvaluateAsync(arg); // 评估每个参数 + } + + var funcName = functionCallNode.FunctionName; + + object? instance = null; // 静态方法不需要传入实例,所以可以传入null + + // 查找并执行对应的函数 + if (_functionTable.TryGetValue(funcName, out DelegateDetails? function)) + { + if (!function.EmitMethodInfo.IsStatic) + { + if(_callFuncOfGetObjects.TryGetValue(funcName, out var action)) + { + instance = action.Invoke();// 非静态的方法需要获取相应的实例 + + if (instance is null) + { + throw new SereinSciptException(functionCallNode, $"函数 {funcName} 尝试获取实例时返回了 null "); + } + } + else + { + throw new SereinSciptException(functionCallNode, $"挂载函数 {funcName} 时需要同时给定获取实例的 Func"); + } + } + + var result = await function.InvokeAsync(instance,arguments); + return result; + } + else + { + throw new Exception($"Unknown function: {functionCallNode.FunctionName}"); + } + } + + + + + public async Task InterpretAsync(ASTNode node) + { + if(node == null) + { + return null; + } + + switch (node) + { + case ProgramNode programNode: // AST树入口 + var scritResult = await ExecutionProgramNodeAsync(programNode); + return scritResult; // 遍历 ProgramNode 中的所有语句并执行它们 + case ClassTypeDefinitionNode classTypeDefinitionNode: // 定义类型 + ExecutionClassTypeDefinitionNode(classTypeDefinitionNode); + break; + case AssignmentNode assignment: // 出现在 = 右侧的表达式 + await ExecutionAssignmentNodeAsync(assignment); + break; + case MemberAssignmentNode memberAssignmentNode: // 设置对象属性 + await SetMemberValue(memberAssignmentNode); + break; + case MemberFunctionCallNode memberFunctionCallNode: + return await CallMemberFunction(memberFunctionCallNode); + break; + case IfNode ifNode: // 执行 if...else... 语句块 + await ExecutionIfNodeAsync(ifNode); + break; + case WhileNode whileNode: // 循环语句块 + await ExectutionWhileNodeAsync(whileNode); + break; + case FunctionCallNode functionCallNode: // 方法调用节点 + return await InterpretFunctionCallAsync(functionCallNode); + case ReturnNode returnNode: + return await EvaluateAsync(returnNode); + default: + throw new SereinSciptException(node, "解释器 InterpretAsync() 未实现节点行为"); + } + return null; + } + + + private async Task EvaluateAsync(ASTNode node) + { + if(node == null) + { + return null; + } + switch (node) + { + case NullNode nullNode: + return null; + case BooleanNode booleanNode: + return booleanNode.Value; // 返回数值 + case NumberNode numberNode: + return numberNode.Value; // 返回数值 + case StringNode stringNode: + return stringNode.Value; // 返回字符串值 + case IdentifierNode identifierNode: + if (_variables.TryGetValue(identifierNode.Name, out var result)) + { + //if(result == null) + //{ + // throw new SereinSciptException(identifierNode, "尝试使用值为null的变量"); + //} + return result; // 获取变量值 + } + else + { + throw new SereinSciptException(identifierNode, "尝试使用未声明的变量"); + } + case BinaryOperationNode binOpNode: + // 递归计算二元操作 + var left = await EvaluateAsync(binOpNode.Left); + //if (left == null ) + //{ + // throw new SereinSciptException(binOpNode.Left, $"左值尝试使用 null"); + //} + + var right = await EvaluateAsync(binOpNode.Right); + //if (right == null) + //{ + // throw new SereinSciptException(binOpNode.Right, "右值尝试使用计算 null"); + //} + return EvaluateBinaryOperation(left, binOpNode.Operator, right); + case ObjectInstantiationNode objectInstantiationNode: + if (_classDefinition.TryGetValue(objectInstantiationNode.TypeName,out var type )) + { + object?[] args = new object[objectInstantiationNode.Arguments.Count]; + for (int i = 0; i < objectInstantiationNode.Arguments.Count; i++) + { + var argNode = objectInstantiationNode.Arguments[i]; + args[i] = await EvaluateAsync(argNode); + } + var obj = Activator.CreateInstance(type,args: args);// 创建对象 + if (obj == null) + { + throw new SereinSciptException(objectInstantiationNode, $"类型创建失败\"{objectInstantiationNode.TypeName}\""); + } + return obj; + } + else + { + + throw new SereinSciptException(objectInstantiationNode, $"使用了未定义的类型\"{objectInstantiationNode.TypeName}\""); + + } + case FunctionCallNode callNode: + return await InterpretFunctionCallAsync(callNode); // 调用方法返回函数的返回值 + case MemberAccessNode memberAccessNode: + return await GetValue(memberAccessNode); + case ReturnNode returnNode: // + return await EvaluateAsync(returnNode.Value); // 直接返回响应的内容 + default: + throw new SereinSciptException(node, "解释器 EvaluateAsync() 未实现节点行为"); + } + } + + private object EvaluateBinaryOperation(object left, string op, object right) + { + + + + // 根据运算符执行不同的运算 + switch (op) + { + case "+": + if (left is string || right is string) + { + return left?.ToString() + right?.ToString(); // 字符串拼接 + } + else if (left is int leftInt && right is int rightInt) + { + return leftInt + rightInt; // 整数加法 + } + else if (left is long leftLong && right is long rightLong) + { + return leftLong + rightLong; // 整数加法 + } + else if (left is double leftDouble && right is double rightDouble) + { + return leftDouble + rightDouble; // 整数加法 + } + else + { + dynamic leftValue = Convert.ToDouble(left); + dynamic rightValue = Convert.ToDouble(right); + return leftValue + rightValue; + } + throw new Exception("Invalid types for + operator"); + case "-": + return (int)left - (int)right; + case "*": + return (int)left * (int)right; + case "/": + return (int)left / (int)right; + + case ">": + return (int)left > (int)right; + case "<": + return (int)left < (int)right; + case "==": + return Equals(left, right); + case "!=": + return !Equals(left, right); + + default: + throw new NotImplementedException("未定义的操作符: " + op); + } + } + + + /// + /// 设置对象成员 + /// + /// + /// + /// + public async Task SetMemberValue(MemberAssignmentNode memberAssignmentNode) + { + var target = await EvaluateAsync(memberAssignmentNode.Object); + var value = await EvaluateAsync(memberAssignmentNode.Value); + // 设置值 + var lastMember = memberAssignmentNode.MemberName; + + var lastProperty = target?.GetType().GetProperty(lastMember); + if (lastProperty is null) + { + var lastField = target?.GetType().GetRuntimeField(lastMember); + if (lastField is null) + { + throw new SereinSciptException(memberAssignmentNode, $"对象没有成员\"{memberAssignmentNode.MemberName}\""); + } + else + { + var convertedValue = Convert.ChangeType(value, lastField.FieldType); + lastField.SetValue(target, convertedValue); + } + } + else + { + var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); + lastProperty.SetValue(target, convertedValue); + } + } + + /// + /// 获取对象成员 + /// + /// + /// + /// + public async Task GetValue(MemberAccessNode memberAccessNode) + { + var target = await EvaluateAsync(memberAccessNode.Object); + var lastMember = memberAccessNode.MemberName; + + var lastProperty = target?.GetType().GetProperty(lastMember); + if (lastProperty is null) + { + var lastField = target?.GetType().GetRuntimeField(lastMember); + if (lastField is null) + { + throw new SereinSciptException(memberAccessNode, $"对象没有成员\"{memberAccessNode.MemberName}\""); + } + else + { + return lastField.GetValue(target); + } + } + else + { + return lastProperty.GetValue(target); + } + } + + /// + /// 缓存method委托 + /// + private Dictionary MethodToDelegateCaches { get; } = new Dictionary(); + + public async Task CallMemberFunction(MemberFunctionCallNode memberFunctionCallNode) + { + var target = await EvaluateAsync(memberFunctionCallNode.Object); + var lastMember = memberFunctionCallNode.FunctionName; + + var methodInfo = target?.GetType().GetMethod(lastMember) ?? throw new SereinSciptException(memberFunctionCallNode, $"对象没有方法\"{memberFunctionCallNode.FunctionName}\""); + if(!MethodToDelegateCaches.TryGetValue(methodInfo.Name, out DelegateDetails? delegateDetails)) + { + delegateDetails = new DelegateDetails(methodInfo); + MethodToDelegateCaches[methodInfo.Name] = delegateDetails; + } + + + + var arguments = new object?[memberFunctionCallNode.Arguments.Count]; + for (int i = 0; i < memberFunctionCallNode.Arguments.Count; i++) + { + ASTNode? arg = memberFunctionCallNode.Arguments[i]; + arguments[i] = await EvaluateAsync(arg); // 评估每个参数 + } + + return await delegateDetails.InvokeAsync(target, arguments); + } + + + } +} diff --git a/Serein.Script/SereinScriptLexer.cs b/Serein.Script/SereinScriptLexer.cs new file mode 100644 index 0000000..40149d2 --- /dev/null +++ b/Serein.Script/SereinScriptLexer.cs @@ -0,0 +1,360 @@ +using Newtonsoft.Json.Linq; +using System.Runtime.CompilerServices; +using System.Xml.Linq; + +namespace Serein.Script +{ + internal enum TokenType + { + /// + /// 预料之外的值 + /// + Null, + /// + /// 标识符 + /// + Identifier, + /// + /// 布尔 + /// + Boolean, + /// + /// 数值 + /// + Number, + /// + /// 字符串 + /// + String, + /// + /// 关键字 + /// + Keyword, + /// + /// 操作符 + /// + Operator, + /// + /// 左小括号 + /// + ParenthesisLeft, + /// + /// 右小括号 + /// + ParenthesisRight, + /// + /// 左中括号 + /// + SquareBracketsLeft, + /// + /// 右中括号 + /// + SquareBracketsRight, + /// + /// 左大括号 + /// + BraceLeft, + /// + /// 右大括号 + /// + BraceRight, + /// + /// 点号 + /// + Dot, + /// + /// 逗号 + /// + Comma, + + /// + /// 分号 + /// + Semicolon, + + /// + /// 行注释 + /// + // RowComment, + + /// + /// 解析完成 + /// + EOF + } + + internal ref struct Token + { + public TokenType Type { get; } + public string Value { get; } + + public int Row { get; set; } + public string Code { get; set; } + public int StartIndex { get; set; } + public int Length { get; set; } + + internal Token(TokenType type, string value) + { + Type = type; + Value = value; + } + } + + internal ref struct SereinScriptLexer + { + private readonly ReadOnlySpan _input; + private int _index; + private int _row ; + + + private string[] _keywords = [ + "let", + "func", + "if", + "else", + "return", + "while", + "new", + "class", + ]; + + internal SereinScriptLexer(string input) + { + _input = input.AsSpan(); + _index = 0; + } + + + internal Token PeekToken() + { + int currentIndex = _index; // 保存当前索引 + Token nextToken = NextToken(); // 获取下一个 token + _index = currentIndex; // 恢复索引到当前位置 + return nextToken; // 返回下一个 token + } + + internal Token NextToken() + { + + // 跳过空白字符 + while (_index < _input.Length && char.IsWhiteSpace(_input[_index])) + { + if (_input[_index] == '\n') + { + _row++; + } + + _index++; + } + + + + if (_index >= _input.Length) return new Token(TokenType.EOF, string.Empty); + + char currentChar = _input[_index]; + + // 识别字符串字面量 + if (currentChar == '"') + { + return ReadString(); + } + + // 跳过注释 + if (_input[_index] == '/' && _input[_index + 1] == '/') + { + // 一直识别到换行符的出现 + while (_index < _input.Length && _input[_index] != '\n') + { + _index++; + } + return NextToken(); // 跳过注释后,返回下一个识别token + } + + // 识别null字面量 + if (currentChar == 'n') + { + if (_input[_index + 1] == 'u' + && _input[_index + 2] == 'l' + && _input[_index + 3] == 'l') + { + var value = _input.Slice(_index, 4).ToString(); + + return CreateToken(TokenType.Null, "null"); + } + } + + // 识别布尔字面量 + if (currentChar == 't') + { + if (_input[_index + 1] == 'r' + && _input[_index + 2] == 'u' + && _input[_index + 3] == 'e') + { + return CreateToken(TokenType.Boolean, "true"); + } + } + else if (currentChar == 'f') + { + if (_input[_index + 1] == 'a' + && _input[_index + 2] == 'l' + && _input[_index + 3] == 's' + && _input[_index + 4] == 'e') + { + return CreateToken(TokenType.Boolean, "false"); + } + } + + + // 识别数字 + 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); + } + + // 识别标识符(变量名、关键字) + if (char.IsLetter(currentChar)) + { + var start = _index; + while (_index < _input.Length && (char.IsLetterOrDigit(_input[_index]) || _input[_index] == '_')) + _index++; + var value = _input.Slice(start, _index - start).ToString(); + _index = start; // 回退索引,索引必须只能在 CreateToken 方法内更新 + return CreateToken(_keywords.Contains(value) ? TokenType.Keyword : TokenType.Identifier, value); + + } + + // 识别符号 + switch (currentChar) + { + case '(': return CreateToken(TokenType.ParenthesisLeft, "("); + case ')': return CreateToken(TokenType.ParenthesisRight, ")"); + case '[': return CreateToken(TokenType.SquareBracketsLeft, "["); + case ']': return CreateToken(TokenType.SquareBracketsRight, "]"); + case '{': return CreateToken(TokenType.BraceLeft, "{"); + case '}': return CreateToken(TokenType.BraceRight, "}"); + case ',': return CreateToken(TokenType.Comma, ","); + case ';': return CreateToken(TokenType.Semicolon, ";"); + case '+': + case '-': + case '*': + case '/': + return CreateToken(TokenType.Operator, currentChar.ToString()); + case '>': // 识别 ">" 或 ">=" + if (_index + 1 < _input.Length && _input[_index + 1] == '=') + { + return CreateToken(TokenType.Operator, ">="); + } + return CreateToken(TokenType.Operator, ">"); + case '<': // 识别 "<" 或 "<=" + if (_index + 1 < _input.Length && _input[_index + 1] == '=') + { + return CreateToken(TokenType.Operator, "<="); + } + return CreateToken(TokenType.Operator, "<"); + case '!': // 识别 "!=" + if (_index + 1 < _input.Length && _input[_index + 1] == '=') + { + return CreateToken(TokenType.Operator, "!="); + } + break; + case '=': // 识别 "==" + if (_index + 1 < _input.Length && _input[_index + 1] == '=') + { + return CreateToken(TokenType.Operator, "=="); + } + else + { + return CreateToken(TokenType.Operator, "="); + } + case '.': + return CreateToken(TokenType.Dot, "."); + } + + throw new Exception("Unexpected character: " + currentChar); + } + + private Token CreateToken(TokenType tokenType, string value) + { + var code = GetLine(_row).ToString(); + var token = new Token(tokenType, value) + { + Row = _row, + StartIndex = _index, + Length = value.Length, + Code = code, + }; + _index += value.Length; + return token; + } + + + /// + /// 读取硬编码的文本 + /// + /// + /// + private Token ReadString() + { + _index++; // 跳过开头的引号 + var start = _index; + + while (_index < _input.Length && _input[_index] != '"') + { + if (_input[_index] == '\\' && _index + 1 < _input.Length && (_input[_index + 1] == '"' || _input[_index + 1] == '\\')) + { + // 处理转义字符 + _index++; + } + _index++; + } + + if (_index >= _input.Length) throw new Exception("Unterminated string literal"); + + var value = _input.Slice(start, _index - start).ToString(); + // var value = _input.Substring(start, _index - start); + + _index = start + 1; // 跳过引号 + return CreateToken(TokenType.String, value); + + // _index++; // 跳过结束的引号 + //return new Token(TokenType.String, value.ToString()); + } + + /// + /// 获取对应行的代码文本 + /// + /// + /// + private ReadOnlySpan GetLine( int lineNumber) + { + ReadOnlySpan text = _input; + int currentLine = 0; + int start = 0; + + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '\n') // 找到换行符 + { + if (currentLine == lineNumber) + { + return text.Slice(start, i - start); // 返回从start到当前位置的行文本 + } + currentLine++; + start = i + 1; // 下一行的起始位置 + } + } + + // 如果没有找到指定行,返回空的Span + return ReadOnlySpan.Empty; + } + + + + } + +} diff --git a/Serein.Script/SereinScriptParser.cs b/Serein.Script/SereinScriptParser.cs new file mode 100644 index 0000000..dc329b5 --- /dev/null +++ b/Serein.Script/SereinScriptParser.cs @@ -0,0 +1,647 @@ +using Newtonsoft.Json.Linq; +using Serein.Library; +using Serein.Script.Node; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Serein.Script +{ + public ref struct SereinScriptParser + { + private SereinScriptLexer _lexer; + private Token _currentToken; + + + public SereinScriptParser(string script) + { + _lexer = new SereinScriptLexer(script); // 语法分析 + _currentToken = _lexer.NextToken(); + } + + public ASTNode Parse() + { + return Program(); + } + + + + private List Statements { get; } = new List(); + + private ASTNode Program() + { + Statements.Clear(); + while (_currentToken.Type != TokenType.EOF) + { + var astNode = Statement(); + if (astNode == null) + { + continue; + } + Statements.Add(astNode); + + //if (astNode is ClassTypeDefinitionNode) + //{ + // // 类型定义置顶 + // statements = [astNode, ..statements]; + //} + //else + //{ + // statements.Add(astNode); + //} + + } + return new ProgramNode(Statements).SetTokenInfo(_currentToken); + } + + private ASTNode Statement() + { + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "let") + { + return ParseLetAssignment(); + } + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "class") + { + return ParseClassDefinition(); + } + + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "if") + { + return ParseIf(); + } + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "while") + { + return ParseWhile(); + } + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "return") + { + return ParseReturn(); + } + if (_currentToken.Type == TokenType.Identifier) + { + return ParseIdentifier(); + } + if (_currentToken.Type == TokenType.Null) + { + return Expression(); + } + + + + // 处理其他语句(如表达式语句等) + if (_currentToken.Type == TokenType.Semicolon) + { + _currentToken = _lexer.NextToken(); + return null; // 表示空语句 + } + + throw new Exception("Unexpected statement: " + _currentToken.Value.ToString()); + } + + + /// + /// 从标识符解析方法调用、变量赋值、获取对象成员行为。 + /// (非符号、关键字) + /// + /// + private ASTNode ParseIdentifier() + { + + // 检查标识符后是否跟有左圆括号 + var _tempToken = _lexer.PeekToken(); + if (_tempToken.Type == TokenType.ParenthesisLeft) + { + // 解析函数调用 + return ParseFunctionCall(); + } + else if (_tempToken.Type == TokenType.Dot) + { + // 对象成员的获取 + return ParseMemberAccessOrAssignment(); + } + else + { + // 不是函数调用,是变量赋值或其他 + return ParseAssignment(); + } + } + + + /// + /// 解析赋值行为 + /// + /// + /// + private ASTNode ParseAssignment() + { + string variableName = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); // consume identifier + + if(_currentToken.Type == TokenType.ParenthesisRight) + { + return new IdentifierNode(variableName).SetTokenInfo(_currentToken); + } + + if(_currentToken.Type == TokenType.Operator && _currentToken.Value == "=") + { + // 赋值行为 + _currentToken = _lexer.NextToken(); // consume "=" + var _tempToken = _lexer.PeekToken(); + ASTNode valueNode; + if (_tempToken.Type == TokenType.ParenthesisLeft) + { + // 解析赋值右边的表达式 + // 是函数调用,解析函数调用 + valueNode = ParseFunctionCall(); + } + else + { + // 解析赋值右边的字面量表达式 + valueNode = Expression(); + } + return new AssignmentNode(variableName, valueNode).SetTokenInfo(_currentToken); + } + if (_currentToken.Type == TokenType.Dot) + { + return ParseMemberAccessOrAssignment(); + } + + + + throw new Exception($"Expected '{_currentToken.Value}' after variable name"); + + } + + /// + /// 解析 let 变量赋值行为 + /// + /// + /// + private ASTNode ParseLetAssignment() + { + _currentToken = _lexer.NextToken(); // Consume "let" + string variable = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); // Consume identifier + ASTNode value; + if (_currentToken.Type == TokenType.Semicolon) + { + // 定义一个变量,初始值为 null + value = new NullNode(); + } + else + { + if (_currentToken.Type != TokenType.Operator || _currentToken.Value != "=") + throw new Exception("Expected '=' after variable name"); + _currentToken = _lexer.NextToken(); + value = Expression(); + _currentToken = _lexer.NextToken(); // Consume semicolon + + } + return new AssignmentNode(variable, value).SetTokenInfo(_currentToken); + + + + } + + private ASTNode ParseClassDefinition() + { + _currentToken = _lexer.NextToken(); // 消耗 class 关键字 + var className = _currentToken.Value.ToString(); // 获取定义的类名 + _currentToken = _lexer.NextToken(); // 消耗括号 + if (_currentToken.Type != TokenType.BraceLeft || _currentToken.Value != "{") + throw new Exception("Expected '{' after class definition"); + var classFields = new Dictionary(); + _currentToken = _lexer.NextToken(); + while (_currentToken.Type != TokenType.BraceRight) + { + 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) + { + break; + } + else + { + _currentToken = _lexer.NextToken(); + } + + } + + _currentToken = _lexer.NextToken(); + _currentToken = _lexer.NextToken(); + return new ClassTypeDefinitionNode(classFields, className).SetTokenInfo(_currentToken); + } + + public ASTNode ParseObjectInstantiation() + { + _currentToken = _lexer.NextToken(); // Consume "new" + string typeName = _currentToken.Value.ToString(); // Get type name + _currentToken = _lexer.NextToken(); + if (_currentToken.Type != TokenType.ParenthesisLeft) + throw new Exception("Expected '(' after function name"); + + _currentToken = _lexer.NextToken(); // consume "(" + + var arguments = new List(); + while (_currentToken.Type != TokenType.ParenthesisRight) + { + arguments.Add(Expression()); + if (_currentToken.Type == TokenType.Comma) + { + _currentToken = _lexer.NextToken(); // consume "," + } + } + + _currentToken = _lexer.NextToken(); // consume ")" + return new ObjectInstantiationNode(typeName, arguments).SetTokenInfo(_currentToken); + } + + + public ASTNode ParseCollectionIndex() + { + string functionName = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); // consume identifier + + if (_currentToken.Type != TokenType.ParenthesisLeft) + throw new Exception("Expected '[' after function name"); + + _currentToken = _lexer.NextToken(); // consume "[" + + ASTNode indexValue = Expression(); // get index value + + _currentToken = _lexer.NextToken(); // consume "]" + return new CollectionIndexNode(indexValue).SetTokenInfo(_currentToken); + } + + /// + /// 获取对象成员 + /// + /// + /// + private ASTNode ParseMemberAccessOrAssignment() + { + var identifierNode = new IdentifierNode(_currentToken.Value).SetTokenInfo(_currentToken); + _currentToken = _lexer.NextToken(); // 消耗当前标识符 + + + // 处理成员访问:identifier.member + if (_currentToken.Type == TokenType.Dot) + { + _currentToken = _lexer.NextToken(); // 消耗 "." + if (_currentToken.Type != TokenType.Identifier) + { + throw new Exception("Expected member name after dot."); + } + + var memberName = _currentToken.Value; + //_currentToken = _lexer.NextToken(); // 消耗成员名 + + var _peekToken = _lexer.PeekToken(); + if (_peekToken.Type == TokenType.Operator && _peekToken.Value == "=") + { + // 成员赋值 obj.Member = xxx; + _currentToken = _lexer.NextToken(); // 消耗 "=" + _currentToken = _lexer.NextToken(); // 消耗 "=" + var valueNode = Expression(); // 解析右值 + return new MemberAssignmentNode(identifierNode, memberName, valueNode).SetTokenInfo(_peekToken); + } + else + { + + + if(_peekToken.Type == TokenType.ParenthesisLeft) + { + // 成员方法调用 obj.Member(xxx); + return ParseMemberFunctionCall(identifierNode); + } + else if (_peekToken.Type == TokenType.SquareBracketsLeft) + { + // 数组 index; 字典 key obj.Member[xxx]; + return ParseCollectionIndex(); + } + else + { + // 成员获取 + return new MemberAccessNode(identifierNode, memberName).SetTokenInfo(_currentToken); + } + + } + } + + return identifierNode; + } + + + + private ASTNode ParseMemberFunctionCall(ASTNode targetNode) + { + string functionName = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); // consume identifier + + if (_currentToken.Type != TokenType.ParenthesisLeft) + throw new Exception("Expected '(' after function name"); + + _currentToken = _lexer.NextToken(); // consume "(" + + var arguments = new List(); + while (_currentToken.Type != TokenType.ParenthesisRight) + { + var arg = Expression(); + _currentToken = _lexer.NextToken(); // consume arg + arguments.Add(arg); + if (_currentToken.Type == TokenType.Comma) + { + _currentToken = _lexer.NextToken(); // consume "," + } + if (_currentToken.Type == TokenType.Semicolon) + { + break; // consume ";" + } + } + + _currentToken = _lexer.NextToken(); // consume ")" + + return new MemberFunctionCallNode(targetNode, functionName, arguments).SetTokenInfo(_currentToken); + } + + + private ASTNode ParseFunctionCall() + { + string functionName = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); // consume identifier + + if (_currentToken.Type != TokenType.ParenthesisLeft) + throw new Exception("Expected '(' after function name"); + + _currentToken = _lexer.NextToken(); // consume "(" + + var arguments = new List(); + while (_currentToken.Type != TokenType.ParenthesisRight) + { + var arg = Expression(); + _currentToken = _lexer.NextToken(); // consume arg + arguments.Add(arg); + if (_currentToken.Type == TokenType.Comma) + { + _currentToken = _lexer.NextToken(); // consume "," + } + if (_currentToken.Type == TokenType.Semicolon) + { + break; // consume ";" + } + } + + _currentToken = _lexer.NextToken(); // consume ")" + + + //var node = Statements[^1]; + //if (node is MemberAccessNode memberAccessNode) + //{ + // // 上一个是对象 + // return new MemberFunctionCallNode(memberAccessNode, functionName, arguments).SetTokenInfo(_currentToken); + //} + //if (node is IdentifierNode identifierNode) + //{ + // return new MemberFunctionCallNode(identifierNode, functionName, arguments).SetTokenInfo(_currentToken); + //} + + // 从挂载的函数表寻找对应的函数,尝试调用 + return new FunctionCallNode(functionName, arguments).SetTokenInfo(_currentToken); + + + } + + public ASTNode ParseReturn() + { + _currentToken = _lexer.NextToken(); + if(_currentToken.Type == TokenType.Semicolon) + { + return new ReturnNode().SetTokenInfo(_currentToken); + } + var resultValue = Expression(); + _currentToken = _lexer.NextToken(); + return new ReturnNode(resultValue).SetTokenInfo(_currentToken); + } + + private ASTNode ParseIf() + { + _currentToken = _lexer.NextToken(); // Consume "if" + _currentToken = _lexer.NextToken(); // Consume "(" + ASTNode condition = Expression(); + _currentToken = _lexer.NextToken(); // Consume ")" + + // 确保遇到左大括号 { 后进入代码块解析 + if (_currentToken.Type != TokenType.BraceLeft) + { + throw new Exception("Expected '{' after if condition"); + } + _currentToken = _lexer.NextToken(); // Consume "{" + + // 解析大括号中的语句 + List trueBranch = new List(); + List falseBranch = new List(); + while (_currentToken.Type != TokenType.BraceRight && _currentToken.Type != TokenType.EOF) + { + var astNode = Statement(); + if (astNode != null) + { + trueBranch.Add(astNode); + } + } + // 确保匹配右大括号 } + if (_currentToken.Type != TokenType.BraceRight) + { + throw new Exception("Expected '}' after if block"); + } + _currentToken = _lexer.NextToken(); // Consume "}" + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "else") + { + _currentToken = _lexer.NextToken(); // Consume "{" + _currentToken = _lexer.NextToken(); // Consume "{" + while (_currentToken.Type != TokenType.BraceRight && _currentToken.Type != TokenType.EOF) + { + var astNode = Statement(); + if (astNode != null) + { + falseBranch.Add(astNode); + } + } + // 确保匹配右大括号 } + if (_currentToken.Type != TokenType.BraceRight) + { + throw new Exception("Expected '}' after if block"); + } + _currentToken = _lexer.NextToken(); // Consume "}" + } + + + return new IfNode(condition, trueBranch, falseBranch).SetTokenInfo(_currentToken); + } + + private ASTNode ParseWhile() + { + _currentToken = _lexer.NextToken(); // Consume "while" + _currentToken = _lexer.NextToken(); // Consume "(" + ASTNode condition = Expression(); + _currentToken = _lexer.NextToken(); // Consume ")" + _currentToken = _lexer.NextToken(); // Consume "{" + List body = new List(); + while (_currentToken.Type != TokenType.BraceRight) + { + body.Add(Statement()); + } + _currentToken = _lexer.NextToken(); // Consume "}" + return new WhileNode(condition, body).SetTokenInfo(_currentToken); + } + + + private ASTNode Expression() + { + ASTNode left = Term(); + while (_currentToken.Type == TokenType.Operator && ( + _currentToken.Value == "+" || _currentToken.Value == "-" || + _currentToken.Value == "*" || _currentToken.Value == "/")) + { + string op = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); + ASTNode right = Term(); + left = new BinaryOperationNode(left, op, right).SetTokenInfo(_currentToken); + } + return left; + } + + private ASTNode Term() + { + ASTNode left = Factor(); + while (_currentToken.Type == TokenType.Operator && + (_currentToken.Value == "<" || _currentToken.Value == ">" || + _currentToken.Value == "<=" || _currentToken.Value == ">=" || + _currentToken.Value == "==" || _currentToken.Value == "!=")) + { + string op = _currentToken.Value.ToString(); + _currentToken = _lexer.NextToken(); + ASTNode right = Factor(); + left = new BinaryOperationNode(left, op, right).SetTokenInfo(_currentToken); + } + return left; + } + + private ASTNode Factor() + { + #region 返回字面量 + if (_currentToken.Type == TokenType.Null) + { + _currentToken = _lexer.NextToken(); // 消耗 null + return new NullNode().SetTokenInfo(_currentToken); + } + if (_currentToken.Type == TokenType.Boolean) + { + var value = bool.Parse(_currentToken.Value); + _currentToken = _lexer.NextToken(); // 消耗布尔量 + return new BooleanNode(value).SetTokenInfo(_currentToken); + } + if (_currentToken.Type == TokenType.String) + { + var text = _currentToken.Value; + _currentToken = _lexer.NextToken(); // 消耗数字 + return new StringNode(text.ToString()).SetTokenInfo(_currentToken); + } + + if (_currentToken.Type == TokenType.Number) + { + var value = int.Parse(_currentToken.Value); + _currentToken = _lexer.NextToken(); // 消耗数字 + return new NumberNode(value).SetTokenInfo(_currentToken); + } + #endregion + + // 方法调用 + if (_currentToken.Type == TokenType.ParenthesisLeft) + { + _currentToken = _lexer.NextToken(); // 消耗 "(" + var expr = Expression(); + if (_currentToken.Type != TokenType.ParenthesisRight) + throw new Exception("非预期的符号,预期符号为\")\"。"); + _currentToken = _lexer.NextToken(); // 消耗 ")" + return expr; + } + + // 创建对象 + if (_currentToken.Type == TokenType.Keyword && _currentToken.Value == "new") + { + return ParseObjectInstantiation(); + } + + // 标识符节点 + if (_currentToken.Type == TokenType.Identifier) + { + var identifier = _currentToken.Value; // 标识符字面量 + var _identifierPeekToken = _lexer.PeekToken(); + // 该标识符是方法调用 + if (_identifierPeekToken.Type == TokenType.ParenthesisLeft) + { + return ParseFunctionCall(); + } + + // 需要从该标识符调用另一个标识符 + if (_identifierPeekToken.Type == TokenType.Dot) + { + return ParseMemberAccessOrAssignment(); + + var identifierNode = new IdentifierNode(identifier).SetTokenInfo(_currentToken); + // 处理成员访问:identifier.member + if (_currentToken.Type == TokenType.Dot) + { + _currentToken = _lexer.NextToken(); // 消耗 "." + if (_currentToken.Type != TokenType.Identifier) + { + throw new Exception("Expected member name after dot."); + } + + var memberName = _currentToken.Value; + _currentToken = _lexer.NextToken(); // 消耗成员名 + if (_currentToken.Type == TokenType.Operator && _currentToken.Value == "=") + { + // 成员赋值 obj.Member = xxx; + _currentToken = _lexer.NextToken(); // 消耗 "=" + var valueNode = Expression(); // 解析右值 + return new MemberAssignmentNode(identifierNode, memberName, valueNode).SetTokenInfo(_currentToken); + } + else + { + var _peekToken = _lexer.PeekToken(); + if (_peekToken.Type == TokenType.ParenthesisLeft) + { + // 成员方法调用 obj.Member(xxx); + return ParseFunctionCall(); + } + else if (_peekToken.Type == TokenType.SquareBracketsLeft) + { + // 数组 index; 字典 key obj.Member[xxx]; + return ParseCollectionIndex(); + } + else + { + // 成员获取 + return new MemberAccessNode(identifierNode, memberName).SetTokenInfo(_currentToken); + } + + } + + } + + return identifierNode; + + } + _currentToken = _lexer.NextToken(); // 消耗标识符 + return new IdentifierNode(identifier.ToString()).SetTokenInfo(_currentToken); + } + + + throw new Exception("Unexpected factor: " + _currentToken.Value.ToString()); + } + } + +} diff --git a/Serein.Script/Tool/DelegateDetails.cs b/Serein.Script/Tool/DelegateDetails.cs new file mode 100644 index 0000000..760d336 --- /dev/null +++ b/Serein.Script/Tool/DelegateDetails.cs @@ -0,0 +1,119 @@ +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static Serein.Library.Utils.EmitHelper; + +namespace Serein.Library +{ + /// + /// Emit创建的委托描述,用于WebApi、WebSocket、NodeFlow动态调用方法的场景。 + /// 一般情况下你无须关注内部细节,只需要调用 Invoke() 方法即可。 + /// + public class DelegateDetails + { + /// + /// 根据方法信息构建Emit委托 + /// + /// + public DelegateDetails(MethodInfo methodInfo) + { + var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var emitDelegate); + _emitMethodInfo = emitMethodType; + _emitDelegate = emitDelegate; + } + + + + /*/// + /// 更新委托方法 + /// + /// + /// + public void Upload(EmitMethodType EmitMethodType, Delegate EmitDelegate) + { + _emitMethodType = EmitMethodType; + _emitDelegate = EmitDelegate; + }*/ + + private Delegate _emitDelegate; + private EmitMethodInfo _emitMethodInfo; + + ///// + ///// 普通方法:Func<object,object[],object> + ///// 异步方法:Func<object,object[],Task> + ///// 异步有返回值方法:Func<object,object[],Task<object>> + ///// + //public Delegate EmitDelegate { get => _emitDelegate; } + ///// + ///// 表示Emit构造的委托类型 + ///// + //public EmitMethodType EmitMethodType { get => _emitMethodType; } + + + + public async Task AutoInvokeAsync(object[] args) + { + if (_emitMethodInfo.IsStatic) + { + return await InvokeAsync(null, args); + } + else + { + var obj = Activator.CreateInstance(_emitMethodInfo.DeclaringType); + return await InvokeAsync(obj, args); + } + throw new Exception("Not static method"); + } + + /// + /// 使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。 + /// + /// 拥有符合委托签名的方法信息的实例 + /// 如果方法没有入参,也需要传入一个空数组 + /// void方法自动返回null + public async Task InvokeAsync(object instance, object[] args) + { + if (args is null) + { + args = Array.Empty(); + } + if(_emitMethodInfo.IsStatic) + { + instance = null; + } + object result = null; + if (_emitDelegate is Func> hasResultTask) + { + result = await hasResultTask(instance, args); + } + else if (_emitDelegate is Func task) + { + await task.Invoke(instance, args); + } + else if (_emitDelegate is Func func) + { + result = func.Invoke(instance, args); + } + else + { + throw new NotImplementedException("创建了非预期委托(应该不会出现)"); + } + + // + return result; + + //try + //{ + + //} + //catch + //{ + // throw; + //} + } + } +} diff --git a/Serein.Script/Tool/EmitHelper.cs b/Serein.Script/Tool/EmitHelper.cs new file mode 100644 index 0000000..8b10c4e --- /dev/null +++ b/Serein.Script/Tool/EmitHelper.cs @@ -0,0 +1,203 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace Serein.Library.Utils +{ + /// + /// Emit创建委托工具类 + /// + public class EmitHelper + { + + public class EmitMethodInfo + { + public Type DeclaringType { get; set; } + /// + /// 是异步方法 + /// + public bool IsTask { get; set; } + /// + /// 是静态的 + /// + public bool IsStatic { get; set; } + } + + public enum EmitMethodType + { + /// + /// 普通的方法。如果方法返回void时,将会返回null。 + /// + Func, + /// + /// 无返回值的异步方法 + /// + Task, + /// + /// 有返回值的异步方法 + /// + HasResultTask, + + /// + /// 普通的方法。如果方法返回void时,将会返回null。 + /// + StaticFunc, + /// + /// 无返回值的异步方法 + /// + StaticTask, + } + + public static bool IsGenericTask(Type returnType, out Type taskResult) + { + // 判断是否为 Task 类型或泛型 Task + if (returnType == typeof(Task)) + { + taskResult = null; + return true; + } + else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) + { + // 获取泛型参数类型 + Type genericArgument = returnType.GetGenericArguments()[0]; + taskResult = genericArgument; + return true; + } + else + { + taskResult = null; + return false; + + } + } + + + /// + /// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托 + /// + /// + /// + /// + public static EmitMethodInfo CreateDynamicMethod(MethodInfo methodInfo,out Delegate @delegate) + { + EmitMethodInfo emitMethodInfo = new EmitMethodInfo(); + bool IsTask = IsGenericTask(methodInfo.ReturnType, out var taskGenericsType); + bool IsTaskGenerics = taskGenericsType != null; + DynamicMethod dynamicMethod; + + Type returnType; + if (!IsTask) + { + // 普通方法 + returnType = typeof(object); + + } + else + { + // 异步方法 + if (IsTaskGenerics) + { + returnType = typeof(Task); + } + else + { + returnType = typeof(Task); + } + } + + + + dynamicMethod = new DynamicMethod( + name: methodInfo.Name + "_DynamicEmitMethod", + returnType: returnType, + parameterTypes: new[] { typeof(object), typeof(object[]) }, // 方法实例、方法入参 + restrictedSkipVisibility: true // 跳过私有方法访问限制 + ); + + var il = dynamicMethod.GetILGenerator(); + + // 判断是否为静态方法 + bool isStatic = methodInfo.IsStatic; + + if (isStatic) + { + // 如果是静态方法,直接跳过实例(不加载Ldarg_0) + } + else + { + // 加载实例 (this) 对于非静态方法 + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, methodInfo.DeclaringType); // 将 ISocketControlBase 转换为目标类类型 + } + // 加载方法参数 + var methodParams = methodInfo.GetParameters(); + for (int i = 0; i < methodParams.Length; i++) + { + il.Emit(OpCodes.Ldarg_1); // 加载参数数组 + il.Emit(OpCodes.Ldc_I4, i); // 加载当前参数索引 + il.Emit(OpCodes.Ldelem_Ref); // 取出数组元素 + + var paramType = methodParams[i].ParameterType; + if (paramType.IsValueType) // 如果参数是值类型,拆箱 + { + il.Emit(OpCodes.Unbox_Any, paramType); + } + //else if (paramType.IsGenericParameter) // 如果是泛型参数,直接转换 + //{ + // il.Emit(OpCodes.Castclass, paramType); + //} + else // 如果是引用类型,直接转换 + { + il.Emit(OpCodes.Castclass, paramType); + } + + } + + // 调用方法:静态方法使用 Call,实例方法使用 Callvirt + if (isStatic) + { + il.Emit(OpCodes.Call, methodInfo); // 对于静态方法,使用 Call + } + else + { + il.Emit(OpCodes.Callvirt, methodInfo); // 对于实例方法,使用 Callvirt + } + + //// 处理返回值,如果没有返回值,则返回null + if (methodInfo.ReturnType == typeof(void)) + { + il.Emit(OpCodes.Ldnull); + } + else if (methodInfo.ReturnType.IsValueType) + { + il.Emit(OpCodes.Box, methodInfo.ReturnType); // 如果是值类型,将其装箱 + } + // 处理返回值,如果没有返回值,则返回null + il.Emit(OpCodes.Ret); // 返回 + if (IsTask) + { + if (IsTaskGenerics) + { + @delegate = dynamicMethod.CreateDelegate(typeof(Func>)); + } + else + { + @delegate = dynamicMethod.CreateDelegate(typeof(Func)); + } + } + else + { + @delegate = dynamicMethod.CreateDelegate(typeof(Func)); + + } + return new EmitMethodInfo + { + DeclaringType = methodInfo.DeclaringType, + IsTask = IsTask, + IsStatic = isStatic + }; + } + + + } + +} diff --git a/SereinFlow.sln b/SereinFlow.sln index d29f8e6..446fed2 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library.NodeGenerato EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.BaseNode", "Serein.BaseNode\Serein.BaseNode.csproj", "{E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Script", "Serein.Script\Serein.Script.csproj", "{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Release|Any CPU.Build.0 = Release|Any CPU + {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index abe8f63..8528204 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -1,7 +1,11 @@ -using Newtonsoft.Json; +using Dm.parser; +using NetTaste; +using Newtonsoft.Json; using Serein.Library; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; +using Serein.NodeFlow.Model; +using Serein.Script; using System.Diagnostics; using System.IO; using System.Linq.Expressions; @@ -12,11 +16,7 @@ using System.Windows.Threading; namespace Serein.Workbench { #if DEBUG - public class A - { - public string Data { get; set; } = "1234"; - public bool Data2 { get; set; } - } + #endif @@ -32,39 +32,9 @@ namespace Serein.Workbench #if DEBUG if (1 == 1) { - //object Data = "false"; - //var expression = "== false"; - //var pass = Serein.Library.Utils.SereinExpression.SereinConditionParser.To(Data, expression); - - - //string[] objects = new string[] - //{ - // "124", - // "true", - // "0.42" - //}; - //Dictionary keyValuePairs = new Dictionary - //{ - // {"value", objects } - //}; - - - //var data = SerinExpressionEvaluator.Evaluate("@Get .[value].[0]", keyValuePairs, out _); - //data = SerinExpressionEvaluator.Evaluate("@Get .[value].[1]", keyValuePairs, out _); - //data = SerinExpressionEvaluator.Evaluate("@Dtc ", data, out _); - //var result = SereinConditionParser.To(data, "== True"); - - - //SereinEnv.AddOrUpdateFlowGlobalData("My", A); - //var data = SerinExpressionEvaluator.Evaluate("@Get #My#",null,out _); - // 这里是我自己的测试代码,你可以删除 string filePath; - filePath = @"F:\临时\project\linux\project.dnf"; - filePath = @"F:\临时\project\linux\http\project.dnf"; - filePath = @"F:\临时\project\yolo flow\project.dnf"; - filePath = @"F:\临时\project\data\project.dnf"; filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\PLCproject.dnf"; filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\banyunqi\project.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 @@ -132,104 +102,5 @@ namespace Serein.Workbench } } -#if DEBUG && false - - public class TestObject - { - - public NestedObject Data { get; set; } - - public class NestedObject - { - public int Code { get; set; } - public int Code2 { get; set; } - - public string Tips { get; set; } - - } - public string ToUpper(string input) - { - return input.ToUpper(); - } - } - - - - - - - - //测试 操作表达式,条件表达式 - private void TestExp() - { - - #region 测试数据 - string expression = ""; - - var testObj = new TestObject - { - Data = new TestObject.NestedObject - { - Code = 15, - Code2 = 20, - Tips = "测试数据" - } - }; - - #endregion - #region 对象操作表达式 - // 获取对象成员 - var result = SerinExpressionEvaluator.Evaluate("get .Data.Code", testObj); - Debug.WriteLine(result); // 15 - - // 设置对象成员 - SerinExpressionEvaluator.Evaluate("set .Data.Code = 20", testObj); - Debug.WriteLine(testObj.Data.Code); // 20 - - SerinExpressionEvaluator.Evaluate("set .Data.Tips = 123", testObj); - // 调用对象方法 - result = SerinExpressionEvaluator.Evaluate($"call .ToUpper({SerinExpressionEvaluator.Evaluate("get .Data.Tips", testObj)})", testObj); - Debug.WriteLine(result); // HELLO - - expression = "@number (@+1)/100"; - result = SerinExpressionEvaluator.Evaluate(expression, 2); - Debug.WriteLine($"{expression} -> {result}"); // HELLO - #endregion - #region 条件表达式 - - expression = ".Data.Code == 15"; - var pass = SerinConditionParser.To(testObj, expression); - Debug.WriteLine($"{expression} -> " + pass); - - expression = ".Data.Code[@*2] == 31"; - //expression = ".Data.Tips contains 数据"; - pass = SerinConditionParser.To(testObj, expression); - Debug.WriteLine($"{expression} -> " + pass); - - expression = ".Data.Code < 20"; - pass = SerinConditionParser.To(testObj, expression); - Debug.WriteLine($"{expression} -> " + pass); - - - - int i = 43; - - expression = "in 11-22"; - pass = SerinConditionParser.To(i, expression); - Debug.WriteLine($"{i} {expression} -> " + pass); - - expression = "== 43"; - pass = SerinConditionParser.To(i, expression); - Debug.WriteLine($"{i} {expression} -> " + pass); - - string str = "MY NAME IS COOOOL"; - expression = "c NAME"; - pass = SerinConditionParser.To(str, expression); - Debug.WriteLine($"{str} {expression} -> " + pass); - - #endregion - - } -#endif - } +} diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index fcfea56..d579e83 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -84,6 +84,7 @@ + diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 5ed8847..f97fbe2 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -177,6 +177,7 @@ namespace Serein.Workbench NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel)); NodeMVVMManagement.RegisterUI(NodeControlType.ConditionRegion, typeof(ConditionRegionControl), typeof(ConditionRegionNodeControlViewModel)); NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel)); #endregion @@ -1406,6 +1407,7 @@ namespace Serein.Workbench Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition, Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp, Type when typeof(GlobalDataControl).IsAssignableFrom(droppedType) => NodeControlType.GlobalData, + Type when typeof(ScriptNodeControl).IsAssignableFrom(droppedType) => NodeControlType.Script, _ => NodeControlType.None, }; if (nodeControlType != NodeControlType.None) @@ -2913,7 +2915,7 @@ namespace Serein.Workbench catch (Exception ex) { - SereinEnv.WriteLine(InfoType.ERROR, $"粘贴节点时发生异常:{ex}"); + //SereinEnv.WriteLine(InfoType.ERROR, $"粘贴节点时发生异常:{ex}"); } diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs index 7e695ee..ed58ff9 100644 --- a/WorkBench/Node/NodeControlViewModelBase.cs +++ b/WorkBench/Node/NodeControlViewModelBase.cs @@ -21,39 +21,6 @@ namespace Serein.Workbench.Node.ViewModel } - - - //private NodeModelBase _nodeModelBase; - - //public NodeModelBase NodeModel - //{ - // get => _nodeModelBase; set - // { - // if (value != null) - // { - // _nodeModelBase = value; - // OnPropertyChanged(); - // } - // } - //} - - - //private bool isSelect; - ///// - ///// 表示节点控件是否被选中 - ///// - //internal bool IsSelect - //{ - // get => isSelect; - // set - // { - // isSelect = value; - // OnPropertyChanged(); - // } - //} - - - private bool isInterrupt; ///// ///// 控制中断状态的视觉效果 @@ -68,45 +35,14 @@ namespace Serein.Workbench.Node.ViewModel } } + + public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { - //Console.WriteLine(propertyName); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - /// - /// 使节点获得中断能力(以及是否启用节点) - /// - //public NodeDebugSetting DebugSetting - //{ - // get => Node.DebugSetting; - // set - // { - // if (value != null) - // { - // Node.DebugSetting = value; - // OnPropertyChanged(); - // } - // } - //} - - /// - /// 使节点能够表达方法信息 - /// - //public MethodDetails MethodDetails - //{ - // get => Node.MethodDetails; - // set - // { - // if(value != null) - // { - // Node.MethodDetails = value; - // OnPropertyChanged(); - // } - // } - //} - } } diff --git a/WorkBench/Serein.WorkBench.csproj b/WorkBench/Serein.WorkBench.csproj index 91f5532..9dbbd06 100644 --- a/WorkBench/Serein.WorkBench.csproj +++ b/WorkBench/Serein.WorkBench.csproj @@ -51,6 +51,7 @@ + diff --git a/Workbench/Node/View/ScriptNodeControl.xaml b/Workbench/Node/View/ScriptNodeControl.xaml new file mode 100644 index 0000000..550d5d0 --- /dev/null +++ b/Workbench/Node/View/ScriptNodeControl.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Workbench/Node/View/ScriptNodeControl.xaml.cs b/Workbench/Node/View/ScriptNodeControl.xaml.cs new file mode 100644 index 0000000..6dc95b7 --- /dev/null +++ b/Workbench/Node/View/ScriptNodeControl.xaml.cs @@ -0,0 +1,95 @@ +using Serein.NodeFlow.Model; +using Serein.Workbench.Node.ViewModel; +using System; +using System.Collections.Generic; +using System.Linq; +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.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; + +namespace Serein.Workbench.Node.View +{ + /// + /// ScriptNodeControl.xaml 的交互逻辑 + /// + public partial class ScriptNodeControl : NodeControlBase + { + private ScriptNodeControlViewModel viewModel => (ScriptNodeControlViewModel)ViewModel; + private DispatcherTimer _debounceTimer; // 用于延迟更新 + private bool _isUpdating = false; // 防止重复更新 + + public ScriptNodeControl() + { + InitializeComponent(); + } + public ScriptNodeControl(ScriptNodeControlViewModel viewModel) : base(viewModel) + { + DataContext = viewModel; + InitializeComponent(); + +#if false + // 初始化定时器 + _debounceTimer = new DispatcherTimer(); + _debounceTimer.Interval = TimeSpan.FromMilliseconds(500); // 停止输入 500ms 后更新 + _debounceTimer.Tick += DebounceTimer_Tick; +#endif + } + + +#if false + // 每次输入时重置定时器 + private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + _debounceTimer.Stop(); + _debounceTimer.Start(); + } + + // 定时器事件,用户停止输入后触发 + private async void DebounceTimer_Tick(object sender, EventArgs e) + { + _debounceTimer.Stop(); + + if (_isUpdating) + return; + + // 开始后台处理语法分析和高亮 + _isUpdating = true; + await Task.Run(() => HighlightKeywordsAsync(viewModel.Script)); + } + + // 异步执行语法高亮操作 + private async Task HighlightKeywordsAsync(string text) + { + if (string.IsNullOrEmpty(text)) + { + return; + } + // 模拟语法分析和高亮(可以替换为实际逻辑) + var highlightedText = text; + + // 在 UI 线程中更新 RichTextBox 的内容 + await Dispatcher.BeginInvoke(() => + { + var range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd); + range.Text = highlightedText; + }); + + _isUpdating = false; + } + +#endif + + + + + } +} diff --git a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs new file mode 100644 index 0000000..90b8d7f --- /dev/null +++ b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs @@ -0,0 +1,62 @@ +using Serein.Library; +using Serein.Library.Core; +using Serein.Library.Utils; +using Serein.NodeFlow.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; + +namespace Serein.Workbench.Node.ViewModel +{ + public class ScriptNodeControlViewModel : NodeControlViewModelBase + { + private SingleScriptNode NodeModel => (SingleScriptNode)base.NodeModel; + + public string? Script + { + get => NodeModel?.Script; + set { NodeModel.Script = value; OnPropertyChanged(); } + } + + + + public ScriptNodeControlViewModel(NodeModelBase nodeModel) : base(nodeModel) + { + CommandExecuting = new RelayCommand(async o => + { + try + { + var result = await NodeModel.ExecutingAsync(new DynamicContext(nodeModel.Env)); + SereinEnv.WriteLine(InfoType.INFO, result?.ToString()); + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, ex.ToString()); + } + }); + + CommandLoadScript = new RelayCommand( o => + { + NodeModel.LoadScript(); + }); + } + + + /// + /// 加载脚本代码 + /// + public ICommand CommandLoadScript{ get; } + + /// + /// 尝试执行 + /// + public ICommand CommandExecuting { get; } + + + + } +} diff --git a/Workbench/Themes/BindableRichTextBox.cs b/Workbench/Themes/BindableRichTextBox.cs new file mode 100644 index 0000000..f7c9d1e --- /dev/null +++ b/Workbench/Themes/BindableRichTextBox.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows; + +namespace Serein.Workbench.Themes +{ + public partial class BindableRichTextBox : RichTextBox + { + public new FlowDocument Document + { + get { return (FlowDocument)GetValue(DocumentProperty); } + set { SetValue(DocumentProperty, value); } + } + // Using a DependencyProperty as the backing store for Document. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DocumentProperty = + DependencyProperty.Register("Document", typeof(FlowDocument), typeof(BindableRichTextBox), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnDucumentChanged))); + private static void OnDucumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + RichTextBox rtb = (RichTextBox)d; + rtb.Document = (FlowDocument)e.NewValue; + } + } +}