mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 00:56:31 +02:00
Performance improvements and code cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
using YesNt.Interpreter.Enums;
|
using YesNt.Interpreter.Enums;
|
||||||
|
|
||||||
@@ -60,6 +60,24 @@ public class StatementAttribute : Attribute
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Separator { get; set; }
|
public string Separator { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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").
|
||||||
|
/// </summary>
|
||||||
|
public string BlockPair { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBlockEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this statement is an intermediate part of a block
|
||||||
|
/// (e.g., "else:" between "if" and "end_if").
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBlockIntermediate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new <see cref="StatementAttribute"/> with a syntax-highlight color.
|
/// Initializes a new <see cref="StatementAttribute"/> with a syntax-highlight color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace YesNt.Interpreter.Runtime;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single source line together with its location metadata.
|
/// Represents a single source line together with its location metadata.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ internal sealed class RuntimeInformation
|
|||||||
|
|
||||||
public Dictionary<string, string> GlobalVariables { get; set; } = [];
|
public Dictionary<string, string> GlobalVariables { get; set; } = [];
|
||||||
public Dictionary<string, int> Functions { get; } = [];
|
public Dictionary<string, int> Functions { get; } = [];
|
||||||
|
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
||||||
|
internal Action PreScanLinesAction { get; set; }
|
||||||
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
||||||
public Stack<string> InParametersStack { get; } = new();
|
public Stack<string> InParametersStack { get; } = new();
|
||||||
public Stack<string> OutParametersStack { get; set; } = new();
|
public Stack<string> OutParametersStack { get; set; } = new();
|
||||||
@@ -173,6 +175,7 @@ internal sealed class RuntimeInformation
|
|||||||
GlobalVariables.Clear();
|
GlobalVariables.Clear();
|
||||||
Labels.Clear();
|
Labels.Clear();
|
||||||
Functions.Clear();
|
Functions.Clear();
|
||||||
|
BlockBoundaries.Clear();
|
||||||
FunctionCallStack.Clear();
|
FunctionCallStack.Clear();
|
||||||
InParametersStack.Clear();
|
InParametersStack.Clear();
|
||||||
OutParametersStack.Clear();
|
OutParametersStack.Clear();
|
||||||
@@ -187,8 +190,7 @@ internal sealed class RuntimeInformation
|
|||||||
IsInFunction = false;
|
IsInFunction = false;
|
||||||
IsLocalSearch = false;
|
IsLocalSearch = false;
|
||||||
LineNumber = 0;
|
LineNumber = 0;
|
||||||
TaskId = internalTaskId + 1;
|
TaskId = ++internalTaskId;
|
||||||
internalTaskId++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks)
|
private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
using YesNt.Interpreter.Attributes;
|
||||||
|
|
||||||
|
namespace YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-calculated statement handler information for faster matching.
|
||||||
|
/// </summary>
|
||||||
|
internal record StatementHandler(StatementAttribute Attribute, Action<string> Handler, string FullName);
|
||||||
@@ -13,4 +13,12 @@ internal abstract class StatementRuntimeInformation
|
|||||||
/// Injected by the generated registry before any handler is invoked.
|
/// Injected by the generated registry before any handler is invoked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RuntimeInformation RuntimeInfo { get; set; }
|
public RuntimeInformation RuntimeInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trims surrounding whitespace and a trailing colon from a block or function name.
|
||||||
|
/// </summary>
|
||||||
|
protected static string NormalizeBlockName(string value)
|
||||||
|
{
|
||||||
|
return value.Trim().TrimEnd(':').Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -13,20 +13,6 @@ namespace YesNt.Interpreter.Runtime;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point for executing YesNt scripts.
|
/// The main entry point for executing YesNt scripts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <example>
|
|
||||||
/// Running a script file:
|
|
||||||
/// <code>
|
|
||||||
/// var interpreter = new YesNtInterpreter();
|
|
||||||
/// interpreter.Execute("path/to/script.ynt");
|
|
||||||
/// </code>
|
|
||||||
/// Running script lines in memory with a custom statement:
|
|
||||||
/// <code>
|
|
||||||
/// var interpreter = new YesNtInterpreter();
|
|
||||||
/// interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End, args =>
|
|
||||||
/// Console.WriteLine($"[LOG] {args}"));
|
|
||||||
/// interpreter.Execute(new List<string> { "log hello world" });
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
public class YesNtInterpreter
|
public class YesNtInterpreter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,6 +29,8 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
||||||
private Dictionary<StatementAttribute, Action<string>> statements;
|
private Dictionary<StatementAttribute, Action<string>> statements;
|
||||||
|
private List<StatementHandler> statementHandlers;
|
||||||
|
private List<List<StatementHandler>> lineMatchingHandlers = [];
|
||||||
private readonly List<KeyValuePair<StaticStatementAttribute, Action>> staticStatements;
|
private readonly List<KeyValuePair<StaticStatementAttribute, Action>> staticStatements;
|
||||||
private readonly Dictionary<string, List<KeyValuePair<StatementAttribute, Action<string>>>> disabledStatements = [];
|
private readonly Dictionary<string, List<KeyValuePair<StatementAttribute, Action<string>>>> disabledStatements = [];
|
||||||
|
|
||||||
@@ -77,11 +65,28 @@ public class YesNtInterpreter
|
|||||||
public YesNtInterpreter()
|
public YesNtInterpreter()
|
||||||
{
|
{
|
||||||
GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements);
|
GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements);
|
||||||
|
UpdateStatementHandlers();
|
||||||
|
runtimeInfo.PreScanLinesAction = PreScanLines;
|
||||||
|
|
||||||
runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s);
|
runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s);
|
||||||
runtimeInfo.OnLineExecuted += e => OnLineExecuted?.Invoke(e);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>.
|
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>.
|
||||||
/// If a statement with the same attribute key (identical field values) already exists it will be replaced;
|
/// 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)
|
.OrderBy(s => s.Key.Priority)
|
||||||
.ThenByDescending(s => s.Key.Name.Length)
|
.ThenByDescending(s => s.Key.Name.Length)
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
UpdateStatementHandlers();
|
||||||
|
PreScanLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom statement without a syntax-highlight color.
|
/// Registers a simple custom statement with default settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The keyword that identifies this statement in source code.</param>
|
/// <param name="name">The keyword to match.</param>
|
||||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||||
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
||||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
||||||
{
|
{
|
||||||
@@ -117,12 +124,12 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom statement with a syntax-highlight color.
|
/// Registers a simple custom statement with a specific syntax-highlight color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The keyword that identifies this statement in source code.</param>
|
/// <param name="name">The keyword to match.</param>
|
||||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||||
/// <param name="consoleColor">The color used for syntax highlighting in the code editor.</param>
|
/// <param name="consoleColor">The color used for syntax highlighting.</param>
|
||||||
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
||||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string> handler)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string> handler)
|
||||||
{
|
{
|
||||||
@@ -130,10 +137,9 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Permanently removes all built-in or custom statements that match <paramref name="name"/>.
|
/// Unregisters all handlers matching the specified keyword <paramref name="name"/>.
|
||||||
/// After removal, any script line that would have matched triggers an "Invalid statement" error.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The keyword of the statement(s) to remove.</param>
|
/// <param name="name">The keyword to remove.</param>
|
||||||
public void RemoveStatement(string name)
|
public void RemoveStatement(string name)
|
||||||
{
|
{
|
||||||
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
|
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
|
||||||
@@ -142,6 +148,8 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = disabledStatements.Remove(name);
|
_ = disabledStatements.Remove(name);
|
||||||
|
UpdateStatementHandlers();
|
||||||
|
PreScanLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -171,6 +179,8 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
statements[kv.Key] = _ => { };
|
statements[kv.Key] = _ => { };
|
||||||
}
|
}
|
||||||
|
UpdateStatementHandlers();
|
||||||
|
PreScanLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -192,6 +202,8 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = disabledStatements.Remove(name);
|
_ = disabledStatements.Remove(name);
|
||||||
|
UpdateStatementHandlers();
|
||||||
|
PreScanLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -236,9 +248,11 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
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();
|
Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +269,7 @@ public class YesNtInterpreter
|
|||||||
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
|
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
PreScanLines();
|
||||||
Execute();
|
Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,20 +282,25 @@ public class YesNtInterpreter
|
|||||||
break;
|
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('#'))
|
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugEventArgs debugEventArgs = new DebugEventArgs()
|
DebugEventArgs debugEventArgs = null;
|
||||||
|
if (runtimeInfo.IsDebugMode)
|
||||||
|
{
|
||||||
|
debugEventArgs = new DebugEventArgs()
|
||||||
{
|
{
|
||||||
LineNumber = runtimeInfo.LineNumber + 1,
|
LineNumber = runtimeInfo.LineNumber + 1,
|
||||||
OriginalLine = runtimeInfo.CurrentLine.FromSafeString(),
|
OriginalLine = runtimeInfo.CurrentLine.FromSafeString(),
|
||||||
IsTask = runtimeInfo.IsTask,
|
IsTask = runtimeInfo.IsTask,
|
||||||
TaskId = runtimeInfo.TaskId
|
TaskId = runtimeInfo.TaskId
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<StaticStatementAttribute, Action> staticStatement in staticStatements)
|
foreach (KeyValuePair<StaticStatementAttribute, Action> staticStatement in staticStatements)
|
||||||
{
|
{
|
||||||
@@ -296,9 +316,11 @@ public class YesNtInterpreter
|
|||||||
bool statementFound = false;
|
bool statementFound = false;
|
||||||
bool notSearchingLabel = !runtimeInfo.IsSearching;
|
bool notSearchingLabel = !runtimeInfo.IsSearching;
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttribute, Action<string>> statement in statements)
|
List<StatementHandler> 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)
|
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
{
|
{
|
||||||
@@ -311,37 +333,31 @@ public class YesNtInterpreter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string name = statementAttribute.SpaceAround switch
|
string name = handler.FullName;
|
||||||
{
|
|
||||||
SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ",
|
|
||||||
SpaceAround.Start => $" {statementAttribute.Name.Trim()}",
|
|
||||||
SpaceAround.End => $"{statementAttribute.Name.Trim()} ",
|
|
||||||
_ => statementAttribute.Name.Trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
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..];
|
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[name.Length..];
|
||||||
statement.Value.Invoke(copyLine);
|
handler.Handler.Invoke(copyLine);
|
||||||
statementFound = true;
|
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);
|
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty);
|
||||||
statement.Value.Invoke(copyLine);
|
handler.Handler.Invoke(copyLine);
|
||||||
statementFound = true;
|
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];
|
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[..^name.Length];
|
||||||
statement.Value.Invoke(copyLine);
|
handler.Handler.Invoke(copyLine);
|
||||||
statementFound = true;
|
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;
|
statementFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +367,7 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
|
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
|
||||||
}
|
}
|
||||||
if (runtimeInfo.IsDebugMode && notSearchingLabel)
|
if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
|
||||||
{
|
{
|
||||||
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
|
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
|
||||||
runtimeInfo.LineExecuted(debugEventArgs);
|
runtimeInfo.LineExecuted(debugEventArgs);
|
||||||
@@ -402,9 +418,93 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
for (int i = 0; i < lines.Length; i++)
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void PreScanLines()
|
||||||
|
{
|
||||||
|
runtimeInfo.BlockBoundaries.Clear();
|
||||||
|
lineMatchingHandlers = new List<List<StatementHandler>>(runtimeInfo.Lines.Count);
|
||||||
|
|
||||||
|
// Dictionary to track open blocks by their expected end statement name
|
||||||
|
var openBlocks = new Dictionary<string, Stack<int>>();
|
||||||
|
|
||||||
|
for (int i = 0; i < runtimeInfo.Lines.Count; i++)
|
||||||
|
{
|
||||||
|
string content = runtimeInfo.Lines[i].Content;
|
||||||
|
List<StatementHandler> 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<int>();
|
||||||
|
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<int>();
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
using YesNt.Interpreter.Attributes;
|
||||||
@@ -80,14 +80,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuntimeInfo.Labels.ContainsKey(key))
|
|
||||||
{
|
|
||||||
RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber;
|
RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.Labels.Add(key, RuntimeInfo.LineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key)
|
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)
|
public void IfBlock(string args)
|
||||||
{
|
{
|
||||||
args = args.Trim();
|
args = args.Trim();
|
||||||
@@ -177,7 +170,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(int targetLine, _) = FindElseOrEndIf(RuntimeInfo.LineNumber);
|
int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||||
if (targetLine < 0)
|
if (targetLine < 0)
|
||||||
{
|
{
|
||||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
||||||
@@ -187,10 +180,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.LineNumber = targetLine;
|
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 _)
|
public void Else(string _)
|
||||||
{
|
{
|
||||||
int targetLine = FindEndIf(RuntimeInfo.LineNumber);
|
int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||||
if (targetLine < 0)
|
if (targetLine < 0)
|
||||||
{
|
{
|
||||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
||||||
@@ -200,12 +193,12 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.LineNumber = targetLine;
|
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 _)
|
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)
|
public void While(string args)
|
||||||
{
|
{
|
||||||
args = args.Trim();
|
args = args.Trim();
|
||||||
@@ -229,7 +222,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int endWhileLine = FindEndWhile(RuntimeInfo.LineNumber);
|
int endWhileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||||
if (endWhileLine < 0)
|
if (endWhileLine < 0)
|
||||||
{
|
{
|
||||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndWhile, true);
|
RuntimeInfo.Exit(ExitMessages.NoMatchingEndWhile, true);
|
||||||
@@ -239,10 +232,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.LineNumber = endWhileLine;
|
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 _)
|
public void EndWhile(string _)
|
||||||
{
|
{
|
||||||
int whileLine = FindWhile(RuntimeInfo.LineNumber);
|
int whileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||||
if (whileLine < 0)
|
if (whileLine < 0)
|
||||||
{
|
{
|
||||||
RuntimeInfo.Exit(ExitMessages.NoMatchingWhile, true);
|
RuntimeInfo.Exit(ExitMessages.NoMatchingWhile, true);
|
||||||
@@ -265,11 +258,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.IsInFunction = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
RuntimeInfo.IsInFunction = false;
|
||||||
RuntimeInfo.Exit(ExitMessages.PlannedTermination, false);
|
RuntimeInfo.Exit(ExitMessages.PlannedTermination, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +276,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.IsInFunction = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
RuntimeInfo.IsInFunction = false;
|
||||||
RuntimeInfo.Exit(ExitMessages.PlannedTerminationCancelingTasks, true);
|
RuntimeInfo.Exit(ExitMessages.PlannedTerminationCancelingTasks, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,136 +293,8 @@ internal class CodeFlowStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.Exit(message, false);
|
RuntimeInfo.Exit(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeBlockName(string value)
|
private int FindBlockBoundary(int currentLine)
|
||||||
{
|
{
|
||||||
return value.Trim().TrimEnd(':').Trim();
|
return RuntimeInfo.BlockBoundaries.TryGetValue(currentLine, out int cached) ? cached : -1;
|
||||||
}
|
|
||||||
|
|
||||||
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(':');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
using YesNt.Interpreter.Attributes;
|
||||||
@@ -33,14 +33,7 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuntimeInfo.Functions.ContainsKey(key))
|
|
||||||
{
|
|
||||||
RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber;
|
RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.Functions.Add(key, RuntimeInfo.LineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchFunction) && RuntimeInfo.SearchFunction == key)
|
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)]
|
[Statement("%out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetOutParameter(string args)
|
public void GetOutParameter(string args)
|
||||||
{
|
{
|
||||||
while (args.Contains("%out"))
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%out", RuntimeInfo.OutParametersStack, RuntimeInfo, ExitMessages.NoOutArgumentInStack);
|
||||||
{
|
|
||||||
if (RuntimeInfo.OutParametersStack.Count == 0)
|
|
||||||
{
|
|
||||||
RuntimeInfo.Exit(ExitMessages.NoOutArgumentInStack, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
args = args.ReplaceFirstOccurrence("%out", RuntimeInfo.OutParametersStack.Pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%has_out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%has_out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
@@ -122,18 +104,7 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (args.Contains("%in"))
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%in", RuntimeInfo.FunctionCallStack.Peek().Arguments, RuntimeInfo, ExitMessages.NoInArgumentInStack);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%has_in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%has_in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
@@ -181,10 +152,8 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.IsInFunction = false;
|
RuntimeInfo.IsInFunction = false;
|
||||||
}
|
|
||||||
|
|
||||||
if (RuntimeInfo.FunctionCallStack.Count > 0)
|
if (RuntimeInfo.FunctionCallStack.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -204,9 +173,4 @@ internal class FunctionStatements : StatementRuntimeInformation
|
|||||||
{
|
{
|
||||||
RuntimeInfo.FunctionCallStack.Clear();
|
RuntimeInfo.FunctionCallStack.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeBlockName(string value)
|
|
||||||
{
|
|
||||||
return value.Trim().TrimEnd(':').Trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
using YesNt.Interpreter.Attributes;
|
||||||
using YesNt.Interpreter.Enums;
|
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)]
|
[Statement("%time", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetUnixTimestamp(string args)
|
public void GetUnixTimestamp(string args)
|
||||||
{
|
{
|
||||||
args = args.Replace("%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()).TrimEnd();
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetOperatingSystem(string args)
|
public void GetOperatingSystem(string args)
|
||||||
{
|
{
|
||||||
args = args.Replace("%os", Environment.OSVersion.Platform.ToString());
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%os", Environment.OSVersion.Platform.ToString()).TrimEnd();
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetProcessorArchitecture(string args)
|
public void GetProcessorArchitecture(string args)
|
||||||
{
|
{
|
||||||
args = args.Replace("%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString());
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()).TrimEnd();
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetIsOperatingSystem64Bit(string args)
|
public void GetIsOperatingSystem64Bit(string args)
|
||||||
{
|
{
|
||||||
args = args.Replace("%is64", $"{Environment.Is64BitOperatingSystem}");
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%is64", Environment.Is64BitOperatingSystem.ToString()).TrimEnd();
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetPi(string args)
|
public void GetPi(string args)
|
||||||
{
|
{
|
||||||
args = args.Replace("%pi", Math.PI.ToString());
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%pi", Math.PI.ToString()).TrimEnd();
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("%rand", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
[Statement("%rand", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||||
public void GetRandom(string args)
|
public void GetRandom(string args)
|
||||||
{
|
{
|
||||||
while (args.Contains("%rand"))
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessDynamicPlaceholders(args, "%rand", () => random.Next(32767, int.MaxValue).ToString()).TrimEnd();
|
||||||
{
|
|
||||||
args = args.ReplaceFirstOccurrence("%rand", random.Next(32767, int.MaxValue).ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation
|
|||||||
[Statement("calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)]
|
[Statement("calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)]
|
||||||
public void Calculate(string args)
|
public void Calculate(string args)
|
||||||
{
|
{
|
||||||
MatchCollection matches = CalculationRegex().Matches(args.FromSafeString());
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessCalculations(args.FromSafeString(), RuntimeInfo, CalculationRegex());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)]
|
[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.Lines.Insert(RuntimeInfo.LineNumber + i, new Line(lines[i], Path.GetFileName(path), i));
|
||||||
}
|
}
|
||||||
|
RuntimeInfo.PreScanLinesAction?.Invoke();
|
||||||
RuntimeInfo.LineNumber--;
|
RuntimeInfo.LineNumber--;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
using YesNt.Interpreter.Attributes;
|
||||||
using YesNt.Interpreter.Enums;
|
using YesNt.Interpreter.Enums;
|
||||||
using YesNt.Interpreter.Runtime;
|
using YesNt.Interpreter.Runtime;
|
||||||
|
using YesNt.Interpreter.Utilities;
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Statements;
|
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 = "=")]
|
[Statement("var", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
|
||||||
public void DefineVariable(string args)
|
public void DefineVariable(string args)
|
||||||
{
|
{
|
||||||
string[] parts = args.Split('=');
|
DefineVariableIn(RuntimeInfo.Variables, args);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Statement("global", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
|
[Statement("global", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
|
||||||
public void DefineGlobalVariable(string args)
|
public void DefineGlobalVariable(string args)
|
||||||
|
{
|
||||||
|
DefineVariableIn(RuntimeInfo.GlobalVariables, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DefineVariableIn(Dictionary<string, string> dict, string args)
|
||||||
{
|
{
|
||||||
string[] parts = args.Split('=');
|
string[] parts = args.Split('=');
|
||||||
if (parts.Length == 2)
|
if (parts.Length == 2)
|
||||||
@@ -47,14 +33,7 @@ internal partial class VariableStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuntimeInfo.GlobalVariables.ContainsKey(key))
|
dict[key] = parts[1].Trim();
|
||||||
{
|
|
||||||
RuntimeInfo.GlobalVariables[key] = parts[1].Trim();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RuntimeInfo.GlobalVariables.Add(key, parts[1].Trim());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -84,38 +63,6 @@ internal partial class VariableStatements : StatementRuntimeInformation
|
|||||||
[Statement("${", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest, Separator = "}")]
|
[Statement("${", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest, Separator = "}")]
|
||||||
public void ReadVariable(string _)
|
public void ReadVariable(string _)
|
||||||
{
|
{
|
||||||
if (!RuntimeInfo.CurrentLine.Contains("${"))
|
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessVariables(RuntimeInfo.CurrentLine, RuntimeInfo);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[GeneratedRegex("\\$\\{([a-zA-Z0-9]+)\\}")]
|
|
||||||
private static partial Regex VariableStatementRegex();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
@@ -19,11 +19,13 @@ internal static partial class Evaluator
|
|||||||
/// </returns>
|
/// </returns>
|
||||||
public static bool? EvaluateCondition(string input)
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
else if (input.ToLower().FromSafeString().Trim() == "false")
|
else if (lower == "false")
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -31,16 +33,16 @@ internal static partial class Evaluator
|
|||||||
string[] parts = input.Split("==");
|
string[] parts = input.Split("==");
|
||||||
if (parts.Length == 2)
|
if (parts.Length == 2)
|
||||||
{
|
{
|
||||||
string part1 = parts[0].FromSafeString().Trim();
|
string part1 = parts[0].Trim();
|
||||||
string part2 = parts[1].FromSafeString().Trim();
|
string part2 = parts[1].Trim();
|
||||||
return part1 == part2;
|
return part1 == part2;
|
||||||
}
|
}
|
||||||
|
|
||||||
parts = input.Split("!=");
|
parts = input.Split("!=");
|
||||||
if (parts.Length == 2)
|
if (parts.Length == 2)
|
||||||
{
|
{
|
||||||
string part1 = parts[0].FromSafeString().Trim();
|
string part1 = parts[0].Trim();
|
||||||
string part2 = parts[1].FromSafeString().Trim();
|
string part2 = parts[1].Trim();
|
||||||
return part1 != part2;
|
return part1 != part2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,23 +91,26 @@ internal static partial class Evaluator
|
|||||||
/// <returns>The result as a culture-invariant numeric string, or <c>"NaN"</c> if evaluation failed.</returns>
|
/// <returns>The result as a culture-invariant numeric string, or <c>"NaN"</c> if evaluation failed.</returns>
|
||||||
public static string Calculate(string input)
|
public static string Calculate(string input)
|
||||||
{
|
{
|
||||||
|
input = input.FromSafeString();
|
||||||
input = PlusPlusRegex().Replace(input, "+");
|
input = PlusPlusRegex().Replace(input, "+");
|
||||||
input = MinusMinusRegex().Replace(input, "+");
|
input = MinusMinusRegex().Replace(input, "+");
|
||||||
input = MinusPlusRegex().Replace(input, "-");
|
input = MinusPlusRegex().Replace(input, "-");
|
||||||
input = PlusMinusRegex().Replace(input, "-");
|
input = PlusMinusRegex().Replace(input, "-");
|
||||||
|
|
||||||
string yes = Calculate(input, '+');
|
return CalculateInternal(input, '+');
|
||||||
return yes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
input = input.FromSafeString();
|
if (input.ToStandardizedNumber(out double quickNum))
|
||||||
|
{
|
||||||
|
return quickNum.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
MatchCollection matches = ParenthesesRegex().Matches(input);
|
MatchCollection matches = ParenthesesRegex().Matches(input);
|
||||||
while (matches.Count > 0)
|
while (matches.Count > 0)
|
||||||
@@ -113,7 +118,7 @@ internal static partial class Evaluator
|
|||||||
for (int i = 0; i < matches.Count; i++)
|
for (int i = 0; i < matches.Count; i++)
|
||||||
{
|
{
|
||||||
string calc = matches[i].Value.Substring(1, matches[i].Length - 2);
|
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);
|
input = input.Replace(matches[i].Value, ret);
|
||||||
}
|
}
|
||||||
matches = ParenthesesRegex().Matches(input);
|
matches = ParenthesesRegex().Matches(input);
|
||||||
@@ -136,11 +141,11 @@ internal static partial class Evaluator
|
|||||||
|
|
||||||
part = op switch
|
part = op switch
|
||||||
{
|
{
|
||||||
'+' => Calculate(part, '-'),
|
'+' => CalculateInternal(part, '-'),
|
||||||
'-' => Calculate(part, '*'),
|
'-' => CalculateInternal(part, '*'),
|
||||||
'*' => Calculate(part, '/'),
|
'*' => CalculateInternal(part, '/'),
|
||||||
'/' => Calculate(part, '%'),
|
'/' => CalculateInternal(part, '%'),
|
||||||
'%' => Calculate(part, '^'),
|
'%' => CalculateInternal(part, '^'),
|
||||||
_ => part
|
_ => part
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ internal delegate void UserCallBack(string data);
|
|||||||
/// <see cref="FixedProcess"/> flushes whatever is in the read buffer immediately, enabling real-time
|
/// <see cref="FixedProcess"/> flushes whatever is in the read buffer immediately, enabling real-time
|
||||||
/// output forwarding for interactive child processes.
|
/// output forwarding for interactive child processes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FixedProcess : Process
|
internal class FixedProcess : Process
|
||||||
{
|
{
|
||||||
public new event DataReceivedEventHandler OutputDataReceived;
|
public new event DataReceivedEventHandler OutputDataReceived;
|
||||||
|
|
||||||
|
|||||||
@@ -64,13 +64,30 @@ public static class StringExtensions
|
|||||||
/// <returns>The safe-string encoded representation.</returns>
|
/// <returns>The safe-string encoded representation.</returns>
|
||||||
public static string ToSafeString(this string input)
|
public static string ToSafeString(this string input)
|
||||||
{
|
{
|
||||||
StringBuilder output = new StringBuilder();
|
if (string.IsNullOrEmpty(input))
|
||||||
foreach (char c in 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,7 +97,35 @@ public static class StringExtensions
|
|||||||
/// <returns>The decoded plain string.</returns>
|
/// <returns>The decoded plain string.</returns>
|
||||||
public static string FromSafeString(this string input)
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,7 +137,11 @@ public static class StringExtensions
|
|||||||
/// <returns><see langword="true"/> if parsing succeeded; otherwise <see langword="false"/>.</returns>
|
/// <returns><see langword="true"/> if parsing succeeded; otherwise <see langword="false"/>.</returns>
|
||||||
public static bool ToStandardizedNumber(this string input, out double result)
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Replaces only the first occurrence of <paramref name="oldValue"/> in the string.</summary>
|
/// <summary>Replaces only the first occurrence of <paramref name="oldValue"/> in the string.</summary>
|
||||||
@@ -131,24 +180,4 @@ public static class StringExtensions
|
|||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ReplaceOnce(string input, Dictionary<string, string> 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<KeyValuePair<string, string>> matches = replacementRules.Where(rule => rule.Key != string.Empty && input.Contains(rule.Key, StringComparison.Ordinal));
|
|
||||||
if (!matches.Any())
|
|
||||||
{
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValuePair<string, string> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides high-performance template substitution for variables and stack parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static class TemplateProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces all occurrences of ${variableName} with their current values.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces all occurrences of a placeholder (e.g., %in, %out) with values popped from a stack.
|
||||||
|
/// </summary>
|
||||||
|
public static string ProcessStackParameters(string input, string placeholder, Stack<string> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces all occurrences of a placeholder with a fixed value.
|
||||||
|
/// </summary>
|
||||||
|
public static string ProcessSimplePlaceholders(string input, string placeholder, string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input)) return input;
|
||||||
|
|
||||||
|
return input.Replace(placeholder, value, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces all occurrences of a placeholder with values generated by a provider function.
|
||||||
|
/// </summary>
|
||||||
|
public static string ProcessDynamicPlaceholders(string input, string placeholder, Func<string> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces all occurrences of arithmetic expressions with their results.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user