diff --git a/src/YesNt.Interpreter/Runtime/StepResult.cs b/src/YesNt.Interpreter/Runtime/StepResult.cs
new file mode 100644
index 0000000..114dca7
--- /dev/null
+++ b/src/YesNt.Interpreter/Runtime/StepResult.cs
@@ -0,0 +1,27 @@
+namespace YesNt.Interpreter.Runtime;
+
+///
+/// The outcome of a single call,
+/// or the aggregate result of /
+/// .
+///
+public enum StepResult
+{
+ ///
+ /// A line was executed and more lines remain. Keep stepping to continue.
+ ///
+ Continue,
+
+ ///
+ /// The step or time budget was exhausted before the script finished.
+ /// is still ;
+ /// call any of the run methods again to resume.
+ ///
+ Paused,
+
+ ///
+ /// The script ran to completion (end-of-file, explicit exit, or error).
+ /// is now .
+ ///
+ Finished,
+}
\ No newline at end of file
diff --git a/src/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/src/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
index c7f3752..c7be90b 100644
--- a/src/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
+++ b/src/YesNt.Interpreter/Runtime/YesNtInterpreter.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -59,6 +60,13 @@ public class YesNtInterpreter
}
}
+ ///
+ /// from the moment (or any
+ /// Execute overload) is called until the script finishes or is stopped.
+ /// Use this to drive step/run loops: while (interpreter.IsRunning) interpreter.Step(10);
+ ///
+ public bool IsRunning { get; private set; }
+
///
/// Initializes a new and registers all built-in statements.
///
@@ -274,7 +282,53 @@ public class YesNtInterpreter
}
///
- /// Executes a YesNt script file.
+ /// Loads a YesNt script file and prepares it for stepped execution.
+ /// After this call is and you can drive
+ /// execution with , , or .
+ ///
+ /// The path to the .ynt script file.
+ ///
+ /// When , output is routed through instead of
+ /// and line-execution events are raised via .
+ ///
+ public void Prepare(string path, bool isDebugMode = false)
+ {
+ runtimeInfo.Reset();
+ runtimeInfo.IsDebugMode = isDebugMode;
+ if (LoadFile(path))
+ {
+ IsRunning = true;
+ }
+ }
+
+ ///
+ /// Loads an in-memory script and prepares it for stepped execution.
+ /// After this call is and you can drive
+ /// execution with , , or .
+ ///
+ /// The script lines to load.
+ ///
+ /// When , output is routed through and
+ /// line-execution events are raised via .
+ ///
+ public void Prepare(IEnumerable 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;
+ }
+
+ ///
+ /// Executes a YesNt script file to completion.
///
/// The path to the .ynt script file.
///
@@ -283,35 +337,22 @@ public class YesNtInterpreter
///
public void Execute(string path, bool isDebugMode = false)
{
- runtimeInfo.Reset();
- runtimeInfo.IsDebugMode = isDebugMode;
- if (LoadFile(path))
- {
- Execute();
- }
+ Prepare(path, isDebugMode);
+ RunToCompletion();
}
///
- /// 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.
///
/// The script lines to execute.
///
/// When , output is routed through and
/// line-execution events are raised via .
///
- public void Execute(List lines, bool isDebugMode = false)
+ public void Execute(IEnumerable lines, bool isDebugMode = false)
{
- runtimeInfo.Reset();
- runtimeInfo.IsDebugMode = isDebugMode;
-
- 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();
+ Prepare(lines, isDebugMode);
+ RunToCompletion();
}
internal void Execute(List lines, Dictionary globalVariables, int startLine, RuntimeInformation parentRuntimeInformation)
@@ -322,22 +363,98 @@ public class YesNtInterpreter
runtimeInfo.LineNumber = startLine;
runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation;
runtimeInfo.GlobalVariables = globalVariables;
+
if (parentRuntimeInformation.StopAllTasks)
{
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
return;
}
+
PreScanLines();
- Execute();
+ IsRunning = true;
+ RunToCompletion();
}
- private void Execute()
+ ///
+ /// Executes up to script lines then pauses, leaving
+ /// so execution can be resumed later.
+ /// Blank lines and comments are skipped transparently and do not consume the budget.
+ ///
+ /// Maximum number of executable lines to run. Defaults to 1.
+ ///
+ /// if the budget was exhausted but the script is not finished;
+ /// if the script ended within the budget.
+ ///
+ 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;
+ }
+
+ ///
+ /// Runs the script for up to of wall-clock time, then pauses.
+ /// The check happens between lines, so a single slow statement may overshoot slightly.
+ ///
+ /// How long to run before pausing.
+ ///
+ /// if the budget expired but the script is not finished;
+ /// if the script ended within the budget.
+ ///
+ 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;
+ }
+
+ ///
+ /// Runs the script to completion from the current position.
+ /// If the script has not been started yet (i.e. is )
+ /// this method returns immediately.
+ ///
+ 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)
{
- break;
+ return FinishExecution();
}
Line lineObj = runtimeInfo.Lines[runtimeInfo.LineNumber];
@@ -345,9 +462,11 @@ public class YesNtInterpreter
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
{
+ runtimeInfo.LineNumber++;
continue;
}
+ // We have a real executable line — run it.
DebugEventArgs debugEventArgs = null;
if (runtimeInfo.IsDebugMode)
{
@@ -362,8 +481,7 @@ public class YesNtInterpreter
foreach (KeyValuePair staticStatement in staticStatements)
{
- StaticStatementAttributeContainer staticStatementAttribute = staticStatement.Key;
- if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
+ if (!staticStatement.Key.ExecuteInSearchMode && runtimeInfo.IsSearching)
{
continue;
}
@@ -374,7 +492,9 @@ public class YesNtInterpreter
bool statementFound = false;
bool notSearchingLabel = !runtimeInfo.IsSearching;
- List handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count) ? lineMatchingHandlers[runtimeInfo.LineNumber] : [];
+ List handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count)
+ ? lineMatchingHandlers[runtimeInfo.LineNumber]
+ : [];
foreach (StatementHandler handler in handlers)
{
@@ -425,13 +545,27 @@ public class YesNtInterpreter
{
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
}
+
if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
{
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
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 (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel))
@@ -446,12 +580,14 @@ public class YesNtInterpreter
{
runtimeInfo.Exit(ExitMessages.EndOfFile, false);
}
-
- if (runtimeInfo.IsDebugMode)
- {
- runtimeInfo.LineExecuted(null);
- }
}
+
+ if (runtimeInfo.IsDebugMode)
+ {
+ runtimeInfo.LineExecuted(null);
+ }
+
+ return StepResult.Finished;
}
private bool LoadFile(string path)