mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
AddStatement now passes runtime context for custom handlers
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.Interpreter.Tests;
|
||||
|
||||
@@ -333,4 +334,44 @@ public class AddStatementTests
|
||||
interpreter.AddStatement("var", SearchMode.StartOfLine, SpaceAround.End, _ => { });
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddStatementWithRuntimeInfoCanSetVariableTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"set_var foo",
|
||||
"${foo}"
|
||||
];
|
||||
|
||||
YesNtAssert.IsLastLineEqualWithSetup(lines, "hello", interpreter =>
|
||||
{
|
||||
interpreter.AddStatement("set_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
|
||||
{
|
||||
rt.Variables[args] = "hello";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddStatementWithRuntimeInfoCanReadVariableTest()
|
||||
{
|
||||
List<string> lines =
|
||||
[
|
||||
"var greeting = world",
|
||||
"echo_var greeting"
|
||||
];
|
||||
|
||||
string? captured = null;
|
||||
|
||||
YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||
{
|
||||
interpreter.AddStatement("echo_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
|
||||
{
|
||||
_ = rt.Variables.TryGetValue(args, out captured);
|
||||
});
|
||||
});
|
||||
|
||||
Assert.AreEqual("world", captured);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the script runtime state accessible to custom statement handlers registered
|
||||
/// via <see cref="YesNtInterpreter.AddStatement"/>.
|
||||
/// </summary>
|
||||
public interface IStatementContext
|
||||
{
|
||||
/// <summary>Gets the local variable table for the current scope.</summary>
|
||||
Dictionary<string, string> Variables { get; }
|
||||
|
||||
/// <summary>Gets or sets the global variable table shared across all scopes.</summary>
|
||||
Dictionary<string, string> GlobalVariables { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the text of the line currently being processed.
|
||||
/// Inline-substitution handlers (e.g. <c>%read_line</c>) write their result here.</summary>
|
||||
string CurrentLine { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the zero-based index of the next line to execute.
|
||||
/// Set this to implement control-flow jumps inside a custom statement.</summary>
|
||||
int LineNumber { get; set; }
|
||||
|
||||
/// <summary>Terminates execution with the given message.</summary>
|
||||
/// <param name="message">The message written to debug output.</param>
|
||||
/// <param name="isError">
|
||||
/// <see langword="true"/> to signal an error termination;
|
||||
/// <see langword="false"/> for a planned, non-error termination.
|
||||
/// </param>
|
||||
void Exit(string message, bool isError);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace YesNt.Interpreter.Runtime;
|
||||
/// <see cref="RuntimeInformation"/> whose <see cref="ParentRuntimeInformation"/> points back
|
||||
/// to the main execution context.
|
||||
/// </summary>
|
||||
internal sealed class RuntimeInformation
|
||||
internal sealed class RuntimeInformation : IStatementContext
|
||||
{
|
||||
public event Action<string> OnDebugOutput;
|
||||
|
||||
|
||||
@@ -111,6 +111,20 @@ public class YesNtInterpreter
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute describing the keyword, search mode, and priority.</param>
|
||||
/// <param name="handler">
|
||||
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(StatementAttribute attribute, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(attribute, args => handler(args, runtimeInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with default settings.
|
||||
/// </summary>
|
||||
@@ -123,6 +137,22 @@ public class YesNtInterpreter
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with default settings,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <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. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with a specific syntax-highlight color.
|
||||
/// </summary>
|
||||
@@ -136,6 +166,23 @@ public class YesNtInterpreter
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with a specific syntax-highlight color,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <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. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters all handlers matching the specified keyword <paramref name="name"/>.
|
||||
/// </summary>
|
||||
|
||||
+82
-2
@@ -124,6 +124,37 @@ interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End, args =>
|
||||
interpreter.Execute(new List<string> { "log Hello from custom statement" });
|
||||
```
|
||||
|
||||
### Accessing script state from a handler
|
||||
|
||||
Pass an `Action<string, IStatementContext>` instead of `Action<string>` to receive the current
|
||||
script state. `IStatementContext` exposes the variable tables, the current line, the line number,
|
||||
and the ability to terminate execution.
|
||||
|
||||
```csharp
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
interpreter.AddStatement("set_var", SearchMode.StartOfLine, SpaceAround.End,
|
||||
(args, ctx) =>
|
||||
{
|
||||
// args is e.g. "result 42" — parse however your syntax demands
|
||||
string[] parts = args.Split(' ', 2);
|
||||
if (parts.Length == 2)
|
||||
ctx.Variables[parts[0]] = parts[1];
|
||||
else
|
||||
ctx.Exit("set_var requires: <name> <value>", isError: true);
|
||||
});
|
||||
```
|
||||
|
||||
`IStatementContext` provides:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------------------ | --------------------------- | -------------------------------------------------------------------------- |
|
||||
| `Variables` | `Dictionary<string,string>` | Local variable table for the current scope |
|
||||
| `GlobalVariables` | `Dictionary<string,string>` | Global variable table shared across all scopes |
|
||||
| `CurrentLine` | `string` | The line text being processed; write here for inline-substitution handlers |
|
||||
| `LineNumber` | `int` | Zero-based index of the _next_ line to execute; set to implement jumps |
|
||||
| `Exit(message, isError)` | `void` | Terminate execution; `isError: true` signals an error condition |
|
||||
|
||||
### With a syntax-highlight colour
|
||||
|
||||
```csharp
|
||||
@@ -240,6 +271,9 @@ interpreter.AddStatement("exec", SearchMode.StartOfLine, SpaceAround.End, args =
|
||||
|
||||
## Stopping a script
|
||||
|
||||
Call `Stop()` from any thread to request graceful termination. The script stops at the next line
|
||||
boundary (or immediately if it is currently blocked waiting for console input).
|
||||
|
||||
```csharp
|
||||
var interpreter = new YesNtInterpreter();
|
||||
|
||||
@@ -253,6 +287,27 @@ interpreter.Stop(); // signals the script to terminate at the next line bounda
|
||||
thread.Join();
|
||||
```
|
||||
|
||||
### Stopping a script that blocks on `%read_key`
|
||||
|
||||
When a script blocks waiting for keyboard input, use the `OnWaitingForInput` event instead of a
|
||||
fixed `Thread.Sleep`. The event fires at the exact moment the interpreter enters the blocking poll
|
||||
loop, so calling `Stop()` immediately after is always safe regardless of system load.
|
||||
|
||||
```csharp
|
||||
var interpreter = new YesNtInterpreter();
|
||||
var waitingForInput = new System.Threading.AutoResetEvent(false);
|
||||
|
||||
interpreter.OnWaitingForInput += () => waitingForInput.Set();
|
||||
|
||||
var thread = new System.Threading.Thread(() =>
|
||||
interpreter.Execute(new List<string> { "var key = %read_key" }));
|
||||
|
||||
thread.Start();
|
||||
waitingForInput.WaitOne(TimeSpan.FromSeconds(5)); // wait until blocked on input
|
||||
interpreter.Stop();
|
||||
thread.Join();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reading registered statements
|
||||
@@ -301,12 +356,15 @@ Creates a new interpreter instance and registers all built-in statements.
|
||||
#### Events
|
||||
|
||||
```csharp
|
||||
public event Action<string> OnDebugOutput;
|
||||
public event Action<string> OnDebugOutput;
|
||||
public event Action<DebugEventArgs> OnLineExecuted;
|
||||
public event Action OnWaitingForInput;
|
||||
```
|
||||
|
||||
Only raised in debug mode (`isDebugMode: true`).
|
||||
`OnDebugOutput` and `OnLineExecuted` are only raised in debug mode (`isDebugMode: true`).
|
||||
`OnLineExecuted` receives `null` only on EOF completion.
|
||||
`OnWaitingForInput` is raised (in any mode) immediately before the interpreter blocks on
|
||||
`%read_key`. Use it to call `Stop()` deterministically without relying on `Thread.Sleep`.
|
||||
|
||||
#### Methods
|
||||
|
||||
@@ -319,10 +377,13 @@ public void Execute(List<string> lines, bool isDebugMode = false);
|
||||
|
||||
// Register a custom statement (full control)
|
||||
public void AddStatement(StatementAttribute attribute, Action<string> handler);
|
||||
public void AddStatement(StatementAttribute attribute, Action<string, IStatementContext> handler);
|
||||
|
||||
// Register a custom statement (convenience overloads)
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler);
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string, IStatementContext> handler);
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color, Action<string> handler);
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color, Action<string, IStatementContext> handler);
|
||||
|
||||
// Remove a built-in or custom statement permanently
|
||||
public void RemoveStatement(string name);
|
||||
@@ -343,3 +404,22 @@ public void Stop();
|
||||
// Read-only snapshot of all registered statements
|
||||
public ReadOnlyCollection<StatementInformation> StatementInformation { get; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `IStatementContext`
|
||||
|
||||
```csharp
|
||||
public interface IStatementContext // YesNt.Interpreter.Runtime
|
||||
```
|
||||
|
||||
Passed to `Action<string, IStatementContext>` handlers registered via `AddStatement`.
|
||||
Provides access to the script state that a built-in statement handler would have.
|
||||
|
||||
| Member | Type | Description |
|
||||
| ------------------------ | --------------------------- | ------------------------------------------------------------------------- |
|
||||
| `Variables` | `Dictionary<string,string>` | Local variable table for the current scope |
|
||||
| `GlobalVariables` | `Dictionary<string,string>` | Global variable table shared across all scopes |
|
||||
| `CurrentLine` | `string` | The line being processed; write here for inline-substitution handlers |
|
||||
| `LineNumber` | `int` | Zero-based index of the next line to execute; set this to implement jumps |
|
||||
| `Exit(message, isError)` | `void` | Terminate execution with a message; `isError: true` signals an error |
|
||||
|
||||
Reference in New Issue
Block a user