From 13ef994d8b7f8f7a5db2730fc8214a7e5fe156ff Mon Sep 17 00:00:00 2001
From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com>
Date: Thu, 5 Mar 2026 22:39:00 +0100
Subject: [PATCH] Performance improvements and code cleanup
---
.../Attributes/StatementAttribute.cs | 22 +-
YesNt.Interpreter/Runtime/Line.cs | 4 +-
.../Runtime/RuntimeInformation.cs | 6 +-
YesNt.Interpreter/Runtime/StatementHandler.cs | 9 +
.../Runtime/StatementRuntimeInfo.cs | 8 +
YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 210 +++++++++++++-----
.../Statements/CodeFlowStatements.cs | 171 ++------------
.../Statements/FunctionStatements.cs | 48 +---
.../PredefinedVariableStatements.cs | 30 +--
.../Statements/ProcessingStatements.cs | 16 +-
.../Statements/VariableStatements.cs | 75 +------
YesNt.Interpreter/Utilities/Evaluator.cs | 43 ++--
YesNt.Interpreter/Utilities/FixedProcess.cs | 2 +-
.../Utilities/StringExtensions.cs | 81 ++++---
.../Utilities/TemplateProcessor.cs | 167 ++++++++++++++
15 files changed, 488 insertions(+), 404 deletions(-)
create mode 100644 YesNt.Interpreter/Runtime/StatementHandler.cs
create mode 100644 YesNt.Interpreter/Utilities/TemplateProcessor.cs
diff --git a/YesNt.Interpreter/Attributes/StatementAttribute.cs b/YesNt.Interpreter/Attributes/StatementAttribute.cs
index 9aded1f..154936e 100644
--- a/YesNt.Interpreter/Attributes/StatementAttribute.cs
+++ b/YesNt.Interpreter/Attributes/StatementAttribute.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using YesNt.Interpreter.Enums;
@@ -60,6 +60,24 @@ public class StatementAttribute : Attribute
///
public string Separator { get; set; }
+ ///
+ /// Gets or sets the name of the statement that marks the end of this block.
+ /// Used for block boundary caching (e.g., "while" has BlockPair = "end_while").
+ ///
+ public string BlockPair { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this statement is the end of a block.
+ /// Used for block boundary caching (e.g., "end_while" has IsBlockEnd = true).
+ ///
+ public bool IsBlockEnd { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this statement is an intermediate part of a block
+ /// (e.g., "else:" between "if" and "end_if").
+ ///
+ public bool IsBlockIntermediate { get; set; }
+
///
/// Initializes a new with a syntax-highlight color.
///
@@ -89,4 +107,4 @@ public class StatementAttribute : Attribute
SpaceAround = spaceAround;
IgnoreSyntaxHighlighting = true;
}
-}
\ No newline at end of file
+}
diff --git a/YesNt.Interpreter/Runtime/Line.cs b/YesNt.Interpreter/Runtime/Line.cs
index fcb53af..03f7337 100644
--- a/YesNt.Interpreter/Runtime/Line.cs
+++ b/YesNt.Interpreter/Runtime/Line.cs
@@ -1,4 +1,6 @@
-namespace YesNt.Interpreter.Runtime;
+using System.Collections.Generic;
+
+namespace YesNt.Interpreter.Runtime;
///
/// Represents a single source line together with its location metadata.
diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs
index 306f40a..742674b 100644
--- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs
+++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs
@@ -26,6 +26,8 @@ internal sealed class RuntimeInformation
public Dictionary GlobalVariables { get; set; } = [];
public Dictionary Functions { get; } = [];
+ public Dictionary BlockBoundaries { get; } = [];
+ internal Action PreScanLinesAction { get; set; }
public Stack FunctionCallStack { get; } = new();
public Stack InParametersStack { get; } = new();
public Stack OutParametersStack { get; set; } = new();
@@ -173,6 +175,7 @@ internal sealed class RuntimeInformation
GlobalVariables.Clear();
Labels.Clear();
Functions.Clear();
+ BlockBoundaries.Clear();
FunctionCallStack.Clear();
InParametersStack.Clear();
OutParametersStack.Clear();
@@ -187,8 +190,7 @@ internal sealed class RuntimeInformation
IsInFunction = false;
IsLocalSearch = false;
LineNumber = 0;
- TaskId = internalTaskId + 1;
- internalTaskId++;
+ TaskId = ++internalTaskId;
}
private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks)
diff --git a/YesNt.Interpreter/Runtime/StatementHandler.cs b/YesNt.Interpreter/Runtime/StatementHandler.cs
new file mode 100644
index 0000000..710a8e1
--- /dev/null
+++ b/YesNt.Interpreter/Runtime/StatementHandler.cs
@@ -0,0 +1,9 @@
+using System;
+using YesNt.Interpreter.Attributes;
+
+namespace YesNt.Interpreter.Runtime;
+
+///
+/// Pre-calculated statement handler information for faster matching.
+///
+internal record StatementHandler(StatementAttribute Attribute, Action Handler, string FullName);
diff --git a/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs b/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs
index a8b89e7..706456c 100644
--- a/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs
+++ b/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs
@@ -13,4 +13,12 @@ internal abstract class StatementRuntimeInformation
/// Injected by the generated registry before any handler is invoked.
///
public RuntimeInformation RuntimeInfo { get; set; }
+
+ ///
+ /// Trims surrounding whitespace and a trailing colon from a block or function name.
+ ///
+ protected static string NormalizeBlockName(string value)
+ {
+ return value.Trim().TrimEnd(':').Trim();
+ }
}
\ No newline at end of file
diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
index e918c78..c76f0ae 100644
--- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
+++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
@@ -13,20 +13,6 @@ namespace YesNt.Interpreter.Runtime;
///
/// The main entry point for executing YesNt scripts.
///
-///
-/// Running a script file:
-///
-/// var interpreter = new YesNtInterpreter();
-/// interpreter.Execute("path/to/script.ynt");
-///
-/// Running script lines in memory with a custom statement:
-///
-/// var interpreter = new YesNtInterpreter();
-/// interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End, args =>
-/// Console.WriteLine($"[LOG] {args}"));
-/// interpreter.Execute(new List<string> { "log hello world" });
-///
-///
public class YesNtInterpreter
{
///
@@ -43,6 +29,8 @@ public class YesNtInterpreter
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
private Dictionary> statements;
+ private List statementHandlers;
+ private List> lineMatchingHandlers = [];
private readonly List> staticStatements;
private readonly Dictionary>>> disabledStatements = [];
@@ -77,11 +65,28 @@ public class YesNtInterpreter
public YesNtInterpreter()
{
GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements);
+ UpdateStatementHandlers();
+ runtimeInfo.PreScanLinesAction = PreScanLines;
runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s);
runtimeInfo.OnLineExecuted += e => OnLineExecuted?.Invoke(e);
}
+ private void UpdateStatementHandlers()
+ {
+ statementHandlers = statements.Select(s =>
+ {
+ string name = s.Key.SpaceAround switch
+ {
+ SpaceAround.StartEnd => $" {s.Key.Name.Trim()} ",
+ SpaceAround.Start => $" {s.Key.Name.Trim()}",
+ SpaceAround.End => $"{s.Key.Name.Trim()} ",
+ _ => s.Key.Name.Trim()
+ };
+ return new StatementHandler(s.Key, s.Value, name);
+ }).ToList();
+ }
+
///
/// Registers a custom statement using a pre-built .
/// If a statement with the same attribute key (identical field values) already exists it will be replaced;
@@ -102,14 +107,16 @@ public class YesNtInterpreter
.OrderBy(s => s.Key.Priority)
.ThenByDescending(s => s.Key.Name.Length)
.ToDictionary(x => x.Key, x => x.Value);
+ UpdateStatementHandlers();
+ PreScanLines();
}
///
- /// Registers a custom statement without a syntax-highlight color.
+ /// Registers a simple custom statement with default settings.
///
- /// The keyword that identifies this statement in source code.
- /// Where in the line the keyword is matched.
- /// Which sides of the keyword require a surrounding space.
+ /// The keyword to match.
+ /// Where in the line the keyword is searched for.
+ /// Which sides of the keyword must be padded with a space.
/// The delegate invoked when the statement matches.
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action handler)
{
@@ -117,12 +124,12 @@ public class YesNtInterpreter
}
///
- /// Registers a custom statement with a syntax-highlight color.
+ /// Registers a simple custom statement with a specific syntax-highlight color.
///
- /// The keyword that identifies this statement in source code.
- /// Where in the line the keyword is matched.
- /// Which sides of the keyword require a surrounding space.
- /// The color used for syntax highlighting in the code editor.
+ /// The keyword to match.
+ /// Where in the line the keyword is searched for.
+ /// Which sides of the keyword must be padded with a space.
+ /// The color used for syntax highlighting.
/// The delegate invoked when the statement matches.
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action handler)
{
@@ -130,10 +137,9 @@ public class YesNtInterpreter
}
///
- /// Permanently removes all built-in or custom statements that match .
- /// After removal, any script line that would have matched triggers an "Invalid statement" error.
+ /// Unregisters all handlers matching the specified keyword .
///
- /// The keyword of the statement(s) to remove.
+ /// The keyword to remove.
public void RemoveStatement(string name)
{
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
@@ -142,6 +148,8 @@ public class YesNtInterpreter
}
_ = disabledStatements.Remove(name);
+ UpdateStatementHandlers();
+ PreScanLines();
}
///
@@ -171,6 +179,8 @@ public class YesNtInterpreter
{
statements[kv.Key] = _ => { };
}
+ UpdateStatementHandlers();
+ PreScanLines();
}
///
@@ -192,6 +202,8 @@ public class YesNtInterpreter
}
_ = disabledStatements.Remove(name);
+ UpdateStatementHandlers();
+ PreScanLines();
}
///
@@ -236,9 +248,11 @@ public class YesNtInterpreter
for (int i = 0; i < lines.Count; i++)
{
- runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName("#Memory#"), i));
+ string content = lines[i].Trim().Replace("\r", string.Empty);
+ runtimeInfo.Lines.Add(new Line(content, Path.GetFileName("#Memory#"), i));
}
+ PreScanLines();
Execute();
}
@@ -255,6 +269,7 @@ public class YesNtInterpreter
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
return;
}
+ PreScanLines();
Execute();
}
@@ -267,20 +282,25 @@ public class YesNtInterpreter
break;
}
- runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].Content.Trim(' ').Replace("\r", string.Empty);
+ Line lineObj = runtimeInfo.Lines[runtimeInfo.LineNumber];
+ runtimeInfo.CurrentLine = lineObj.Content;
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
{
continue;
}
- DebugEventArgs debugEventArgs = new DebugEventArgs()
+ DebugEventArgs debugEventArgs = null;
+ if (runtimeInfo.IsDebugMode)
{
- LineNumber = runtimeInfo.LineNumber + 1,
- OriginalLine = runtimeInfo.CurrentLine.FromSafeString(),
- IsTask = runtimeInfo.IsTask,
- TaskId = runtimeInfo.TaskId
- };
+ debugEventArgs = new DebugEventArgs()
+ {
+ LineNumber = runtimeInfo.LineNumber + 1,
+ OriginalLine = runtimeInfo.CurrentLine.FromSafeString(),
+ IsTask = runtimeInfo.IsTask,
+ TaskId = runtimeInfo.TaskId
+ };
+ }
foreach (KeyValuePair staticStatement in staticStatements)
{
@@ -296,9 +316,11 @@ public class YesNtInterpreter
bool statementFound = false;
bool notSearchingLabel = !runtimeInfo.IsSearching;
- foreach (KeyValuePair> statement in statements)
+ List handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count) ? lineMatchingHandlers[runtimeInfo.LineNumber] : [];
+
+ foreach (StatementHandler handler in handlers)
{
- StatementAttribute statementAttribute = statement.Key;
+ StatementAttribute statementAttribute = handler.Attribute;
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
{
@@ -311,37 +333,31 @@ public class YesNtInterpreter
break;
}
- string name = statementAttribute.SpaceAround switch
- {
- SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ",
- SpaceAround.Start => $" {statementAttribute.Name.Trim()}",
- SpaceAround.End => $"{statementAttribute.Name.Trim()} ",
- _ => statementAttribute.Name.Trim()
- };
+ string name = handler.FullName;
- if (statementAttribute.Separator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Separator))
+ if (statementAttribute.Separator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Separator, StringComparison.Ordinal))
{
- if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name))
+ if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name, StringComparison.Ordinal))
{
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[name.Length..];
- statement.Value.Invoke(copyLine);
+ handler.Handler.Invoke(copyLine);
statementFound = true;
}
- else if (statementAttribute.SearchMode == SearchMode.Contains && runtimeInfo.CurrentLine.Contains(name))
+ else if (statementAttribute.SearchMode == SearchMode.Contains && runtimeInfo.CurrentLine.Contains(name, StringComparison.Ordinal))
{
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty);
- statement.Value.Invoke(copyLine);
+ handler.Handler.Invoke(copyLine);
statementFound = true;
}
- else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name))
+ else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name, StringComparison.Ordinal))
{
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[..^name.Length];
- statement.Value.Invoke(copyLine);
+ handler.Handler.Invoke(copyLine);
statementFound = true;
}
- else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name))
+ else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name, StringComparison.Ordinal))
{
- statement.Value.Invoke(runtimeInfo.CurrentLine);
+ handler.Handler.Invoke(runtimeInfo.CurrentLine);
statementFound = true;
}
}
@@ -351,7 +367,7 @@ public class YesNtInterpreter
{
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
}
- if (runtimeInfo.IsDebugMode && notSearchingLabel)
+ if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
{
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
runtimeInfo.LineExecuted(debugEventArgs);
@@ -402,9 +418,93 @@ public class YesNtInterpreter
for (int i = 0; i < lines.Length; i++)
{
- runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName(path), i));
+ string content = lines[i].Trim().Replace("\r", string.Empty);
+ runtimeInfo.Lines.Add(new Line(content, Path.GetFileName(path), i));
}
+ PreScanLines();
return true;
}
+
+ internal void PreScanLines()
+ {
+ runtimeInfo.BlockBoundaries.Clear();
+ lineMatchingHandlers = new List>(runtimeInfo.Lines.Count);
+
+ // Dictionary to track open blocks by their expected end statement name
+ var openBlocks = new Dictionary>();
+
+ for (int i = 0; i < runtimeInfo.Lines.Count; i++)
+ {
+ string content = runtimeInfo.Lines[i].Content;
+ List matchingHandlers = [];
+
+ foreach (StatementHandler handler in statementHandlers)
+ {
+ if (IsPossibleMatch(content, handler))
+ {
+ matchingHandlers.Add(handler);
+
+ // Track block starts (skip intermediates — they are handled separately below)
+ string blockPair = handler.Attribute.BlockPair;
+ if (!string.IsNullOrEmpty(blockPair) && !handler.Attribute.IsBlockIntermediate)
+ {
+ if (!openBlocks.TryGetValue(blockPair, out var stack))
+ {
+ stack = new Stack();
+ openBlocks[blockPair] = stack;
+ }
+ stack.Push(i);
+ }
+
+ // Track block ends
+ if (handler.Attribute.IsBlockEnd)
+ {
+ if (openBlocks.TryGetValue(handler.Attribute.Name, out var stack) && stack.Count > 0)
+ {
+ int startLine = stack.Pop();
+ runtimeInfo.BlockBoundaries[startLine] = i;
+ runtimeInfo.BlockBoundaries[i] = startLine;
+ }
+ }
+
+ // Track block intermediates (e.g., else:): pop the opener, record boundary, push self
+ if (handler.Attribute.IsBlockIntermediate)
+ {
+ string intermediatePair = handler.Attribute.BlockPair;
+ if (!string.IsNullOrEmpty(intermediatePair))
+ {
+ if (!openBlocks.TryGetValue(intermediatePair, out var stack))
+ {
+ stack = new Stack();
+ openBlocks[intermediatePair] = stack;
+ }
+ if (stack.Count > 0)
+ {
+ int startLine = stack.Pop();
+ runtimeInfo.BlockBoundaries[startLine] = i;
+ }
+ stack.Push(i);
+ }
+ }
+ }
+ }
+ lineMatchingHandlers.Add(matchingHandlers);
+ }
+ }
+
+ private static bool IsPossibleMatch(string content, StatementHandler handler)
+ {
+ StatementAttribute attr = handler.Attribute;
+ string fullName = handler.FullName;
+
+ return attr.SearchMode switch
+ {
+ SearchMode.Exact => content == fullName,
+ SearchMode.StartOfLine => content.StartsWith(fullName, StringComparison.Ordinal),
+ SearchMode.EndOfLine => content.EndsWith(fullName, StringComparison.Ordinal),
+ SearchMode.Contains => content.Contains(fullName, StringComparison.Ordinal),
+ _ => false
+ };
+ }
}
diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs
index ab77405..b3623c1 100644
--- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs
+++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using YesNt.Interpreter.Attributes;
@@ -80,14 +80,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
return;
}
- if (RuntimeInfo.Labels.ContainsKey(key))
- {
- RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber;
- }
- else
- {
- RuntimeInfo.Labels.Add(key, RuntimeInfo.LineNumber);
- }
+ RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber;
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key)
{
@@ -153,7 +146,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
}
}
- [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")]
+ [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":", BlockPair = "end_if")]
public void IfBlock(string args)
{
args = args.Trim();
@@ -177,7 +170,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
return;
}
- (int targetLine, _) = FindElseOrEndIf(RuntimeInfo.LineNumber);
+ int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
if (targetLine < 0)
{
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
@@ -187,10 +180,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation
RuntimeInfo.LineNumber = targetLine;
}
- [Statement("else:", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)]
+ [Statement("else:", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockIntermediate = true, BlockPair = "end_if")]
public void Else(string _)
{
- int targetLine = FindEndIf(RuntimeInfo.LineNumber);
+ int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
if (targetLine < 0)
{
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
@@ -200,12 +193,12 @@ internal class CodeFlowStatements : StatementRuntimeInformation
RuntimeInfo.LineNumber = targetLine;
}
- [Statement("end_if", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)]
+ [Statement("end_if", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockEnd = true)]
public void EndIf(string _)
{
}
- [Statement("while", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")]
+ [Statement("while", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":", BlockPair = "end_while")]
public void While(string args)
{
args = args.Trim();
@@ -229,7 +222,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
return;
}
- int endWhileLine = FindEndWhile(RuntimeInfo.LineNumber);
+ int endWhileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
if (endWhileLine < 0)
{
RuntimeInfo.Exit(ExitMessages.NoMatchingEndWhile, true);
@@ -239,10 +232,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation
RuntimeInfo.LineNumber = endWhileLine;
}
- [Statement("end_while", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)]
+ [Statement("end_while", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockEnd = true)]
public void EndWhile(string _)
{
- int whileLine = FindWhile(RuntimeInfo.LineNumber);
+ int whileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
if (whileLine < 0)
{
RuntimeInfo.Exit(ExitMessages.NoMatchingWhile, true);
@@ -265,11 +258,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
return;
}
- else
- {
- RuntimeInfo.IsInFunction = false;
- }
+ RuntimeInfo.IsInFunction = false;
RuntimeInfo.Exit(ExitMessages.PlannedTermination, false);
}
@@ -286,11 +276,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
return;
}
- else
- {
- RuntimeInfo.IsInFunction = false;
- }
+ RuntimeInfo.IsInFunction = false;
RuntimeInfo.Exit(ExitMessages.PlannedTerminationCancelingTasks, true);
}
@@ -306,136 +293,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
RuntimeInfo.Exit(message, false);
}
- private static string NormalizeBlockName(string value)
+ private int FindBlockBoundary(int currentLine)
{
- return value.Trim().TrimEnd(':').Trim();
- }
-
- private (int TargetLine, bool IsElse) FindElseOrEndIf(int currentLine)
- {
- int depth = 0;
-
- for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++)
- {
- string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty);
-
- if (IsIfStart(line))
- {
- depth++;
- continue;
- }
-
- if (line == "end_if")
- {
- if (depth == 0)
- {
- return (i, false);
- }
-
- depth--;
- continue;
- }
-
- if (line == "else:" && depth == 0)
- {
- return (i, true);
- }
- }
-
- return (-1, false);
- }
-
- private int FindEndIf(int currentLine)
- {
- int depth = 0;
-
- for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++)
- {
- string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty);
-
- if (IsIfStart(line))
- {
- depth++;
- continue;
- }
-
- if (line == "end_if")
- {
- if (depth == 0)
- {
- return i;
- }
-
- depth--;
- }
- }
-
- return -1;
- }
-
- private static bool IsIfStart(string line)
- {
- return line.StartsWith("if ", StringComparison.Ordinal) && line.EndsWith(':');
- }
-
- private int FindEndWhile(int currentLine)
- {
- int depth = 0;
-
- for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++)
- {
- string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty);
-
- if (IsWhileStart(line))
- {
- depth++;
- continue;
- }
-
- if (line == "end_while")
- {
- if (depth == 0)
- {
- return i;
- }
-
- depth--;
- }
- }
-
- return -1;
- }
-
- private int FindWhile(int currentLine)
- {
- int depth = 0;
-
- for (int i = currentLine - 1; i >= 0; i--)
- {
- string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty);
-
- if (line == "end_while")
- {
- depth++;
- continue;
- }
-
- if (IsWhileStart(line))
- {
- if (depth == 0)
- {
- return i;
- }
-
- depth--;
- }
- }
-
- return -1;
- }
-
- private static bool IsWhileStart(string line)
- {
- return line.StartsWith("while ", StringComparison.Ordinal) && line.EndsWith(':');
+ return RuntimeInfo.BlockBoundaries.TryGetValue(currentLine, out int cached) ? cached : -1;
}
}
diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs
index 3c60faf..fb83927 100644
--- a/YesNt.Interpreter/Statements/FunctionStatements.cs
+++ b/YesNt.Interpreter/Statements/FunctionStatements.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using YesNt.Interpreter.Attributes;
@@ -33,14 +33,7 @@ internal class FunctionStatements : StatementRuntimeInformation
return;
}
- if (RuntimeInfo.Functions.ContainsKey(key))
- {
- RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber;
- }
- else
- {
- RuntimeInfo.Functions.Add(key, RuntimeInfo.LineNumber);
- }
+ RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber;
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchFunction) && RuntimeInfo.SearchFunction == key)
{
@@ -59,18 +52,7 @@ internal class FunctionStatements : StatementRuntimeInformation
[Statement("%out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetOutParameter(string args)
{
- while (args.Contains("%out"))
- {
- if (RuntimeInfo.OutParametersStack.Count == 0)
- {
- RuntimeInfo.Exit(ExitMessages.NoOutArgumentInStack, true);
- return;
- }
-
- args = args.ReplaceFirstOccurrence("%out", RuntimeInfo.OutParametersStack.Pop());
- }
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%out", RuntimeInfo.OutParametersStack, RuntimeInfo, ExitMessages.NoOutArgumentInStack);
}
[Statement("%has_out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
@@ -122,18 +104,7 @@ internal class FunctionStatements : StatementRuntimeInformation
return;
}
- while (args.Contains("%in"))
- {
- if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0)
- {
- RuntimeInfo.Exit(ExitMessages.NoInArgumentInStack, true);
- return;
- }
-
- args = args.ReplaceFirstOccurrence("%in", RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop());
- }
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%in", RuntimeInfo.FunctionCallStack.Peek().Arguments, RuntimeInfo, ExitMessages.NoInArgumentInStack);
}
[Statement("%has_in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
@@ -181,10 +152,8 @@ internal class FunctionStatements : StatementRuntimeInformation
}
return;
}
- else
- {
- RuntimeInfo.IsInFunction = false;
- }
+
+ RuntimeInfo.IsInFunction = false;
if (RuntimeInfo.FunctionCallStack.Count > 0)
{
@@ -204,9 +173,4 @@ internal class FunctionStatements : StatementRuntimeInformation
{
RuntimeInfo.FunctionCallStack.Clear();
}
-
- private static string NormalizeBlockName(string value)
- {
- return value.Trim().TrimEnd(':').Trim();
- }
}
diff --git a/YesNt.Interpreter/Statements/PredefinedVariableStatements.cs b/YesNt.Interpreter/Statements/PredefinedVariableStatements.cs
index 4036f94..c1c1c11 100644
--- a/YesNt.Interpreter/Statements/PredefinedVariableStatements.cs
+++ b/YesNt.Interpreter/Statements/PredefinedVariableStatements.cs
@@ -1,4 +1,5 @@
-using System;
+using System;
+using System.Runtime.InteropServices;
using YesNt.Interpreter.Attributes;
using YesNt.Interpreter.Enums;
@@ -14,51 +15,36 @@ internal class PredefinedVariableStatements : StatementRuntimeInformation
[Statement("%time", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetUnixTimestamp(string args)
{
- args = args.Replace("%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()).TrimEnd();
}
[Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetOperatingSystem(string args)
{
- args = args.Replace("%os", Environment.OSVersion.Platform.ToString());
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%os", Environment.OSVersion.Platform.ToString()).TrimEnd();
}
[Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetProcessorArchitecture(string args)
{
- args = args.Replace("%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString());
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()).TrimEnd();
}
[Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetIsOperatingSystem64Bit(string args)
{
- args = args.Replace("%is64", $"{Environment.Is64BitOperatingSystem}");
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%is64", Environment.Is64BitOperatingSystem.ToString()).TrimEnd();
}
[Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetPi(string args)
{
- args = args.Replace("%pi", Math.PI.ToString());
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%pi", Math.PI.ToString()).TrimEnd();
}
[Statement("%rand", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
public void GetRandom(string args)
{
- while (args.Contains("%rand"))
- {
- args = args.ReplaceFirstOccurrence("%rand", random.Next(32767, int.MaxValue).ToString());
- }
-
- RuntimeInfo.CurrentLine = args.TrimEnd();
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessDynamicPlaceholders(args, "%rand", () => random.Next(32767, int.MaxValue).ToString()).TrimEnd();
}
}
diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs
index 010389d..d1c4983 100644
--- a/YesNt.Interpreter/Statements/ProcessingStatements.cs
+++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs
@@ -16,20 +16,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation
[Statement("calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)]
public void Calculate(string args)
{
- MatchCollection matches = CalculationRegex().Matches(args.FromSafeString());
-
- for (int i = 0; i < matches.Count; i++)
- {
- string res = Evaluator.Calculate(matches[i].Value);
- if (res is null)
- {
- RuntimeInfo.Exit(ExitMessages.InvalidOperation, true);
- return;
- }
- args = args.FromSafeString().Replace(matches[i].Value, res);
- }
-
- RuntimeInfo.CurrentLine = args;
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessCalculations(args.FromSafeString(), RuntimeInfo, CalculationRegex());
}
[Statement("eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)]
@@ -100,6 +87,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation
{
RuntimeInfo.Lines.Insert(RuntimeInfo.LineNumber + i, new Line(lines[i], Path.GetFileName(path), i));
}
+ RuntimeInfo.PreScanLinesAction?.Invoke();
RuntimeInfo.LineNumber--;
}
catch
diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs
index f07f640..a690b27 100644
--- a/YesNt.Interpreter/Statements/VariableStatements.cs
+++ b/YesNt.Interpreter/Statements/VariableStatements.cs
@@ -1,8 +1,10 @@
-using System.Text.RegularExpressions;
+using System;
+using System.Collections.Generic;
using YesNt.Interpreter.Attributes;
using YesNt.Interpreter.Enums;
using YesNt.Interpreter.Runtime;
+using YesNt.Interpreter.Utilities;
namespace YesNt.Interpreter.Statements;
@@ -11,32 +13,16 @@ internal partial class VariableStatements : StatementRuntimeInformation
[Statement("var", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
public void DefineVariable(string args)
{
- string[] parts = args.Split('=');
- if (parts.Length == 2)
- {
- string key = parts[0].Trim();
- if (key.Contains(' '))
- {
- RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
- }
-
- if (RuntimeInfo.Variables.ContainsKey(key))
- {
- RuntimeInfo.Variables[key] = parts[1].Trim();
- }
- else
- {
- RuntimeInfo.Variables.Add(key, parts[1].Trim());
- }
- }
- else
- {
- RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
- }
+ DefineVariableIn(RuntimeInfo.Variables, args);
}
[Statement("global", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
public void DefineGlobalVariable(string args)
+ {
+ DefineVariableIn(RuntimeInfo.GlobalVariables, args);
+ }
+
+ private void DefineVariableIn(Dictionary dict, string args)
{
string[] parts = args.Split('=');
if (parts.Length == 2)
@@ -47,14 +33,7 @@ internal partial class VariableStatements : StatementRuntimeInformation
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
}
- if (RuntimeInfo.GlobalVariables.ContainsKey(key))
- {
- RuntimeInfo.GlobalVariables[key] = parts[1].Trim();
- }
- else
- {
- RuntimeInfo.GlobalVariables.Add(key, parts[1].Trim());
- }
+ dict[key] = parts[1].Trim();
}
else
{
@@ -84,38 +63,6 @@ internal partial class VariableStatements : StatementRuntimeInformation
[Statement("${", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest, Separator = "}")]
public void ReadVariable(string _)
{
- if (!RuntimeInfo.CurrentLine.Contains("${"))
- {
- return;
- }
-
- MatchCollection matches = VariableStatementRegex().Matches(RuntimeInfo.CurrentLine);
-
- if (matches.Count <= 0)
- {
- return;
- }
-
- for (int i = 0; i < matches.Count; i++)
- {
- string varName = matches[i].Groups[1].Value;
- if (RuntimeInfo.Variables.TryGetValue(varName, out string value))
- {
- RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace(matches[i].Value, value);
- }
- else if (RuntimeInfo.GlobalVariables.TryGetValue(varName, out value))
- {
- RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace(matches[i].Value, value);
- }
- else if (!RuntimeInfo.IsSearching)
- {
- RuntimeInfo.Exit(ExitMessages.VariableNotFound(varName), true);
- return;
- }
- }
+ RuntimeInfo.CurrentLine = TemplateProcessor.ProcessVariables(RuntimeInfo.CurrentLine, RuntimeInfo);
}
-
- [GeneratedRegex("\\$\\{([a-zA-Z0-9]+)\\}")]
- private static partial Regex VariableStatementRegex();
}
-
diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs
index 2aad718..a3baba1 100644
--- a/YesNt.Interpreter/Utilities/Evaluator.cs
+++ b/YesNt.Interpreter/Utilities/Evaluator.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Text.RegularExpressions;
@@ -19,11 +19,13 @@ internal static partial class Evaluator
///
public static bool? EvaluateCondition(string input)
{
- if (input.ToLower().FromSafeString().Trim() == "true")
+ input = input.FromSafeString();
+ string lower = input.ToLower().Trim();
+ if (lower == "true")
{
return true;
}
- else if (input.ToLower().FromSafeString().Trim() == "false")
+ else if (lower == "false")
{
return false;
}
@@ -31,16 +33,16 @@ internal static partial class Evaluator
string[] parts = input.Split("==");
if (parts.Length == 2)
{
- string part1 = parts[0].FromSafeString().Trim();
- string part2 = parts[1].FromSafeString().Trim();
+ string part1 = parts[0].Trim();
+ string part2 = parts[1].Trim();
return part1 == part2;
}
parts = input.Split("!=");
if (parts.Length == 2)
{
- string part1 = parts[0].FromSafeString().Trim();
- string part2 = parts[1].FromSafeString().Trim();
+ string part1 = parts[0].Trim();
+ string part2 = parts[1].Trim();
return part1 != part2;
}
@@ -89,23 +91,26 @@ internal static partial class Evaluator
/// The result as a culture-invariant numeric string, or "NaN" if evaluation failed.
public static string Calculate(string input)
{
+ input = input.FromSafeString();
input = PlusPlusRegex().Replace(input, "+");
input = MinusMinusRegex().Replace(input, "+");
input = MinusPlusRegex().Replace(input, "-");
input = PlusMinusRegex().Replace(input, "-");
- string yes = Calculate(input, '+');
- return yes;
+ return CalculateInternal(input, '+');
}
- private static string Calculate(string input, char op)
+ private static string CalculateInternal(string input, char op)
{
- if (input is null)
+ if (string.IsNullOrWhiteSpace(input))
{
return null;
}
- input = input.FromSafeString();
+ if (input.ToStandardizedNumber(out double quickNum))
+ {
+ return quickNum.ToString(System.Globalization.CultureInfo.InvariantCulture);
+ }
MatchCollection matches = ParenthesesRegex().Matches(input);
while (matches.Count > 0)
@@ -113,7 +118,7 @@ internal static partial class Evaluator
for (int i = 0; i < matches.Count; i++)
{
string calc = matches[i].Value.Substring(1, matches[i].Length - 2);
- string ret = Calculate(calc);
+ string ret = CalculateInternal(calc, '+');
input = input.Replace(matches[i].Value, ret);
}
matches = ParenthesesRegex().Matches(input);
@@ -136,11 +141,11 @@ internal static partial class Evaluator
part = op switch
{
- '+' => Calculate(part, '-'),
- '-' => Calculate(part, '*'),
- '*' => Calculate(part, '/'),
- '/' => Calculate(part, '%'),
- '%' => Calculate(part, '^'),
+ '+' => CalculateInternal(part, '-'),
+ '-' => CalculateInternal(part, '*'),
+ '*' => CalculateInternal(part, '/'),
+ '/' => CalculateInternal(part, '%'),
+ '%' => CalculateInternal(part, '^'),
_ => part
};
@@ -208,4 +213,4 @@ internal static partial class Evaluator
[GeneratedRegex("(\\+ +\\-)+")]
private static partial Regex PlusMinusRegex();
-}
\ No newline at end of file
+}
diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs
index 9430c88..1d2cdc2 100644
--- a/YesNt.Interpreter/Utilities/FixedProcess.cs
+++ b/YesNt.Interpreter/Utilities/FixedProcess.cs
@@ -19,7 +19,7 @@ internal delegate void UserCallBack(string data);
/// flushes whatever is in the read buffer immediately, enabling real-time
/// output forwarding for interactive child processes.
///
-public class FixedProcess : Process
+internal class FixedProcess : Process
{
public new event DataReceivedEventHandler OutputDataReceived;
diff --git a/YesNt.Interpreter/Utilities/StringExtensions.cs b/YesNt.Interpreter/Utilities/StringExtensions.cs
index b5ff31b..fb665c7 100644
--- a/YesNt.Interpreter/Utilities/StringExtensions.cs
+++ b/YesNt.Interpreter/Utilities/StringExtensions.cs
@@ -64,13 +64,30 @@ public static class StringExtensions
/// The safe-string encoded representation.
public static string ToSafeString(this string input)
{
- StringBuilder output = new StringBuilder();
- foreach (char c in input)
+ if (string.IsNullOrEmpty(input))
{
- _ = output.Append($"\v{c}\v");
+ return input;
}
- return ReplaceOnce(output.ToString(), ReplacementRules);
+ StringBuilder output = new StringBuilder(input.Length * 3);
+ foreach (char c in input)
+ {
+ string s = c.ToString();
+ if (ReplacementRules.TryGetValue(s, out string replacement))
+ {
+ _ = output.Append('\v');
+ _ = output.Append(replacement);
+ _ = output.Append('\v');
+ }
+ else
+ {
+ _ = output.Append('\v');
+ _ = output.Append(c);
+ _ = output.Append('\v');
+ }
+ }
+
+ return output.ToString();
}
///
@@ -80,7 +97,35 @@ public static class StringExtensions
/// The decoded plain string.
public static string FromSafeString(this string input)
{
- return ReplaceOnce(input.Replace("\v", string.Empty), reverseReplacementRules);
+ if (string.IsNullOrEmpty(input) || (!input.Contains('\v') && !input.Contains('\x01')))
+ {
+ return input;
+ }
+
+ string stripped = input.Replace("\v", string.Empty);
+ if (!stripped.Contains('\x01'))
+ {
+ return stripped;
+ }
+
+ StringBuilder output = new StringBuilder(stripped.Length);
+ for (int i = 0; i < stripped.Length; i++)
+ {
+ if (stripped[i] == '\x01' && i + 4 < stripped.Length && stripped[i + 4] == '\x01')
+ {
+ string code = stripped.Substring(i, 5);
+ if (reverseReplacementRules.TryGetValue(code, out string value))
+ {
+ _ = output.Append(value);
+ i += 4;
+ continue;
+ }
+ }
+
+ _ = output.Append(stripped[i]);
+ }
+
+ return output.ToString();
}
///
@@ -92,7 +137,11 @@ public static class StringExtensions
/// if parsing succeeded; otherwise .
public static bool ToStandardizedNumber(this string input, out double result)
{
- return double.TryParse(input.FromSafeString().Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out result);
+ if (input.IndexOf(',') != -1)
+ {
+ input = input.Replace(',', '.');
+ }
+ return double.TryParse(input, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
}
/// Replaces only the first occurrence of in the string.
@@ -131,24 +180,4 @@ public static class StringExtensions
return count;
}
-
- private static string ReplaceOnce(string input, Dictionary replacementRules)
- {
- // \x01emp\x01/string.Empty is a special case, it is used to represent empty strings and won't work with the normal rules because an empty string always matches and causes an infinite loop 3 letter abbreviation
- IEnumerable> matches = replacementRules.Where(rule => rule.Key != string.Empty && input.Contains(rule.Key, StringComparison.Ordinal));
- if (!matches.Any())
- {
- return input;
- }
-
- KeyValuePair match = matches.First();
- int startIndex = input.IndexOf(match.Key, StringComparison.Ordinal);
- int endIndex = startIndex + match.Key.Length;
-
- string before = ReplaceOnce(input[..startIndex], replacementRules);
- string replaced = match.Value;
- string after = ReplaceOnce(input[endIndex..], replacementRules);
-
- return before + replaced + after;
- }
}
\ No newline at end of file
diff --git a/YesNt.Interpreter/Utilities/TemplateProcessor.cs b/YesNt.Interpreter/Utilities/TemplateProcessor.cs
new file mode 100644
index 0000000..368a171
--- /dev/null
+++ b/YesNt.Interpreter/Utilities/TemplateProcessor.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+using YesNt.Interpreter.Runtime;
+
+namespace YesNt.Interpreter.Utilities;
+
+///
+/// Provides high-performance template substitution for variables and stack parameters.
+///
+internal static class TemplateProcessor
+{
+ ///
+ /// Replaces all occurrences of ${variableName} with their current values.
+ ///
+ public static string ProcessVariables(string input, RuntimeInformation runtimeInfo)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+
+ int startIdx = input.IndexOf("${", StringComparison.Ordinal);
+ if (startIdx == -1) return input;
+
+ StringBuilder sb = new StringBuilder(input.Length);
+ int lastIdx = 0;
+
+ while (startIdx != -1)
+ {
+ sb.Append(input, lastIdx, startIdx - lastIdx);
+ int endIdx = input.IndexOf('}', startIdx + 2);
+ if (endIdx == -1)
+ {
+ sb.Append("${");
+ lastIdx = startIdx + 2;
+ }
+ else
+ {
+ string varName = input.Substring(startIdx + 2, endIdx - (startIdx + 2));
+ if (runtimeInfo.Variables.TryGetValue(varName, out string value))
+ {
+ sb.Append(value);
+ }
+ else if (runtimeInfo.GlobalVariables.TryGetValue(varName, out value))
+ {
+ sb.Append(value);
+ }
+ else if (!runtimeInfo.IsSearching)
+ {
+ runtimeInfo.Exit(ExitMessages.VariableNotFound(varName), true);
+ return input;
+ }
+ else
+ {
+ sb.Append("${");
+ sb.Append(varName);
+ sb.Append('}');
+ }
+
+ lastIdx = endIdx + 1;
+ }
+
+ startIdx = input.IndexOf("${", lastIdx, StringComparison.Ordinal);
+ }
+
+ sb.Append(input, lastIdx, input.Length - lastIdx);
+ return sb.ToString();
+ }
+
+ ///
+ /// Replaces all occurrences of a placeholder (e.g., %in, %out) with values popped from a stack.
+ ///
+ public static string ProcessStackParameters(string input, string placeholder, Stack stack, RuntimeInformation runtimeInfo, string emptyStackMessage)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+
+ int startIdx = input.IndexOf(placeholder, StringComparison.Ordinal);
+ if (startIdx == -1) return input;
+
+ StringBuilder sb = new StringBuilder(input.Length);
+ int lastIdx = 0;
+ int placeholderLen = placeholder.Length;
+
+ while (startIdx != -1)
+ {
+ sb.Append(input, lastIdx, startIdx - lastIdx);
+ if (stack.Count == 0)
+ {
+ runtimeInfo.Exit(emptyStackMessage, true);
+ return input;
+ }
+ sb.Append(stack.Pop());
+ lastIdx = startIdx + placeholderLen;
+ startIdx = input.IndexOf(placeholder, lastIdx, StringComparison.Ordinal);
+ }
+
+ sb.Append(input, lastIdx, input.Length - lastIdx);
+ return sb.ToString();
+ }
+
+ ///
+ /// Replaces all occurrences of a placeholder with a fixed value.
+ ///
+ public static string ProcessSimplePlaceholders(string input, string placeholder, string value)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+
+ return input.Replace(placeholder, value, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Replaces all occurrences of a placeholder with values generated by a provider function.
+ ///
+ public static string ProcessDynamicPlaceholders(string input, string placeholder, Func valueProvider)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+
+ int startIdx = input.IndexOf(placeholder, StringComparison.Ordinal);
+ if (startIdx == -1) return input;
+
+ StringBuilder sb = new StringBuilder(input.Length);
+ int lastIdx = 0;
+ int placeholderLen = placeholder.Length;
+
+ while (startIdx != -1)
+ {
+ sb.Append(input, lastIdx, startIdx - lastIdx);
+ sb.Append(valueProvider());
+ lastIdx = startIdx + placeholderLen;
+ startIdx = input.IndexOf(placeholder, lastIdx, StringComparison.Ordinal);
+ }
+
+ sb.Append(input, lastIdx, input.Length - lastIdx);
+ return sb.ToString();
+ }
+
+ ///
+ /// Replaces all occurrences of arithmetic expressions with their results.
+ ///
+ public static string ProcessCalculations(string input, RuntimeInformation runtimeInfo, Regex calculationRegex)
+ {
+ if (string.IsNullOrEmpty(input)) return input;
+
+ MatchCollection matches = calculationRegex.Matches(input);
+ if (matches.Count == 0) return input;
+
+ StringBuilder sb = new StringBuilder(input.Length);
+ int lastIdx = 0;
+
+ for (int i = 0; i < matches.Count; i++)
+ {
+ Match match = matches[i];
+ sb.Append(input, lastIdx, match.Index - lastIdx);
+
+ string res = Evaluator.Calculate(match.Value);
+ if (res is null)
+ {
+ runtimeInfo.Exit(ExitMessages.InvalidOperation, true);
+ return input;
+ }
+ sb.Append(res);
+ lastIdx = match.Index + match.Length;
+ }
+
+ sb.Append(input, lastIdx, input.Length - lastIdx);
+ return sb.ToString();
+ }
+}