mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
暂时实现了简陋的脚本AST分析解释,后面再绑定到控件上
This commit is contained in:
58
Library/Api/IScriptFlowApi.cs
Normal file
58
Library/Api/IScriptFlowApi.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// 脚本代码中关于流程运行的API
|
||||
/// </summary>
|
||||
public interface IScriptFlowApi
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前流程
|
||||
/// </summary>
|
||||
IFlowEnvironment Env { get; }
|
||||
/// <summary>
|
||||
/// 对应的节点
|
||||
/// </summary>
|
||||
NodeModelBase NodeModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态流程上下文
|
||||
/// </summary>
|
||||
IDynamicContext Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据索引从入参数据获取数据
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
object GetDataOfParams(int index);
|
||||
/// <summary>
|
||||
/// 根据入参名称从入参数据获取数据
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
object GetDataOfParams(string name);
|
||||
/// <summary>
|
||||
/// 获取全局数据
|
||||
/// </summary>
|
||||
/// <param name="keyName"></param>
|
||||
/// <returns></returns>
|
||||
object GetGlobalData(string keyName);
|
||||
/// <summary>
|
||||
/// 获取流程当前传递的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object GetFlowData();
|
||||
/// <summary>
|
||||
/// 立即调用某个节点并获取其返回值
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <returns></returns>
|
||||
Task<object> CallNode(string nodeGuid);
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,10 @@ namespace Serein.Library
|
||||
/// 全局数据
|
||||
/// </summary>
|
||||
GlobalData,
|
||||
/// <summary>
|
||||
/// 脚本节点
|
||||
/// </summary>
|
||||
Script,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 记录Emit委托
|
||||
/// </summary>
|
||||
/// <param name="EmitMethodType"></param>
|
||||
/// <param name="EmitDelegate"></param>
|
||||
public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate)
|
||||
{
|
||||
_emitMethodType = EmitMethodType;
|
||||
_emitDelegate = EmitDelegate;
|
||||
}
|
||||
|
||||
|
||||
/*/// <summary>
|
||||
/// 更新委托方法
|
||||
@@ -50,9 +39,13 @@ namespace Serein.Library
|
||||
_emitDelegate = EmitDelegate;
|
||||
}*/
|
||||
|
||||
|
||||
private Delegate _emitDelegate;
|
||||
private EmitMethodType _emitMethodType;
|
||||
private EmitMethodInfo _emitMethodInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 该Emit委托的相应信息
|
||||
/// </summary>
|
||||
public EmitMethodInfo EmitMethodInfo => _emitMethodInfo;
|
||||
|
||||
///// <summary>
|
||||
///// <para>普通方法:Func<object,object[],object></para>
|
||||
@@ -65,6 +58,22 @@ namespace Serein.Library
|
||||
///// </summary>
|
||||
//public EmitMethodType EmitMethodType { get => _emitMethodType; }
|
||||
|
||||
|
||||
|
||||
public async Task<object> 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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。</para>
|
||||
/// </summary>
|
||||
@@ -77,16 +86,20 @@ namespace Serein.Library
|
||||
{
|
||||
args = Array.Empty<object>();
|
||||
}
|
||||
if(_emitMethodInfo.IsStatic)
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
object result = null;
|
||||
if (_emitMethodType == EmitMethodType.HasResultTask && _emitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
if (_emitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(instance, args);
|
||||
}
|
||||
else if (_emitMethodType == EmitMethodType.Task && _emitDelegate is Func<object, object[], Task> task)
|
||||
else if (_emitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(instance, args);
|
||||
}
|
||||
else if (_emitMethodType == EmitMethodType.Func && _emitDelegate is Func<object, object[], object> func)
|
||||
else if (_emitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(instance, args);
|
||||
}
|
||||
|
||||
@@ -69,14 +69,6 @@ namespace Serein.Library
|
||||
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法
|
||||
/// </summary>
|
||||
public virtual void OnCreating()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NodeModelBase(IFlowEnvironment environment)
|
||||
{
|
||||
PreviousNodes = new Dictionary<ConnectionInvokeType, List<NodeModelBase>>();
|
||||
|
||||
@@ -29,6 +29,15 @@ namespace Serein.Library
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
{
|
||||
#region 节点相关事件
|
||||
/// <summary>
|
||||
/// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法
|
||||
/// </summary>
|
||||
public virtual void OnCreating()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 保存自定义信息
|
||||
@@ -55,6 +64,7 @@ namespace Serein.Library
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除该节点
|
||||
/// </summary>
|
||||
@@ -95,7 +105,6 @@ namespace Serein.Library
|
||||
this.Env = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 输出方法参数信息
|
||||
/// </summary>
|
||||
@@ -125,7 +134,6 @@ namespace Serein.Library
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出为节点信息
|
||||
/// </summary>
|
||||
@@ -164,8 +172,6 @@ namespace Serein.Library
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从节点信息加载节点
|
||||
/// </summary>
|
||||
@@ -244,6 +250,8 @@ namespace Serein.Library
|
||||
|
||||
#region 节点方法的执行
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否应该退出执行
|
||||
/// </summary>
|
||||
|
||||
56
Library/FlowNode/ScriptFlowApi.cs
Normal file
56
Library/FlowNode/ScriptFlowApi.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 脚本代码中关于流程运行的API
|
||||
/// </summary>
|
||||
public class ScriptFlowApi : IScriptFlowApi
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程环境
|
||||
/// </summary>
|
||||
public IFlowEnvironment Env { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建流程脚本接口
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public ScriptFlowApi(IFlowEnvironment environment)
|
||||
{
|
||||
Env = environment;
|
||||
}
|
||||
|
||||
Task<object> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -12,11 +12,11 @@ namespace Serein.Library
|
||||
/// <summary>
|
||||
/// 全局触发器CTS
|
||||
/// </summary>
|
||||
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
|
||||
public const string FlipFlopCtsName = "$FlowFlipFlopCts";
|
||||
/// <summary>
|
||||
/// 流程运行CTS
|
||||
/// </summary>
|
||||
public const string FlowRungCtsName = "<>.FlowRungCtsName";
|
||||
public const string FlowRungCtsName = "$FlowRungCtsName";
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="FlowNode\Attribute.cs" />
|
||||
<Compile Remove="FlowNode\ScriptFlowApi.cs" />
|
||||
<Compile Remove="Utils\NativeDllHelper.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -66,6 +66,95 @@ namespace Serein.Library.Utils
|
||||
}
|
||||
|
||||
|
||||
public static Type CreateTypeWithProperties(IDictionary<string, Type> 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<Dictionary<string, Type>>) // 处理数组类型
|
||||
{
|
||||
var nestedPropValue = (propValue as IList<Dictionary<string, Type>>)[0];
|
||||
var nestedType = CreateTypeWithProperties(nestedPropValue, $"{propName}Element");
|
||||
propType = nestedType.GetType().MakeArrayType(); // 创建数组类型
|
||||
}
|
||||
else if (propValue is Dictionary<string, Type> 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<string, object> properties, string typeName)
|
||||
@@ -298,6 +387,7 @@ namespace Serein.Library.Utils
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,20 @@ namespace Serein.Library.Utils
|
||||
/// </summary>
|
||||
public class EmitHelper
|
||||
{
|
||||
|
||||
public class EmitMethodInfo
|
||||
{
|
||||
public Type DeclaringType { get; set; }
|
||||
/// <summary>
|
||||
/// 是异步方法
|
||||
/// </summary>
|
||||
public bool IsTask { get; set; }
|
||||
/// <summary>
|
||||
/// 是静态的
|
||||
/// </summary>
|
||||
public bool IsStatic { get; set; }
|
||||
}
|
||||
|
||||
public enum EmitMethodType
|
||||
{
|
||||
/// <summary>
|
||||
@@ -28,6 +42,15 @@ namespace Serein.Library.Utils
|
||||
/// 有返回值的异步方法
|
||||
/// </summary>
|
||||
HasResultTask,
|
||||
|
||||
/// <summary>
|
||||
/// 普通的方法。如果方法返回void时,将会返回null。
|
||||
/// </summary>
|
||||
StaticFunc,
|
||||
/// <summary>
|
||||
/// 无返回值的异步方法
|
||||
/// </summary>
|
||||
StaticTask,
|
||||
}
|
||||
|
||||
public static bool IsGenericTask(Type returnType, out Type taskResult)
|
||||
@@ -60,17 +83,19 @@ namespace Serein.Library.Utils
|
||||
/// <param name="methodInfo"></param>
|
||||
/// <param name="delegate"></param>
|
||||
/// <returns></returns>
|
||||
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<object, object[], Task<object>>));
|
||||
}
|
||||
else
|
||||
{
|
||||
emitMethodType = EmitMethodType.Task;
|
||||
@delegate = dynamicMethod.CreateDelegate(typeof(Func<object, object[], Task>));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
emitMethodType = EmitMethodType.Func;
|
||||
@delegate = dynamicMethod.CreateDelegate(typeof(Func<object, object[], object>));
|
||||
|
||||
}
|
||||
return emitMethodType;
|
||||
return new EmitMethodInfo
|
||||
{
|
||||
DeclaringType = methodInfo.DeclaringType,
|
||||
IsTask = IsTask,
|
||||
IsStatic = isStatic
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Serein.Library.Utils
|
||||
/// </summary>
|
||||
public class SereinIOC/* : ISereinIOC*/
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 类型集合,暂放待实例化的类型,完成实例化之后移除
|
||||
/// </summary>
|
||||
|
||||
@@ -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<IScriptFlowApi, ScriptFlowApi>(); // 注册脚本接口
|
||||
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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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<SingleFlipflopNode, CancellationTokenSource> dictGlobalFlipflop = [];
|
||||
|
||||
/// <summary>
|
||||
|
||||
143
NodeFlow/Model/SingleScriptNode.cs
Normal file
143
NodeFlow/Model/SingleScriptNode.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流程脚本节点
|
||||
/// </summary>
|
||||
public partial class SingleScriptNode : NodeModelBase
|
||||
{
|
||||
private IScriptFlowApi ScriptFlowApi { get; }
|
||||
|
||||
private ASTNode mainNode;
|
||||
|
||||
/// <summary>
|
||||
/// 构建流程脚本节点
|
||||
/// </summary>
|
||||
/// <param name="environment"></param>
|
||||
public SingleScriptNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
//ScriptFlowApi = environment.IOC.Get<ScriptFlowApi>();
|
||||
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); // 加载基础方法
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载脚本代码
|
||||
/// </summary>
|
||||
public void LoadScript()
|
||||
{
|
||||
try
|
||||
{
|
||||
mainNode = new SereinScriptParser(Script).Parse();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<object?> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
NodeFlow/ScriptFlowApi.cs
Normal file
66
NodeFlow/ScriptFlowApi.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 脚本代码中关于流程运行的API
|
||||
/// </summary>
|
||||
public class ScriptFlowApi : IScriptFlowApi
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程环境
|
||||
/// </summary>
|
||||
public IFlowEnvironment Env { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应的节点
|
||||
/// </summary>
|
||||
public NodeModelBase NodeModel { get; private set; }
|
||||
|
||||
IDynamicContext IScriptFlowApi.Context { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
/// <summary>
|
||||
/// 创建流程脚本接口
|
||||
/// </summary>
|
||||
/// <param name="environment">运行环境</param>
|
||||
/// <param name="nodeModel">节点</param>
|
||||
public ScriptFlowApi(IFlowEnvironment environment, NodeModelBase nodeModel)
|
||||
{
|
||||
Env = environment;
|
||||
NodeModel = nodeModel;
|
||||
}
|
||||
|
||||
Task<object> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
<ProjectReference Include="..\Library.Core\Serein.Library.Core.csproj" />
|
||||
<ProjectReference Include="..\Library.Framework\Serein.Library.Framework.csproj" />
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
<ProjectReference Include="..\Serein.Script\Serein.Script.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
26
Serein.Script/Node/ASTNode.cs
Normal file
26
Serein.Script/Node/ASTNode.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
21
Serein.Script/Node/AssignmentNode.cs
Normal file
21
Serein.Script/Node/AssignmentNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 变量节点
|
||||
/// </summary>
|
||||
public class AssignmentNode : ASTNode
|
||||
{
|
||||
public string Variable { get; }
|
||||
public ASTNode Value { get; }
|
||||
public AssignmentNode(string variable, ASTNode value) => (Variable, Value) = (variable, value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
26
Serein.Script/Node/BinaryOperationNode.cs
Normal file
26
Serein.Script/Node/BinaryOperationNode.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 二元表达式节点
|
||||
/// </summary>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Serein.Script/Node/BooleanNode.cs
Normal file
17
Serein.Script/Node/BooleanNode.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 布尔字面量
|
||||
/// </summary>
|
||||
public class BooleanNode : ASTNode
|
||||
{
|
||||
public bool Value { get; }
|
||||
public BooleanNode(bool value) => Value = value;
|
||||
}
|
||||
}
|
||||
24
Serein.Script/Node/ClassTypeDefinitionNode.cs
Normal file
24
Serein.Script/Node/ClassTypeDefinitionNode.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 动态类型定义
|
||||
/// </summary>
|
||||
public class ClassTypeDefinitionNode : ASTNode
|
||||
{
|
||||
public string ClassName { get; }
|
||||
public Dictionary<string, Type> Fields { get; }
|
||||
|
||||
public ClassTypeDefinitionNode(Dictionary<string, Type> fields, string className)
|
||||
{
|
||||
this.Fields = fields;
|
||||
this.ClassName = className;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
20
Serein.Script/Node/CollectionIndexNode.cs
Normal file
20
Serein.Script/Node/CollectionIndexNode.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 集合索引获取
|
||||
/// </summary>
|
||||
public class CollectionIndexNode : ASTNode
|
||||
{
|
||||
public ASTNode IndexValue { get; }
|
||||
public CollectionIndexNode(ASTNode indexValue)
|
||||
{
|
||||
this.IndexValue = indexValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Serein.Script/Node/FunctionCallNode.cs
Normal file
24
Serein.Script/Node/FunctionCallNode.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 挂载函数调用
|
||||
/// </summary>
|
||||
public class FunctionCallNode : ASTNode
|
||||
{
|
||||
public string FunctionName { get; }
|
||||
public List<ASTNode> Arguments { get; }
|
||||
|
||||
public FunctionCallNode(string functionName, List<ASTNode> arguments)
|
||||
{
|
||||
FunctionName = functionName;
|
||||
Arguments = arguments;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
Serein.Script/Node/IdentifierNode.cs
Normal file
17
Serein.Script/Node/IdentifierNode.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 标识符(变量)
|
||||
/// </summary>
|
||||
public class IdentifierNode : ASTNode
|
||||
{
|
||||
public string Name { get; }
|
||||
public IdentifierNode(string name) => Name = name;
|
||||
}
|
||||
}
|
||||
21
Serein.Script/Node/IfNode.cs
Normal file
21
Serein.Script/Node/IfNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件节点
|
||||
/// </summary>
|
||||
public class IfNode : ASTNode
|
||||
{
|
||||
public ASTNode Condition { get; }
|
||||
public List<ASTNode> TrueBranch { get; }
|
||||
public List<ASTNode> FalseBranch { get; }
|
||||
public IfNode(ASTNode condition, List<ASTNode> trueBranch, List<ASTNode> falseBranch)
|
||||
=> (Condition, TrueBranch, FalseBranch) = (condition, trueBranch, falseBranch);
|
||||
}
|
||||
|
||||
}
|
||||
23
Serein.Script/Node/MemberAccessNode.cs
Normal file
23
Serein.Script/Node/MemberAccessNode.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示对象的成员访问
|
||||
/// </summary>
|
||||
public class MemberAccessNode : ASTNode
|
||||
{
|
||||
public ASTNode Object { get; }
|
||||
public string MemberName { get; }
|
||||
|
||||
public MemberAccessNode(ASTNode obj, string memberName)
|
||||
{
|
||||
Object = obj;
|
||||
MemberName = memberName;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Serein.Script/Node/MemberAssignmentNode.cs
Normal file
25
Serein.Script/Node/MemberAssignmentNode.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示对对象成员的赋值
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Serein.Script/Node/MemberFunctionCallNode.cs
Normal file
25
Serein.Script/Node/MemberFunctionCallNode.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象成员方法调用
|
||||
/// </summary>
|
||||
public class MemberFunctionCallNode : ASTNode
|
||||
{
|
||||
public ASTNode Object { get; }
|
||||
public string FunctionName { get; }
|
||||
public List<ASTNode> Arguments { get; }
|
||||
|
||||
public MemberFunctionCallNode(ASTNode @object, string functionName, List<ASTNode> arguments)
|
||||
{
|
||||
Object = @object;
|
||||
FunctionName = functionName;
|
||||
Arguments = arguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Serein.Script/Node/NullNode.cs
Normal file
15
Serein.Script/Node/NullNode.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Null节点
|
||||
/// </summary>
|
||||
public class NullNode : ASTNode
|
||||
{
|
||||
}
|
||||
}
|
||||
21
Serein.Script/Node/NumberNode.cs
Normal file
21
Serein.Script/Node/NumberNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
23
Serein.Script/Node/ObjectInstantiationNode.cs
Normal file
23
Serein.Script/Node/ObjectInstantiationNode.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 类型创建
|
||||
/// </summary>
|
||||
public class ObjectInstantiationNode : ASTNode
|
||||
{
|
||||
public string TypeName { get; }
|
||||
public List<ASTNode> Arguments { get; }
|
||||
public ObjectInstantiationNode(string typeName, List<ASTNode> arguments)
|
||||
{
|
||||
this.TypeName = typeName;
|
||||
this.Arguments = arguments;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
Serein.Script/Node/ProgramNode.cs
Normal file
22
Serein.Script/Node/ProgramNode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序入口
|
||||
/// </summary>
|
||||
public class ProgramNode : ASTNode
|
||||
{
|
||||
public List<ASTNode> Statements { get; }
|
||||
|
||||
public ProgramNode(List<ASTNode> statements)
|
||||
{
|
||||
Statements = statements;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
Serein.Script/Node/ReturnNode.cs
Normal file
25
Serein.Script/Node/ReturnNode.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回值
|
||||
/// </summary>
|
||||
public class ReturnNode : ASTNode
|
||||
{
|
||||
public ASTNode Value { get; }
|
||||
|
||||
public ReturnNode(ASTNode returnNode)
|
||||
{
|
||||
Value = returnNode;
|
||||
}
|
||||
public ReturnNode()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
Serein.Script/Node/StringNode.cs
Normal file
61
Serein.Script/Node/StringNode.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串字面量节点
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
19
Serein.Script/Node/WhileNode.cs
Normal file
19
Serein.Script/Node/WhileNode.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Script.Node
|
||||
{
|
||||
/// <summary>
|
||||
/// 循环条件节点
|
||||
/// </summary>
|
||||
public class WhileNode : ASTNode
|
||||
{
|
||||
public ASTNode Condition { get; }
|
||||
public List<ASTNode> Body { get; }
|
||||
public WhileNode(ASTNode condition, List<ASTNode> body) => (Condition, Body) = (condition, body);
|
||||
}
|
||||
|
||||
}
|
||||
19
Serein.Script/Serein.Script.csproj
Normal file
19
Serein.Script/Serein.Script.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Tool\**" />
|
||||
<EmbeddedResource Remove="Tool\**" />
|
||||
<None Remove="Tool\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
532
Serein.Script/SereinScriptInterpreter.cs
Normal file
532
Serein.Script/SereinScriptInterpreter.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义的变量
|
||||
/// </summary>
|
||||
private Dictionary<string, object> _variables = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// 挂载的函数
|
||||
/// </summary>
|
||||
private static Dictionary<string, DelegateDetails> _functionTable = new Dictionary<string, DelegateDetails>();
|
||||
|
||||
/// <summary>
|
||||
/// 挂载的函数调用的对象(用于函数需要实例才能调用的场景)
|
||||
/// </summary>
|
||||
private static Dictionary<string, Func<object>> _callFuncOfGetObjects = new Dictionary<string, Func<object>>();
|
||||
|
||||
/// <summary>
|
||||
/// 定义的类型
|
||||
/// </summary>
|
||||
private static Dictionary<string, Type> _classDefinition = new Dictionary<string, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// 重置的变量
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 挂载函数
|
||||
/// </summary>
|
||||
/// <param name="functionName">函数名称</param>
|
||||
/// <param name="methodInfo">方法信息</param>
|
||||
public static void AddFunction(string functionName, MethodInfo methodInfo, Func<object>? 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 挂载类型
|
||||
/// </summary>
|
||||
/// <param name="typeName">函数名称</param>
|
||||
/// <param name="type">方法信息</param>
|
||||
public static void AddClassType(Type type , string typeName = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName))
|
||||
{
|
||||
typeName = type.Name;
|
||||
}
|
||||
if (!_classDefinition.ContainsKey(typeName))
|
||||
{
|
||||
_classDefinition[typeName] = type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 入口节点
|
||||
/// </summary>
|
||||
/// <param name="programNode"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<object?> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型定义
|
||||
/// </summary>
|
||||
/// <param name="programNode"></param>
|
||||
/// <returns></returns>
|
||||
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; // 定义对象
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IF...ELSE... 语句块
|
||||
/// </summary>
|
||||
/// <param name="ifNode"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WHILE(){...} 语句块
|
||||
/// </summary>
|
||||
/// <param name="whileNode"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作节点
|
||||
/// </summary>
|
||||
/// <param name="assignmentNode"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ExecutionAssignmentNodeAsync(AssignmentNode assignmentNode)
|
||||
{
|
||||
var tmp = await EvaluateAsync(assignmentNode.Value);
|
||||
_variables[assignmentNode.Variable] = tmp;
|
||||
}
|
||||
private async Task<object> 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<object>");
|
||||
}
|
||||
}
|
||||
|
||||
var result = await function.InvokeAsync(instance,arguments);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Unknown function: {functionCallNode.FunctionName}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<object?> 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<object?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置对象成员
|
||||
/// </summary>
|
||||
/// <param name="memberAssignmentNode"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="SereinSciptException"></exception>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象成员
|
||||
/// </summary>
|
||||
/// <param name="memberAccessNode"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="SereinSciptException"></exception>
|
||||
public async Task<object?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存method委托
|
||||
/// </summary>
|
||||
private Dictionary<string, DelegateDetails> MethodToDelegateCaches { get; } = new Dictionary<string, DelegateDetails>();
|
||||
|
||||
public async Task<object?> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
360
Serein.Script/SereinScriptLexer.cs
Normal file
360
Serein.Script/SereinScriptLexer.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Serein.Script
|
||||
{
|
||||
internal enum TokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// 预料之外的值
|
||||
/// </summary>
|
||||
Null,
|
||||
/// <summary>
|
||||
/// 标识符
|
||||
/// </summary>
|
||||
Identifier,
|
||||
/// <summary>
|
||||
/// 布尔
|
||||
/// </summary>
|
||||
Boolean,
|
||||
/// <summary>
|
||||
/// 数值
|
||||
/// </summary>
|
||||
Number,
|
||||
/// <summary>
|
||||
/// 字符串
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// 关键字
|
||||
/// </summary>
|
||||
Keyword,
|
||||
/// <summary>
|
||||
/// 操作符
|
||||
/// </summary>
|
||||
Operator,
|
||||
/// <summary>
|
||||
/// 左小括号
|
||||
/// </summary>
|
||||
ParenthesisLeft,
|
||||
/// <summary>
|
||||
/// 右小括号
|
||||
/// </summary>
|
||||
ParenthesisRight,
|
||||
/// <summary>
|
||||
/// 左中括号
|
||||
/// </summary>
|
||||
SquareBracketsLeft,
|
||||
/// <summary>
|
||||
/// 右中括号
|
||||
/// </summary>
|
||||
SquareBracketsRight,
|
||||
/// <summary>
|
||||
/// 左大括号
|
||||
/// </summary>
|
||||
BraceLeft,
|
||||
/// <summary>
|
||||
/// 右大括号
|
||||
/// </summary>
|
||||
BraceRight,
|
||||
/// <summary>
|
||||
/// 点号
|
||||
/// </summary>
|
||||
Dot,
|
||||
/// <summary>
|
||||
/// 逗号
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// 分号
|
||||
/// </summary>
|
||||
Semicolon,
|
||||
|
||||
/// <summary>
|
||||
/// 行注释
|
||||
/// </summary>
|
||||
// RowComment,
|
||||
|
||||
/// <summary>
|
||||
/// 解析完成
|
||||
/// </summary>
|
||||
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<char> _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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取硬编码的文本
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对应行的代码文本
|
||||
/// </summary>
|
||||
/// <param name="lineNumber"></param>
|
||||
/// <returns></returns>
|
||||
private ReadOnlySpan<char> GetLine( int lineNumber)
|
||||
{
|
||||
ReadOnlySpan<char> 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<char>.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
647
Serein.Script/SereinScriptParser.cs
Normal file
647
Serein.Script/SereinScriptParser.cs
Normal file
@@ -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<ASTNode> Statements { get; } = new List<ASTNode>();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从标识符解析方法调用、变量赋值、获取对象成员行为。
|
||||
/// (非符号、关键字)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析赋值行为
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 let 变量赋值行为
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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<string, Type>();
|
||||
_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<ASTNode>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象成员
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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<ASTNode>();
|
||||
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<ASTNode>();
|
||||
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<ASTNode> trueBranch = new List<ASTNode>();
|
||||
List<ASTNode> falseBranch = new List<ASTNode>();
|
||||
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<ASTNode> body = new List<ASTNode>();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
119
Serein.Script/Tool/DelegateDetails.cs
Normal file
119
Serein.Script/Tool/DelegateDetails.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Emit创建的委托描述,用于WebApi、WebSocket、NodeFlow动态调用方法的场景。
|
||||
/// 一般情况下你无须关注内部细节,只需要调用 Invoke() 方法即可。
|
||||
/// </summary>
|
||||
public class DelegateDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据方法信息构建Emit委托
|
||||
/// </summary>
|
||||
/// <param name="methodInfo"></param>
|
||||
public DelegateDetails(MethodInfo methodInfo)
|
||||
{
|
||||
var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var emitDelegate);
|
||||
_emitMethodInfo = emitMethodType;
|
||||
_emitDelegate = emitDelegate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*/// <summary>
|
||||
/// 更新委托方法
|
||||
/// </summary>
|
||||
/// <param name="EmitMethodType"></param>
|
||||
/// <param name="EmitDelegate"></param>
|
||||
public void Upload(EmitMethodType EmitMethodType, Delegate EmitDelegate)
|
||||
{
|
||||
_emitMethodType = EmitMethodType;
|
||||
_emitDelegate = EmitDelegate;
|
||||
}*/
|
||||
|
||||
private Delegate _emitDelegate;
|
||||
private EmitMethodInfo _emitMethodInfo;
|
||||
|
||||
///// <summary>
|
||||
///// <para>普通方法:Func<object,object[],object></para>
|
||||
///// <para>异步方法:Func<object,object[],Task></para>
|
||||
///// <para>异步有返回值方法:Func<object,object[],Task<object>></para>
|
||||
///// </summary>
|
||||
//public Delegate EmitDelegate { get => _emitDelegate; }
|
||||
///// <summary>
|
||||
///// 表示Emit构造的委托类型
|
||||
///// </summary>
|
||||
//public EmitMethodType EmitMethodType { get => _emitMethodType; }
|
||||
|
||||
|
||||
|
||||
public async Task<object> 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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。</para>
|
||||
/// </summary>
|
||||
/// <param name="instance">拥有符合委托签名的方法信息的实例</param>
|
||||
/// <param name="args">如果方法没有入参,也需要传入一个空数组</param>
|
||||
/// <returns>void方法自动返回null</returns>
|
||||
public async Task<object> InvokeAsync(object instance, object[] args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
args = Array.Empty<object>();
|
||||
}
|
||||
if(_emitMethodInfo.IsStatic)
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
object result = null;
|
||||
if (_emitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(instance, args);
|
||||
}
|
||||
else if (_emitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(instance, args);
|
||||
}
|
||||
else if (_emitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(instance, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("创建了非预期委托(应该不会出现)");
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
|
||||
//try
|
||||
//{
|
||||
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// throw;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
203
Serein.Script/Tool/EmitHelper.cs
Normal file
203
Serein.Script/Tool/EmitHelper.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Serein.Library.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Emit创建委托工具类
|
||||
/// </summary>
|
||||
public class EmitHelper
|
||||
{
|
||||
|
||||
public class EmitMethodInfo
|
||||
{
|
||||
public Type DeclaringType { get; set; }
|
||||
/// <summary>
|
||||
/// 是异步方法
|
||||
/// </summary>
|
||||
public bool IsTask { get; set; }
|
||||
/// <summary>
|
||||
/// 是静态的
|
||||
/// </summary>
|
||||
public bool IsStatic { get; set; }
|
||||
}
|
||||
|
||||
public enum EmitMethodType
|
||||
{
|
||||
/// <summary>
|
||||
/// 普通的方法。如果方法返回void时,将会返回null。
|
||||
/// </summary>
|
||||
Func,
|
||||
/// <summary>
|
||||
/// 无返回值的异步方法
|
||||
/// </summary>
|
||||
Task,
|
||||
/// <summary>
|
||||
/// 有返回值的异步方法
|
||||
/// </summary>
|
||||
HasResultTask,
|
||||
|
||||
/// <summary>
|
||||
/// 普通的方法。如果方法返回void时,将会返回null。
|
||||
/// </summary>
|
||||
StaticFunc,
|
||||
/// <summary>
|
||||
/// 无返回值的异步方法
|
||||
/// </summary>
|
||||
StaticTask,
|
||||
}
|
||||
|
||||
public static bool IsGenericTask(Type returnType, out Type taskResult)
|
||||
{
|
||||
// 判断是否为 Task 类型或泛型 Task<T>
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托
|
||||
/// </summary>
|
||||
/// <param name="methodInfo"></param>
|
||||
/// <param name="delegate"></param>
|
||||
/// <returns></returns>
|
||||
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<object>);
|
||||
}
|
||||
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<object, object[], Task<object>>));
|
||||
}
|
||||
else
|
||||
{
|
||||
@delegate = dynamicMethod.CreateDelegate(typeof(Func<object, object[], Task>));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@delegate = dynamicMethod.CreateDelegate(typeof(Func<object, object[], object>));
|
||||
|
||||
}
|
||||
return new EmitMethodInfo
|
||||
{
|
||||
DeclaringType = methodInfo.DeclaringType,
|
||||
IsTask = IsTask,
|
||||
IsStatic = isStatic
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, object> keyValuePairs = new Dictionary<string, object>
|
||||
//{
|
||||
// {"value", objects }
|
||||
//};
|
||||
|
||||
|
||||
//var data = SerinExpressionEvaluator.Evaluate("@Get .[value].[0]<int>", keyValuePairs, out _);
|
||||
//data = SerinExpressionEvaluator.Evaluate("@Get .[value].[1]<bool>", keyValuePairs, out _);
|
||||
//data = SerinExpressionEvaluator.Evaluate("@Dtc <bool>", 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<int>[@*2] == 31";
|
||||
//expression = ".Data.Tips<string> contains 数据";
|
||||
pass = SerinConditionParser.To(testObj, expression);
|
||||
Debug.WriteLine($"{expression} -> " + pass);
|
||||
|
||||
expression = ".Data.Code<int> < 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
<!--暂时隐藏基础面板 Visibility="Collapsed" -->
|
||||
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<nodeView:ScriptNodeControl x:Name="ScriptNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
<nodeView:GlobalDataControl x:Name="GlobalDataControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
<nodeView:ExpOpNodeControl x:Name="ExpOpNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
<nodeView:ConditionNodeControl x:Name="ConditionNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
///// <summary>
|
||||
///// 表示节点控件是否被选中
|
||||
///// </summary>
|
||||
//internal bool IsSelect
|
||||
//{
|
||||
// get => isSelect;
|
||||
// set
|
||||
// {
|
||||
// isSelect = value;
|
||||
// OnPropertyChanged();
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
private bool isInterrupt;
|
||||
///// <summary>
|
||||
///// 控制中断状态的视觉效果
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 使节点获得中断能力(以及是否启用节点)
|
||||
/// </summary>
|
||||
//public NodeDebugSetting DebugSetting
|
||||
//{
|
||||
// get => Node.DebugSetting;
|
||||
// set
|
||||
// {
|
||||
// if (value != null)
|
||||
// {
|
||||
// Node.DebugSetting = value;
|
||||
// OnPropertyChanged();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 使节点能够表达方法信息
|
||||
/// </summary>
|
||||
//public MethodDetails MethodDetails
|
||||
//{
|
||||
// get => Node.MethodDetails;
|
||||
// set
|
||||
// {
|
||||
// if(value != null)
|
||||
// {
|
||||
// Node.MethodDetails = value;
|
||||
// OnPropertyChanged();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<ProjectReference Include="..\Library.Framework\Serein.Library.Framework.csproj" />
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
<ProjectReference Include="..\NodeFlow\Serein.NodeFlow.csproj" />
|
||||
<ProjectReference Include="..\Serein.Script\Serein.Script.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
75
Workbench/Node/View/ScriptNodeControl.xaml
Normal file
75
Workbench/Node/View/ScriptNodeControl.xaml
Normal file
@@ -0,0 +1,75 @@
|
||||
<local:NodeControlBase x:Class="Serein.Workbench.Node.View.ScriptNodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.Workbench.Node.View"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel"
|
||||
xmlns:themes="clr-namespace:Serein.Workbench.Themes"
|
||||
d:DataContext="{d:DesignInstance vm:ScriptNodeControlViewModel}"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="50">
|
||||
|
||||
<Grid Background="#FEFAF4">
|
||||
<!--<Grid.ToolTip>
|
||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding NodeModel.MethodDetails.MethodAnotherName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid.ToolTip>-->
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Background="#E7EFF5" >
|
||||
<!--<Grid Grid.Row="0" >-->
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
|
||||
<Border Grid.Column="1" BorderThickness="1" HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="脚本节点" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<local:NextStepJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="NextStepJunctionControl" HorizontalAlignment="Right" Grid.RowSpan="2"/>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Margin="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="脚本代码:" Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
|
||||
|
||||
<Button Content="加载" Margin="3,0,1,0" Command="{Binding CommandLoadScript}" Height="17.2"></Button>
|
||||
<Button Content="执行" Margin="3,0,1,0" Command="{Binding CommandExecuting}" Height="17.2"></Button>
|
||||
<!--<Button Content="刷新 " Command="{Binding CommandCopyDataExp}" Height="17.2" Margin="2,0,0,0"></Button>-->
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Row="1" MinHeight="20" MinWidth="100" MaxWidth="270" TextWrapping="Wrap" AcceptsReturn="True" Text="{Binding Script}"></TextBox>
|
||||
|
||||
<!--<RichTextBox x:Name="richTextBox" VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
|
||||
TextChanged="RichTextBox_TextChanged"/>-->
|
||||
|
||||
|
||||
<!--<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<local:ResultJunctionControl Grid.Column="2" MyNode="{Binding NodelModel}" x:Name="ResultJunctionControl" HorizontalAlignment="Right"/>
|
||||
<TextBlock Text="设置数据源" Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
|
||||
</StackPanel>-->
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
95
Workbench/Node/View/ScriptNodeControl.xaml.cs
Normal file
95
Workbench/Node/View/ScriptNodeControl.xaml.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptNodeControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
62
Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs
Normal file
62
Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs
Normal file
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载脚本代码
|
||||
/// </summary>
|
||||
public ICommand CommandLoadScript{ get; }
|
||||
|
||||
/// <summary>
|
||||
/// 尝试执行
|
||||
/// </summary>
|
||||
public ICommand CommandExecuting { get; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
28
Workbench/Themes/BindableRichTextBox.cs
Normal file
28
Workbench/Themes/BindableRichTextBox.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user