From c07c64be1eee362150054d1a767351b155b8ef34 Mon Sep 17 00:00:00 2001
From: ninemine <1371605831@qq.com>
Date: Fri, 10 Oct 2025 11:04:43 +0800
Subject: [PATCH] =?UTF-8?q?EP=20RScript=20=E5=AE=8C=E6=88=90=E6=8E=A7?=
=?UTF-8?q?=E5=88=B6=E6=B5=81,=20=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../[RScript]/PublicTypes/RScriptException.cs | 6 +-
Convention/[RScript]/RScriptContext.cs | 107 +++++++----------
Convention/[RScript]/RScriptEngine.cs | 108 ++++++++++++++----
[Test]/Program.cs | 28 +++--
4 files changed, 145 insertions(+), 104 deletions(-)
diff --git a/Convention/[RScript]/PublicTypes/RScriptException.cs b/Convention/[RScript]/PublicTypes/RScriptException.cs
index 0d865ec..35422f0 100644
--- a/Convention/[RScript]/PublicTypes/RScriptException.cs
+++ b/Convention/[RScript]/PublicTypes/RScriptException.cs
@@ -3,9 +3,9 @@
namespace Convention.RScript
{
[Serializable]
- public class RScriptExceptionException : Exception
+ public class RScriptException : Exception
{
- public RScriptExceptionException(string message, int runtimePointer) : base($"when running {runtimePointer}, {message}") { }
- public RScriptExceptionException(string message, int runtimePointer, Exception inner) : base($"when running {runtimePointer}, {message}", inner) { }
+ public RScriptException(string message, int runtimePointer) : base($"when running {runtimePointer}, {message}") { }
+ public RScriptException(string message, int runtimePointer, Exception inner) : base($"when running {runtimePointer}, {message}", inner) { }
}
}
diff --git a/Convention/[RScript]/RScriptContext.cs b/Convention/[RScript]/RScriptContext.cs
index 55fc4fb..c39a518 100644
--- a/Convention/[RScript]/RScriptContext.cs
+++ b/Convention/[RScript]/RScriptContext.cs
@@ -31,20 +31,18 @@ namespace Convention.RScript
///
ExitNamespace,
///
- /// 标签, 格式: label: 标签名
+ /// 标签, 格式: label(labelname)
///
Label,
///
- /// 跳转到指定标签, 格式: goto 标签名
+ /// 跳转到指定标签, 格式: goto(a,b,labelname)
+ /// 当a大于b时跳转到labelname
///
Goto,
- ///
- /// 条件判断, 格式: if (条件表达式)
- ///
- If
}
public string content;
+ public List info;
public Mode mode;
}
@@ -74,15 +72,16 @@ namespace Convention.RScript
result.mode = RScriptSentence.Mode.ExitNamespace;
}
- Regex DefineVariableRegex = new(@"^(string|int|double|float|bool|var) [a-zA-Z_][a-zA-Z0-9_]*$");
+ Regex DefineVariableRegex = new(@"^(string|int|double|float|bool|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)$");
var DefineVariableMatch = DefineVariableRegex.Match(expression);
if (DefineVariableMatch.Success)
{
result.mode = RScriptSentence.Mode.DefineVariable;
+ result.info = new() { DefineVariableMatch.Groups[1].Value, DefineVariableMatch.Groups[2].Value };
return result;
}
- Regex LabelRegex = new(@"^label:\s*([a-zA-Z_][a-zA-Z0-9_]*)$");
+ Regex LabelRegex = new(@"^label\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)$");
var LabelMatch = LabelRegex.Match(expression);
if (LabelMatch.Success)
{
@@ -91,21 +90,13 @@ namespace Convention.RScript
return result;
}
- Regex GotoRegex = new(@"^goto\s+([a-zA-Z_][a-zA-Z0-9_]*)$");
+ Regex GotoRegex = new(@"^goto\s*\(\s*(.+)\s*,\s*(.*)\s*,([a-zA-Z_][a-zA-Z0-9_]*)\s*\)$");
var GotoMatch = GotoRegex.Match(expression);
if (GotoMatch.Success)
{
result.mode = RScriptSentence.Mode.Goto;
- result.content = GotoMatch.Groups[1].Value;
- return result;
- }
-
- Regex IfRegex = new(@"^if\s*\((.*)\)$");
- var IfMatch = IfRegex.Match(expression);
- if (IfMatch.Success)
- {
- result.mode = RScriptSentence.Mode.If;
- result.content = IfMatch.Groups[1].Value;
+ result.content = GotoMatch.Groups[3].Value;
+ result.info = new() { GotoMatch.Groups[1].Value, GotoMatch.Groups[2].Value, GotoMatch.Groups[3].Value };
return result;
}
@@ -127,14 +118,14 @@ namespace Convention.RScript
else if (Sentences[i].mode == RScriptSentence.Mode.ExitNamespace)
{
if (namespaceLayers.Count == 0)
- throw new RScriptExceptionException("Namespace exit without enter.", i);
+ throw new RScriptException("Namespace exit without enter.", i);
var enterPointer = namespaceLayers.Pop();
namespaceIndicator[enterPointer] = i;
}
}
if (namespaceLayers.Count > 0)
{
- throw new RScriptExceptionException("Namespace enter without exit.", namespaceLayers.Peek());
+ throw new RScriptException("Namespace enter without exit.", namespaceLayers.Peek());
}
}
@@ -165,62 +156,62 @@ namespace Convention.RScript
case RScriptSentence.Mode.DefineVariable:
{
// 定义变量
- var content = sentence.content.Split(' ');
+ var varTypeName = sentence.info[0];
+ var varName = sentence.info[1];
Type varType;
object varDefaultValue;
{
- if (content[0] == "string")
+ if (varTypeName == "string")
{
varType = typeof(string);
varDefaultValue = string.Empty;
}
- else if (content[0] == "int")
+ else if (varTypeName == "int")
{
varType = typeof(int);
varDefaultValue = 0;
}
- else if (content[0] == "double")
+ else if (varTypeName == "double")
{
varType = typeof(double);
varDefaultValue = 0.0;
}
- else if (content[0] == "float")
+ else if (varTypeName == "float")
{
varType = typeof(float);
varDefaultValue = 0.0f;
}
- else if (content[0] == "bool")
+ else if (varTypeName == "bool")
{
varType = typeof(bool);
varDefaultValue = false;
}
- else if (content[0] == "var")
+ else if (varTypeName == "var")
{
varType = typeof(object);
varDefaultValue = null;
}
else
{
- throw new RScriptExceptionException($"Unsupported variable type '{content[0]}'.", CurrentRuntimePointer);
+ throw new RScriptException($"Unsupported variable type '{varTypeName}'.", CurrentRuntimePointer);
}
}
- var varName = content[1];
- if (CurrentLocalSpaceVariableNames.Contains(varName) == false)
+ if (CurrentLocalSpaceVariableNames.Peek().Contains(varName) == false)
{
Variables.Add(varName, new() { type = varType, data = varDefaultValue });
parser.context.Variables[varName] = varDefaultValue;
- CurrentLocalSpaceVariableNames.Add(varName);
+ CurrentLocalSpaceVariableNames.Peek().Add(varName);
}
else
{
- throw new RScriptExceptionException($"Variable '{varName}' already defined on this namespace.", CurrentRuntimePointer);
+ throw new RScriptException($"Variable '{varName}' already defined on this namespace.", CurrentRuntimePointer);
}
}
break;
case RScriptSentence.Mode.EnterNamespace:
{
// 准备记录当前命名空间中定义的变量, 清空上层命名空间的变量
- CurrentLocalSpaceVariableNames.Clear();
+ CurrentLocalSpaceVariableNames.Push(new());
// 更新变量值
foreach (var (varName, varValue) in parser.context.Variables)
{
@@ -231,52 +222,34 @@ namespace Convention.RScript
case RScriptSentence.Mode.ExitNamespace:
{
// 移除在本命名空间中定义的变量
- foreach (var local in CurrentLocalSpaceVariableNames)
+ foreach (var local in CurrentLocalSpaceVariableNames.Peek())
{
Variables.Remove(local);
parser.context.Variables.Remove(local);
}
- CurrentLocalSpaceVariableNames.Clear();
// 还原上层命名空间的变量
- foreach (var local in Variables.Keys)
+ foreach (var local in CurrentLocalSpaceVariableNames.Peek())
{
- CurrentLocalSpaceVariableNames.Add(local);
parser.context.Variables[local] = Variables[local].data;
}
+ CurrentLocalSpaceVariableNames.Pop();
}
break;
case RScriptSentence.Mode.Goto:
{
- // 跳转到指定标签
- if (Labels.TryGetValue(sentence.content, out var labelPointer))
+ // 检查并跳转到指定标签
+ var leftValue = parser.Evaluate(sentence.info[0]);
+ var rightValue = parser.Evaluate(sentence.info[1]);
+ if (leftValue > rightValue)
{
- CurrentRuntimePointer = labelPointer;
- }
- else
- {
- throw new RScriptExceptionException($"Label '{sentence.content}' not found.", CurrentRuntimePointer);
- }
- }
- break;
- case RScriptSentence.Mode.If:
- {
- // 条件跳转
- var conditionResult = parser.Evaluate(sentence.content);
- if (conditionResult is bool b)
- {
- if (b == false)
+ if (Labels.TryGetValue(sentence.content, out var labelPointer))
{
- if (Namespace.TryGetValue(CurrentRuntimePointer + 1, out var exitPointer) == false)
- {
- // 没有命名空间时只跳过下一句, +1后在外层循环末尾再+1, 最终结果为下一次循环开始时已经指向第二句
- exitPointer = CurrentRuntimePointer + 1;
- }
- CurrentRuntimePointer = exitPointer;
+ CurrentRuntimePointer = labelPointer;
+ }
+ else
+ {
+ throw new RScriptException($"Label '{sentence.content}' not found.", CurrentRuntimePointer);
}
- }
- else
- {
- throw new RScriptExceptionException($"If condition must be bool, but got {conditionResult?.GetType().ToString() ?? "null"}.", CurrentRuntimePointer);
}
}
break;
@@ -288,12 +261,14 @@ namespace Convention.RScript
private readonly Stack RuntimePointerStack = new();
private int CurrentRuntimePointer = 0;
- private readonly HashSet CurrentLocalSpaceVariableNames = new();
+ private readonly Stack> CurrentLocalSpaceVariableNames = new();
public Dictionary Run(ExpressionParser parser)
{
CurrentLocalSpaceVariableNames.Clear();
RuntimePointerStack.Clear();
+ CurrentLocalSpaceVariableNames.Clear();
+ CurrentLocalSpaceVariableNames.Push(new());
for (CurrentRuntimePointer = 0; CurrentRuntimePointer < Sentences.Length; CurrentRuntimePointer++)
{
RunNextStep(parser);
diff --git a/Convention/[RScript]/RScriptEngine.cs b/Convention/[RScript]/RScriptEngine.cs
index 9e5f79c..7ed71ee 100644
--- a/Convention/[RScript]/RScriptEngine.cs
+++ b/Convention/[RScript]/RScriptEngine.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Convention.RScript
@@ -12,29 +13,96 @@ namespace Convention.RScript
private ExpressionParser parser;
private RScriptContext context;
+ private IEnumerable SplitScript(string script)
+ {
+ StringBuilder builder = new();
+ List statements = new();
+
+ for (int i = 0, e = script.Length; i < e; i++)
+ {
+ char c = script[i];
+ if (c == ';')
+ {
+ if (builder.Length > 0)
+ {
+ statements.Add(builder.ToString().Trim());
+ builder.Clear();
+ }
+ }
+ else if (c == '/' && i + 1 < e)
+ {
+ // Skip single-line comment
+ if (script[i + 1] == '/')
+ {
+ while (i < script.Length && script[i] != '\n')
+ i++;
+ }
+ // Skip multi-line comment
+ else if (script[i + 1] == '*')
+ {
+ i += 2;
+ while (i + 1 < script.Length && !(script[i] == '*' && script[i + 1] == '/'))
+ i++;
+ i++;
+ }
+ else
+ {
+ builder.Append(c);
+ }
+ }
+ else if (c == '#')
+ {
+ // Skip single-line comment
+ while (i < script.Length && script[i] != '\n')
+ i++;
+ }
+ else if (c == '\"')
+ {
+ for (i++; i < e; i++)
+ {
+ builder.Append(script[i]);
+ if (script[i] == '\"')
+ {
+ break;
+ }
+ else if (script[i] == '\\')
+ {
+ i++;
+ if (i < e)
+ builder.Append(script[i]);
+ else
+ throw new RScriptException("Invalid escape sequence in string literal", -1);
+ }
+ }
+ }
+ else if (c == '{' || c == '}')
+ {
+ // Treat braces as statement separators
+ if (builder.Length > 0)
+ {
+ statements.Add(builder.ToString().Trim());
+ builder.Clear();
+ }
+ statements.Add(c.ToString());
+ }
+ else
+ {
+ builder.Append(c);
+ }
+ }
+ if (builder.Length > 0)
+ {
+ statements.Add(builder.ToString().Trim());
+ builder.Clear();
+ }
+
+ return statements.Where(s => !string.IsNullOrWhiteSpace(s));
+ }
+
public Dictionary Run(string script, RScriptImportClass import = null, RScriptVariables variables = null)
{
parser = new(new());
- string newScript = "";
- foreach (var item in script.Split('\n'))
- {
- var line = item.Trim();
- if (string.IsNullOrEmpty(line))
- continue;
- if (line.StartsWith("//"))
- continue;
- if (line.StartsWith('#'))
- continue;
- newScript += line + ";"; // 添加分号分隔符
- }
-
- // 按分号分割并过滤空语句
- var statements = newScript.Split(';', StringSplitOptions.RemoveEmptyEntries)
- .Select(s => s.Trim())
- .Where(s => !string.IsNullOrEmpty(s))
- .ToArray();
-
- context = new(statements, import, variables);
+ context = new(SplitScript(script).ToArray(), import, variables);
foreach (var type in context.Import)
parser.context.Imports.AddType(type);
return context.Run(parser);
diff --git a/[Test]/Program.cs b/[Test]/Program.cs
index 102291a..03455ef 100644
--- a/[Test]/Program.cs
+++ b/[Test]/Program.cs
@@ -7,22 +7,20 @@ public class Program
static void Main(string[] args)
{
RScriptEngine engine = new();
- RScriptImportClass import = new();
- import.Add(typeof(Math));
+ RScriptImportClass import = new()
+ {
+ typeof(Math)
+ };
var result = engine.Run(@"
-double x;
-double y;
-double z;
-double result;
-bool stats;
-stats = true;
-x = 10.0;
-y = 20.0;
-z = x + y;
-// This is a comment;
-# This is another comment;
-result = z * 2;
-result = Pow(result,2.0);
+double i;
+i = 2.0;
+{
+ label(test);
+ i = Pow(i,2.0);
+ goto(100,i,test);
+}
+string result;
+result = i.ToString()+i.ToString();
", import);
Console.WriteLine($"Script executed successfully. Result: {result["result"].data}");
}