commit d42726a4dc96d6e1b55cd0b0bc668f0ee41c6ffb Author: ninemine <1371605831@qq.com> Date: Mon Oct 13 19:39:36 2025 +0800 EP RScript diff --git a/Matcher/BackMatcher.cs b/Matcher/BackMatcher.cs new file mode 100644 index 0000000..95e5793 --- /dev/null +++ b/Matcher/BackMatcher.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace Convention.RScript.Matcher +{ + public class BackMatcher : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + Regex LabelRegex = new(@"back\s*\(\s*(.+)\s*\)"); + var LabelMatch = LabelRegex.Match(expression); + if (LabelMatch.Success) + { + sentence.mode = RScriptSentence.Mode.Backpoint; + sentence.content = LabelMatch.Groups[1].Value; + return true; + } + return false; + } + } +} diff --git a/Matcher/BreakMatcher.cs b/Matcher/BreakMatcher.cs new file mode 100644 index 0000000..7549d24 --- /dev/null +++ b/Matcher/BreakMatcher.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace Convention.RScript.Matcher +{ + public class BreakMatcher : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + Regex LabelRegex = new(@"break\s*\(\s*(.+)\s*\)"); + var LabelMatch = LabelRegex.Match(expression); + if (LabelMatch.Success) + { + sentence.mode = RScriptSentence.Mode.Breakpoint; + sentence.content = LabelMatch.Groups[1].Value; + return true; + } + return false; + } + } +} diff --git a/Matcher/DefineVariableMatcher.cs b/Matcher/DefineVariableMatcher.cs new file mode 100644 index 0000000..f3d9018 --- /dev/null +++ b/Matcher/DefineVariableMatcher.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace Convention.RScript.Matcher +{ + public class DefineVariableMatcher : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + 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) + { + sentence.mode = RScriptSentence.Mode.DefineVariable; + sentence.info = new() { DefineVariableMatch.Groups[1].Value, DefineVariableMatch.Groups[2].Value }; + return true; + } + return false; + } + } +} diff --git a/Matcher/GotoMatcher.cs b/Matcher/GotoMatcher.cs new file mode 100644 index 0000000..93e2ffe --- /dev/null +++ b/Matcher/GotoMatcher.cs @@ -0,0 +1,22 @@ +using System.Text.RegularExpressions; + +namespace Convention.RScript.Matcher +{ + public class GotoMatcher : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + + Regex GotoRegex = new(@"goto\s*\(\s*(.+)\s*,\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)"); + var GotoMatch = GotoRegex.Match(expression); + if (GotoMatch.Success) + { + sentence.mode = RScriptSentence.Mode.Goto; + sentence.content = GotoMatch.Groups[2].Value; + sentence.info = new() { GotoMatch.Groups[1].Value, GotoMatch.Groups[2].Value }; + return true; + } + return false; + } + } +} diff --git a/Matcher/LabelMatcher.cs b/Matcher/LabelMatcher.cs new file mode 100644 index 0000000..2b7d622 --- /dev/null +++ b/Matcher/LabelMatcher.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace Convention.RScript.Matcher +{ + public class LabelMatcher : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + Regex LabelRegex = new(@"label\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)"); + var LabelMatch = LabelRegex.Match(expression); + if (LabelMatch.Success) + { + sentence.mode = RScriptSentence.Mode.Label; + sentence.content = LabelMatch.Groups[1].Value; + return true; + } + return false; + } + } +} diff --git a/Matcher/NamespaceMater.cs b/Matcher/NamespaceMater.cs new file mode 100644 index 0000000..0059bed --- /dev/null +++ b/Matcher/NamespaceMater.cs @@ -0,0 +1,20 @@ +namespace Convention.RScript.Matcher +{ + public class NamespaceMater : IRSentenceMatcher + { + public bool Match(string expression, ref RScriptSentence sentence) + { + if (expression == "{") + { + sentence.mode = RScriptSentence.Mode.EnterNamespace; + return true; + } + else if (expression == "}") + { + sentence.mode = RScriptSentence.Mode.ExitNamespace; + return true; + } + return false; + } + } +} diff --git a/Parser/ExpressionParser.cs b/Parser/ExpressionParser.cs new file mode 100644 index 0000000..937bc95 --- /dev/null +++ b/Parser/ExpressionParser.cs @@ -0,0 +1,76 @@ +using Flee.PublicTypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Convention.RScript.Parser +{ + public static class ExpressionExtension + { + public const double DefaultDoubleAccuracy = 1e-7; + + public static bool IsClose(this double value1, double value2, double maximumAbsoluteError = DefaultDoubleAccuracy) + { + if (double.IsInfinity(value1) || double.IsInfinity(value2)) + { + return Equals(value1, value2); + } + + if (double.IsNaN(value1) || double.IsNaN(value2)) + { + return false; + } + + var delta = value1 - value2; + return !(delta > maximumAbsoluteError || delta < -maximumAbsoluteError); + } + + public static bool IsCloseToZero(this double value, double maximumAbsoluteError = DefaultDoubleAccuracy) + { + return !(double.IsInfinity(value) || double.IsNaN(value) || value > maximumAbsoluteError || value < -maximumAbsoluteError); + } + } + + public class ExpressionParser + { + public readonly ExpressionContext context; + + public ExpressionParser(ExpressionContext context) + { + this.context = context; + } + + private readonly Dictionary CompileGenericExpression = new(); + private readonly Dictionary CompileDynamicExpression = new(); + + public void ClearCache() + { + CompileGenericExpression.Clear(); + CompileDynamicExpression.Clear(); + } + + public T Evaluate(string expression) + { + if (CompileGenericExpression.TryGetValue(expression, out var result)) + { + return (result as IGenericExpression).Evaluate(); + } + var compile = context.CompileGeneric(expression); + CompileGenericExpression[expression] = compile; + return compile.Evaluate(); + } + + public object Evaluate(string expression) + { + if (CompileDynamicExpression.TryGetValue(expression, out var result)) + { + return result.Evaluate(); + } + var compile = context.CompileDynamic(expression); + CompileDynamicExpression[expression] = compile; + return compile.Evaluate(); + } + } +} diff --git a/PublicTypes/RScriptException.cs b/PublicTypes/RScriptException.cs new file mode 100644 index 0000000..35422f0 --- /dev/null +++ b/PublicTypes/RScriptException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Convention.RScript +{ + [Serializable] + public class RScriptException : Exception + { + 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/PublicTypes/RScriptImportClass.cs b/PublicTypes/RScriptImportClass.cs new file mode 100644 index 0000000..1b0dee7 --- /dev/null +++ b/PublicTypes/RScriptImportClass.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Convention.RScript +{ + public class RScriptImportClass : ICollection + { + private readonly HashSet importedTypes = new(); + private readonly Dictionary> cacheImportedFunctions = new(); + + public int Count => ((ICollection)importedTypes).Count; + + public bool IsReadOnly => ((ICollection)importedTypes).IsReadOnly; + + private void DoAdd(Type type) + { + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + if (cacheImportedFunctions.ContainsKey(method.Name) == false) + cacheImportedFunctions.Add(method.Name, new()); + cacheImportedFunctions[method.Name].Add(method); + } + } + + public Type[] GetImports() + { + return importedTypes.ToArray(); + } + + public int CountMethod(string methodName) + { + return cacheImportedFunctions.TryGetValue(methodName, out var list) ? list.Count : 0; + } + + public MethodInfo GetMethod(string methodName) + { + if (cacheImportedFunctions.TryGetValue(methodName, out var list)) + { + if (list.Count != 1) + { + throw new AmbiguousMatchException($"Have more than one {methodName} is imported"); + } + return list[0]; + } + return null; + } + + public MethodInfo GetMethodByReturn(string methodName, Type returnType) + { + if (cacheImportedFunctions.TryGetValue(methodName, out var list)) + { + var query = from item in list where item.ReturnType == returnType select item; + if (query.Count() != 1) + { + throw new AmbiguousMatchException($"Have more than one {methodName} is imported"); + } + return query.First(); + } + return null; + } + + public MethodInfo GetMethod(string methodName, params Type[] parameters) + { + static bool Pr(Type[] parameters1, Type[] parameters2) + { + if (parameters1.Length != parameters2.Length) + return false; + for (int i = 0, e = parameters1.Length; i != e; i++) + { + if (parameters1[i] != parameters2[i]) + return false; + } + return true; + } + + if (cacheImportedFunctions.TryGetValue(methodName, out var list)) + { + var query = from item in list + where Pr((from _param in item.GetParameters() select _param.ParameterType).ToArray(), parameters) + select item; + if (query.Count() != 1) + { + throw new AmbiguousMatchException($"Have more than one {methodName} is imported"); + } + return query.First(); + } + return null; + } + + public bool TryAdd(Type type) + { + var stats = importedTypes.Add(type); + if (stats) + { + DoAdd(type); + } + return stats; + } + + public void Add(Type type) + { + TryAdd(type); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)importedTypes).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)importedTypes).GetEnumerator(); + } + + public void Clear() + { + ((ICollection)importedTypes).Clear(); + } + + public bool Contains(Type item) + { + return ((ICollection)importedTypes).Contains(item); + } + + public void CopyTo(Type[] array, int arrayIndex) + { + ((ICollection)importedTypes).CopyTo(array, arrayIndex); + } + + public bool Remove(Type item) + { + return ((ICollection)importedTypes).Remove(item); + } + } +} diff --git a/PublicTypes/RScriptVariables.cs b/PublicTypes/RScriptVariables.cs new file mode 100644 index 0000000..2502924 --- /dev/null +++ b/PublicTypes/RScriptVariables.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Convention.RScript +{ + public struct RScriptVariableEntry + { + public Type type; + public object data; + } + public class RScriptVariables : IDictionary + { + private readonly Dictionary> variables = new(); + + public RScriptVariableEntry this[string key] + { + get + { + return variables[key].Peek(); + } + set + { + var current = variables[key].Peek(); + if (current.type != value.type) + throw new ArgumentException($"current type is {current.type}, but setter.value is {value.type}"); + variables[key].Pop(); + variables[key].Push(value); + } + } + + public ICollection Keys => variables.Keys; + + public ICollection Values => (from item in variables.Values select item.Peek()).ToArray(); + + public int Count => variables.Count; + + public bool IsReadOnly => false; + + public void Add(string key, RScriptVariableEntry value) + { + if (variables.ContainsKey(key) == false) + variables.Add(key, new()); + variables[key].Push(value); + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void ClearAllLayers() + { + variables.Clear(); + } + + /// + /// + /// + public void Clear() + { + ClearAllLayers(); + } + + public bool Contains(KeyValuePair item) + { + if (variables.TryGetValue(item.Key, out var items)) + { + var current = items.Peek(); + return current.data == item.Value.data; + } + return false; + } + + public bool ContainsKey(string key) + { + return variables.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var (key, items) in variables) + { + array[arrayIndex++] = new(key, items.Peek()); + } + } + + public IEnumerator> GetEnumerator() + { + return (from items in variables select new KeyValuePair(items.Key, items.Value.Peek())).GetEnumerator(); + } + + public bool Remove(string key) + { + if (variables.TryGetValue(key, out var items)) + { + items.Pop(); + if (items.Count == 0) + { + variables.Remove(key); + } + return true; + } + return false; + } + + public bool Remove(KeyValuePair item) + { + if (variables.TryGetValue(item.Key, out var items)) + { + if (item.Value.data == items.Peek().data) + { + items.Pop(); + if (items.Count == 0) + { + variables.Remove(item.Key); + } + return true; + } + } + return false; + } + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out RScriptVariableEntry value) + { + if (variables.TryGetValue(key, out var items)) + { + value = items.Peek(); + return true; + } + value = default; + return false; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void SetValue(string varName, object value) + { + var top = variables[varName].Pop(); + top.data = value; + variables[varName].Push(top); + } + } +} diff --git a/RScriptContext.cs b/RScriptContext.cs new file mode 100644 index 0000000..f122ffa --- /dev/null +++ b/RScriptContext.cs @@ -0,0 +1,368 @@ +using Convention.RScript.Matcher; +using Convention.RScript.Parser; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Convention.RScript +{ + public struct RScriptSentence + { + public enum Mode + { + /// + /// 表达式, 格式: 任意合法表达式 + /// + Expression, + /// + /// 定义变量, 格式: 类型 变量名 + /// 类型支持: string, int, double, float, bool, var + /// 每层命名空间中不可重复定义变量, 不可使用未定义的变量, 不存在时会自动向上查找上级空间的变量 + /// + DefineVariable, + /// + /// 进入新的命名空间, 格式: { + /// 命名空间是一对花括号包裹内容空间, 格式: {...} + /// + EnterNamespace, + /// + /// 退出当前命名空间, 格式: } + /// 命名空间是一对花括号包裹内容空间, 格式: {...} + /// + ExitNamespace, + /// + /// 标签, 格式: label(labelname) + /// + Label, + /// + /// 跳转到指定标签, 格式: goto(boolean,labelname) + /// 判断为真时跳转到labelname + /// + Goto, + /// + /// 跳转到当前命名空间的结束位置, 格式: break(boolean); + /// + Breakpoint, + /// + /// 跳转到上次跳转的位置的后一个位置, 格式: back(boolean); + /// + Backpoint, + } + + public string content; + public List info; + public Mode mode; + } + + public interface IRSentenceMatcher + { + bool Match(string expression, ref RScriptSentence sentence); + } + + public partial class RScriptContext + { + public readonly RScriptImportClass Import; + public readonly RScriptVariables Variables; + private readonly RScriptSentence[] Sentences; + private readonly Dictionary Labels; + private readonly Dictionary Namespace; + + public List SentenceParser = new() + { + new NamespaceMater(), + new DefineVariableMatcher(), + new LabelMatcher(), + new GotoMatcher(), + new BreakMatcher(), + new BackMatcher(), + }; + + private RScriptSentence ParseToSentence(string expression) + { + RScriptSentence result = new() + { + content = expression, + mode = RScriptSentence.Mode.Expression + }; + expression = expression.Trim(); + expression.TrimEnd(';'); + SentenceParser.Any(matcher => matcher.Match(expression, ref result)); + return result; + } + + private void BuildUpLabelsAndNamespace(ref Dictionary labelIndicator, ref Dictionary namespaceIndicator) + { + Stack namespaceLayers = new(); + for (int i = 0, e = Sentences.Length; i != e; i++) + { + if (Sentences[i].mode == RScriptSentence.Mode.Label) + { + labelIndicator[Sentences[i].content] = i; + } + else if (Sentences[i].mode == RScriptSentence.Mode.EnterNamespace) + { + namespaceLayers.Push(i); + } + else if (Sentences[i].mode == RScriptSentence.Mode.ExitNamespace) + { + if (namespaceLayers.Count == 0) + throw new RScriptException("Namespace exit without enter.", i); + var enterPointer = namespaceLayers.Pop(); + namespaceIndicator[enterPointer] = i; + } + } + if (namespaceLayers.Count > 0) + { + throw new RScriptException("Namespace enter without exit.", namespaceLayers.Peek()); + } + } + + public RScriptContext(string[] expressions, RScriptImportClass import = null, RScriptVariables variables = null) + { + this.Import = import ?? new(); + this.Variables = variables ?? new(); + this.Sentences = (from item in expressions select ParseToSentence(item)).ToArray(); + this.Labels = new(); + this.Namespace = new(); + BuildUpLabelsAndNamespace(ref this.Labels, ref this.Namespace); + } + + + public RScriptSentence CurrentSentence => Sentences[CurrentRuntimePointer]; + + private void DoDefineVariable(ExpressionParser parser, RScriptSentence sentence) + { + // 定义变量 + var varTypeName = sentence.info[0]; + var varName = sentence.info[1]; + Type varType; + object varDefaultValue; + { + if (varTypeName == "string") + { + varType = typeof(string); + varDefaultValue = string.Empty; + } + else if (varTypeName == "int") + { + varType = typeof(int); + varDefaultValue = 0; + } + else if (varTypeName == "double") + { + varType = typeof(double); + varDefaultValue = 0.0; + } + else if (varTypeName == "float") + { + varType = typeof(float); + varDefaultValue = 0.0f; + } + else if (varTypeName == "bool") + { + varType = typeof(bool); + varDefaultValue = false; + } + else if (varTypeName == "var") + { + varType = typeof(object); + varDefaultValue = new object(); + } + else + { + throw new RScriptException($"Unsupported variable type '{varTypeName}'.", CurrentRuntimePointer); + } + } + if (CurrentLocalSpaceVariableNames.Peek().Contains(varName) == false) + { + Variables.Add(varName, new() { type = varType, data = varDefaultValue }); + parser.context.Variables[varName] = varDefaultValue; + CurrentLocalSpaceVariableNames.Peek().Add(varName); + } + else + { + throw new RScriptException($"Variable '{varName}' already defined on this namespace.", CurrentRuntimePointer); + } + } + + private void DoEnterNamespace(ExpressionParser parser) + { + // 准备记录当前命名空间中定义的变量, 清空上层命名空间的变量 + CurrentLocalSpaceVariableNames.Push(new()); + // 更新变量值 + foreach (var (varName, varValue) in parser.context.Variables) + { + Variables.SetValue(varName, varValue); + } + // 压栈 + RuntimePointerStack.Push(CurrentRuntimePointer); + } + + private void DoExitNamespace(ExpressionParser parser) + { + // 移除当前命名空间的变量 + foreach (var local in CurrentLocalSpaceVariableNames.Peek()) + { + Variables.Remove(local); + parser.context.Variables.Remove(local); + } + // 还原上层命名空间的变量 + foreach (var local in CurrentLocalSpaceVariableNames.Peek()) + { + parser.context.Variables[local] = Variables[local].data; + } + CurrentLocalSpaceVariableNames.Pop(); + // 弹栈 + RuntimePointerStack.Pop(); + } + + private void DoJumpRuntimePointer(ExpressionParser parser, int target) + { + bool isForwardMove = target > CurrentRuntimePointer; + int step = isForwardMove ? 1 : -1; + for (; CurrentRuntimePointer != target; CurrentRuntimePointer += step) + { + if (CurrentSentence.mode == RScriptSentence.Mode.ExitNamespace) + { + if (isForwardMove) + DoExitNamespace(parser); + else + DoEnterNamespace(parser); + } + else if (CurrentSentence.mode == RScriptSentence.Mode.EnterNamespace) + { + if (isForwardMove) + DoEnterNamespace(parser); + else + DoExitNamespace(parser); + } + } + } + + private void DoGoto(ExpressionParser parser, RScriptSentence sentence) + { + // 检查并跳转到指定标签 + if (parser.Evaluate(sentence.info[0])) + { + if (Labels.TryGetValue(sentence.content, out var labelPointer)) + { + GotoPointerStack.Push(CurrentRuntimePointer); + DoJumpRuntimePointer(parser, labelPointer); + } + else + { + throw new RScriptException($"Label '{sentence.content}' not found.", CurrentRuntimePointer); + } + } + } + + private void DoBreakpoint(ExpressionParser parser, RScriptSentence sentence) + { + // 检查并跳转到当前命名空间的结束位置 + if (parser.Evaluate(sentence.content)) + { + if (RuntimePointerStack.Count == 0) + { + CurrentRuntimePointer = Sentences.Length; + } + else if (Namespace.TryGetValue(RuntimePointerStack.Peek(), out var exitPointer)) + { + CurrentRuntimePointer = exitPointer; + DoExitNamespace(parser); + } + else + { + throw new NotImplementedException($"No namespace to break."); + } + } + } + + private void DoBackpoint(ExpressionParser parser, RScriptSentence sentence) + { + // 检查并跳转到上次跳转的位置 + if (parser.Evaluate(sentence.content)) + { + if (GotoPointerStack.Count == 0) + { + throw new NotImplementedException($"No position to back."); + } + else + { + DoJumpRuntimePointer(parser, GotoPointerStack.Pop()); + } + } + } + + private void RunNextStep(ExpressionParser parser) + { + var sentence = CurrentSentence; + switch (sentence.mode) + { + case RScriptSentence.Mode.Expression: + { + // 执行表达式 + parser.Evaluate(sentence.content); + } + break; + case RScriptSentence.Mode.DefineVariable: + { + DoDefineVariable(parser, sentence); + } + break; + case RScriptSentence.Mode.EnterNamespace: + { + DoEnterNamespace(parser); + } + break; + case RScriptSentence.Mode.ExitNamespace: + { + DoExitNamespace(parser); + } + break; + case RScriptSentence.Mode.Goto: + { + DoGoto(parser, sentence); + } + break; + case RScriptSentence.Mode.Breakpoint: + { + DoBreakpoint(parser,sentence); + } + break; + case RScriptSentence.Mode.Backpoint: + { + DoBackpoint(parser, sentence); + } + break; + default: + // Do nothing + break; + } + } + + private readonly Stack RuntimePointerStack = new(); + private readonly Stack GotoPointerStack = new(); + private int CurrentRuntimePointer = 0; + private readonly Stack> CurrentLocalSpaceVariableNames = new(); + + public Dictionary Run(ExpressionParser parser) + { + CurrentLocalSpaceVariableNames.Clear(); + RuntimePointerStack.Clear(); + GotoPointerStack.Clear(); + CurrentLocalSpaceVariableNames.Clear(); + CurrentLocalSpaceVariableNames.Push(new()); + for (CurrentRuntimePointer = 0; CurrentRuntimePointer < Sentences.Length; CurrentRuntimePointer++) + { + RunNextStep(parser); + } + // 更新上下文变量 + foreach (var (varName, varValue) in parser.context.Variables) + { + if (Variables.ContainsKey(varName)) + Variables.SetValue(varName, varValue); + } + return Variables.ToDictionary(); + } + } +} diff --git a/RScriptEngine.cs b/RScriptEngine.cs new file mode 100644 index 0000000..7ed71ee --- /dev/null +++ b/RScriptEngine.cs @@ -0,0 +1,111 @@ +using Convention.RScript.Parser; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Convention.RScript +{ + public class RScriptEngine + { + 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()); + context = new(SplitScript(script).ToArray(), import, variables); + foreach (var type in context.Import) + parser.context.Imports.AddType(type); + return context.Run(parser); + } + } +}