16 KiB
YesNt Library API
The YesNt.Interpreter project is a .NET 10 class library. You can reference it from any C# project
to embed the YesNt interpreter and run scripts programmatically.
Table of contents
- Adding the reference
- Running a script file
- Running an in-memory script
- Capturing output (debug mode)
- Adding custom statements
- Removing and disabling built-in statements
- Stopping a script
- Reading registered statements
- API reference
Adding the reference
Via NuGet (recommended)
Install the YesNt.Interpreter package using one of the following methods.
.NET CLI:
dotnet add package YesNt.Interpreter
Package Manager Console:
Install-Package YesNt.Interpreter
.csproj:
<ItemGroup>
<PackageReference Include="YesNt.Interpreter" Version="*" />
</ItemGroup>
Via project reference
If you have cloned the repository and want to reference the source directly, add a project reference in your .csproj:
<ItemGroup>
<ProjectReference Include="..\YesNt.Interpreter\YesNt.Interpreter.csproj" />
</ItemGroup>
Then add the using directive:
using YesNt.Interpreter.Runtime;
Running a script file
var interpreter = new YesNtInterpreter();
interpreter.Execute("path/to/script.ynt");
Execute is synchronous - it returns when the script finishes (or terminates with an error).
Running an in-memory script
Supply a List<string> instead of a file path:
var lines = new List<string>
{
"var x = 42",
"print_line The answer is ${x}",
};
var interpreter = new YesNtInterpreter();
interpreter.Execute(lines);
Stepwise and time-budgeted execution
For interactive environments, game loops, or time-sliced applications, you can prepare a script and run it incrementally.
Preparing the interpreter
Call Prepare instead of Execute to load the script without running it immediately:
var interpreter = new YesNtInterpreter();
interpreter.Prepare("path/to/script.ynt");
// or: interpreter.Prepare(lines);
Driving execution
Use IsRunning to check if there are more lines to execute, and step by line count or run with a time budget:
// Execute 5 lines of the script
StepResult result = interpreter.Step(5);
if (result == StepResult.Paused)
{
// The line budget was exhausted; resume execution later
}
Or run the interpreter with a wall-clock time limit (useful for preventing freezing in game loops):
// Run for up to 10 milliseconds
StepResult result = interpreter.RunFor(TimeSpan.FromMilliseconds(10));
You can also run the remaining script to completion:
interpreter.RunToCompletion();
Capturing output (debug mode)
Pass isDebugMode: true to suppress direct console writes. Output is delivered through the
OnDebugOutput event instead, and OnLineExecuted fires after executed lines.
var interpreter = new YesNtInterpreter();
var output = new System.Text.StringBuilder();
interpreter.OnDebugOutput += text => output.Append(text);
interpreter.OnLineExecuted += args =>
{
if (args is null)
{
// null means execution reached end-of-file (EOF)
Console.WriteLine("Script reached EOF.");
return;
}
Console.WriteLine($"Line {args.LineNumber}: {args.CurrentLine}");
};
interpreter.Execute(new List<string> { "print_line hello" }, isDebugMode: true);
Console.Write(output);
OnLineExecuted receives a DebugEventArgs with:
| Property | Type | Description |
|---|---|---|
LineNumber |
int |
1-based line number |
OriginalLine |
string |
Raw source text |
CurrentLine |
string |
Text after all substitutions |
IsTask |
bool |
true if executed inside a background task |
TaskId |
int |
Task identifier (0 for the main thread) |
OnLineExecuted is invoked with null only when execution reaches end-of-file (EOF).
If execution stops via exit, throw, error, or Stop(), no terminal null event is emitted.
Adding custom statements
Use AddStatement to register keywords before calling Execute.
Simple keyword at the start of a line
using YesNt.Interpreter.Enums;
var interpreter = new YesNtInterpreter();
interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End, args =>
{
Console.WriteLine($"[LOG] {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.
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
interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End,
ConsoleColor.Cyan,
args => Console.WriteLine($"[LOG] {args}"));
Using a StatementInformation
using YesNt.Interpreter.Runtime;
var attr = new StatementInformation("log", SearchMode.StartOfLine, SpaceAround.End)
{
Priority = Priority.VeryLow,
};
interpreter.AddStatement(attr, args => Console.WriteLine($"[LOG] {args}"));
SearchMode values
| Value | The keyword matches when… |
|---|---|
StartOfLine |
the line starts with the keyword |
EndOfLine |
the line ends with the keyword |
Contains |
the keyword appears anywhere in the line |
Exact |
the line is exactly the keyword |
SpaceAround values
| Value | Space requirement |
|---|---|
None |
No surrounding spaces required |
Start |
A space must precede the keyword |
End |
A space must follow the keyword |
StartEnd |
Spaces required on both sides |
Custom statements run at Priority.Normal by default. Statements with a higher-ranking enum member (PreProcessing → Highest → … → VeryLow) run first; VeryLow runs last.
Use StatementInformation.Priority to control ordering relative to built-in statements.
Removing and disabling built-in statements
Use these methods to restrict which built-in keywords are available, useful for sandboxing or replacing a built-in with a custom implementation.
RemoveStatement - permanent removal
Removes all handlers for the given keyword. Any script line that would have matched the keyword now triggers an "Invalid statement" error.
var interpreter = new YesNtInterpreter();
// Prevent scripts from launching external processes.
interpreter.RemoveStatement("exec");
interpreter.Execute(new List<string> { "exec notepad" });
// Terminates with: Invalid statement
DisableStatement - silent no-op
Disables all handlers for the keyword. The keyword still matches (so no error is raised),
but has no effect. Use EnableStatement to restore the original behaviour.
var interpreter = new YesNtInterpreter();
// Make sleep a no-op so tests don't actually wait.
interpreter.DisableStatement("sleep");
interpreter.Execute(new List<string>
{
"sleep 10000", // does nothing
"var x = done",
"print_line ${x}", // prints: done
});
EnableStatement - restore a disabled statement
Restores the original handlers saved when DisableStatement was called.
Has no effect if the statement is not currently disabled.
interpreter.DisableStatement("sleep");
// ... configure other things ...
interpreter.EnableStatement("sleep"); // sleep works normally again
Replacing a built-in statement
Call RemoveStatement to remove the built-in handlers, then AddStatement to install your own.
Simply calling AddStatement with the same keyword name will not replace the built-in statement,
it will add a second handler that fires alongside the original.
// Replace the built-in 'exec' with a sandboxed version that only allows 'echo'.
interpreter.RemoveStatement("exec");
interpreter.AddStatement("exec", SearchMode.StartOfLine, SpaceAround.End, args =>
{
if (args.Trim() != "echo")
throw new InvalidOperationException("exec is restricted");
System.Diagnostics.Process.Start("cmd", "/c echo (sandboxed)");
});
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).
var interpreter = new YesNtInterpreter();
// Start the script on a background thread so we can stop it from this thread.
var task = Task.Run(() =>
interpreter.Execute(new List<string> { "while True:", "sleep 100", "end_while" }));
await Task.Delay(500);
interpreter.Stop(); // signals the script to terminate at the next line boundary
await task;
Reading registered statements
StatementInformation returns a read-only snapshot of every registered statement.
This is useful for building syntax highlighters or tooling.
var interpreter = new YesNtInterpreter();
foreach (var info in interpreter.StatementInformation)
{
Console.WriteLine($"{info.Name,-20} {info.SearchMode,-12} color={info.Color}");
}
Each StatementInformation object exposes:
| Property | Type | Description |
|---|---|---|
Name |
string |
The keyword |
SearchMode |
SearchMode |
Where the keyword is matched |
SpaceAround |
SpaceAround |
Required surrounding spaces |
Color |
ConsoleColor |
Syntax-highlight colour |
IgnoreSyntaxHighlighting |
bool |
Whether to skip highlighting |
Separator |
string? |
Optional required sub-string |
API reference
YesNtInterpreter
public class YesNtInterpreter
Constructor
public YesNtInterpreter()
Creates a new interpreter instance and registers all built-in statements.
Events
public event Action<string> OnDebugOutput;
public event Action<DebugEventArgs> OnLineExecuted;
public event Action OnWaitingForInput;
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
// Execute a .ynt file to completion
public void Execute(string path, bool isDebugMode = false);
// Execute in-memory lines to completion
public void Execute(IEnumerable<string> lines, bool isDebugMode = false);
// Prepare a script file for stepwise execution
public void Prepare(string path, bool isDebugMode = false);
// Prepare in-memory lines for stepwise execution
public void Prepare(IEnumerable<string> lines, bool isDebugMode = false);
// Execute up to standard line count then pause
public StepResult Step(int lines = 1);
// Run the script for up to budget duration then pause
public StepResult RunFor(TimeSpan budget);
// Execute the remaining script lines to completion
public void RunToCompletion();
// Register a custom statement (full control)
public void AddStatement(StatementInformation attribute, Action<string> handler);
public void AddStatement(StatementInformation 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);
// Disable a statement (silent no-op; reversible)
public void DisableStatement(string name);
// Re-enable a previously disabled statement
public void EnableStatement(string name);
// Request graceful stop
public void Stop();
Properties
// Read-only snapshot of all registered statements
public ReadOnlyCollection<StatementInformation> StatementInformation { get; }
// Whether a prepared script is currently active/running
public bool IsRunning { get; }
IStatementContext
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 |
StepResult
public enum StepResult // YesNt.Interpreter.Runtime
Returned by Step and RunFor to indicate the outcome of the incremental execution.
| Value | Description |
|---|---|
Continue |
A line was executed and more lines remain. |
Paused |
The step or time budget was exhausted before the script ended. |
Finished |
The script ran to completion (or terminated/exited). |