mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
Add stepwise and time-based script execution with Step and RunFor methods
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
namespace YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The outcome of a single <see cref="YesNtInterpreter.StepOnce"/> call,
|
||||||
|
/// or the aggregate result of <see cref="YesNtInterpreter.Step"/> /
|
||||||
|
/// <see cref="YesNtInterpreter.RunFor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public enum StepResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A line was executed and more lines remain. Keep stepping to continue.
|
||||||
|
/// </summary>
|
||||||
|
Continue,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The step or time budget was exhausted before the script finished.
|
||||||
|
/// <see cref="YesNtInterpreter.IsRunning"/> is still <see langword="true"/>;
|
||||||
|
/// call any of the run methods again to resume.
|
||||||
|
/// </summary>
|
||||||
|
Paused,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The script ran to completion (end-of-file, explicit exit, or error).
|
||||||
|
/// <see cref="YesNtInterpreter.IsRunning"/> is now <see langword="false"/>.
|
||||||
|
/// </summary>
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -59,6 +60,13 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see langword="true"/> from the moment <see cref="Prepare(string, bool)"/> (or any
|
||||||
|
/// <c>Execute</c> overload) is called until the script finishes or is stopped.
|
||||||
|
/// Use this to drive step/run loops: <c>while (interpreter.IsRunning) interpreter.Step(10);</c>
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new <see cref="YesNtInterpreter"/> and registers all built-in statements.
|
/// Initializes a new <see cref="YesNtInterpreter"/> and registers all built-in statements.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -274,7 +282,53 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes a YesNt script file.
|
/// Loads a YesNt script file and prepares it for stepped execution.
|
||||||
|
/// After this call <see cref="IsRunning"/> is <see langword="true"/> and you can drive
|
||||||
|
/// execution with <see cref="Step"/>, <see cref="RunFor"/>, or <see cref="RunToCompletion"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to the <c>.ynt</c> script file.</param>
|
||||||
|
/// <param name="isDebugMode">
|
||||||
|
/// When <see langword="true"/>, output is routed through <see cref="OnDebugOutput"/> instead of
|
||||||
|
/// <see cref="Console"/> and line-execution events are raised via <see cref="OnLineExecuted"/>.
|
||||||
|
/// </param>
|
||||||
|
public void Prepare(string path, bool isDebugMode = false)
|
||||||
|
{
|
||||||
|
runtimeInfo.Reset();
|
||||||
|
runtimeInfo.IsDebugMode = isDebugMode;
|
||||||
|
if (LoadFile(path))
|
||||||
|
{
|
||||||
|
IsRunning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an in-memory script and prepares it for stepped execution.
|
||||||
|
/// After this call <see cref="IsRunning"/> is <see langword="true"/> and you can drive
|
||||||
|
/// execution with <see cref="Step"/>, <see cref="RunFor"/>, or <see cref="RunToCompletion"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lines">The script lines to load.</param>
|
||||||
|
/// <param name="isDebugMode">
|
||||||
|
/// When <see langword="true"/>, output is routed through <see cref="OnDebugOutput"/> and
|
||||||
|
/// line-execution events are raised via <see cref="OnLineExecuted"/>.
|
||||||
|
/// </param>
|
||||||
|
public void Prepare(IEnumerable<string> lines, bool isDebugMode = false)
|
||||||
|
{
|
||||||
|
runtimeInfo.Reset();
|
||||||
|
runtimeInfo.IsDebugMode = isDebugMode;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
string content = line.Trim().Replace("\r", string.Empty);
|
||||||
|
runtimeInfo.Lines.Add(new Line(content, "#Memory#", i++));
|
||||||
|
}
|
||||||
|
|
||||||
|
PreScanLines();
|
||||||
|
IsRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a YesNt script file to completion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path to the <c>.ynt</c> script file.</param>
|
/// <param name="path">The path to the <c>.ynt</c> script file.</param>
|
||||||
/// <param name="isDebugMode">
|
/// <param name="isDebugMode">
|
||||||
@@ -283,35 +337,22 @@ public class YesNtInterpreter
|
|||||||
/// </param>
|
/// </param>
|
||||||
public void Execute(string path, bool isDebugMode = false)
|
public void Execute(string path, bool isDebugMode = false)
|
||||||
{
|
{
|
||||||
runtimeInfo.Reset();
|
Prepare(path, isDebugMode);
|
||||||
runtimeInfo.IsDebugMode = isDebugMode;
|
RunToCompletion();
|
||||||
if (LoadFile(path))
|
|
||||||
{
|
|
||||||
Execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes a YesNt script supplied as an in-memory list of lines.
|
/// Executes a YesNt script supplied as an in-memory list of lines to completion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lines">The script lines to execute.</param>
|
/// <param name="lines">The script lines to execute.</param>
|
||||||
/// <param name="isDebugMode">
|
/// <param name="isDebugMode">
|
||||||
/// When <see langword="true"/>, output is routed through <see cref="OnDebugOutput"/> and
|
/// When <see langword="true"/>, output is routed through <see cref="OnDebugOutput"/> and
|
||||||
/// line-execution events are raised via <see cref="OnLineExecuted"/>.
|
/// line-execution events are raised via <see cref="OnLineExecuted"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
public void Execute(List<string> lines, bool isDebugMode = false)
|
public void Execute(IEnumerable<string> lines, bool isDebugMode = false)
|
||||||
{
|
{
|
||||||
runtimeInfo.Reset();
|
Prepare(lines, isDebugMode);
|
||||||
runtimeInfo.IsDebugMode = isDebugMode;
|
RunToCompletion();
|
||||||
|
|
||||||
for (int i = 0; i < lines.Count; i++)
|
|
||||||
{
|
|
||||||
string content = lines[i].Trim().Replace("\r", string.Empty);
|
|
||||||
runtimeInfo.Lines.Add(new Line(content, Path.GetFileName("#Memory#"), i));
|
|
||||||
}
|
|
||||||
|
|
||||||
PreScanLines();
|
|
||||||
Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Execute(List<Line> lines, Dictionary<string, string> globalVariables, int startLine, RuntimeInformation parentRuntimeInformation)
|
internal void Execute(List<Line> lines, Dictionary<string, string> globalVariables, int startLine, RuntimeInformation parentRuntimeInformation)
|
||||||
@@ -322,22 +363,98 @@ public class YesNtInterpreter
|
|||||||
runtimeInfo.LineNumber = startLine;
|
runtimeInfo.LineNumber = startLine;
|
||||||
runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation;
|
runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation;
|
||||||
runtimeInfo.GlobalVariables = globalVariables;
|
runtimeInfo.GlobalVariables = globalVariables;
|
||||||
|
|
||||||
if (parentRuntimeInformation.StopAllTasks)
|
if (parentRuntimeInformation.StopAllTasks)
|
||||||
{
|
{
|
||||||
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
|
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PreScanLines();
|
PreScanLines();
|
||||||
Execute();
|
IsRunning = true;
|
||||||
|
RunToCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Execute()
|
/// <summary>
|
||||||
|
/// Executes up to <paramref name="lines"/> script lines then pauses, leaving
|
||||||
|
/// <see cref="IsRunning"/> <see langword="true"/> so execution can be resumed later.
|
||||||
|
/// Blank lines and comments are skipped transparently and do not consume the budget.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lines">Maximum number of executable lines to run. Defaults to 1.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see cref="StepResult.Paused"/> if the budget was exhausted but the script is not finished;
|
||||||
|
/// <see cref="StepResult.Finished"/> if the script ended within the budget.
|
||||||
|
/// </returns>
|
||||||
|
public StepResult Step(int lines = 1)
|
||||||
{
|
{
|
||||||
for (; runtimeInfo.LineNumber < runtimeInfo.Lines.Count; runtimeInfo.LineNumber++)
|
for (int i = 0; i < lines; i++)
|
||||||
|
{
|
||||||
|
StepResult result = StepOnce();
|
||||||
|
if (result != StepResult.Continue)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StepResult.Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the script for up to <paramref name="budget"/> of wall-clock time, then pauses.
|
||||||
|
/// The check happens between lines, so a single slow statement may overshoot slightly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="budget">How long to run before pausing.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see cref="StepResult.Paused"/> if the budget expired but the script is not finished;
|
||||||
|
/// <see cref="StepResult.Finished"/> if the script ended within the budget.
|
||||||
|
/// </returns>
|
||||||
|
public StepResult RunFor(TimeSpan budget)
|
||||||
|
{
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
return StepResult.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
while (sw.Elapsed < budget)
|
||||||
|
{
|
||||||
|
StepResult result = StepOnce();
|
||||||
|
if (result != StepResult.Continue)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StepResult.Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the script to completion from the current position.
|
||||||
|
/// If the script has not been started yet (i.e. <see cref="IsRunning"/> is <see langword="false"/>)
|
||||||
|
/// this method returns immediately.
|
||||||
|
/// </summary>
|
||||||
|
public void RunToCompletion()
|
||||||
|
{
|
||||||
|
while (IsRunning)
|
||||||
|
{
|
||||||
|
_ = StepOnce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StepResult StepOnce()
|
||||||
|
{
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
return StepResult.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip blank lines and comments without consuming the step budget.
|
||||||
|
while (runtimeInfo.LineNumber < runtimeInfo.Lines.Count)
|
||||||
{
|
{
|
||||||
if (runtimeInfo.Stop)
|
if (runtimeInfo.Stop)
|
||||||
{
|
{
|
||||||
break;
|
return FinishExecution();
|
||||||
}
|
}
|
||||||
|
|
||||||
Line lineObj = runtimeInfo.Lines[runtimeInfo.LineNumber];
|
Line lineObj = runtimeInfo.Lines[runtimeInfo.LineNumber];
|
||||||
@@ -345,9 +462,11 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
|
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
|
||||||
{
|
{
|
||||||
|
runtimeInfo.LineNumber++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have a real executable line — run it.
|
||||||
DebugEventArgs debugEventArgs = null;
|
DebugEventArgs debugEventArgs = null;
|
||||||
if (runtimeInfo.IsDebugMode)
|
if (runtimeInfo.IsDebugMode)
|
||||||
{
|
{
|
||||||
@@ -362,8 +481,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
foreach (KeyValuePair<StaticStatementAttributeContainer, Action> staticStatement in staticStatements)
|
foreach (KeyValuePair<StaticStatementAttributeContainer, Action> staticStatement in staticStatements)
|
||||||
{
|
{
|
||||||
StaticStatementAttributeContainer staticStatementAttribute = staticStatement.Key;
|
if (!staticStatement.Key.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -374,7 +492,9 @@ public class YesNtInterpreter
|
|||||||
bool statementFound = false;
|
bool statementFound = false;
|
||||||
bool notSearchingLabel = !runtimeInfo.IsSearching;
|
bool notSearchingLabel = !runtimeInfo.IsSearching;
|
||||||
|
|
||||||
List<StatementHandler> handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count) ? lineMatchingHandlers[runtimeInfo.LineNumber] : [];
|
List<StatementHandler> handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count)
|
||||||
|
? lineMatchingHandlers[runtimeInfo.LineNumber]
|
||||||
|
: [];
|
||||||
|
|
||||||
foreach (StatementHandler handler in handlers)
|
foreach (StatementHandler handler in handlers)
|
||||||
{
|
{
|
||||||
@@ -425,13 +545,27 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
|
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
|
if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
|
||||||
{
|
{
|
||||||
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
|
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
|
||||||
runtimeInfo.LineExecuted(debugEventArgs);
|
runtimeInfo.LineExecuted(debugEventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtimeInfo.LineNumber++;
|
||||||
|
|
||||||
|
// A statement may have set Stop (e.g. an explicit exit keyword).
|
||||||
|
return runtimeInfo.Stop ? FinishExecution() : StepResult.Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fell off the end of the script.
|
||||||
|
return FinishExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StepResult FinishExecution()
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
if (!runtimeInfo.Stop)
|
if (!runtimeInfo.Stop)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel))
|
if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel))
|
||||||
@@ -446,12 +580,14 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
runtimeInfo.Exit(ExitMessages.EndOfFile, false);
|
runtimeInfo.Exit(ExitMessages.EndOfFile, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (runtimeInfo.IsDebugMode)
|
if (runtimeInfo.IsDebugMode)
|
||||||
{
|
{
|
||||||
runtimeInfo.LineExecuted(null);
|
runtimeInfo.LineExecuted(null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return StepResult.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool LoadFile(string path)
|
private bool LoadFile(string path)
|
||||||
|
|||||||
Reference in New Issue
Block a user