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;
|
||||
|
||||
@@ -60,6 +60,24 @@ public class StatementAttribute : Attribute
|
||||
/// </summary>
|
||||
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>
|
||||
/// Initializes a new <see cref="StatementAttribute"/> with a syntax-highlight color.
|
||||
/// </summary>
|
||||
@@ -89,4 +107,4 @@ public class StatementAttribute : Attribute
|
||||
SpaceAround = spaceAround;
|
||||
IgnoreSyntaxHighlighting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// 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, int> Functions { get; } = [];
|
||||
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
||||
internal Action PreScanLinesAction { get; set; }
|
||||
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
||||
public Stack<string> InParametersStack { get; } = new();
|
||||
public Stack<string> 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)
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
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.ObjectModel;
|
||||
using System.IO;
|
||||
@@ -13,20 +13,6 @@ namespace YesNt.Interpreter.Runtime;
|
||||
/// <summary>
|
||||
/// The main entry point for executing YesNt scripts.
|
||||
/// </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
|
||||
{
|
||||
/// <summary>
|
||||
@@ -43,6 +29,8 @@ public class YesNtInterpreter
|
||||
|
||||
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
||||
private Dictionary<StatementAttribute, Action<string>> statements;
|
||||
private List<StatementHandler> statementHandlers;
|
||||
private List<List<StatementHandler>> lineMatchingHandlers = [];
|
||||
private readonly List<KeyValuePair<StaticStatementAttribute, Action>> staticStatements;
|
||||
private readonly Dictionary<string, List<KeyValuePair<StatementAttribute, Action<string>>>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom statement without a syntax-highlight color.
|
||||
/// Registers a simple custom statement with default settings.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword that identifies this statement in source code.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</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>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
||||
{
|
||||
@@ -117,12 +124,12 @@ public class YesNtInterpreter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom statement with a syntax-highlight color.
|
||||
/// Registers a simple custom statement with a specific syntax-highlight color.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword that identifies this statement in source code.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
||||
/// <param name="consoleColor">The color used for syntax highlighting in the code editor.</param>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</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.</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)
|
||||
{
|
||||
@@ -130,10 +137,9 @@ public class YesNtInterpreter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permanently removes all built-in or custom statements that match <paramref name="name"/>.
|
||||
/// After removal, any script line that would have matched triggers an "Invalid statement" error.
|
||||
/// Unregisters all handlers matching the specified keyword <paramref name="name"/>.
|
||||
/// </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)
|
||||
{
|
||||
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
|
||||
@@ -142,6 +148,8 @@ public class YesNtInterpreter
|
||||
}
|
||||
|
||||
_ = disabledStatements.Remove(name);
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,6 +179,8 @@ public class YesNtInterpreter
|
||||
{
|
||||
statements[kv.Key] = _ => { };
|
||||
}
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -192,6 +202,8 @@ public class YesNtInterpreter
|
||||
}
|
||||
|
||||
_ = disabledStatements.Remove(name);
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<StaticStatementAttribute, Action> staticStatement in staticStatements)
|
||||
{
|
||||
@@ -296,9 +316,11 @@ public class YesNtInterpreter
|
||||
bool statementFound = false;
|
||||
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)
|
||||
{
|
||||
@@ -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<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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, string> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -19,11 +19,13 @@ internal static partial class Evaluator
|
||||
/// </returns>
|
||||
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
|
||||
/// <returns>The result as a culture-invariant numeric string, or <c>"NaN"</c> if evaluation failed.</returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ internal delegate void UserCallBack(string data);
|
||||
/// <see cref="FixedProcess"/> flushes whatever is in the read buffer immediately, enabling real-time
|
||||
/// output forwarding for interactive child processes.
|
||||
/// </summary>
|
||||
public class FixedProcess : Process
|
||||
internal class FixedProcess : Process
|
||||
{
|
||||
public new event DataReceivedEventHandler OutputDataReceived;
|
||||
|
||||
|
||||
@@ -64,13 +64,30 @@ public static class StringExtensions
|
||||
/// <returns>The safe-string encoded representation.</returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +97,35 @@ public static class StringExtensions
|
||||
/// <returns>The decoded plain string.</returns>
|
||||
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>
|
||||
@@ -92,7 +137,11 @@ public static class StringExtensions
|
||||
/// <returns><see langword="true"/> if parsing succeeded; otherwise <see langword="false"/>.</returns>
|
||||
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>
|
||||
@@ -131,24 +180,4 @@ public static class StringExtensions
|
||||
|
||||
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