mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 00:56:31 +02:00
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>yesnt</id>
|
<id>yesnt</id>
|
||||||
<version>1.0.0.0</version>
|
<version>1.1.0.0</version>
|
||||||
<title>YesNt</title>
|
<title>YesNt</title>
|
||||||
<authors>Stone_Red</authors>
|
<authors>Stone_Red</authors>
|
||||||
<projectUrl>https://github.com/Stone-Red-Code/YesNt-Interpreter</projectUrl>
|
<projectUrl>https://github.com/Stone-Red-Code/YesNt-Interpreter</projectUrl>
|
||||||
|
|||||||
+84
-9
@@ -88,6 +88,47 @@ 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:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 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):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Run for up to 10 milliseconds
|
||||||
|
StepResult result = interpreter.RunFor(TimeSpan.FromMilliseconds(10));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the remaining script to completion:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
interpreter.RunToCompletion();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Capturing output (debug mode)
|
## Capturing output (debug mode)
|
||||||
|
|
||||||
Pass `isDebugMode: true` to suppress direct console writes. Output is delivered through the
|
Pass `isDebugMode: true` to suppress direct console writes. Output is delivered through the
|
||||||
@@ -186,12 +227,12 @@ interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End,
|
|||||||
args => Console.WriteLine($"[LOG] {args}"));
|
args => Console.WriteLine($"[LOG] {args}"));
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using a `StatementAttribute`
|
### Using a `StatementInformation`
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using YesNt.Interpreter.Attributes;
|
using YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
var attr = new StatementAttribute("log", SearchMode.StartOfLine, SpaceAround.End)
|
var attr = new StatementInformation("log", SearchMode.StartOfLine, SpaceAround.End)
|
||||||
{
|
{
|
||||||
Priority = Priority.VeryLow,
|
Priority = Priority.VeryLow,
|
||||||
};
|
};
|
||||||
@@ -218,7 +259,7 @@ interpreter.AddStatement(attr, args => Console.WriteLine($"[LOG] {args}"));
|
|||||||
| `StartEnd` | Spaces required on both sides |
|
| `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.
|
Custom statements run at `Priority.Normal` by default. Statements with a higher-ranking enum member (`PreProcessing` → `Highest` → … → `VeryLow`) run first; `VeryLow` runs last.
|
||||||
Use `StatementAttribute.Priority` to control ordering relative to built-in statements.
|
Use `StatementInformation.Priority` to control ordering relative to built-in statements.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -370,15 +411,30 @@ public event Action OnWaitingForInput;
|
|||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Execute a .ynt file
|
// Execute a .ynt file to completion
|
||||||
public void Execute(string path, bool isDebugMode = false);
|
public void Execute(string path, bool isDebugMode = false);
|
||||||
|
|
||||||
// Execute in-memory lines
|
// Execute in-memory lines to completion
|
||||||
public void Execute(List<string> lines, bool isDebugMode = false);
|
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)
|
// Register a custom statement (full control)
|
||||||
public void AddStatement(StatementAttribute attribute, Action<string> handler);
|
public void AddStatement(StatementInformation attribute, Action<string> handler);
|
||||||
public void AddStatement(StatementAttribute attribute, Action<string, IStatementContext> handler);
|
public void AddStatement(StatementInformation attribute, Action<string, IStatementContext> handler);
|
||||||
|
|
||||||
// Register a custom statement (convenience overloads)
|
// 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> handler);
|
||||||
@@ -404,6 +460,9 @@ public void Stop();
|
|||||||
```csharp
|
```csharp
|
||||||
// Read-only snapshot of all registered statements
|
// Read-only snapshot of all registered statements
|
||||||
public ReadOnlyCollection<StatementInformation> StatementInformation { get; }
|
public ReadOnlyCollection<StatementInformation> StatementInformation { get; }
|
||||||
|
|
||||||
|
// Whether a prepared script is currently active/running
|
||||||
|
public bool IsRunning { get; }
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -424,3 +483,19 @@ Provides access to the script state that a built-in statement handler would have
|
|||||||
| `CurrentLine` | `string` | The line being processed; write here for inline-substitution handlers |
|
| `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 |
|
| `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 |
|
| `Exit(message, isError)` | `void` | Terminate execution with a message; `isError: true` signals an error |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `StepResult`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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). |
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -27,8 +28,14 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods);
|
CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods);
|
||||||
|
|
||||||
string source = GenerateRegistrySource(statementMethods, staticStatementMethods);
|
INamedTypeSymbol? statementAttrSymbol = compilation.GetTypeByMetadataName(StatementAttributeName);
|
||||||
context.AddSource("GeneratedStatementRegistry.g.cs", source);
|
INamedTypeSymbol? staticStatementAttrSymbol = compilation.GetTypeByMetadataName(StaticStatementAttributeName);
|
||||||
|
|
||||||
|
string infoSource = GenerateInformationClassesSource(statementAttrSymbol, staticStatementAttrSymbol, compilation);
|
||||||
|
context.AddSource("GeneratedStatementInformation.g.cs", infoSource);
|
||||||
|
|
||||||
|
string registrySource = GenerateRegistrySource(statementMethods, staticStatementMethods);
|
||||||
|
context.AddSource("GeneratedStatementRegistry.g.cs", registrySource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CollectMethods(
|
private static void CollectMethods(
|
||||||
@@ -77,6 +84,302 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Information class generation
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static string GenerateInformationClassesSource(
|
||||||
|
INamedTypeSymbol? statementAttrSymbol,
|
||||||
|
INamedTypeSymbol? staticStatementAttrSymbol,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
_ = sb.AppendLine("// <auto-generated />");
|
||||||
|
_ = sb.AppendLine("#nullable enable");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
|
||||||
|
if (statementAttrSymbol is not null)
|
||||||
|
{
|
||||||
|
EmitInformationClass(sb, statementAttrSymbol, "StatementInformation", compilation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticStatementAttrSymbol is not null)
|
||||||
|
{
|
||||||
|
EmitInformationClass(sb, staticStatementAttrSymbol, "StaticStatementInformation", compilation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitInformationClass(
|
||||||
|
StringBuilder sb,
|
||||||
|
INamedTypeSymbol attributeSymbol,
|
||||||
|
string className,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
List<IMethodSymbol> ctors = attributeSymbol.Constructors
|
||||||
|
.Where(c => !c.IsImplicitlyDeclared)
|
||||||
|
.OrderByDescending(c => c.Parameters.Length)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// All parameters across all ctors that are covered by a ctor (get-only properties).
|
||||||
|
HashSet<string> ctorParamNames = new HashSet<string>(
|
||||||
|
ctors.SelectMany(c => c.Parameters).Select(p => p.Name),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Settable properties NOT covered by any ctor parameter.
|
||||||
|
List<IPropertySymbol> settableProps = attributeSymbol
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(p => !p.IsStatic
|
||||||
|
&& p.DeclaredAccessibility == Accessibility.Public
|
||||||
|
&& !ctorParamNames.Contains(p.Name))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// All properties ever assigned by any ctor, deduplicated by name.
|
||||||
|
// We need a get-only property for each of them.
|
||||||
|
var ctorProps = ctors
|
||||||
|
.SelectMany(c => c.Parameters)
|
||||||
|
.Select(p => {
|
||||||
|
string propName = ResolvePropertyName(attributeSymbol, p);
|
||||||
|
IPropertySymbol? propSymbol = attributeSymbol
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.FirstOrDefault(sym => string.Equals(sym.Name, propName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
return (PropName: propName, PropType: p.Type, PropSymbol: propSymbol);
|
||||||
|
})
|
||||||
|
.GroupBy(x => x.PropName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(g => g.First())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// ---- class declaration ----
|
||||||
|
_ = sb.Append(GetFormattedComment(attributeSymbol, attributeSymbol, className, $"Information about the {className} class.", ""));
|
||||||
|
_ = sb.AppendLine($"public sealed class {className}");
|
||||||
|
_ = sb.AppendLine("{");
|
||||||
|
|
||||||
|
// ---- one constructor per attribute constructor ----
|
||||||
|
foreach (IMethodSymbol ctor in ctors)
|
||||||
|
{
|
||||||
|
if (ctor.Parameters.Length == 0)
|
||||||
|
{
|
||||||
|
_ = sb.Append(GetFormattedComment(ctor, attributeSymbol, className, $"Initializes a new instance of the <see cref=\"{className}\"/> class.", " "));
|
||||||
|
_ = sb.AppendLine($" public {className}() {{ }}");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ctorParams = string.Join(", ",
|
||||||
|
ctor.Parameters.Select(p => $"{GlobalType(p.Type)} {p.Name}"));
|
||||||
|
|
||||||
|
_ = sb.Append(GetFormattedComment(ctor, attributeSymbol, className, $"Initializes a new instance of the <see cref=\"{className}\"/> class.", " "));
|
||||||
|
_ = sb.AppendLine($" public {className}({ctorParams})");
|
||||||
|
_ = sb.AppendLine(" {");
|
||||||
|
|
||||||
|
foreach (IParameterSymbol p in ctor.Parameters)
|
||||||
|
{
|
||||||
|
string propName = ResolvePropertyName(attributeSymbol, p);
|
||||||
|
_ = sb.AppendLine($" {propName} = {p.Name};");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = sb.AppendLine(" }");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- get-only properties sourced from ctor parameters ----
|
||||||
|
foreach (var ctorProp in ctorProps)
|
||||||
|
{
|
||||||
|
if (ctorProp.PropSymbol is not null)
|
||||||
|
{
|
||||||
|
_ = sb.Append(GetFormattedComment(ctorProp.PropSymbol, attributeSymbol, className, $"Gets the {ctorProp.PropName} property.", " "));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = sb.AppendLine(" /// <summary>");
|
||||||
|
_ = sb.AppendLine($" /// Gets the {ctorProp.PropName} property.");
|
||||||
|
_ = sb.AppendLine(" /// </summary>");
|
||||||
|
}
|
||||||
|
_ = sb.AppendLine($" public {GlobalType(ctorProp.PropType)} {ctorProp.PropName} {{ get; }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- settable properties (named-argument style) ----
|
||||||
|
foreach (IPropertySymbol prop in settableProps)
|
||||||
|
{
|
||||||
|
string defaultClause = GetDefaultClause(prop, compilation);
|
||||||
|
_ = sb.Append(GetFormattedComment(prop, attributeSymbol, className, $"Gets or sets the {prop.Name} property.", " "));
|
||||||
|
_ = sb.AppendLine($" public {GlobalType(prop.Type)} {prop.Name} {{ get; init; }}{defaultClause}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = sb.AppendLine("}");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFormattedComment(
|
||||||
|
ISymbol symbol,
|
||||||
|
INamedTypeSymbol attributeSymbol,
|
||||||
|
string className,
|
||||||
|
string fallbackSummary,
|
||||||
|
string indent = " ")
|
||||||
|
{
|
||||||
|
string? xml = symbol.GetDocumentationCommentXml();
|
||||||
|
if (!string.IsNullOrEmpty(xml))
|
||||||
|
{
|
||||||
|
xml = xml!.Replace(attributeSymbol.Name, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
string formatted = FormatXmlComment(xml, indent);
|
||||||
|
if (!string.IsNullOrWhiteSpace(formatted))
|
||||||
|
{
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
_ = sb.AppendLine($"{indent}/// <summary>");
|
||||||
|
_ = sb.AppendLine($"{indent}/// {fallbackSummary}");
|
||||||
|
_ = sb.AppendLine($"{indent}/// </summary>");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatXmlComment(string? xml, string indent)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(xml))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var element = System.Xml.Linq.XElement.Parse(xml!);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var child in element.Elements())
|
||||||
|
{
|
||||||
|
string nodeXml = child.ToString();
|
||||||
|
string[] lines = nodeXml.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
string trimmedLine = line.Trim();
|
||||||
|
if (trimmedLine.Length > 0)
|
||||||
|
{
|
||||||
|
_ = sb.AppendLine($"{indent}/// {trimmedLine}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the attribute property that corresponds to a constructor parameter.
|
||||||
|
private static string ResolvePropertyName(INamedTypeSymbol attributeSymbol, IParameterSymbol parameter)
|
||||||
|
{
|
||||||
|
IPropertySymbol? match = attributeSymbol
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.FirstOrDefault(p => string.Equals(p.Name, parameter.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return match?.Name ?? ToPascalCase(parameter.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GlobalType(ITypeSymbol type)
|
||||||
|
{
|
||||||
|
// Primitive aliases don't have a global:: form — emit the C# keyword instead.
|
||||||
|
string keyword = type.SpecialType switch
|
||||||
|
{
|
||||||
|
SpecialType.System_String => "string",
|
||||||
|
SpecialType.System_Boolean => "bool",
|
||||||
|
SpecialType.System_Byte => "byte",
|
||||||
|
SpecialType.System_SByte => "sbyte",
|
||||||
|
SpecialType.System_Int16 => "short",
|
||||||
|
SpecialType.System_UInt16 => "ushort",
|
||||||
|
SpecialType.System_Int32 => "int",
|
||||||
|
SpecialType.System_UInt32 => "uint",
|
||||||
|
SpecialType.System_Int64 => "long",
|
||||||
|
SpecialType.System_UInt64 => "ulong",
|
||||||
|
SpecialType.System_Single => "float",
|
||||||
|
SpecialType.System_Double => "double",
|
||||||
|
SpecialType.System_Decimal => "decimal",
|
||||||
|
SpecialType.System_Char => "char",
|
||||||
|
SpecialType.System_Object => "object",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
if (keyword != "")
|
||||||
|
{
|
||||||
|
// Preserve nullability annotation (e.g. string?)
|
||||||
|
return type.NullableAnnotation == NullableAnnotation.Annotated
|
||||||
|
? keyword + "?"
|
||||||
|
: keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"global::{type.ToDisplayString()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToPascalCase(string name)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(name) ? name : char.ToUpperInvariant(name[0]) + name.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetDefaultClause(
|
||||||
|
IPropertySymbol prop,
|
||||||
|
Compilation compilation)
|
||||||
|
{
|
||||||
|
foreach (SyntaxReference syntaxRef in prop.DeclaringSyntaxReferences)
|
||||||
|
{
|
||||||
|
if (syntaxRef.GetSyntax() is not Microsoft.CodeAnalysis.CSharp.Syntax.PropertyDeclarationSyntax syntax)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syntax.Initializer?.Value is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SemanticModel model = compilation.GetSemanticModel(syntax.SyntaxTree);
|
||||||
|
ExpressionSyntax expr = syntax.Initializer.Value;
|
||||||
|
|
||||||
|
// 1. Try constant evaluation first (SAFE)
|
||||||
|
Optional<object?> constant = model.GetConstantValue(expr);
|
||||||
|
if (constant.HasValue)
|
||||||
|
{
|
||||||
|
return $" = {ToLiteral(constant.Value!, prop.Type)};";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try symbol resolution (enum fields etc.)
|
||||||
|
ISymbol? symbolInfo = model.GetSymbolInfo(expr).Symbol;
|
||||||
|
|
||||||
|
if (symbolInfo is IFieldSymbol field)
|
||||||
|
{
|
||||||
|
string typeName = GlobalType(field.ContainingType);
|
||||||
|
return $" = {typeName}.{field.Name};";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. fallback: raw expression (last resort)
|
||||||
|
return $" = {expr};";
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback defaults
|
||||||
|
return prop.Type.IsReferenceType || prop.NullableAnnotation == NullableAnnotation.Annotated
|
||||||
|
? " = null!;"
|
||||||
|
: prop.Type.SpecialType switch
|
||||||
|
{
|
||||||
|
SpecialType.System_Boolean => " = false;",
|
||||||
|
SpecialType.System_Int32 => " = 0;",
|
||||||
|
SpecialType.System_String => " = \"\";",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registry source generation (unchanged from original)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
private static string GenerateRegistrySource(
|
private static string GenerateRegistrySource(
|
||||||
List<MethodRegistration> statementMethods,
|
List<MethodRegistration> statementMethods,
|
||||||
List<MethodRegistration> staticStatementMethods)
|
List<MethodRegistration> staticStatementMethods)
|
||||||
@@ -87,7 +390,6 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
_ = sb.AppendLine("#nullable enable");
|
_ = sb.AppendLine("#nullable enable");
|
||||||
_ = sb.AppendLine("using System;");
|
_ = sb.AppendLine("using System;");
|
||||||
_ = sb.AppendLine("using System.Collections.Generic;");
|
_ = sb.AppendLine("using System.Collections.Generic;");
|
||||||
_ = sb.AppendLine("using System.Linq;");
|
|
||||||
_ = sb.AppendLine();
|
_ = sb.AppendLine();
|
||||||
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
||||||
_ = sb.AppendLine();
|
_ = sb.AppendLine();
|
||||||
@@ -95,8 +397,8 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
_ = sb.AppendLine("{");
|
_ = sb.AppendLine("{");
|
||||||
_ = sb.AppendLine(" internal static void Register(");
|
_ = sb.AppendLine(" internal static void Register(");
|
||||||
_ = sb.AppendLine(" RuntimeInformation runtimeInfo,");
|
_ = sb.AppendLine(" RuntimeInformation runtimeInfo,");
|
||||||
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Attributes.StatementAttribute, Action<string>> statements,");
|
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>> statements,");
|
||||||
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Attributes.StaticStatementAttribute, Action>> staticStatements)");
|
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>> staticStatements)");
|
||||||
_ = sb.AppendLine(" {");
|
_ = sb.AppendLine(" {");
|
||||||
|
|
||||||
List<INamedTypeSymbol> allTypes = statementMethods
|
List<INamedTypeSymbol> allTypes = statementMethods
|
||||||
@@ -117,36 +419,39 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
_ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;");
|
_ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;");
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = sb.AppendLine(" var statementEntries = new List<KeyValuePair<global::YesNt.Interpreter.Attributes.StatementAttribute, Action<string>>>();");
|
_ = sb.AppendLine(" var statementEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>>();");
|
||||||
|
|
||||||
foreach (MethodRegistration method in statementMethods
|
foreach (MethodRegistration method in statementMethods
|
||||||
.OrderBy(x => x.ContainingType.ToDisplayString())
|
.OrderBy(x => x.ContainingType.ToDisplayString())
|
||||||
.ThenBy(x => x.Method.Name))
|
.ThenBy(x => x.Method.Name))
|
||||||
{
|
{
|
||||||
string instanceName = instanceNames[method.ContainingType];
|
string instanceName = instanceNames[method.ContainingType];
|
||||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StatementAttribute", method.Attribute);
|
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StatementInformation", method.Attribute);
|
||||||
_ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
_ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = sb.AppendLine(" var staticEntries = new List<KeyValuePair<global::YesNt.Interpreter.Attributes.StaticStatementAttribute, Action>>();");
|
_ = sb.AppendLine(" var staticEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>>();");
|
||||||
|
|
||||||
foreach (MethodRegistration method in staticStatementMethods
|
foreach (MethodRegistration method in staticStatementMethods
|
||||||
.OrderBy(x => x.ContainingType.ToDisplayString())
|
.OrderBy(x => x.ContainingType.ToDisplayString())
|
||||||
.ThenBy(x => x.Method.Name))
|
.ThenBy(x => x.Method.Name))
|
||||||
{
|
{
|
||||||
string instanceName = instanceNames[method.ContainingType];
|
string instanceName = instanceNames[method.ContainingType];
|
||||||
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StaticStatementAttribute", method.Attribute);
|
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StaticStatementInformation", method.Attribute);
|
||||||
_ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
_ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = sb.AppendLine(" statements = statementEntries");
|
_ = sb.AppendLine(" statementEntries.Sort((a, b) =>");
|
||||||
_ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)");
|
_ = sb.AppendLine(" {");
|
||||||
_ = sb.AppendLine(" .ThenByDescending(s => s.Key.Name.Length)");
|
_ = sb.AppendLine(" int cmp = a.Key.Priority.CompareTo(b.Key.Priority);");
|
||||||
_ = sb.AppendLine(" .ToDictionary(x => x.Key, x => x.Value);");
|
_ = sb.AppendLine(" return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);");
|
||||||
|
_ = sb.AppendLine(" });");
|
||||||
|
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>();");
|
||||||
|
_ = sb.AppendLine(" foreach (var entry in statementEntries)");
|
||||||
|
_ = sb.AppendLine(" statements.Add(entry.Key, entry.Value);");
|
||||||
_ = sb.AppendLine();
|
_ = sb.AppendLine();
|
||||||
_ = sb.AppendLine(" staticStatements = staticEntries");
|
_ = sb.AppendLine(" staticEntries.Sort((a, b) => a.Key.Priority.CompareTo(b.Key.Priority));");
|
||||||
_ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)");
|
_ = sb.AppendLine(" staticStatements = staticEntries;");
|
||||||
_ = sb.AppendLine(" .ToList();");
|
|
||||||
_ = sb.AppendLine(" }");
|
_ = sb.AppendLine(" }");
|
||||||
_ = sb.AppendLine("}");
|
_ = sb.AppendLine("}");
|
||||||
|
|
||||||
@@ -203,6 +508,38 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ToLiteral(object value, ITypeSymbol type)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
return "null!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.TypeKind == TypeKind.Enum)
|
||||||
|
{
|
||||||
|
string enumType = GlobalType(type);
|
||||||
|
|
||||||
|
// value is already boxed enum OR underlying integral type
|
||||||
|
long underlying = Convert.ToInt64(value);
|
||||||
|
|
||||||
|
// fallback: cast
|
||||||
|
return $"({enumType}){underlying}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.SpecialType switch
|
||||||
|
{
|
||||||
|
SpecialType.System_String => $"\"{EscapeString((string)value)}\"",
|
||||||
|
SpecialType.System_Char => $"'{EscapeChar((char)value)}'",
|
||||||
|
SpecialType.System_Boolean => (bool)value ? "true" : "false",
|
||||||
|
SpecialType.System_Int32 => ((int)value).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||||
|
SpecialType.System_Int64 => ((long)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "L",
|
||||||
|
SpecialType.System_Single => ((float)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "f",
|
||||||
|
SpecialType.System_Double => ((double)value).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||||
|
SpecialType.System_Decimal => ((decimal)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "m",
|
||||||
|
_ => value.ToString() ?? "null!"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static string EscapeString(string value)
|
private static string EscapeString(string value)
|
||||||
{
|
{
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
|
||||||
using YesNt.Interpreter.Enums;
|
using YesNt.Interpreter.Enums;
|
||||||
using YesNt.Interpreter.Runtime;
|
using YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ public class AddStatementTests
|
|||||||
|
|
||||||
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||||
{
|
{
|
||||||
StatementAttribute attr = new StatementAttribute("attr_cmd", SearchMode.StartOfLine, SpaceAround.End);
|
StatementInformation attr = new StatementInformation("attr_cmd", SearchMode.StartOfLine, SpaceAround.End);
|
||||||
interpreter.AddStatement(attr, _ =>
|
interpreter.AddStatement(attr, _ =>
|
||||||
{
|
{
|
||||||
handlerCalled = true;
|
handlerCalled = true;
|
||||||
@@ -282,11 +281,11 @@ public class AddStatementTests
|
|||||||
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||||
{
|
{
|
||||||
interpreter.AddStatement(
|
interpreter.AddStatement(
|
||||||
new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.High },
|
new StatementInformation("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.High },
|
||||||
_ => highPriorityOrder = callOrder++);
|
_ => highPriorityOrder = callOrder++);
|
||||||
|
|
||||||
interpreter.AddStatement(
|
interpreter.AddStatement(
|
||||||
new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal },
|
new StatementInformation("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal },
|
||||||
_ => normalPriorityOrder = callOrder++);
|
_ => normalPriorityOrder = callOrder++);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -364,7 +363,7 @@ public class AddStatementTests
|
|||||||
|
|
||||||
string? captured = null;
|
string? captured = null;
|
||||||
|
|
||||||
YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||||
{
|
{
|
||||||
interpreter.AddStatement("echo_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
|
interpreter.AddStatement("echo_var", SearchMode.StartOfLine, SpaceAround.End, (args, rt) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace YesNt.Interpreter.Runtime;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exposes the script runtime state accessible to custom statement handlers registered
|
/// Exposes the script runtime state accessible to custom statement handlers registered
|
||||||
/// via <see cref="YesNtInterpreter.AddStatement"/>.
|
/// via <see cref="YesNtInterpreter.AddStatement(YesNt.Interpreter.Runtime.StatementInformation, System.Action{string, YesNt.Interpreter.Runtime.IStatementContext})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStatementContext
|
public interface IStatementContext
|
||||||
{
|
{
|
||||||
@@ -24,9 +24,9 @@ public interface IStatementContext
|
|||||||
|
|
||||||
/// <summary>Terminates execution with the given message.</summary>
|
/// <summary>Terminates execution with the given message.</summary>
|
||||||
/// <param name="message">The message written to debug output.</param>
|
/// <param name="message">The message written to debug output.</param>
|
||||||
/// <param name="isError">
|
/// <param name="stopAllTasks">
|
||||||
/// <see langword="true"/> to signal an error termination;
|
/// If <see langword="true"/>, also terminates all tasks spawned by the <c>task</c> statement.
|
||||||
/// <see langword="false"/> for a planned, non-error termination.
|
/// If <see langword="false"/>, only terminates the current execution context (main script or individual task).
|
||||||
/// </param>
|
/// </param>
|
||||||
void Exit(string message, bool isError);
|
void Exit(string message, bool stopAllTasks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
using YesNt.Interpreter.Attributes;
|
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Runtime;
|
namespace YesNt.Interpreter.Runtime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pre-calculated statement handler information for faster matching.
|
/// Pre-calculated statement handler information for faster matching.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal record StatementHandler(StatementAttribute Attribute, Action<string> Handler, string FullName);
|
internal record StatementHandler(StatementInformation Attribute, Action<string> Handler, string FullName);
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
using YesNt.Interpreter.Enums;
|
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Runtime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A read-only snapshot of a registered statement's metadata, used for tooling such as
|
|
||||||
/// syntax highlighters. Instances are obtained from <see cref="YesNtInterpreter.StatementInformation"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class StatementInformation
|
|
||||||
{
|
|
||||||
/// <summary>Gets the keyword that identifies this statement in source code.</summary>
|
|
||||||
public string Name { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>Gets where in the line the keyword is searched for.</summary>
|
|
||||||
public SearchMode SearchMode { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>Gets which sides of the keyword must be padded with a space.</summary>
|
|
||||||
public SpaceAround SpaceAround { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>Gets the syntax-highlight color for this statement.</summary>
|
|
||||||
public ConsoleColor Color { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this statement is excluded from syntax highlighting.
|
|
||||||
/// </summary>
|
|
||||||
public bool IgnoreSyntaxHighlighting { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the optional sub-string that must be present in the line for this statement to match,
|
|
||||||
/// or <see langword="null"/> if no separator is required.
|
|
||||||
/// </summary>
|
|
||||||
public string Separator { get; set; }
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -28,36 +29,24 @@ public class YesNtInterpreter
|
|||||||
public event Action<string> OnDebugOutput;
|
public event Action<string> OnDebugOutput;
|
||||||
|
|
||||||
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
||||||
private Dictionary<StatementAttribute, Action<string>> statements;
|
private Dictionary<StatementInformation, Action<string>> statements;
|
||||||
private List<StatementHandler> statementHandlers;
|
private List<StatementHandler> statementHandlers;
|
||||||
private List<List<StatementHandler>> lineMatchingHandlers = [];
|
private List<List<StatementHandler>> lineMatchingHandlers = [];
|
||||||
private readonly List<KeyValuePair<StaticStatementAttribute, Action>> staticStatements;
|
private readonly List<KeyValuePair<StaticStatementInformation, Action>> staticStatements;
|
||||||
private readonly Dictionary<string, List<KeyValuePair<StatementAttribute, Action<string>>>> disabledStatements = [];
|
private readonly Dictionary<string, List<KeyValuePair<StatementInformation, Action<string>>>> disabledStatements = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only snapshot of all currently registered statements.
|
/// Gets a read-only snapshot of all currently registered statements.
|
||||||
/// Useful for building syntax highlighters or documentation tools.
|
/// Useful for building syntax highlighters or documentation tools.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyCollection<StatementInformation> StatementInformation
|
public ReadOnlyCollection<StatementInformation> StatementInformation => statements.Keys.ToList().AsReadOnly();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
List<StatementInformation> information = statements.Select(s =>
|
|
||||||
{
|
|
||||||
return new StatementInformation()
|
|
||||||
{
|
|
||||||
Name = s.Key.Name,
|
|
||||||
SearchMode = s.Key.SearchMode,
|
|
||||||
SpaceAround = s.Key.SpaceAround,
|
|
||||||
Color = s.Key.Color,
|
|
||||||
IgnoreSyntaxHighlighting = s.Key.IgnoreSyntaxHighlighting,
|
|
||||||
Separator = s.Key.Separator
|
|
||||||
};
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return new ReadOnlyCollection<StatementInformation>(information);
|
/// <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.
|
||||||
@@ -88,7 +77,7 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>.
|
/// Registers a custom statement using a pre-built <see cref="StatementInformation"/>.
|
||||||
/// If a statement with the same attribute key (identical field values) already exists it will be replaced;
|
/// If a statement with the same attribute key (identical field values) already exists it will be replaced;
|
||||||
/// otherwise a new entry is added. Built-in statements use distinct attribute instances, so passing a
|
/// otherwise a new entry is added. Built-in statements use distinct attribute instances, so passing a
|
||||||
/// newly constructed attribute with the same name will <b>add</b> a second handler rather than replacing
|
/// newly constructed attribute with the same name will <b>add</b> a second handler rather than replacing
|
||||||
@@ -98,15 +87,26 @@ public class YesNtInterpreter
|
|||||||
/// <param name="attribute">The attribute describing the keyword, search mode, and priority.</param>
|
/// <param name="attribute">The attribute describing the keyword, search mode, and priority.</param>
|
||||||
/// <param name="handler">
|
/// <param name="handler">
|
||||||
/// The delegate invoked when the statement matches. Receives the argument text
|
/// The delegate invoked when the statement matches. Receives the argument text
|
||||||
/// (the part of the line after the keyword, unless <see cref="StatementAttribute.KeepStatementInArgs"/> is set).
|
/// (the part of the line after the keyword, unless <see cref="StatementInformation.KeepStatementInArgs"/> is set).
|
||||||
/// </param>
|
/// </param>
|
||||||
public void AddStatement(StatementAttribute attribute, Action<string> handler)
|
public void AddStatement(StatementInformation attribute, Action<string> handler)
|
||||||
{
|
{
|
||||||
statements[attribute] = handler;
|
statements[attribute] = handler;
|
||||||
statements = statements
|
|
||||||
.OrderBy(s => s.Key.Priority)
|
List<KeyValuePair<StatementInformation, Action<string>>> entries = [.. statements];
|
||||||
.ThenByDescending(s => s.Key.Name.Length)
|
entries.Sort((a, b) =>
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
{
|
||||||
|
int cmp = a.Key.Priority.CompareTo(b.Key.Priority);
|
||||||
|
return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);
|
||||||
|
});
|
||||||
|
|
||||||
|
statements = [];
|
||||||
|
|
||||||
|
foreach (KeyValuePair<StatementInformation, Action<string>> entry in entries)
|
||||||
|
{
|
||||||
|
statements.Add(entry.Key, entry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
UpdateStatementHandlers();
|
UpdateStatementHandlers();
|
||||||
PreScanLines();
|
PreScanLines();
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ public class YesNtInterpreter
|
|||||||
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
||||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||||
/// </param>
|
/// </param>
|
||||||
public void AddStatement(StatementAttribute attribute, Action<string, IStatementContext> handler)
|
public void AddStatement(StatementInformation attribute, Action<string, IStatementContext> handler)
|
||||||
{
|
{
|
||||||
AddStatement(attribute, args => handler(args, runtimeInfo));
|
AddStatement(attribute, args => handler(args, runtimeInfo));
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ public class YesNtInterpreter
|
|||||||
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
||||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
||||||
{
|
{
|
||||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -150,7 +150,7 @@ public class YesNtInterpreter
|
|||||||
/// </param>
|
/// </param>
|
||||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string, IStatementContext> handler)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string, IStatementContext> handler)
|
||||||
{
|
{
|
||||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -163,7 +163,7 @@ public class YesNtInterpreter
|
|||||||
/// <param name="handler">The delegate invoked when the statement matches.</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)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string> handler)
|
||||||
{
|
{
|
||||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround, consoleColor), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -180,7 +180,7 @@ public class YesNtInterpreter
|
|||||||
/// </param>
|
/// </param>
|
||||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string, IStatementContext> handler)
|
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string, IStatementContext> handler)
|
||||||
{
|
{
|
||||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround, consoleColor), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -189,7 +189,7 @@ public class YesNtInterpreter
|
|||||||
/// <param name="name">The keyword to remove.</param>
|
/// <param name="name">The keyword to remove.</param>
|
||||||
public void RemoveStatement(string name)
|
public void RemoveStatement(string name)
|
||||||
{
|
{
|
||||||
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
|
foreach (StatementInformation key in statements.Keys.Where(k => k.Name == name).ToList())
|
||||||
{
|
{
|
||||||
_ = statements.Remove(key);
|
_ = statements.Remove(key);
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ public class YesNtInterpreter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<KeyValuePair<StatementAttribute, Action<string>>> matching =
|
List<KeyValuePair<StatementInformation, Action<string>>> matching =
|
||||||
statements.Where(kv => kv.Key.Name == name).ToList();
|
statements.Where(kv => kv.Key.Name == name).ToList();
|
||||||
|
|
||||||
if (matching.Count == 0)
|
if (matching.Count == 0)
|
||||||
@@ -222,7 +222,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
disabledStatements[name] = matching;
|
disabledStatements[name] = matching;
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttribute, Action<string>> kv in matching)
|
foreach (KeyValuePair<StatementInformation, Action<string>> kv in matching)
|
||||||
{
|
{
|
||||||
statements[kv.Key] = _ => { };
|
statements[kv.Key] = _ => { };
|
||||||
}
|
}
|
||||||
@@ -238,12 +238,12 @@ public class YesNtInterpreter
|
|||||||
/// <param name="name">The keyword of the statement(s) to re-enable.</param>
|
/// <param name="name">The keyword of the statement(s) to re-enable.</param>
|
||||||
public void EnableStatement(string name)
|
public void EnableStatement(string name)
|
||||||
{
|
{
|
||||||
if (!disabledStatements.TryGetValue(name, out List<KeyValuePair<StatementAttribute, Action<string>>> saved))
|
if (!disabledStatements.TryGetValue(name, out List<KeyValuePair<StatementInformation, Action<string>>> saved))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttribute, Action<string>> kv in saved)
|
foreach (KeyValuePair<StatementInformation, Action<string>> kv in saved)
|
||||||
{
|
{
|
||||||
statements[kv.Key] = kv.Value;
|
statements[kv.Key] = kv.Value;
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,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">
|
||||||
@@ -272,35 +318,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)
|
||||||
@@ -311,22 +344,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];
|
||||||
@@ -334,9 +443,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)
|
||||||
{
|
{
|
||||||
@@ -349,10 +460,9 @@ public class YesNtInterpreter
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<StaticStatementAttribute, Action> staticStatement in staticStatements)
|
foreach (KeyValuePair<StaticStatementInformation, Action> staticStatement in staticStatements)
|
||||||
{
|
{
|
||||||
StaticStatementAttribute staticStatementAttribute = staticStatement.Key;
|
if (!staticStatement.Key.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -363,11 +473,13 @@ 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)
|
||||||
{
|
{
|
||||||
StatementAttribute statementAttribute = handler.Attribute;
|
StatementInformation statementAttribute = handler.Attribute;
|
||||||
|
|
||||||
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
{
|
{
|
||||||
@@ -414,13 +526,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))
|
||||||
@@ -435,12 +561,14 @@ public class YesNtInterpreter
|
|||||||
{
|
{
|
||||||
runtimeInfo.Exit(ExitMessages.EndOfFile, false);
|
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)
|
private bool LoadFile(string path)
|
||||||
@@ -534,7 +662,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
private static bool IsPossibleMatch(string content, StatementHandler handler)
|
private static bool IsPossibleMatch(string content, StatementHandler handler)
|
||||||
{
|
{
|
||||||
StatementAttribute attr = handler.Attribute;
|
StatementInformation attr = handler.Attribute;
|
||||||
string fullName = handler.FullName;
|
string fullName = handler.FullName;
|
||||||
|
|
||||||
return attr.SearchMode switch
|
return attr.SearchMode switch
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ internal class ConsoleStatements : StatementRuntimeInformation
|
|||||||
RuntimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true);
|
RuntimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
args = args.ReplaceFirstOccurrence("%read_line ", input.ToSafeString() + " ");
|
args = args.ReplaceFirstOccurrence("%read_line", input.ToSafeString());
|
||||||
}
|
}
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ internal class ConsoleStatements : StatementRuntimeInformation
|
|||||||
while (args.Contains("%read_key"))
|
while (args.Contains("%read_key"))
|
||||||
{
|
{
|
||||||
string input = ConsoleExtensions.ReadKey(RuntimeInfo).ToString();
|
string input = ConsoleExtensions.ReadKey(RuntimeInfo).ToString();
|
||||||
args = args.ReplaceFirstOccurrence("%read_key ", input.ToSafeString() + " ");
|
args = args.ReplaceFirstOccurrence("%read_key", input.ToSafeString());
|
||||||
}
|
}
|
||||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ namespace YesNt.Interpreter.Utilities;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides high-performance template substitution for variables and stack parameters.
|
/// Provides high-performance template substitution for variables and stack parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class TemplateProcessor
|
public static class TemplateProcessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces all occurrences of ${variableName} with their current values.
|
/// Replaces all occurrences of ${variableName} with their current values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ProcessVariables(string input, RuntimeInformation runtimeInfo)
|
internal static string ProcessVariables(string input, RuntimeInformation runtimeInfo)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
{
|
{
|
||||||
@@ -76,7 +76,7 @@ internal static class TemplateProcessor
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces all occurrences of a placeholder (e.g., %in, %out) with values popped from a stack.
|
/// Replaces all occurrences of a placeholder (e.g., %in, %out) with values popped from a stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ProcessStackParameters(string input, string placeholder, Stack<string> stack, RuntimeInformation runtimeInfo, string emptyStackMessage)
|
internal static string ProcessStackParameters(string input, string placeholder, Stack<string> stack, RuntimeInformation runtimeInfo, string emptyStackMessage)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
{
|
{
|
||||||
@@ -153,7 +153,7 @@ internal static class TemplateProcessor
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces all occurrences of arithmetic expressions with their results.
|
/// Replaces all occurrences of arithmetic expressions with their results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ProcessCalculations(string input, RuntimeInformation runtimeInfo, Regex calculationRegex)
|
internal static string ProcessCalculations(string input, RuntimeInformation runtimeInfo, Regex calculationRegex)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<PackageIcon>Logo.png</PackageIcon>
|
<PackageIcon>Logo.png</PackageIcon>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
<Version>1.1.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
Reference in New Issue
Block a user