暂时实现了简陋的脚本AST分析解释,后面再绑定到控件上

This commit is contained in:
fengjiayi
2024-12-20 23:39:29 +08:00
parent 114e81424b
commit ef119e11e3
52 changed files with 3175 additions and 261 deletions

View 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);
}
}

View File

@@ -97,6 +97,10 @@ namespace Serein.Library
/// 全局数据
/// </summary>
GlobalData,
/// <summary>
/// 脚本节点
/// </summary>
Script,
}
}

View File

@@ -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&lt;object,object[],object&gt;</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);
}

View File

@@ -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>>();

View File

@@ -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>

View 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();
}
}
}

View File

@@ -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>

View File

@@ -32,6 +32,7 @@
<ItemGroup>
<Compile Remove="FlowNode\Attribute.cs" />
<Compile Remove="FlowNode\ScriptFlowApi.cs" />
<Compile Remove="Utils\NativeDllHelper.cs" />
</ItemGroup>

View File

@@ -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
}
}

View File

@@ -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
};
}

View File

@@ -15,6 +15,8 @@ namespace Serein.Library.Utils
/// </summary>
public class SereinIOC/* : ISereinIOC*/
{
/// <summary>
/// 类型集合,暂放待实例化的类型,完成实例化之后移除
/// </summary>

View File

@@ -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);

View File

@@ -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,
};

View File

@@ -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>

View 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
View 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();
}
}
}

View File

@@ -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>

View 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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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
{
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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()
{
}
}
}

View 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();
}
}
}

View 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);
}
}

View 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>

View 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);
}
}
}

View 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;
}
}
}

View 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());
}
}
}

View 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&lt;object,object[],object&gt;</para>
///// <para>异步方法Func&lt;object,object[],Task&gt;</para>
///// <para>异步有返回值方法Func&lt;object,object[],Task&lt;object&gt;&gt;</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;
//}
}
}
}

View 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
};
}
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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"/>

View File

@@ -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}");
}

View File

@@ -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();
// }
// }
//}
}
}

View File

@@ -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>

View 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>

View 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
}
}

View 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; }
}
}

View 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;
}
}
}