mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 09:06:41 +02:00
Generate StatementInformation classes with code generator
This commit is contained in:
@@ -27,8 +27,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);
|
||||||
|
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 +83,187 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Information class generation
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static string GenerateInformationClassesSource(
|
||||||
|
INamedTypeSymbol? statementAttrSymbol,
|
||||||
|
INamedTypeSymbol? staticStatementAttrSymbol)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticStatementAttrSymbol is not null)
|
||||||
|
{
|
||||||
|
EmitInformationClass(sb, staticStatementAttrSymbol, "StaticStatementInformation");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitInformationClass(
|
||||||
|
StringBuilder sb,
|
||||||
|
INamedTypeSymbol attributeSymbol,
|
||||||
|
string className)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
&& p.SetMethod is not null
|
||||||
|
&& !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.
|
||||||
|
List<(string PropName, ITypeSymbol PropType)> ctorProps = ctors
|
||||||
|
.SelectMany(c => c.Parameters)
|
||||||
|
.Select(p => (
|
||||||
|
PropName: ResolvePropertyName(attributeSymbol, p),
|
||||||
|
PropType: p.Type))
|
||||||
|
.GroupBy(x => x.PropName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(g => g.First())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// ---- class declaration ----
|
||||||
|
_ = sb.AppendLine($"public sealed class {className}");
|
||||||
|
_ = sb.AppendLine("{");
|
||||||
|
|
||||||
|
// ---- one constructor per attribute constructor ----
|
||||||
|
foreach (IMethodSymbol ctor in ctors)
|
||||||
|
{
|
||||||
|
if (ctor.Parameters.Length == 0)
|
||||||
|
{
|
||||||
|
_ = sb.AppendLine($" public {className}() {{ }}");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ctorParams = string.Join(", ",
|
||||||
|
ctor.Parameters.Select(p => $"{GlobalType(p.Type)} {p.Name}"));
|
||||||
|
|
||||||
|
_ = 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 ((string propName, ITypeSymbol propType) in ctorProps)
|
||||||
|
{
|
||||||
|
_ = sb.AppendLine($" public {GlobalType(propType)} {propName} {{ get; }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- settable properties (named-argument style) ----
|
||||||
|
foreach (IPropertySymbol prop in settableProps)
|
||||||
|
{
|
||||||
|
string defaultClause = GetDefaultClause(prop);
|
||||||
|
_ = sb.AppendLine($" public {GlobalType(prop.Type)} {prop.Name} {{ get; init; }}{defaultClause}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = sb.AppendLine("}");
|
||||||
|
_ = sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// Only emit a default for a handful of well-known "safe" defaults so the
|
||||||
|
// generated code compiles even when the caller omits the named argument.
|
||||||
|
return prop.Type.IsReferenceType || prop.Type.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)
|
||||||
@@ -94,8 +281,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.StatementAttributeContainer, Action<string>> statements,");
|
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>> statements,");
|
||||||
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Attributes.StaticStatementAttributeContainer, 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
|
||||||
@@ -116,25 +303,25 @@ 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.StatementAttributeContainer, 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.StatementAttributeContainer", 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.StaticStatementAttributeContainer, 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.StaticStatementAttributeContainer", 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}));");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +330,7 @@ public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|||||||
_ = sb.AppendLine(" int cmp = a.Key.Priority.CompareTo(b.Key.Priority);");
|
_ = sb.AppendLine(" int cmp = a.Key.Priority.CompareTo(b.Key.Priority);");
|
||||||
_ = sb.AppendLine(" return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);");
|
_ = sb.AppendLine(" return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);");
|
||||||
_ = sb.AppendLine(" });");
|
_ = sb.AppendLine(" });");
|
||||||
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Attributes.StatementAttributeContainer, Action<string>>();");
|
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>();");
|
||||||
_ = sb.AppendLine(" foreach (var entry in statementEntries)");
|
_ = sb.AppendLine(" foreach (var entry in statementEntries)");
|
||||||
_ = sb.AppendLine(" statements.Add(entry.Key, entry.Value);");
|
_ = sb.AppendLine(" statements.Add(entry.Key, entry.Value);");
|
||||||
_ = sb.AppendLine();
|
_ = sb.AppendLine();
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ 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;
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Tests;
|
namespace YesNt.Interpreter.Tests;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public class AddStatementTests
|
|||||||
|
|
||||||
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||||
{
|
{
|
||||||
StatementAttributeContainer attr = new StatementAttributeContainer("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;
|
||||||
@@ -281,11 +281,11 @@ public class AddStatementTests
|
|||||||
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
_ = YesNtAssert.GetLastLineWithSetup(lines, interpreter =>
|
||||||
{
|
{
|
||||||
interpreter.AddStatement(
|
interpreter.AddStatement(
|
||||||
new StatementAttributeContainer("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 StatementAttributeContainer("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal },
|
new StatementInformation("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal },
|
||||||
_ => normalPriorityOrder = callOrder++);
|
_ => normalPriorityOrder = callOrder++);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
using YesNt.Interpreter.Enums;
|
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Attributes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks a method as a YesNt statement handler.
|
|
||||||
/// The interpreter matches source lines against the <see cref="Name"/> keyword according to
|
|
||||||
/// <see cref="SearchMode"/> and <see cref="SpaceAround"/> rules, then invokes the decorated method
|
|
||||||
/// with the remaining argument text.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Methods decorated with this attribute must be instance methods on a class that inherits
|
|
||||||
/// <see cref="Runtime.StatementRuntimeInformation"/> and must accept a single <see cref="string"/> parameter.
|
|
||||||
/// </remarks>
|
|
||||||
public class StatementAttributeContainer
|
|
||||||
{
|
|
||||||
/// <summary>Gets the keyword that identifies this statement in source code.</summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>Gets where in the line the keyword is searched for.</summary>
|
|
||||||
public SearchMode SearchMode { get; }
|
|
||||||
|
|
||||||
/// <summary>Gets which sides of the keyword must be padded with a space.</summary>
|
|
||||||
public SpaceAround SpaceAround { get; }
|
|
||||||
|
|
||||||
/// <summary>Gets or sets the syntax-highlight color used by the code editor.</summary>
|
|
||||||
public ConsoleColor Color { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the execution priority. Statements with a lower <see cref="Priority"/> value
|
|
||||||
/// run before those with a higher value. Defaults to <see cref="Priority.Normal"/>.
|
|
||||||
/// </summary>
|
|
||||||
public Priority Priority { get; set; } = Priority.Normal;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this statement is still invoked while the interpreter
|
|
||||||
/// is in search mode (scanning for a label or function definition). Defaults to <see langword="false"/>.
|
|
||||||
/// </summary>
|
|
||||||
public bool ExecuteInSearchMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the full current line (including the keyword itself)
|
|
||||||
/// is passed as the argument, rather than stripping the keyword prefix/suffix first.
|
|
||||||
/// Defaults to <see langword="false"/>.
|
|
||||||
/// </summary>
|
|
||||||
public bool KeepStatementInArgs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this statement should be excluded from syntax highlighting.
|
|
||||||
/// Set to <see langword="true"/> when no <see cref="Color"/> is provided.
|
|
||||||
/// </summary>
|
|
||||||
public bool IgnoreSyntaxHighlighting { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets an optional sub-string that must also be present in the line for this statement
|
|
||||||
/// to match. Used to differentiate overloaded keywords (e.g. <c>call</c> vs <c>call … with …</c>).
|
|
||||||
/// </summary>
|
|
||||||
public string Separator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the statement that marks the end of this block.
|
|
||||||
/// Used for block boundary caching (e.g., "while" has BlockPair = "end_while").
|
|
||||||
/// </summary>
|
|
||||||
public string BlockPair { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this statement is the end of a block.
|
|
||||||
/// Used for block boundary caching (e.g., "end_while" has IsBlockEnd = true).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsBlockEnd { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this statement is an intermediate part of a block
|
|
||||||
/// (e.g., "else:" between "if" and "end_if").
|
|
||||||
/// </summary>
|
|
||||||
public bool IsBlockIntermediate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new <see cref="StatementAttribute"/> with a syntax-highlight color.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The keyword that identifies this statement.</param>
|
|
||||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
|
||||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
|
||||||
/// <param name="color">The color used for syntax highlighting in the code editor.</param>
|
|
||||||
public StatementAttributeContainer(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
SearchMode = searchMode;
|
|
||||||
SpaceAround = spaceAround;
|
|
||||||
Color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new <see cref="StatementAttribute"/> without a syntax-highlight color.
|
|
||||||
/// The statement will be excluded from syntax highlighting.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The keyword that identifies this statement.</param>
|
|
||||||
/// <param name="searchMode">Where in the line the keyword is matched.</param>
|
|
||||||
/// <param name="spaceAround">Which sides of the keyword require a surrounding space.</param>
|
|
||||||
public StatementAttributeContainer(string name, SearchMode searchMode, SpaceAround spaceAround)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
SearchMode = searchMode;
|
|
||||||
SpaceAround = spaceAround;
|
|
||||||
IgnoreSyntaxHighlighting = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using YesNt.Interpreter.Enums;
|
|
||||||
|
|
||||||
namespace YesNt.Interpreter.Attributes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks a parameterless method as a YesNt static statement handler.
|
|
||||||
/// Static statements are invoked once per line before regular statement matching begins,
|
|
||||||
/// regardless of whether the line matches any keyword. They are typically used for
|
|
||||||
/// pre-processing tasks such as transforming the current line before other statements run.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Methods decorated with this attribute must be instance methods on a class that inherits
|
|
||||||
/// <see cref="Runtime.StatementRuntimeInformation"/> and must have no parameters.
|
|
||||||
/// </remarks>
|
|
||||||
public class StaticStatementAttributeContainer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this handler is still invoked while the interpreter
|
|
||||||
/// is in search mode (scanning for a label or function definition). Defaults to <see langword="false"/>.
|
|
||||||
/// </summary>
|
|
||||||
public bool ExecuteInSearchMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the execution priority relative to other static statements.
|
|
||||||
/// Defaults to <see cref="Priority.Normal"/>.
|
|
||||||
/// </summary>
|
|
||||||
public Priority Priority { get; set; } = Priority.Normal;
|
|
||||||
}
|
|
||||||
@@ -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(StatementAttributeContainer 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; }
|
|
||||||
}
|
|
||||||
@@ -29,36 +29,17 @@ 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<StatementAttributeContainer, 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<StaticStatementAttributeContainer, Action>> staticStatements;
|
private readonly List<KeyValuePair<StaticStatementInformation, Action>> staticStatements;
|
||||||
private readonly Dictionary<string, List<KeyValuePair<StatementAttributeContainer, 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>
|
/// <summary>
|
||||||
/// <see langword="true"/> from the moment <see cref="Prepare(string, bool)"/> (or any
|
/// <see langword="true"/> from the moment <see cref="Prepare(string, bool)"/> (or any
|
||||||
@@ -96,7 +77,7 @@ public class YesNtInterpreter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a custom statement using a pre-built <see cref="StatementAttributeContainer"/>.
|
/// 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
|
||||||
@@ -106,13 +87,13 @@ 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="StatementAttributeContainer.KeepStatementInArgs"/> is set).
|
/// (the part of the line after the keyword, unless <see cref="StatementInformation.KeepStatementInArgs"/> is set).
|
||||||
/// </param>
|
/// </param>
|
||||||
public void AddStatement(StatementAttributeContainer attribute, Action<string> handler)
|
public void AddStatement(StatementInformation attribute, Action<string> handler)
|
||||||
{
|
{
|
||||||
statements[attribute] = handler;
|
statements[attribute] = handler;
|
||||||
|
|
||||||
List<KeyValuePair<StatementAttributeContainer, Action<string>>> entries = [.. statements];
|
List<KeyValuePair<StatementInformation, Action<string>>> entries = [.. statements];
|
||||||
entries.Sort((a, b) =>
|
entries.Sort((a, b) =>
|
||||||
{
|
{
|
||||||
int cmp = a.Key.Priority.CompareTo(b.Key.Priority);
|
int cmp = a.Key.Priority.CompareTo(b.Key.Priority);
|
||||||
@@ -121,7 +102,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
statements = [];
|
statements = [];
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttributeContainer, Action<string>> entry in entries)
|
foreach (KeyValuePair<StatementInformation, Action<string>> entry in entries)
|
||||||
{
|
{
|
||||||
statements.Add(entry.Key, entry.Value);
|
statements.Add(entry.Key, entry.Value);
|
||||||
}
|
}
|
||||||
@@ -139,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(StatementAttributeContainer 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));
|
||||||
}
|
}
|
||||||
@@ -153,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 StatementAttributeContainer(name, searchMode, spaceAround), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -169,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 StatementAttributeContainer(name, searchMode, spaceAround), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -182,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 StatementAttributeContainer(name, searchMode, spaceAround, consoleColor), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround, consoleColor), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -199,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 StatementAttributeContainer(name, searchMode, spaceAround, consoleColor), handler);
|
AddStatement(new StatementInformation(name, searchMode, spaceAround, consoleColor), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -208,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 (StatementAttributeContainer 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);
|
||||||
}
|
}
|
||||||
@@ -231,7 +212,7 @@ public class YesNtInterpreter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<KeyValuePair<StatementAttributeContainer, 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)
|
||||||
@@ -241,7 +222,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
disabledStatements[name] = matching;
|
disabledStatements[name] = matching;
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttributeContainer, Action<string>> kv in matching)
|
foreach (KeyValuePair<StatementInformation, Action<string>> kv in matching)
|
||||||
{
|
{
|
||||||
statements[kv.Key] = _ => { };
|
statements[kv.Key] = _ => { };
|
||||||
}
|
}
|
||||||
@@ -257,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<StatementAttributeContainer, Action<string>>> saved))
|
if (!disabledStatements.TryGetValue(name, out List<KeyValuePair<StatementInformation, Action<string>>> saved))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<StatementAttributeContainer, Action<string>> kv in saved)
|
foreach (KeyValuePair<StatementInformation, Action<string>> kv in saved)
|
||||||
{
|
{
|
||||||
statements[kv.Key] = kv.Value;
|
statements[kv.Key] = kv.Value;
|
||||||
}
|
}
|
||||||
@@ -479,7 +460,7 @@ public class YesNtInterpreter
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<StaticStatementAttributeContainer, Action> staticStatement in staticStatements)
|
foreach (KeyValuePair<StaticStatementInformation, Action> staticStatement in staticStatements)
|
||||||
{
|
{
|
||||||
if (!staticStatement.Key.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
if (!staticStatement.Key.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
{
|
{
|
||||||
@@ -498,7 +479,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
foreach (StatementHandler handler in handlers)
|
foreach (StatementHandler handler in handlers)
|
||||||
{
|
{
|
||||||
StatementAttributeContainer statementAttribute = handler.Attribute;
|
StatementInformation statementAttribute = handler.Attribute;
|
||||||
|
|
||||||
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||||
{
|
{
|
||||||
@@ -681,7 +662,7 @@ public class YesNtInterpreter
|
|||||||
|
|
||||||
private static bool IsPossibleMatch(string content, StatementHandler handler)
|
private static bool IsPossibleMatch(string content, StatementHandler handler)
|
||||||
{
|
{
|
||||||
StatementAttributeContainer attr = handler.Attribute;
|
StatementInformation attr = handler.Attribute;
|
||||||
string fullName = handler.FullName;
|
string fullName = handler.FullName;
|
||||||
|
|
||||||
return attr.SearchMode switch
|
return attr.SearchMode switch
|
||||||
|
|||||||
Reference in New Issue
Block a user