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