mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-07 23:43:07 +02:00
Add snapcraft & chocolatey manifests and move code to src
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class StatementAttribute : Attribute
|
||||
{
|
||||
/// <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 StatementAttribute(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 StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround)
|
||||
{
|
||||
Name = name;
|
||||
SearchMode = searchMode;
|
||||
SpaceAround = spaceAround;
|
||||
IgnoreSyntaxHighlighting = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
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>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class StaticStatementAttribute : Attribute
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace YesNt.Interpreter.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the execution order of statements. Lower values run first.
|
||||
/// </summary>
|
||||
public enum Priority
|
||||
{
|
||||
/// <summary>Runs before all other statements. Used for syntax pre-processing such as string literals.</summary>
|
||||
PreProcessing,
|
||||
|
||||
/// <summary>Runs very early. Used for inline substitutions such as variable reads and parameter pops.</summary>
|
||||
Highest,
|
||||
|
||||
/// <summary>Runs early.</summary>
|
||||
VeryHigh,
|
||||
|
||||
/// <summary>Runs above normal order.</summary>
|
||||
High,
|
||||
|
||||
/// <summary>Default execution order.</summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>Runs below normal order.</summary>
|
||||
Low,
|
||||
|
||||
/// <summary>Runs last. Used for control-flow and variable definitions that depend on substitutions being complete.</summary>
|
||||
VeryLow
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace YesNt.Interpreter.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Determines where in a source line the interpreter searches for a statement keyword.
|
||||
/// </summary>
|
||||
public enum SearchMode
|
||||
{
|
||||
/// <summary>The keyword must appear at the beginning of the line.</summary>
|
||||
StartOfLine,
|
||||
|
||||
/// <summary>The keyword must appear at the end of the line.</summary>
|
||||
EndOfLine,
|
||||
|
||||
/// <summary>The keyword may appear anywhere in the line.</summary>
|
||||
Contains,
|
||||
|
||||
/// <summary>The entire line must exactly match the keyword.</summary>
|
||||
Exact
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace YesNt.Interpreter.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which sides of a statement keyword must be surrounded by a space when matching.
|
||||
/// </summary>
|
||||
public enum SpaceAround
|
||||
{
|
||||
/// <summary>A space is required both before and after the keyword.</summary>
|
||||
StartEnd,
|
||||
|
||||
/// <summary>A space is required before the keyword only.</summary>
|
||||
Start,
|
||||
|
||||
/// <summary>A space is required after the keyword only.</summary>
|
||||
End,
|
||||
|
||||
/// <summary>No surrounding spaces are required.</summary>
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"profiles": {
|
||||
"YesNt-Interpreter": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "code.ynt"
|
||||
},
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"environmentVariables": {},
|
||||
"distributionName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Provides per-line execution data raised through <see cref="YesNtInterpreter.OnLineExecuted"/>.
|
||||
/// </summary>
|
||||
public class DebugEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Gets the 1-based line number of the executed line within its source file.</summary>
|
||||
public int LineNumber { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line content after all statement transformations have been applied
|
||||
/// (e.g. after variable substitution). May differ from <see cref="OriginalLine"/>.
|
||||
/// </summary>
|
||||
public string CurrentLine { get; internal set; }
|
||||
|
||||
/// <summary>Gets the raw line content as it appeared in the source file.</summary>
|
||||
public string OriginalLine { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task identifier of the task that executed this line, or <c>0</c> if the line
|
||||
/// was executed on the main thread.
|
||||
/// </summary>
|
||||
public int TaskId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this line was executed inside a background task
|
||||
/// (spawned with the <c>task</c> statement).
|
||||
/// </summary>
|
||||
public bool IsTask { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Central repository of all exit/error message strings used by <see cref="RuntimeInformation.Exit"/>.
|
||||
/// Keeping messages here ensures consistency and makes them easy to find or localise.
|
||||
/// </summary>
|
||||
internal static class ExitMessages
|
||||
{
|
||||
internal const string InvalidSyntax = "Invalid syntax";
|
||||
internal const string InvalidSyntaxColonRequired = "Invalid syntax. Statement must end with ':'";
|
||||
internal const string InvalidOperation = "Invalid operation";
|
||||
internal const string InvalidStatement = "Invalid statement";
|
||||
internal const string InvalidStringLiteral = "Invalid string literal";
|
||||
internal const string EndOfFile = "End of file";
|
||||
internal const string TerminatedByExternalProcess = "Terminated by external process";
|
||||
internal const string TerminatedByChildTask = "Terminated by child task";
|
||||
internal const string TerminatedByParentTask = "Terminated by parent task";
|
||||
internal const string PlannedTermination = "Planned termination by code";
|
||||
internal const string PlannedTerminationCancelingTasks = "Planned termination by code. Canceling all tasks";
|
||||
internal const string NoMatchingEndIf = "No matching end_if found";
|
||||
internal const string NoMatchingEndWhile = "No matching end_while found";
|
||||
internal const string NoMatchingWhile = "No matching while found";
|
||||
internal const string NestedFunctionsNotAllowed = "Nested functions are not allowed";
|
||||
internal const string NoOutArgumentInStack = "No out argument in stack";
|
||||
internal const string StatementNotAllowedOutsideFunction = "Statement not allowed outside of function";
|
||||
internal const string NoInArgumentInStack = "No in argument in stack";
|
||||
internal const string NoFunctionInStack = "No function in stack";
|
||||
|
||||
internal static string LabelNotFound(string label)
|
||||
{
|
||||
return $"Label \"{label}\" not found";
|
||||
}
|
||||
|
||||
internal static string FunctionNotFound(string function)
|
||||
{
|
||||
return $"Function \"{function}\" not found";
|
||||
}
|
||||
|
||||
internal static string VariableNotFound(string variable)
|
||||
{
|
||||
return $"Variable \"{variable}\" not found";
|
||||
}
|
||||
|
||||
internal static string ListNotFound(string list)
|
||||
{
|
||||
return $"List \"{list}\" not found";
|
||||
}
|
||||
|
||||
internal static string InvalidIndex(string rawIndex)
|
||||
{
|
||||
return $"\"{rawIndex}\" is not a valid index";
|
||||
}
|
||||
|
||||
internal static string IndexOutOfRange(int index)
|
||||
{
|
||||
return $"Index {index} out of range";
|
||||
}
|
||||
|
||||
internal static string InvalidTimeoutValue(string value)
|
||||
{
|
||||
return $"\"{value}\" is not a valid time-out value";
|
||||
}
|
||||
|
||||
internal static string CouldNotLoadFile(string path)
|
||||
{
|
||||
return $"Could not load file \"{path}\"";
|
||||
}
|
||||
|
||||
internal static string CouldNotFindFile(string path)
|
||||
{
|
||||
return $"Could not find file \"{path}\"";
|
||||
}
|
||||
|
||||
internal static string CannotFindFile(string path)
|
||||
{
|
||||
return $"Cannot find file \"{path}\".";
|
||||
}
|
||||
|
||||
internal static string FailedToStart(string program, string message)
|
||||
{
|
||||
return $"Failed to start \"{program}\". {message}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Represents one frame on the function call stack. Created when a <c>call</c> statement is
|
||||
/// executed and popped when the matching <c>return</c> is reached.
|
||||
/// </summary>
|
||||
internal class FunctionScope(int callerLine, Stack<string> arguments)
|
||||
{
|
||||
/// <summary>Gets the zero-based line index to return to after this function completes.</summary>
|
||||
public int CallerLine { get; } = callerLine;
|
||||
|
||||
/// <summary>Gets the local variable table for this function invocation.</summary>
|
||||
public Dictionary<string, string> Variables { get; } = [];
|
||||
|
||||
/// <summary>Gets the local list table for this function invocation.</summary>
|
||||
public Dictionary<string, List<string>> Lists { get; } = [];
|
||||
|
||||
/// <summary>Gets the local label table for this function invocation.</summary>
|
||||
public Dictionary<string, int> Labels { get; } = [];
|
||||
|
||||
/// <summary>Gets the stack of input arguments passed to this function via <c>push_in</c>.</summary>
|
||||
public Stack<string> Arguments { get; } = arguments;
|
||||
|
||||
/// <summary>Gets the stack of output values pushed via <c>push_out</c>, consumed by the caller via <c>%out</c>.</summary>
|
||||
public Stack<string> Results { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the script runtime state accessible to custom statement handlers registered
|
||||
/// via <see cref="YesNtInterpreter.AddStatement"/>.
|
||||
/// </summary>
|
||||
public interface IStatementContext
|
||||
{
|
||||
/// <summary>Gets the local variable table for the current scope.</summary>
|
||||
Dictionary<string, string> Variables { get; }
|
||||
|
||||
/// <summary>Gets or sets the global variable table shared across all scopes.</summary>
|
||||
Dictionary<string, string> GlobalVariables { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the text of the line currently being processed.
|
||||
/// Inline-substitution handlers (e.g. <c>%read_line</c>) write their result here.</summary>
|
||||
string CurrentLine { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the zero-based index of the next line to execute.
|
||||
/// Set this to implement control-flow jumps inside a custom statement.</summary>
|
||||
int LineNumber { get; set; }
|
||||
|
||||
/// <summary>Terminates execution with the given message.</summary>
|
||||
/// <param name="message">The message written to debug output.</param>
|
||||
/// <param name="isError">
|
||||
/// <see langword="true"/> to signal an error termination;
|
||||
/// <see langword="false"/> for a planned, non-error termination.
|
||||
/// </param>
|
||||
void Exit(string message, bool isError);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single source line together with its location metadata.
|
||||
/// </summary>
|
||||
internal class Line(string content, string fileName, int lineNumber)
|
||||
{
|
||||
/// <summary>Gets or sets the raw text content of the line.</summary>
|
||||
public string Content { get; set; } = content;
|
||||
|
||||
/// <summary>Gets or sets the name of the source file this line originated from.</summary>
|
||||
public string FileName { get; set; } = fileName;
|
||||
|
||||
/// <summary>Gets or sets the zero-based line index within <see cref="FileName"/>.</summary>
|
||||
public int LineNumber { get; set; } = lineNumber;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Holds all mutable runtime state for a single script execution, including variables, lists,
|
||||
/// labels, functions, the call stack, the line counter, and stop flags.
|
||||
/// Each background task spawned by the <c>task</c> statement owns its own
|
||||
/// <see cref="RuntimeInformation"/> whose <see cref="ParentRuntimeInformation"/> points back
|
||||
/// to the main execution context.
|
||||
/// </summary>
|
||||
internal sealed class RuntimeInformation : IStatementContext
|
||||
{
|
||||
public event Action<string> OnDebugOutput;
|
||||
|
||||
public event Action<DebugEventArgs> OnLineExecuted;
|
||||
|
||||
private event Action<string, bool> OnExit;
|
||||
|
||||
private static int internalTaskId = 0;
|
||||
private readonly Dictionary<string, string> topVariables = [];
|
||||
private readonly Dictionary<string, List<string>> topLists = [];
|
||||
|
||||
public Dictionary<string, string> GlobalVariables { get; set; } = [];
|
||||
public Dictionary<string, int> Functions { get; } = [];
|
||||
public Dictionary<int, int> BlockBoundaries { get; } = [];
|
||||
internal Action PreScanLinesAction { get; set; }
|
||||
public Stack<FunctionScope> FunctionCallStack { get; } = new();
|
||||
public Stack<string> InParametersStack { get; } = new();
|
||||
public Stack<string> OutParametersStack { get; set; } = new();
|
||||
public List<Line> Lines { get; set; } = [];
|
||||
public string CurrentLine { get; set; } = string.Empty;
|
||||
public string SearchLabel { get; set; } = string.Empty;
|
||||
public string SearchFunction { get; set; } = string.Empty;
|
||||
public int LineNumber { get; set; } = 0;
|
||||
public bool Stop { get; private set; } = false;
|
||||
public bool StopAllTasks { get; private set; } = false;
|
||||
public bool IsDebugMode { get; set; } = false;
|
||||
public string WorkingDirectory { get; set; } = string.Empty;
|
||||
public bool IsTask => ParentRuntimeInformation is not null;
|
||||
public int TaskId { get => IsTask ? field : 0; private set; } = 0;
|
||||
public bool InternalIsInFunction { get; set; }
|
||||
|
||||
public bool IsInFunction
|
||||
{
|
||||
get => InternalIsInFunction || FunctionCallStack.Count > 0;
|
||||
set => InternalIsInFunction = value;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables;
|
||||
public Dictionary<string, List<string>> Lists => FunctionCallStack.Count == 0 ? topLists : FunctionCallStack.Peek().Lists;
|
||||
|
||||
public Dictionary<string, int> Labels { get => FunctionCallStack.Count == 0 ? field : FunctionCallStack.Peek().Labels; } = [];
|
||||
|
||||
public RuntimeInformation ParentRuntimeInformation
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
field?.OnExit += ParentRuntimeInformation_OnExit;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSearching => !string.IsNullOrWhiteSpace(SearchLabel + SearchFunction) || (IsInFunction && FunctionCallStack.Count == 0);
|
||||
public bool IsLocalSearch { get; set; }
|
||||
|
||||
public void WriteLine(string output, bool forceWrite = false)
|
||||
{
|
||||
if ((Stop && !forceWrite) || (ParentRuntimeInformation?.StopAllTasks == true && !forceWrite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsDebugMode)
|
||||
{
|
||||
if (IsTask)
|
||||
{
|
||||
ParentRuntimeInformation!.WriteLine(output.FromSafeString(), forceWrite);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnDebugOutput?.Invoke(output.FromSafeString() + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(output.FromSafeString());
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string output, bool forceWrite = false)
|
||||
{
|
||||
if ((Stop && !forceWrite) || (ParentRuntimeInformation?.StopAllTasks == true && !forceWrite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsDebugMode)
|
||||
{
|
||||
if (IsTask)
|
||||
{
|
||||
ParentRuntimeInformation!.Write(output.FromSafeString(), forceWrite);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnDebugOutput?.Invoke(output.FromSafeString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(output.FromSafeString());
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit(string message, bool stopAllTasks)
|
||||
{
|
||||
if (!Stop)
|
||||
{
|
||||
if (Lines.Count == 0)
|
||||
{
|
||||
WriteLine($"{Environment.NewLine}[{(IsTask ? $"Task {TaskId}" : "The process")} was terminated with the message: {message}]", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Line line = Lines[Math.Min(LineNumber, Lines.Count - 1)];
|
||||
WriteLine($"{Environment.NewLine}[{(IsTask ? $"Task {TaskId}" : "The process")} was terminated at line {line.LineNumber + 1} in the file \"{line.FileName}\" with the message: {message}]", true);
|
||||
}
|
||||
|
||||
while (FunctionCallStack.Count > 0)
|
||||
{
|
||||
int stackLineNumber = FunctionCallStack.Pop().CallerLine;
|
||||
List<Line> targetLines = ParentRuntimeInformation?.Lines ?? Lines;
|
||||
if (stackLineNumber >= 0 && stackLineNumber < targetLines.Count)
|
||||
{
|
||||
Line stackLine = targetLines[stackLineNumber];
|
||||
WriteLine($" at line {stackLine.LineNumber + 1} in the file \"{stackLine.FileName}\"", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine(" at unknown location (source not available)", true);
|
||||
}
|
||||
}
|
||||
|
||||
Stop = true;
|
||||
}
|
||||
if (stopAllTasks && !StopAllTasks)
|
||||
{
|
||||
StopAllTasks = true;
|
||||
OnExit?.Invoke(message, StopAllTasks);
|
||||
ParentRuntimeInformation?.Exit(ExitMessages.TerminatedByChildTask, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void LineExecuted(DebugEventArgs debugEventArgs)
|
||||
{
|
||||
if (IsTask)
|
||||
{
|
||||
ParentRuntimeInformation.LineExecuted(debugEventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnLineExecuted?.Invoke(debugEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
topVariables.Clear();
|
||||
topLists.Clear();
|
||||
Lines.Clear();
|
||||
GlobalVariables.Clear();
|
||||
Labels.Clear();
|
||||
Functions.Clear();
|
||||
BlockBoundaries.Clear();
|
||||
FunctionCallStack.Clear();
|
||||
InParametersStack.Clear();
|
||||
OutParametersStack.Clear();
|
||||
ParentRuntimeInformation = null;
|
||||
SearchLabel = string.Empty;
|
||||
SearchFunction = string.Empty;
|
||||
WorkingDirectory = string.Empty;
|
||||
CurrentLine = string.Empty;
|
||||
Stop = false;
|
||||
StopAllTasks = false;
|
||||
IsDebugMode = false;
|
||||
IsInFunction = false;
|
||||
IsLocalSearch = false;
|
||||
LineNumber = 0;
|
||||
#pragma warning disable S2696 // internalTaskId is a shared counter intentionally incremented by each Reset call
|
||||
TaskId = Interlocked.Increment(ref internalTaskId);
|
||||
#pragma warning restore S2696
|
||||
}
|
||||
|
||||
private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks)
|
||||
{
|
||||
Exit(ExitMessages.TerminatedByParentTask, stopAllTasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-calculated statement handler information for faster matching.
|
||||
/// </summary>
|
||||
internal record StatementHandler(StatementAttribute Attribute, Action<string> Handler, string FullName);
|
||||
@@ -0,0 +1,35 @@
|
||||
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,24 @@
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all classes that host statement handler methods.
|
||||
/// Subclasses declare methods decorated with <see cref="Attributes.StatementAttribute"/> or
|
||||
/// <see cref="Attributes.StaticStatementAttribute"/>; the source generator
|
||||
/// (<c>GeneratedStatementRegistry</c>) discovers these at compile time and wires them up.
|
||||
/// </summary>
|
||||
internal abstract class StatementRuntimeInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the runtime state for the current execution context.
|
||||
/// Injected by the generated registry before any handler is invoked.
|
||||
/// </summary>
|
||||
public RuntimeInformation RuntimeInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Trims surrounding whitespace and a trailing colon from a block or function name.
|
||||
/// </summary>
|
||||
protected static string NormalizeBlockName(string value)
|
||||
{
|
||||
return value.Trim().TrimEnd(':').Trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for executing YesNt scripts.
|
||||
/// </summary>
|
||||
public class YesNtInterpreter
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised after each line is executed in debug mode. The argument is <see langword="null"/>
|
||||
/// when execution ends (either normally or due to an error), allowing callers to detect completion.
|
||||
/// </summary>
|
||||
public event Action<DebugEventArgs> OnLineExecuted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised in debug mode whenever the script produces output (e.g. via <c>print_line</c>).
|
||||
/// In non-debug mode output is written directly to <see cref="Console"/>.
|
||||
/// </summary>
|
||||
public event Action<string> OnDebugOutput;
|
||||
|
||||
private readonly RuntimeInformation runtimeInfo = new RuntimeInformation();
|
||||
private Dictionary<StatementAttribute, Action<string>> statements;
|
||||
private List<StatementHandler> statementHandlers;
|
||||
private List<List<StatementHandler>> lineMatchingHandlers = [];
|
||||
private readonly List<KeyValuePair<StaticStatementAttribute, Action>> staticStatements;
|
||||
private readonly Dictionary<string, List<KeyValuePair<StatementAttribute, Action<string>>>> disabledStatements = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only snapshot of all currently registered statements.
|
||||
/// Useful for building syntax highlighters or documentation tools.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<StatementInformation> StatementInformation
|
||||
{
|
||||
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>
|
||||
/// Initializes a new <see cref="YesNtInterpreter"/> and registers all built-in statements.
|
||||
/// </summary>
|
||||
public YesNtInterpreter()
|
||||
{
|
||||
GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements);
|
||||
UpdateStatementHandlers();
|
||||
runtimeInfo.PreScanLinesAction = PreScanLines;
|
||||
|
||||
runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s);
|
||||
runtimeInfo.OnLineExecuted += e => OnLineExecuted?.Invoke(e);
|
||||
}
|
||||
|
||||
private void UpdateStatementHandlers()
|
||||
{
|
||||
statementHandlers = statements.Select(s =>
|
||||
{
|
||||
string name = s.Key.SpaceAround switch
|
||||
{
|
||||
SpaceAround.StartEnd => $" {s.Key.Name.Trim()} ",
|
||||
SpaceAround.Start => $" {s.Key.Name.Trim()}",
|
||||
SpaceAround.End => $"{s.Key.Name.Trim()} ",
|
||||
_ => s.Key.Name.Trim()
|
||||
};
|
||||
return new StatementHandler(s.Key, s.Value, name);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>.
|
||||
/// 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
|
||||
/// newly constructed attribute with the same name will <b>add</b> a second handler rather than replacing
|
||||
/// the built-in. Use <see cref="RemoveStatement"/> first to replace a built-in keyword.
|
||||
/// The statement list is re-sorted by priority after insertion.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute describing the keyword, search mode, and priority.</param>
|
||||
/// <param name="handler">
|
||||
/// The delegate invoked when the statement matches. Receives the argument text
|
||||
/// (the part of the line after the keyword, unless <see cref="StatementAttribute.KeepStatementInArgs"/> is set).
|
||||
/// </param>
|
||||
public void AddStatement(StatementAttribute attribute, Action<string> handler)
|
||||
{
|
||||
statements[attribute] = handler;
|
||||
statements = statements
|
||||
.OrderBy(s => s.Key.Priority)
|
||||
.ThenByDescending(s => s.Key.Name.Length)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom statement using a pre-built <see cref="StatementAttribute"/>,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <param name="attribute">The attribute describing the keyword, search mode, and priority.</param>
|
||||
/// <param name="handler">
|
||||
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(StatementAttribute attribute, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(attribute, args => handler(args, runtimeInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with default settings.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with default settings,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||
/// <param name="handler">
|
||||
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with a specific syntax-highlight color.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||
/// <param name="consoleColor">The color used for syntax highlighting.</param>
|
||||
/// <param name="handler">The delegate invoked when the statement matches.</param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple custom statement with a specific syntax-highlight color,
|
||||
/// with access to the script's <see cref="IStatementContext"/> (variables, line number, output, etc.).
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword to match.</param>
|
||||
/// <param name="searchMode">Where in the line the keyword is searched for.</param>
|
||||
/// <param name="spaceAround">Which sides of the keyword must be padded with a space.</param>
|
||||
/// <param name="consoleColor">The color used for syntax highlighting.</param>
|
||||
/// <param name="handler">
|
||||
/// The delegate invoked when the statement matches. Receives the argument text and the current
|
||||
/// <see cref="IStatementContext"/> for reading/writing script state.
|
||||
/// </param>
|
||||
public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action<string, IStatementContext> handler)
|
||||
{
|
||||
AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters all handlers matching the specified keyword <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword to remove.</param>
|
||||
public void RemoveStatement(string name)
|
||||
{
|
||||
foreach (StatementAttribute key in statements.Keys.Where(k => k.Name == name).ToList())
|
||||
{
|
||||
_ = statements.Remove(key);
|
||||
}
|
||||
|
||||
_ = disabledStatements.Remove(name);
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables all statements matching <paramref name="name"/> by replacing their handlers with
|
||||
/// a no-op. The keyword still matches (so no "Invalid statement" error is raised), but the
|
||||
/// statement has no effect. Use <see cref="EnableStatement"/> to restore original behavior.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword of the statement(s) to disable.</param>
|
||||
public void DisableStatement(string name)
|
||||
{
|
||||
if (disabledStatements.ContainsKey(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<KeyValuePair<StatementAttribute, Action<string>>> matching =
|
||||
statements.Where(kv => kv.Key.Name == name).ToList();
|
||||
|
||||
if (matching.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
disabledStatements[name] = matching;
|
||||
|
||||
foreach (KeyValuePair<StatementAttribute, Action<string>> kv in matching)
|
||||
{
|
||||
statements[kv.Key] = _ => { };
|
||||
}
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-enables statements previously disabled with <see cref="DisableStatement"/>,
|
||||
/// restoring their original handlers.
|
||||
/// Has no effect if the statement is not currently disabled.
|
||||
/// </summary>
|
||||
/// <param name="name">The keyword of the statement(s) to re-enable.</param>
|
||||
public void EnableStatement(string name)
|
||||
{
|
||||
if (!disabledStatements.TryGetValue(name, out List<KeyValuePair<StatementAttribute, Action<string>>> saved))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<StatementAttribute, Action<string>> kv in saved)
|
||||
{
|
||||
statements[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
_ = disabledStatements.Remove(name);
|
||||
UpdateStatementHandlers();
|
||||
PreScanLines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a graceful stop of the currently executing script.
|
||||
/// The interpreter will terminate at the next line boundary.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a YesNt script file.
|
||||
/// </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 Execute(string path, bool isDebugMode = false)
|
||||
{
|
||||
runtimeInfo.Reset();
|
||||
runtimeInfo.IsDebugMode = isDebugMode;
|
||||
if (LoadFile(path))
|
||||
{
|
||||
Execute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a YesNt script supplied as an in-memory list of lines.
|
||||
/// </summary>
|
||||
/// <param name="lines">The script lines to execute.</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 Execute(List<string> lines, bool isDebugMode = false)
|
||||
{
|
||||
runtimeInfo.Reset();
|
||||
runtimeInfo.IsDebugMode = isDebugMode;
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
string content = lines[i].Trim().Replace("\r", string.Empty);
|
||||
runtimeInfo.Lines.Add(new Line(content, Path.GetFileName("#Memory#"), i));
|
||||
}
|
||||
|
||||
PreScanLines();
|
||||
Execute();
|
||||
}
|
||||
|
||||
internal void Execute(List<Line> lines, Dictionary<string, string> globalVariables, int startLine, RuntimeInformation parentRuntimeInformation)
|
||||
{
|
||||
runtimeInfo.Reset();
|
||||
runtimeInfo.IsDebugMode = parentRuntimeInformation.IsDebugMode;
|
||||
runtimeInfo.Lines = lines;
|
||||
runtimeInfo.LineNumber = startLine;
|
||||
runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation;
|
||||
runtimeInfo.GlobalVariables = globalVariables;
|
||||
if (parentRuntimeInformation.StopAllTasks)
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks);
|
||||
return;
|
||||
}
|
||||
PreScanLines();
|
||||
Execute();
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
for (; runtimeInfo.LineNumber < runtimeInfo.Lines.Count; runtimeInfo.LineNumber++)
|
||||
{
|
||||
if (runtimeInfo.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Line lineObj = runtimeInfo.Lines[runtimeInfo.LineNumber];
|
||||
runtimeInfo.CurrentLine = lineObj.Content;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugEventArgs debugEventArgs = null;
|
||||
if (runtimeInfo.IsDebugMode)
|
||||
{
|
||||
debugEventArgs = new DebugEventArgs()
|
||||
{
|
||||
LineNumber = runtimeInfo.LineNumber + 1,
|
||||
OriginalLine = runtimeInfo.CurrentLine.FromSafeString(),
|
||||
IsTask = runtimeInfo.IsTask,
|
||||
TaskId = runtimeInfo.TaskId
|
||||
};
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<StaticStatementAttribute, Action> staticStatement in staticStatements)
|
||||
{
|
||||
StaticStatementAttribute staticStatementAttribute = staticStatement.Key;
|
||||
if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
staticStatement.Value.Invoke();
|
||||
}
|
||||
|
||||
bool statementFound = false;
|
||||
bool notSearchingLabel = !runtimeInfo.IsSearching;
|
||||
|
||||
List<StatementHandler> handlers = (runtimeInfo.LineNumber < lineMatchingHandlers.Count) ? lineMatchingHandlers[runtimeInfo.LineNumber] : [];
|
||||
|
||||
foreach (StatementHandler handler in handlers)
|
||||
{
|
||||
StatementAttribute statementAttribute = handler.Attribute;
|
||||
|
||||
if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching)
|
||||
{
|
||||
statementFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runtimeInfo.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string name = handler.FullName;
|
||||
|
||||
if (statementAttribute.Separator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Separator, StringComparison.Ordinal))
|
||||
{
|
||||
if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name, StringComparison.Ordinal))
|
||||
{
|
||||
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[name.Length..];
|
||||
handler.Handler.Invoke(copyLine);
|
||||
statementFound = true;
|
||||
}
|
||||
else if (statementAttribute.SearchMode == SearchMode.Contains && runtimeInfo.CurrentLine.Contains(name, StringComparison.Ordinal))
|
||||
{
|
||||
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty);
|
||||
handler.Handler.Invoke(copyLine);
|
||||
statementFound = true;
|
||||
}
|
||||
else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name, StringComparison.Ordinal))
|
||||
{
|
||||
string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[..^name.Length];
|
||||
handler.Handler.Invoke(copyLine);
|
||||
statementFound = true;
|
||||
}
|
||||
else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name, StringComparison.Ordinal))
|
||||
{
|
||||
handler.Handler.Invoke(runtimeInfo.CurrentLine);
|
||||
statementFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!statementFound)
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.InvalidStatement, true);
|
||||
}
|
||||
if (runtimeInfo.IsDebugMode && notSearchingLabel && debugEventArgs != null)
|
||||
{
|
||||
debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString();
|
||||
runtimeInfo.LineExecuted(debugEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
if (!runtimeInfo.Stop)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel))
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.LabelNotFound(runtimeInfo.SearchLabel), true);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchFunction))
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.FunctionNotFound(runtimeInfo.SearchFunction), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.EndOfFile, false);
|
||||
}
|
||||
|
||||
if (runtimeInfo.IsDebugMode)
|
||||
{
|
||||
runtimeInfo.LineExecuted(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadFile(string path)
|
||||
{
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] lines = File.ReadAllLines(path);
|
||||
|
||||
runtimeInfo.WorkingDirectory = Path.GetDirectoryName(path);
|
||||
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string content = lines[i].Trim().Replace("\r", string.Empty);
|
||||
runtimeInfo.Lines.Add(new Line(content, Path.GetFileName(path), i));
|
||||
}
|
||||
|
||||
PreScanLines();
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void PreScanLines()
|
||||
{
|
||||
runtimeInfo.BlockBoundaries.Clear();
|
||||
lineMatchingHandlers = new List<List<StatementHandler>>(runtimeInfo.Lines.Count);
|
||||
|
||||
// Dictionary to track open blocks by their expected end statement name
|
||||
Dictionary<string, Stack<int>> openBlocks = [];
|
||||
|
||||
for (int i = 0; i < runtimeInfo.Lines.Count; i++)
|
||||
{
|
||||
string content = runtimeInfo.Lines[i].Content;
|
||||
List<StatementHandler> matchingHandlers = [];
|
||||
|
||||
#pragma warning disable S3267 // foreach + if is intentional here; LINQ .Where() would add overhead in this scan loop
|
||||
foreach (StatementHandler handler in statementHandlers)
|
||||
{
|
||||
if (IsPossibleMatch(content, handler))
|
||||
#pragma warning restore S3267
|
||||
{
|
||||
matchingHandlers.Add(handler);
|
||||
|
||||
// Track block starts (skip intermediates — they are handled separately below)
|
||||
string blockPair = handler.Attribute.BlockPair;
|
||||
if (!string.IsNullOrEmpty(blockPair) && !handler.Attribute.IsBlockIntermediate)
|
||||
{
|
||||
if (!openBlocks.TryGetValue(blockPair, out Stack<int> stack))
|
||||
{
|
||||
stack = new Stack<int>();
|
||||
openBlocks[blockPair] = stack;
|
||||
}
|
||||
stack.Push(i);
|
||||
}
|
||||
|
||||
// Track block ends
|
||||
if (handler.Attribute.IsBlockEnd && openBlocks.TryGetValue(handler.Attribute.Name, out Stack<int> endStack) && endStack.Count > 0)
|
||||
{
|
||||
int startLine = endStack.Pop();
|
||||
runtimeInfo.BlockBoundaries[startLine] = i;
|
||||
runtimeInfo.BlockBoundaries[i] = startLine;
|
||||
}
|
||||
|
||||
// Track block intermediates (e.g., else:): pop the opener, record boundary, push self
|
||||
if (handler.Attribute.IsBlockIntermediate)
|
||||
{
|
||||
string intermediatePair = handler.Attribute.BlockPair;
|
||||
if (!string.IsNullOrEmpty(intermediatePair))
|
||||
{
|
||||
if (!openBlocks.TryGetValue(intermediatePair, out Stack<int> stack))
|
||||
{
|
||||
stack = new Stack<int>();
|
||||
openBlocks[intermediatePair] = stack;
|
||||
}
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
int startLine = stack.Pop();
|
||||
runtimeInfo.BlockBoundaries[startLine] = i;
|
||||
}
|
||||
stack.Push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lineMatchingHandlers.Add(matchingHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPossibleMatch(string content, StatementHandler handler)
|
||||
{
|
||||
StatementAttribute attr = handler.Attribute;
|
||||
string fullName = handler.FullName;
|
||||
|
||||
return attr.SearchMode switch
|
||||
{
|
||||
SearchMode.Exact => content == fullName,
|
||||
SearchMode.StartOfLine => content.StartsWith(fullName, StringComparison.Ordinal),
|
||||
SearchMode.EndOfLine => content.EndsWith(fullName, StringComparison.Ordinal),
|
||||
SearchMode.Contains => content.Contains(fullName, StringComparison.Ordinal),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class CodeFlowStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("goto", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow)]
|
||||
public void Jump(string args)
|
||||
{
|
||||
string key = NormalizeBlockName(args);
|
||||
|
||||
if (RuntimeInfo.Labels.TryGetValue(key, out int value))
|
||||
{
|
||||
RuntimeInfo.LineNumber = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.SearchLabel = key;
|
||||
RuntimeInfo.IsLocalSearch = RuntimeInfo.IsInFunction;
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = " goto ")]
|
||||
public void JumpIf(string args)
|
||||
{
|
||||
string[] parts = args.Split(" goto ", 2, StringSplitOptions.None);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string condition = parts[0].Trim();
|
||||
string key = NormalizeBlockName(parts[1]);
|
||||
|
||||
bool? result = Evaluator.EvaluateCondition(condition);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidOperation, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuntimeInfo.Labels.TryGetValue(key, out int value))
|
||||
{
|
||||
RuntimeInfo.LineNumber = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.SearchLabel = key;
|
||||
RuntimeInfo.IsLocalSearch = RuntimeInfo.IsInFunction;
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("label", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true, Separator = ":")]
|
||||
public void FindLabel(string args)
|
||||
{
|
||||
string labelDeclaration = args.Trim();
|
||||
if (!labelDeclaration.EndsWith(':'))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string key = NormalizeBlockName(labelDeclaration);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key)
|
||||
{
|
||||
RuntimeInfo.SearchLabel = string.Empty;
|
||||
RuntimeInfo.IsLocalSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("call", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow)]
|
||||
public void Call(string args)
|
||||
{
|
||||
CallFunction(NormalizeBlockName(args));
|
||||
}
|
||||
|
||||
[Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow, Separator = " call ")]
|
||||
public void CallIf(string args)
|
||||
{
|
||||
string[] parts = args.Split(" call ", 2, StringSplitOptions.None);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string condition = parts[0].Trim();
|
||||
bool? result = Evaluator.EvaluateCondition(condition);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidOperation, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
CallFunction(NormalizeBlockName(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":", BlockPair = "end_if")]
|
||||
public void IfBlock(string args)
|
||||
{
|
||||
args = args.Trim();
|
||||
if (!args.EndsWith(':'))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string condition = args[..^1].Trim();
|
||||
bool? result = Evaluator.EvaluateCondition(condition);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidOperation, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||
if (targetLine < 0)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.LineNumber = targetLine;
|
||||
}
|
||||
|
||||
[Statement("else:", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockIntermediate = true, BlockPair = "end_if")]
|
||||
public void Else(string _)
|
||||
{
|
||||
int targetLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||
if (targetLine < 0)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.LineNumber = targetLine;
|
||||
}
|
||||
|
||||
[Statement("end_if", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockEnd = true)]
|
||||
public void EndIf(string _)
|
||||
{
|
||||
// Intentionally empty: end_if is a block-boundary marker only; no runtime action needed.
|
||||
}
|
||||
|
||||
[Statement("while", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":", BlockPair = "end_while")]
|
||||
public void While(string args)
|
||||
{
|
||||
args = args.Trim();
|
||||
if (!args.EndsWith(':'))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string condition = args[..^1].Trim();
|
||||
bool? result = Evaluator.EvaluateCondition(condition);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidOperation, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int endWhileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||
if (endWhileLine < 0)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoMatchingEndWhile, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.LineNumber = endWhileLine;
|
||||
}
|
||||
|
||||
[Statement("end_while", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green, IsBlockEnd = true)]
|
||||
public void EndWhile(string _)
|
||||
{
|
||||
int whileLine = FindBlockBoundary(RuntimeInfo.LineNumber);
|
||||
if (whileLine < 0)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoMatchingWhile, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.LineNumber = whileLine - 1;
|
||||
}
|
||||
|
||||
[Statement("exit", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)]
|
||||
public void End(string _)
|
||||
{
|
||||
HandleExit(ExitMessages.PlannedTermination, false);
|
||||
}
|
||||
|
||||
[Statement("abort_all", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)]
|
||||
public void Terminate(string _)
|
||||
{
|
||||
HandleExit(ExitMessages.PlannedTerminationCancelingTasks, true);
|
||||
}
|
||||
|
||||
[Statement("throw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)]
|
||||
public void Throw(string message)
|
||||
{
|
||||
RuntimeInfo.Exit(message, true);
|
||||
}
|
||||
|
||||
[Statement("error", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)]
|
||||
public void Error(string message)
|
||||
{
|
||||
RuntimeInfo.Exit(message, false);
|
||||
}
|
||||
|
||||
private void CallFunction(string key)
|
||||
{
|
||||
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
|
||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||
{
|
||||
RuntimeInfo.LineNumber = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.SearchFunction = key;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleExit(string exitMessage, bool isError)
|
||||
{
|
||||
if (RuntimeInfo.IsSearching)
|
||||
{
|
||||
RuntimeInfo.IsInFunction = false;
|
||||
if (RuntimeInfo.IsLocalSearch)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.LabelNotFound(RuntimeInfo.SearchLabel), true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.IsInFunction = false;
|
||||
RuntimeInfo.Exit(exitMessage, isError);
|
||||
}
|
||||
|
||||
private int FindBlockBoundary(int currentLine)
|
||||
{
|
||||
return RuntimeInfo.BlockBoundaries.TryGetValue(currentLine, out int cached) ? cached : -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class ConsoleStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("print_line", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)]
|
||||
public void WriteLineEmpty(string _)
|
||||
{
|
||||
RuntimeInfo.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
[Statement("print_line", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)]
|
||||
public void WriteLine(string args)
|
||||
{
|
||||
RuntimeInfo.WriteLine(args);
|
||||
}
|
||||
|
||||
[Statement("print", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)]
|
||||
public void Write(string args)
|
||||
{
|
||||
RuntimeInfo.Write(args);
|
||||
}
|
||||
|
||||
[Statement("%read_line", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void ReadLine(string args)
|
||||
{
|
||||
args += " ";
|
||||
while (args.Contains("%read_line"))
|
||||
{
|
||||
string input = Console.ReadLine();
|
||||
if (input is null)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true);
|
||||
return;
|
||||
}
|
||||
args = args.ReplaceFirstOccurrence("%read_line ", input.ToSafeString() + " ");
|
||||
}
|
||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%read_key", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void ReadKey(string args)
|
||||
{
|
||||
args += " ";
|
||||
while (args.Contains("%read_key"))
|
||||
{
|
||||
string input = ConsoleExtensions.ReadKey(RuntimeInfo).ToString();
|
||||
args = args.ReplaceFirstOccurrence("%read_key ", input.ToSafeString() + " ");
|
||||
}
|
||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("clear", SearchMode.Exact, SpaceAround.None, ConsoleColor.Magenta)]
|
||||
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Won't work if static")]
|
||||
public void Clear(string _)
|
||||
{
|
||||
Console.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class FunctionStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("func", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true, Separator = ":")]
|
||||
public void FindFunction(string args)
|
||||
{
|
||||
if (RuntimeInfo.InternalIsInFunction)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NestedFunctionsNotAllowed, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string functionDeclaration = args.Trim();
|
||||
if (!functionDeclaration.EndsWith(':'))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string key = NormalizeBlockName(functionDeclaration);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchFunction) && RuntimeInfo.SearchFunction == key)
|
||||
{
|
||||
RuntimeInfo.SearchFunction = string.Empty;
|
||||
}
|
||||
|
||||
RuntimeInfo.IsInFunction = true;
|
||||
}
|
||||
|
||||
[Statement("push_in", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)]
|
||||
public void AddInParameter(string args)
|
||||
{
|
||||
RuntimeInfo.InParametersStack.Push(args);
|
||||
}
|
||||
|
||||
[Statement("%out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetOutParameter(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%out", RuntimeInfo.OutParametersStack, RuntimeInfo, ExitMessages.NoOutArgumentInStack);
|
||||
}
|
||||
|
||||
[Statement("%has_out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void CheckIfOutParameterAvailable(string args)
|
||||
{
|
||||
args = args.Replace("%has_out", (RuntimeInfo.OutParametersStack.Count > 0).ToString());
|
||||
|
||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("call", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.Low, Separator = " with ")]
|
||||
public void Call(string args)
|
||||
{
|
||||
string[] parts = args.Split(" with ", 2, StringSplitOptions.None);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string key = NormalizeBlockName(parts[0]);
|
||||
string[] functionArguments = parts[1].Split(',');
|
||||
|
||||
foreach (string argument in functionArguments)
|
||||
{
|
||||
RuntimeInfo.InParametersStack.Push(argument.Trim());
|
||||
}
|
||||
|
||||
RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack<string>(RuntimeInfo.InParametersStack)));
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
RuntimeInfo.CurrentLine = string.Empty;
|
||||
|
||||
if (RuntimeInfo.Functions.TryGetValue(key, out int value))
|
||||
{
|
||||
RuntimeInfo.LineNumber = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.SearchFunction = key;
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("%in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetInParameter(string args)
|
||||
{
|
||||
if (!RuntimeInfo.IsInFunction)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessStackParameters(args, "%in", RuntimeInfo.FunctionCallStack.Peek().Arguments, RuntimeInfo, ExitMessages.NoInArgumentInStack);
|
||||
}
|
||||
|
||||
[Statement("%has_in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void CheckIfInParameterAvailable(string args)
|
||||
{
|
||||
if (!RuntimeInfo.IsInFunction)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true);
|
||||
return;
|
||||
}
|
||||
|
||||
args = args.Replace("%has_in", (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count > 0).ToString());
|
||||
|
||||
RuntimeInfo.CurrentLine = args.TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("push_out", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)]
|
||||
public void AddOutParameter(string args)
|
||||
{
|
||||
if (!RuntimeInfo.IsInFunction)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.FunctionCallStack.Peek().Results.Push(args);
|
||||
}
|
||||
|
||||
[Statement("return", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)]
|
||||
public void Return(string _)
|
||||
{
|
||||
if (!RuntimeInfo.IsInFunction)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuntimeInfo.IsSearching)
|
||||
{
|
||||
RuntimeInfo.IsInFunction = false;
|
||||
|
||||
if (RuntimeInfo.IsLocalSearch)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.LabelNotFound(RuntimeInfo.SearchLabel), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.IsInFunction = false;
|
||||
|
||||
if (RuntimeInfo.FunctionCallStack.Count > 0)
|
||||
{
|
||||
FunctionScope functionScope = RuntimeInfo.FunctionCallStack.Pop();
|
||||
|
||||
RuntimeInfo.OutParametersStack = new Stack<string>(functionScope.Results);
|
||||
RuntimeInfo.LineNumber = functionScope.CallerLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.NoFunctionInStack, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("clear_call_stack", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red)]
|
||||
public void ClearCallStack(string _)
|
||||
{
|
||||
RuntimeInfo.FunctionCallStack.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class ListStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " new")]
|
||||
public void Create(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " new");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = parts[0];
|
||||
if (!RuntimeInfo.Lists.TryGetValue(name, out List<string> value))
|
||||
{
|
||||
RuntimeInfo.Lists.Add(name, []);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " delete")]
|
||||
public void Delete(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " delete");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string name = parts[0];
|
||||
|
||||
if (!RuntimeInfo.Lists.ContainsKey(name))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.ListNotFound(name), true);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = RuntimeInfo.Lists.Remove(name);
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " clear")]
|
||||
public void Clear(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " clear");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " length")]
|
||||
public void Length(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " length");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Push(list.Count.ToString());
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " add ")]
|
||||
public void Add(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " add ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parts[1]))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return;
|
||||
}
|
||||
|
||||
list.Add(parts[1].Trim());
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " get ")]
|
||||
public void Get(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " get ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseIndex(parts[1], out int index, list.Count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Push(list[index]);
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " remove ")]
|
||||
public void Remove(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " remove ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseIndex(parts[1], out int index, list.Count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " set ")]
|
||||
public void Set(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " set ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] indexAndValue = SplitIndexAndValue(parts[1]);
|
||||
if (indexAndValue.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseIndex(indexAndValue[0], out int index, list.Count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list[index] = indexAndValue[1];
|
||||
}
|
||||
|
||||
[Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " insert ")]
|
||||
public void Insert(string args)
|
||||
{
|
||||
string[] parts = SplitTwo(args, " insert ");
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetList(parts[0], out List<string> list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] indexAndValue = SplitIndexAndValue(parts[1]);
|
||||
if (indexAndValue.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseIndex(indexAndValue[0], out int index, list.Count + 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list.Insert(index, indexAndValue[1]);
|
||||
}
|
||||
|
||||
private string[] SplitTwo(string input, string separator)
|
||||
{
|
||||
string[] parts = input.Split(separator, 2, StringSplitOptions.None);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
parts[0] = parts[0].Trim();
|
||||
parts[1] = parts[1].Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parts[0]))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private bool TryGetList(string name, out List<string> list)
|
||||
{
|
||||
if (!RuntimeInfo.Lists.TryGetValue(name, out list))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.ListNotFound(name), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string[] SplitIndexAndValue(string input)
|
||||
{
|
||||
string[] parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
return [];
|
||||
}
|
||||
|
||||
parts[0] = parts[0].Trim();
|
||||
parts[1] = parts[1].Trim();
|
||||
return parts;
|
||||
}
|
||||
|
||||
private bool TryParseIndex(string rawIndex, out int index, int maxExclusive)
|
||||
{
|
||||
bool success = int.TryParse(rawIndex.Trim(), out index);
|
||||
if (!success)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidIndex(rawIndex), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= maxExclusive)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.IndexOutOfRange(index), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class PredefinedVariableStatements : StatementRuntimeInformation
|
||||
{
|
||||
private readonly Random random = new Random();
|
||||
|
||||
[Statement("%time", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetUnixTimestamp(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()).TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetOperatingSystem(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%os", Environment.OSVersion.Platform.ToString()).TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetProcessorArchitecture(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()).TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetIsOperatingSystem64Bit(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%is64", Environment.Is64BitOperatingSystem.ToString()).TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetPi(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessSimplePlaceholders(args, "%pi", Math.PI.ToString()).TrimEnd();
|
||||
}
|
||||
|
||||
[Statement("%rand", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)]
|
||||
public void GetRandom(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessDynamicPlaceholders(args, "%rand", () => random.Next(32767, int.MaxValue).ToString()).TrimEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal partial class ProcessingStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)]
|
||||
public void Calculate(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessCalculations(args.FromSafeString(), RuntimeInfo, CalculationRegex());
|
||||
}
|
||||
|
||||
[Statement("eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)]
|
||||
public void Evaluate(string args)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = args.FromSafeString();
|
||||
}
|
||||
|
||||
[Statement("task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)]
|
||||
public void RunTask(string line)
|
||||
{
|
||||
int lineNumber = RuntimeInfo.LineNumber;
|
||||
List<Line> lines = RuntimeInfo.Lines.GetRange(0, RuntimeInfo.Lines.Count);
|
||||
|
||||
Line oldLine = lines[lineNumber];
|
||||
|
||||
lines[lineNumber] = new Line(line, oldLine.FileName, oldLine.LineNumber);
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
YesNtInterpreter interpreter = new YesNtInterpreter();
|
||||
interpreter.Execute(lines, RuntimeInfo.GlobalVariables, lineNumber, RuntimeInfo);
|
||||
});
|
||||
|
||||
RuntimeInfo.CurrentLine = string.Empty;
|
||||
}
|
||||
|
||||
[Statement("sleep", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)]
|
||||
public void Sleep(string args)
|
||||
{
|
||||
if (int.TryParse(args, out int millisecondsTimeout))
|
||||
{
|
||||
ConsoleExtensions.Sleep(millisecondsTimeout, RuntimeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidTimeoutValue(args), true);
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("length", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)]
|
||||
public void Length(string args)
|
||||
{
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
|
||||
RuntimeInfo.OutParametersStack.Push(args.FromSafeString().Length.ToString());
|
||||
}
|
||||
|
||||
[Statement("import", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)]
|
||||
public void Import(string path)
|
||||
{
|
||||
path = path.FromSafeString();
|
||||
path = Path.Combine(RuntimeInfo.WorkingDirectory, path);
|
||||
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(path)))
|
||||
{
|
||||
path = Path.ChangeExtension(path, "ynt");
|
||||
}
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeInfo.Lines.RemoveAt(RuntimeInfo.LineNumber);
|
||||
string[] lines = File.ReadAllLines(path);
|
||||
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
RuntimeInfo.Lines.Insert(RuntimeInfo.LineNumber + i, new Line(lines[i], Path.GetFileName(path), i));
|
||||
}
|
||||
RuntimeInfo.PreScanLinesAction?.Invoke();
|
||||
RuntimeInfo.LineNumber--;
|
||||
}
|
||||
catch
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.CouldNotLoadFile(path), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.CouldNotFindFile(path), true);
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("[0-9*+().,^%/-]+[0-9*+ ().,^%/-]+[0-9*+().,^%/-]+")]
|
||||
private static partial Regex CalculationRegex();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Text;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class StringLiteralStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("\"", SearchMode.Contains, SpaceAround.None, System.ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)]
|
||||
public void ParseStringLiterals(string args)
|
||||
{
|
||||
if (!args.Contains('"'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder(args.Length);
|
||||
|
||||
#pragma warning disable S127 // i is intentionally advanced to track position within quoted literals and escape sequences
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
char current = args[i];
|
||||
if (current != '"')
|
||||
{
|
||||
_ = output.Append(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
StringBuilder literal = new StringBuilder();
|
||||
bool closed = false;
|
||||
i++;
|
||||
|
||||
for (; i < args.Length; i++)
|
||||
{
|
||||
char ch = args[i];
|
||||
if (ch == '\\' && i + 1 < args.Length)
|
||||
{
|
||||
i++;
|
||||
_ = literal.Append(ParseEscape(args[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '"')
|
||||
{
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
_ = literal.Append(ch);
|
||||
}
|
||||
#pragma warning restore S127
|
||||
|
||||
if (!closed)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidStringLiteral, true);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = output.Append(literal.ToString().ToSafeString());
|
||||
}
|
||||
|
||||
RuntimeInfo.CurrentLine = output.ToString();
|
||||
}
|
||||
|
||||
private static char ParseEscape(char escapeChar)
|
||||
{
|
||||
return escapeChar switch
|
||||
{
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'b' => '\b',
|
||||
'f' => '\f',
|
||||
'a' => '\a',
|
||||
'v' => '\v',
|
||||
'"' => '"',
|
||||
'\\' => '\\',
|
||||
_ => escapeChar
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal class SystemStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("exec", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Separator = " with ")]
|
||||
public void ExecuteProgramWithArgs(string input)
|
||||
{
|
||||
string[] parts = input.FromSafeString().Split(" with ", 2, StringSplitOptions.None);
|
||||
string program = parts[0].Trim();
|
||||
|
||||
foreach (string argument in parts[1].Split(','))
|
||||
{
|
||||
RuntimeInfo.InParametersStack.Push(argument.Trim());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
StartProcess(program, string.Join(" ", RuntimeInfo.InParametersStack.Reverse()));
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.CannotFindFile(program), false);
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.FailedToStart(program, ex.Message), false);
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("exec", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)]
|
||||
public void ExecuteProgram(string input)
|
||||
{
|
||||
// This is to prevent the "exec with" statement from being triggered by this one, since they both start with "exec"
|
||||
if (input.Contains(" with "))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
input = input.FromSafeString();
|
||||
|
||||
try
|
||||
{
|
||||
StartProcess(input, string.Join(" ", RuntimeInfo.InParametersStack.Reverse()));
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.CannotFindFile(input), false);
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.FailedToStart(input, ex.Message), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Process_ErrorDataReceived(Utilities.DataReceivedEventArgs e, Stack<string> outputStack)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outputStack.Push(e.Data.ToSafeString());
|
||||
RuntimeInfo.Write("Error: " + e.Data);
|
||||
}
|
||||
|
||||
private void Process_OutputDataReceived(Utilities.DataReceivedEventArgs e, Stack<string> outputStack)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outputStack.Push(e.Data.ToSafeString());
|
||||
RuntimeInfo.Write(e.Data);
|
||||
}
|
||||
|
||||
private void StartProcess(string name, string args)
|
||||
{
|
||||
RuntimeInfo.OutParametersStack.Clear();
|
||||
|
||||
Stack<string> outputStack = new Stack<string>();
|
||||
|
||||
FixedProcess process = new FixedProcess
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = name,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
}
|
||||
};
|
||||
|
||||
process.OutputDataReceived += (s, e) => Process_OutputDataReceived(e, outputStack);
|
||||
process.ErrorDataReceived += (s, e) => Process_ErrorDataReceived(e, outputStack);
|
||||
|
||||
_ = process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
RuntimeInfo.InParametersStack.Clear();
|
||||
|
||||
RuntimeInfo.OutParametersStack = new(outputStack);
|
||||
RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using YesNt.Interpreter.Attributes;
|
||||
using YesNt.Interpreter.Enums;
|
||||
using YesNt.Interpreter.Runtime;
|
||||
using YesNt.Interpreter.Utilities;
|
||||
|
||||
namespace YesNt.Interpreter.Statements;
|
||||
|
||||
internal partial class VariableStatements : StatementRuntimeInformation
|
||||
{
|
||||
[Statement("var", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
|
||||
public void DefineVariable(string args)
|
||||
{
|
||||
DefineVariableIn(RuntimeInfo.Variables, args);
|
||||
}
|
||||
|
||||
[Statement("global", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")]
|
||||
public void DefineGlobalVariable(string args)
|
||||
{
|
||||
DefineVariableIn(RuntimeInfo.GlobalVariables, args);
|
||||
}
|
||||
|
||||
private void DefineVariableIn(Dictionary<string, string> dict, string args)
|
||||
{
|
||||
string[] parts = args.Split('=');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string key = parts[0].Trim();
|
||||
if (key.Contains(' '))
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
}
|
||||
|
||||
dict[key] = parts[1].Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("delete", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.Red, Priority = Priority.VeryLow)]
|
||||
public void DeleteVariable(string args)
|
||||
{
|
||||
string key = args.Trim();
|
||||
|
||||
if (RuntimeInfo.Variables.ContainsKey(key))
|
||||
{
|
||||
_ = RuntimeInfo.Variables.Remove(key);
|
||||
}
|
||||
else if (RuntimeInfo.GlobalVariables.ContainsKey(key))
|
||||
{
|
||||
_ = RuntimeInfo.GlobalVariables.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
RuntimeInfo.Exit(ExitMessages.VariableNotFound(key), true);
|
||||
}
|
||||
}
|
||||
|
||||
[Statement("${", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest, Separator = "}")]
|
||||
public void ReadVariable(string _)
|
||||
{
|
||||
RuntimeInfo.CurrentLine = TemplateProcessor.ProcessVariables(RuntimeInfo.CurrentLine, RuntimeInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.Interpreter.Utilities;
|
||||
|
||||
internal static class ConsoleExtensions
|
||||
{
|
||||
public static char ReadKey(RuntimeInformation runtimeInformation)
|
||||
{
|
||||
while (!runtimeInformation.Stop)
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
return Console.ReadKey().KeyChar;
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
|
||||
public static void Sleep(int millisecondsTimeout, RuntimeInformation runtimeInformation)
|
||||
{
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
while (!runtimeInformation.Stop)
|
||||
{
|
||||
if (stopwatch.ElapsedMilliseconds > millisecondsTimeout)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace YesNt.Interpreter.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides expression evaluation used by conditional and arithmetic statements.
|
||||
/// </summary>
|
||||
internal static partial class Evaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates a boolean condition string such as <c>a == b</c>, <c>x > 3</c>, or <c>true</c>.
|
||||
/// </summary>
|
||||
/// <param name="input">The condition expression, which may contain safe-string encoded values.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> or <see langword="false"/> if the condition could be evaluated;
|
||||
/// <see langword="null"/> if the expression is not a recognized condition form (treated as an error by callers).
|
||||
/// </returns>
|
||||
public static bool? EvaluateCondition(string input)
|
||||
{
|
||||
input = input.FromSafeString();
|
||||
string lower = input.ToLower().Trim();
|
||||
if (lower == "true")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (lower == "false")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] parts = input.Split("==");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string part1 = parts[0].Trim();
|
||||
string part2 = parts[1].Trim();
|
||||
return part1 == part2;
|
||||
}
|
||||
|
||||
parts = input.Split("!=");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string part1 = parts[0].Trim();
|
||||
string part2 = parts[1].Trim();
|
||||
return part1 != part2;
|
||||
}
|
||||
|
||||
parts = input.Split(">=");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
bool succ1 = parts[0].ToStandardizedNumber(out double part1);
|
||||
bool succ2 = parts[1].ToStandardizedNumber(out double part2);
|
||||
return succ1 && succ2 && part1 >= part2;
|
||||
}
|
||||
|
||||
parts = input.Split("<=");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
bool succ1 = parts[0].ToStandardizedNumber(out double part1);
|
||||
bool succ2 = parts[1].ToStandardizedNumber(out double part2);
|
||||
return succ1 && succ2 && part1 <= part2;
|
||||
}
|
||||
|
||||
parts = input.Split(">");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
bool succ1 = parts[0].ToStandardizedNumber(out double part1);
|
||||
bool succ2 = parts[1].ToStandardizedNumber(out double part2);
|
||||
return succ1 && succ2 && part1 > part2;
|
||||
}
|
||||
|
||||
parts = input.Split("<");
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
bool succ1 = parts[0].ToStandardizedNumber(out double part1);
|
||||
bool succ2 = parts[1].ToStandardizedNumber(out double part2);
|
||||
return succ1 && succ2 && part1 < part2;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a numeric arithmetic expression string and returns the result as a string.
|
||||
/// Supports <c>+</c>, <c>-</c>, <c>*</c>, <c>/</c>, <c>%</c> (modulo), and <c>^</c> (power) operators
|
||||
/// with standard precedence (<c>^</c> highest, <c>+</c>/<c>-</c> lowest) and parentheses.
|
||||
/// Adjacent sign characters (<c>++</c>, <c>--</c>, <c>-+</c>, <c>+-</c>) are normalized before evaluation.
|
||||
/// </summary>
|
||||
/// <param name="input">The arithmetic expression to evaluate.</param>
|
||||
/// <returns>The result as a culture-invariant numeric string, or <c>"NaN"</c> if evaluation failed.</returns>
|
||||
public static string Calculate(string input)
|
||||
{
|
||||
input = input.FromSafeString();
|
||||
input = PlusPlusRegex().Replace(input, "+");
|
||||
input = MinusMinusRegex().Replace(input, "+");
|
||||
input = MinusPlusRegex().Replace(input, "-");
|
||||
input = PlusMinusRegex().Replace(input, "-");
|
||||
|
||||
return CalculateInternal(input, '+');
|
||||
}
|
||||
|
||||
private static string CalculateInternal(string input, char op)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.ToStandardizedNumber(out double quickNum))
|
||||
{
|
||||
return quickNum.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
MatchCollection matches = ParenthesesRegex().Matches(input);
|
||||
while (matches.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
string calc = matches[i].Value.Substring(1, matches[i].Length - 2);
|
||||
string ret = CalculateInternal(calc, '+');
|
||||
input = input.Replace(matches[i].Value, ret);
|
||||
}
|
||||
matches = ParenthesesRegex().Matches(input);
|
||||
}
|
||||
|
||||
string[] parts = input.Split(op);
|
||||
|
||||
// If the expression starts with the operator (e.g. "-3 + 5" split by '-' gives ["", "3 + 5"]),
|
||||
// prepend the operator back onto the first real part so it isn't lost.
|
||||
if (parts.Length >= 2 && string.IsNullOrWhiteSpace(parts[0]))
|
||||
{
|
||||
parts[1] = $"{op}{parts[1]}";
|
||||
parts = parts.Skip(1).ToArray();
|
||||
}
|
||||
|
||||
double number = double.NaN;
|
||||
|
||||
foreach (string p in parts)
|
||||
{
|
||||
string part = p;
|
||||
|
||||
part = op switch
|
||||
{
|
||||
'+' => CalculateInternal(part, '-'),
|
||||
'-' => CalculateInternal(part, '*'),
|
||||
'*' => CalculateInternal(part, '/'),
|
||||
'/' => CalculateInternal(part, '%'),
|
||||
'%' => CalculateInternal(part, '^'),
|
||||
_ => part
|
||||
};
|
||||
|
||||
if (part is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (part.ToStandardizedNumber(out double num))
|
||||
{
|
||||
if (double.IsNaN(number))
|
||||
{
|
||||
number = num;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case '+':
|
||||
number += num;
|
||||
break;
|
||||
|
||||
case '-':
|
||||
number -= num;
|
||||
break;
|
||||
|
||||
case '*':
|
||||
number *= num;
|
||||
break;
|
||||
|
||||
case '/':
|
||||
number /= num;
|
||||
break;
|
||||
|
||||
case '%':
|
||||
number %= num;
|
||||
break;
|
||||
|
||||
case '^':
|
||||
number = Math.Pow(number, num);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return number.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\(([^()]+)\\)")]
|
||||
private static partial Regex ParenthesesRegex();
|
||||
|
||||
[GeneratedRegex("(\\+ +\\+)+")]
|
||||
private static partial Regex PlusPlusRegex();
|
||||
|
||||
[GeneratedRegex("(\\- +\\-)+")]
|
||||
private static partial Regex MinusMinusRegex();
|
||||
|
||||
[GeneratedRegex("(\\- +\\+)+")]
|
||||
private static partial Regex MinusPlusRegex();
|
||||
|
||||
[GeneratedRegex("(\\+ +\\-)+")]
|
||||
private static partial Regex PlusMinusRegex();
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace YesNt.Interpreter.Utilities;
|
||||
|
||||
/// <summary>Represents the method that handles the <see cref="FixedProcess.OutputDataReceived"/> and <see cref="FixedProcess.ErrorDataReceived"/> events.</summary>
|
||||
public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
|
||||
|
||||
internal delegate void UserCallBack(string data);
|
||||
|
||||
/// <summary>
|
||||
/// A workaround replacement for <see cref="System.Diagnostics.Process"/> that fixes a buffering
|
||||
/// issue in <see cref="System.Diagnostics.Process.BeginOutputReadLine"/> / <see cref="System.Diagnostics.Process.BeginErrorReadLine"/>:
|
||||
/// the BCL implementation only delivers data when a newline is encountered, which means partial
|
||||
/// lines are not raised until the process writes another newline or exits.
|
||||
/// <see cref="FixedProcess"/> flushes whatever is in the read buffer immediately, enabling real-time
|
||||
/// output forwarding for interactive child processes.
|
||||
/// </summary>
|
||||
internal class FixedProcess : Process
|
||||
{
|
||||
public new event DataReceivedEventHandler OutputDataReceived;
|
||||
|
||||
public new event DataReceivedEventHandler ErrorDataReceived;
|
||||
|
||||
internal AsyncStreamReader output;
|
||||
internal AsyncStreamReader error;
|
||||
|
||||
public new void BeginOutputReadLine()
|
||||
{
|
||||
Stream baseStream = StandardOutput.BaseStream;
|
||||
output = new AsyncStreamReader(baseStream, new UserCallBack(FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding);
|
||||
output.BeginReadLine();
|
||||
}
|
||||
|
||||
public new void BeginErrorReadLine()
|
||||
{
|
||||
Stream baseStream = StandardError.BaseStream;
|
||||
error = new AsyncStreamReader(baseStream, new UserCallBack(FixedErrorReadNotifyUser), StandardError.CurrentEncoding);
|
||||
error.BeginReadLine();
|
||||
}
|
||||
|
||||
internal void FixedOutputReadNotifyUser(string data)
|
||||
{
|
||||
DataReceivedEventHandler outputDataReceived = OutputDataReceived;
|
||||
if (outputDataReceived != null)
|
||||
{
|
||||
DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data);
|
||||
if (SynchronizingObject != null && SynchronizingObject.InvokeRequired)
|
||||
{
|
||||
_ = SynchronizingObject.Invoke(outputDataReceived,
|
||||
[
|
||||
this,
|
||||
dataReceivedEventArgs
|
||||
]);
|
||||
return;
|
||||
}
|
||||
outputDataReceived(this, dataReceivedEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
internal void FixedErrorReadNotifyUser(string data)
|
||||
{
|
||||
DataReceivedEventHandler errorDataReceived = ErrorDataReceived;
|
||||
if (errorDataReceived != null)
|
||||
{
|
||||
DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data);
|
||||
if (SynchronizingObject != null && SynchronizingObject.InvokeRequired)
|
||||
{
|
||||
_ = SynchronizingObject.Invoke(errorDataReceived,
|
||||
[
|
||||
this,
|
||||
dataReceivedEventArgs
|
||||
]);
|
||||
return;
|
||||
}
|
||||
errorDataReceived(this, dataReceivedEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Provides data for the <see cref="FixedProcess.OutputDataReceived"/> and <see cref="FixedProcess.ErrorDataReceived"/> events.</summary>
|
||||
public class DataReceivedEventArgs : EventArgs
|
||||
{
|
||||
internal string _data;
|
||||
|
||||
/// <summary>Gets the line of characters that was written to a redirected <see cref="T:System.Diagnostics.Process" /> output stream.</summary>
|
||||
/// <returns>The line that was written by an associated <see cref="T:System.Diagnostics.Process" /> to its redirected <see cref="P:System.Diagnostics.Process.StandardOutput" /> or <see cref="P:System.Diagnostics.Process.StandardError" /> stream.</returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public string Data => _data;
|
||||
|
||||
internal DataReceivedEventArgs(string data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsyncStreamReader : IDisposable
|
||||
{
|
||||
internal const int DefaultBufferSize = 1024;
|
||||
private readonly Queue messageQueue;
|
||||
private Stream stream;
|
||||
private Encoding encoding;
|
||||
private Decoder decoder;
|
||||
private byte[] byteBuffer;
|
||||
private char[] charBuffer;
|
||||
private UserCallBack userCallBack;
|
||||
private bool cancelOperation;
|
||||
private ManualResetEvent eofEvent;
|
||||
private StringBuilder sb;
|
||||
private bool bLastCarriageReturn;
|
||||
public virtual Encoding CurrentEncoding => encoding;
|
||||
public virtual Stream BaseStream => stream;
|
||||
|
||||
internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding) : this(stream, callback, encoding, 1024)
|
||||
{
|
||||
}
|
||||
|
||||
internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
||||
{
|
||||
Init(stream, callback, encoding, bufferSize);
|
||||
messageQueue = new Queue();
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal void BeginReadLine()
|
||||
{
|
||||
if (cancelOperation)
|
||||
{
|
||||
cancelOperation = false;
|
||||
}
|
||||
if (sb == null)
|
||||
{
|
||||
sb = new StringBuilder(1024);
|
||||
_ = stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null);
|
||||
return;
|
||||
}
|
||||
FlushMessageQueue();
|
||||
}
|
||||
|
||||
internal void CancelOperation()
|
||||
{
|
||||
cancelOperation = true;
|
||||
}
|
||||
|
||||
internal void WaitUtilEOF()
|
||||
{
|
||||
if (eofEvent != null)
|
||||
{
|
||||
_ = eofEvent.WaitOne();
|
||||
eofEvent.Close();
|
||||
eofEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && stream != null)
|
||||
{
|
||||
stream.Close();
|
||||
}
|
||||
if (stream != null)
|
||||
{
|
||||
stream = null;
|
||||
encoding = null;
|
||||
decoder = null;
|
||||
byteBuffer = null;
|
||||
charBuffer = null;
|
||||
}
|
||||
|
||||
eofEvent?.Close();
|
||||
eofEvent = null;
|
||||
}
|
||||
|
||||
private void Init(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.encoding = encoding;
|
||||
userCallBack = callback;
|
||||
decoder = encoding.GetDecoder();
|
||||
if (bufferSize < 128)
|
||||
{
|
||||
bufferSize = 128;
|
||||
}
|
||||
byteBuffer = new byte[bufferSize];
|
||||
int _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
|
||||
charBuffer = new char[_maxCharsPerBuffer];
|
||||
cancelOperation = false;
|
||||
eofEvent = new ManualResetEvent(false);
|
||||
sb = null;
|
||||
bLastCarriageReturn = false;
|
||||
}
|
||||
|
||||
private void ReadBuffer(IAsyncResult ar)
|
||||
{
|
||||
int num;
|
||||
try
|
||||
{
|
||||
num = stream.EndRead(ar);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
if (num == 0)
|
||||
{
|
||||
lock (messageQueue)
|
||||
{
|
||||
if (sb.Length != 0)
|
||||
{
|
||||
messageQueue.Enqueue(sb.ToString());
|
||||
sb.Length = 0;
|
||||
}
|
||||
messageQueue.Enqueue(null);
|
||||
}
|
||||
try
|
||||
{
|
||||
FlushMessageQueue();
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = eofEvent.Set();
|
||||
}
|
||||
}
|
||||
int chars = decoder.GetChars(byteBuffer, 0, num, charBuffer, 0);
|
||||
_ = sb.Append(charBuffer, 0, chars);
|
||||
GetLinesFromStringBuilder();
|
||||
_ = stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null);
|
||||
}
|
||||
|
||||
private void GetLinesFromStringBuilder()
|
||||
{
|
||||
int i = 0;
|
||||
int num = 0;
|
||||
int length = sb.Length;
|
||||
if (bLastCarriageReturn && length > 0 && sb[0] == '\n')
|
||||
{
|
||||
i = 1;
|
||||
num = 1;
|
||||
bLastCarriageReturn = false;
|
||||
}
|
||||
while (i < length)
|
||||
{
|
||||
char c = sb[i];
|
||||
if (c is '\r' or '\n')
|
||||
{
|
||||
if (c == '\r' && i + 1 < length && sb[i + 1] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
string obj = sb.ToString(num, i + 1 - num);
|
||||
|
||||
num = i + 1;
|
||||
|
||||
lock (messageQueue)
|
||||
{
|
||||
messageQueue.Enqueue(obj);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Flush Fix: Send Whatever is left in the buffer
|
||||
string endOfBuffer = sb.ToString(num, length - num);
|
||||
lock (messageQueue)
|
||||
{
|
||||
messageQueue.Enqueue(endOfBuffer);
|
||||
num = length;
|
||||
}
|
||||
// End Flush Fix
|
||||
|
||||
if (sb[length - 1] == '\r')
|
||||
{
|
||||
bLastCarriageReturn = true;
|
||||
}
|
||||
if (num < length)
|
||||
{
|
||||
_ = sb.Remove(0, num);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Length = 0;
|
||||
}
|
||||
FlushMessageQueue();
|
||||
}
|
||||
|
||||
private void FlushMessageQueue()
|
||||
{
|
||||
while (messageQueue.Count > 0)
|
||||
{
|
||||
lock (messageQueue)
|
||||
{
|
||||
if (messageQueue.Count > 0)
|
||||
{
|
||||
string data = (string)messageQueue.Dequeue();
|
||||
if (!cancelOperation)
|
||||
{
|
||||
userCallBack(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace YesNt.Interpreter.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for string manipulation used throughout the interpreter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// YesNt uses a "safe string" encoding to pass values through the interpreter pipeline without
|
||||
/// accidentally triggering keyword matching. Special characters (spaces, operators, punctuation,
|
||||
/// control characters) are replaced with SOH-delimited three-letter codes
|
||||
/// (e.g. space → <c>\x01spc\x01</c>, newline → <c>\x01nli\x01</c>). These codes use the
|
||||
/// non-printable SOH character (U+0001) as a sentinel. Written via string concatenation
|
||||
/// (<c>"\x01" + "spc" + "\x01"</c>) to avoid C#'s greedy <c>\x</c> hex escape absorbing
|
||||
/// following hex-digit letters. U+0001 cannot appear in normal user source, preventing raw
|
||||
/// source text from being accidentally decoded.
|
||||
/// The mapping is defined in <see cref="ReplacementRules"/>. Use <see cref="ToSafeString"/>
|
||||
/// to encode and <see cref="FromSafeString"/> to decode.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the table that maps special characters to their safe-string escape codes.
|
||||
/// Keys are the original characters; values are the three-letter tilde codes.
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> ReplacementRules { get; } = new()
|
||||
{
|
||||
{"~", "\x01" + "til" + "\x01" },
|
||||
{" ", "\x01" + "spc" + "\x01" },
|
||||
{"%", "\x01" + "per" + "\x01" },
|
||||
{"<", "\x01" + "let" + "\x01" },
|
||||
{">", "\x01" + "grt" + "\x01" },
|
||||
{",", "\x01" + "com" + "\x01" },
|
||||
{"!", "\x01" + "exm" + "\x01" },
|
||||
{"|", "\x01" + "pip" + "\x01" },
|
||||
{"\n", "\x01" + "nli" + "\x01" },
|
||||
{"\r", "\x01" + "ret" + "\x01" },
|
||||
{"\t", "\x01" + "tab" + "\x01" },
|
||||
{"\b", "\x01" + "bac" + "\x01" },
|
||||
{"\f", "\x01" + "for" + "\x01" },
|
||||
{"\a", "\x01" + "ale" + "\x01" },
|
||||
{"", "\x01" + "emp" + "\x01" },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> reverseReplacementRules = ReplacementRules.ToDictionary(x => x.Value, x => x.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a string into safe-string format so that special characters cannot accidentally
|
||||
/// trigger interpreter keyword matching. Each character is wrapped with vertical-tab sentinels
|
||||
/// before rule substitution so that multi-character replacements do not overlap.
|
||||
/// </summary>
|
||||
/// <param name="input">The plain string to encode.</param>
|
||||
/// <returns>The safe-string encoded representation.</returns>
|
||||
public static string ToSafeString(this string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder(input.Length * 3);
|
||||
foreach (char c in input)
|
||||
{
|
||||
string s = c.ToString();
|
||||
if (ReplacementRules.TryGetValue(s, out string replacement))
|
||||
{
|
||||
_ = output.Append('\v');
|
||||
_ = output.Append(replacement);
|
||||
_ = output.Append('\v');
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = output.Append('\v');
|
||||
_ = output.Append(c);
|
||||
_ = output.Append('\v');
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a safe-string back to its original plain-text form.
|
||||
/// </summary>
|
||||
/// <param name="input">A safe-string encoded string.</param>
|
||||
/// <returns>The decoded plain string.</returns>
|
||||
public static string FromSafeString(this string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || (!input.Contains('\v') && !input.Contains('\x01')))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
string stripped = input.Replace("\v", string.Empty);
|
||||
if (!stripped.Contains('\x01'))
|
||||
{
|
||||
return stripped;
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder(stripped.Length);
|
||||
#pragma warning disable S127 // i is intentionally advanced by 4 when a 5-char escape code is consumed
|
||||
for (int i = 0; i < stripped.Length; i++)
|
||||
{
|
||||
if (stripped[i] == '\x01' && i + 4 < stripped.Length && stripped[i + 4] == '\x01')
|
||||
{
|
||||
string code = stripped.Substring(i, 5);
|
||||
if (reverseReplacementRules.TryGetValue(code, out string value))
|
||||
{
|
||||
_ = output.Append(value);
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_ = output.Append(stripped[i]);
|
||||
}
|
||||
#pragma warning restore S127
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the string as a <see cref="double"/>, first decoding safe-string encoding
|
||||
/// and normalising decimal separators (comma → period).
|
||||
/// </summary>
|
||||
/// <param name="input">The string to parse (may be safe-string encoded).</param>
|
||||
/// <param name="result">When this method returns, contains the parsed value if successful.</param>
|
||||
/// <returns><see langword="true"/> if parsing succeeded; otherwise <see langword="false"/>.</returns>
|
||||
public static bool ToStandardizedNumber(this string input, out double result)
|
||||
{
|
||||
if (input.IndexOf(',') != -1)
|
||||
{
|
||||
input = input.Replace(',', '.');
|
||||
}
|
||||
return double.TryParse(input, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
/// <summary>Replaces only the first occurrence of <paramref name="oldValue"/> in the string.</summary>
|
||||
/// <param name="input">The source string.</param>
|
||||
/// <param name="oldValue">The substring to find.</param>
|
||||
/// <param name="newValue">The replacement value.</param>
|
||||
/// <returns>A new string with the first occurrence replaced.</returns>
|
||||
public static string ReplaceFirstOccurrence(this string input, string oldValue, string newValue)
|
||||
{
|
||||
int place = input.IndexOf(oldValue);
|
||||
return input.Remove(place, oldValue.Length).Insert(place, newValue);
|
||||
}
|
||||
|
||||
/// <summary>Replaces only the last occurrence of <paramref name="oldValue"/> in the string.</summary>
|
||||
/// <param name="input">The source string.</param>
|
||||
/// <param name="oldValue">The substring to find.</param>
|
||||
/// <param name="newValue">The replacement value.</param>
|
||||
/// <returns>A new string with the last occurrence replaced.</returns>
|
||||
public static string ReplaceLastOccurrence(this string input, string oldValue, string newValue)
|
||||
{
|
||||
int place = input.LastIndexOf(oldValue);
|
||||
return input.Remove(place, Math.Min(oldValue.Length, input.Length - place)).Insert(place, newValue);
|
||||
}
|
||||
|
||||
/// <summary>Counts the number of trailing whitespace characters in the string.</summary>
|
||||
/// <param name="input">The source string.</param>
|
||||
/// <returns>The number of whitespace characters at the end of the string.</returns>
|
||||
public static int WhiteSpaceAtEnd(this string input)
|
||||
{
|
||||
int count = 0;
|
||||
int index = input.Length - 1;
|
||||
while (index >= 0 && char.IsWhiteSpace(input[index--]))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace YesNt.Interpreter.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides high-performance template substitution for variables and stack parameters.
|
||||
/// </summary>
|
||||
internal static class TemplateProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of ${variableName} with their current values.
|
||||
/// </summary>
|
||||
public static string ProcessVariables(string input, RuntimeInformation runtimeInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
int startIdx = input.IndexOf("${", StringComparison.Ordinal);
|
||||
if (startIdx == -1)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.Length);
|
||||
int lastIdx = 0;
|
||||
|
||||
while (startIdx != -1)
|
||||
{
|
||||
_ = sb.Append(input, lastIdx, startIdx - lastIdx);
|
||||
int endIdx = input.IndexOf('}', startIdx + 2);
|
||||
if (endIdx == -1)
|
||||
{
|
||||
_ = sb.Append("${");
|
||||
lastIdx = startIdx + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
string varName = input[(startIdx + 2)..endIdx];
|
||||
if (runtimeInfo.Variables.TryGetValue(varName, out string value))
|
||||
{
|
||||
_ = sb.Append(value);
|
||||
}
|
||||
else if (runtimeInfo.GlobalVariables.TryGetValue(varName, out value))
|
||||
{
|
||||
_ = sb.Append(value);
|
||||
}
|
||||
else if (!runtimeInfo.IsSearching)
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.VariableNotFound(varName), true);
|
||||
return input;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = sb.Append("${");
|
||||
_ = sb.Append(varName);
|
||||
_ = sb.Append('}');
|
||||
}
|
||||
|
||||
lastIdx = endIdx + 1;
|
||||
}
|
||||
|
||||
startIdx = input.IndexOf("${", lastIdx, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
_ = sb.Append(input, lastIdx, input.Length - lastIdx);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of a placeholder (e.g., %in, %out) with values popped from a stack.
|
||||
/// </summary>
|
||||
public static string ProcessStackParameters(string input, string placeholder, Stack<string> stack, RuntimeInformation runtimeInfo, string emptyStackMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
int startIdx = input.IndexOf(placeholder, StringComparison.Ordinal);
|
||||
if (startIdx == -1)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.Length);
|
||||
int lastIdx = 0;
|
||||
int placeholderLen = placeholder.Length;
|
||||
|
||||
while (startIdx != -1)
|
||||
{
|
||||
_ = sb.Append(input, lastIdx, startIdx - lastIdx);
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
runtimeInfo.Exit(emptyStackMessage, true);
|
||||
return input;
|
||||
}
|
||||
_ = sb.Append(stack.Pop());
|
||||
lastIdx = startIdx + placeholderLen;
|
||||
startIdx = input.IndexOf(placeholder, lastIdx, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
_ = sb.Append(input, lastIdx, input.Length - lastIdx);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of a placeholder with a fixed value.
|
||||
/// </summary>
|
||||
public static string ProcessSimplePlaceholders(string input, string placeholder, string value)
|
||||
{
|
||||
return string.IsNullOrEmpty(input) ? input : input.Replace(placeholder, value, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of a placeholder with values generated by a provider function.
|
||||
/// </summary>
|
||||
public static string ProcessDynamicPlaceholders(string input, string placeholder, Func<string> valueProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
int startIdx = input.IndexOf(placeholder, StringComparison.Ordinal);
|
||||
if (startIdx == -1)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.Length);
|
||||
int lastIdx = 0;
|
||||
int placeholderLen = placeholder.Length;
|
||||
|
||||
while (startIdx != -1)
|
||||
{
|
||||
_ = sb.Append(input, lastIdx, startIdx - lastIdx);
|
||||
_ = sb.Append(valueProvider());
|
||||
lastIdx = startIdx + placeholderLen;
|
||||
startIdx = input.IndexOf(placeholder, lastIdx, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
_ = sb.Append(input, lastIdx, input.Length - lastIdx);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of arithmetic expressions with their results.
|
||||
/// </summary>
|
||||
public static string ProcessCalculations(string input, RuntimeInformation runtimeInfo, Regex calculationRegex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
MatchCollection matches = calculationRegex.Matches(input);
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.Length);
|
||||
int lastIdx = 0;
|
||||
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
Match match = matches[i];
|
||||
_ = sb.Append(input, lastIdx, match.Index - lastIdx);
|
||||
|
||||
string res = Evaluator.Calculate(match.Value);
|
||||
if (res is null)
|
||||
{
|
||||
runtimeInfo.Exit(ExitMessages.InvalidOperation, true);
|
||||
return input;
|
||||
}
|
||||
_ = sb.Append(res);
|
||||
lastIdx = match.Index + match.Length;
|
||||
}
|
||||
|
||||
_ = sb.Append(input, lastIdx, input.Length - lastIdx);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>YesNt.Interpreter</RootNamespace>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Library</OutputType>
|
||||
<StartupObject />
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/Stone-Red-Code/YesNt-Interpreter/</RepositoryUrl>
|
||||
<PackageTags>scripting, modding, language</PackageTags>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\assets\Logo.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\..\README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.20.0.135146">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YesNt.Interpreter.Generator\YesNt.Interpreter.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user