diff --git a/YesNt.Interpreter/Attributes/StatementAttribute.cs b/YesNt.Interpreter/Attributes/StatementAttribute.cs index b2aabf4..6f8f543 100644 --- a/YesNt.Interpreter/Attributes/StatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StatementAttribute.cs @@ -4,19 +4,69 @@ using YesNt.Interpreter.Enums; namespace YesNt.Interpreter.Attributes; +/// +/// Marks a method as a YesNt statement handler. +/// The interpreter matches source lines against the keyword according to +/// and rules, then invokes the decorated method +/// with the remaining argument text. +/// +/// +/// Methods decorated with this attribute must be instance methods on a class that inherits +/// and must accept a single parameter. +/// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class StatementAttribute : Attribute { + /// Gets the keyword that identifies this statement in source code. public string Name { get; } + + /// Gets where in the line the keyword is searched for. public SearchMode SearchMode { get; } + + /// Gets which sides of the keyword must be padded with a space. public SpaceAround SpaceAround { get; } + + /// Gets or sets the syntax-highlight colour used by the code editor. public ConsoleColor Color { get; set; } + + /// + /// Gets or sets the execution priority. Statements with a lower value + /// run before those with a higher value. Defaults to . + /// public Priority Priority { get; set; } = Priority.Normal; + + /// + /// 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 . + /// public bool ExecuteInSearchMode { get; set; } + + /// + /// 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 . + /// public bool KeepStatementInArgs { get; set; } + + /// + /// Gets a value indicating whether this statement should be excluded from syntax highlighting. + /// Set to when no is provided. + /// public bool IgnoreSyntaxHighlighting { get; } + + /// + /// 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. call vs call … with …). + /// public string Separator { get; set; } + /// + /// Initialises a new with a syntax-highlight colour. + /// + /// The keyword that identifies this statement. + /// Where in the line the keyword is matched. + /// Which sides of the keyword require a surrounding space. + /// The colour used for syntax highlighting in the code editor. public StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color) { Name = name; @@ -25,6 +75,13 @@ public class StatementAttribute : Attribute Color = color; } + /// + /// Initialises a new without a syntax-highlight colour. + /// The statement will be excluded from syntax highlighting. + /// + /// The keyword that identifies this statement. + /// Where in the line the keyword is matched. + /// Which sides of the keyword require a surrounding space. public StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) { Name = name; diff --git a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs index 59f80e3..4ed6682 100644 --- a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs @@ -4,9 +4,28 @@ using YesNt.Interpreter.Enums; namespace YesNt.Interpreter.Attributes; +/// +/// 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. +/// +/// +/// Methods decorated with this attribute must be instance methods on a class that inherits +/// and must have no parameters. +/// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class StaticStatementAttribute : Attribute { + /// + /// 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 . + /// public bool ExecuteInSearchMode { get; set; } + + /// + /// Gets or sets the execution priority relative to other static statements. + /// Defaults to . + /// public Priority Priority { get; set; } = Priority.Normal; } \ No newline at end of file diff --git a/YesNt.Interpreter/Enums/Priority.cs b/YesNt.Interpreter/Enums/Priority.cs index eb8353d..8fbaf51 100644 --- a/YesNt.Interpreter/Enums/Priority.cs +++ b/YesNt.Interpreter/Enums/Priority.cs @@ -1,12 +1,28 @@ namespace YesNt.Interpreter.Enums; +/// +/// Controls the execution order of statements. Lower values run first. +/// public enum Priority { + /// Runs before all other statements. Used for syntax pre-processing such as string literals. PreProcessing, + + /// Runs very early. Used for inline substitutions such as variable reads and parameter pops. Highest, + + /// Runs early. VeryHigh, + + /// Runs above normal order. High, + + /// Default execution order. Normal, + + /// Runs below normal order. Low, + + /// Runs last. Used for control-flow and variable definitions that depend on substitutions being complete. VeryLow } \ No newline at end of file diff --git a/YesNt.Interpreter/Enums/SearchMode.cs b/YesNt.Interpreter/Enums/SearchMode.cs index 0226142..a13e749 100644 --- a/YesNt.Interpreter/Enums/SearchMode.cs +++ b/YesNt.Interpreter/Enums/SearchMode.cs @@ -1,9 +1,19 @@ namespace YesNt.Interpreter.Enums; +/// +/// Determines where in a source line the interpreter searches for a statement keyword. +/// public enum SearchMode { + /// The keyword must appear at the beginning of the line. StartOfLine, + + /// The keyword must appear at the end of the line. EndOfLine, + + /// The keyword may appear anywhere in the line. Contains, + + /// The entire line must exactly match the keyword. Exact } \ No newline at end of file diff --git a/YesNt.Interpreter/Enums/SpaceAround.cs b/YesNt.Interpreter/Enums/SpaceAround.cs index 0ee0990..c9e4292 100644 --- a/YesNt.Interpreter/Enums/SpaceAround.cs +++ b/YesNt.Interpreter/Enums/SpaceAround.cs @@ -1,9 +1,19 @@ namespace YesNt.Interpreter.Enums; +/// +/// Specifies which sides of a statement keyword must be surrounded by a space when matching. +/// public enum SpaceAround { + /// A space is required both before and after the keyword. StartEnd, + + /// A space is required before the keyword only. Start, + + /// A space is required after the keyword only. End, + + /// No surrounding spaces are required. None } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/DebugEventArgs.cs b/YesNt.Interpreter/Runtime/DebugEventArgs.cs index 0f2d2fe..0a0a842 100644 --- a/YesNt.Interpreter/Runtime/DebugEventArgs.cs +++ b/YesNt.Interpreter/Runtime/DebugEventArgs.cs @@ -2,11 +2,32 @@ namespace YesNt.Interpreter.Runtime; +/// +/// Provides per-line execution data raised through . +/// public class DebugEventArgs : EventArgs { + /// Gets the 1-based line number of the executed line within its source file. public int LineNumber { get; internal set; } + + /// + /// Gets the line content after all statement transformations have been applied + /// (e.g. after variable substitution). May differ from . + /// public string CurrentLine { get; internal set; } + + /// Gets the raw line content as it appeared in the source file. public string OriginalLine { get; internal set; } + + /// + /// Gets the task identifier of the task that executed this line, or 0 if the line + /// was executed on the main thread. + /// public int TaskId { get; internal set; } + + /// + /// Gets a value indicating whether this line was executed inside a background task + /// (spawned with the task statement). + /// public bool IsTask { get; internal set; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/ExitMessages.cs b/YesNt.Interpreter/Runtime/ExitMessages.cs index a86b289..4d08b09 100644 --- a/YesNt.Interpreter/Runtime/ExitMessages.cs +++ b/YesNt.Interpreter/Runtime/ExitMessages.cs @@ -1,5 +1,9 @@ namespace YesNt.Interpreter.Runtime; +/// +/// Central repository of all exit/error message strings used by . +/// Keeping messages here ensures consistency and makes them easy to find or localise. +/// internal static class ExitMessages { internal const string InvalidSyntax = "Invalid syntax"; diff --git a/YesNt.Interpreter/Runtime/FunctionScope.cs b/YesNt.Interpreter/Runtime/FunctionScope.cs index 4bea9c6..e7c7abd 100644 --- a/YesNt.Interpreter/Runtime/FunctionScope.cs +++ b/YesNt.Interpreter/Runtime/FunctionScope.cs @@ -2,12 +2,27 @@ namespace YesNt.Interpreter.Runtime; +/// +/// Represents one frame on the function call stack. Created when a call statement is +/// executed and popped when the matching return is reached. +/// internal class FunctionScope(int callerLine, Stack arguments) { + /// Gets the zero-based line index to return to after this function completes. public int CallerLine { get; } = callerLine; + + /// Gets the local variable table for this function invocation. public Dictionary Variables { get; } = []; + + /// Gets the local list table for this function invocation. public Dictionary> Lists { get; } = []; + + /// Gets the local label table for this function invocation. public Dictionary Labels { get; } = []; + + /// Gets the stack of input arguments passed to this function via push_in. public Stack Arguments { get; } = arguments; + + /// Gets the stack of output values pushed via push_out, consumed by the caller via %out. public Stack Results { get; } = new(); } diff --git a/YesNt.Interpreter/Runtime/Line.cs b/YesNt.Interpreter/Runtime/Line.cs index 7f3447d..fcb53af 100644 --- a/YesNt.Interpreter/Runtime/Line.cs +++ b/YesNt.Interpreter/Runtime/Line.cs @@ -1,10 +1,16 @@ namespace YesNt.Interpreter.Runtime; +/// +/// Represents a single source line together with its location metadata. +/// internal class Line(string content, string fileName, int lineNumber) { + /// Gets or sets the raw text content of the line. public string Content { get; set; } = content; + /// Gets or sets the name of the source file this line originated from. public string FileName { get; set; } = fileName; + /// Gets or sets the zero-based line index within . public int LineNumber { get; set; } = lineNumber; } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 3e9a49c..7ad1ca1 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -5,6 +5,13 @@ using YesNt.Interpreter.Utilities; namespace YesNt.Interpreter.Runtime; +/// +/// 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 task statement owns its own +/// whose points back +/// to the main execution context. +/// internal sealed class RuntimeInformation { public event Action OnDebugOutput; diff --git a/YesNt.Interpreter/Runtime/StatementInformation.cs b/YesNt.Interpreter/Runtime/StatementInformation.cs index ab7e9a0..e08f4a6 100644 --- a/YesNt.Interpreter/Runtime/StatementInformation.cs +++ b/YesNt.Interpreter/Runtime/StatementInformation.cs @@ -4,12 +4,32 @@ using YesNt.Interpreter.Enums; namespace YesNt.Interpreter.Runtime; +/// +/// A read-only snapshot of a registered statement's metadata, used for tooling such as +/// syntax highlighters. Instances are obtained from . +/// public class StatementInformation { + /// Gets the keyword that identifies this statement in source code. public string Name { get; internal set; } + + /// Gets where in the line the keyword is searched for. public SearchMode SearchMode { get; internal set; } + + /// Gets which sides of the keyword must be padded with a space. public SpaceAround SpaceAround { get; internal set; } + + /// Gets the syntax-highlight colour for this statement. public ConsoleColor Color { get; internal set; } + + /// + /// Gets a value indicating whether this statement is excluded from syntax highlighting. + /// public bool IgnoreSyntaxHighlighting { get; internal set; } + + /// + /// Gets the optional sub-string that must be present in the line for this statement to match, + /// or if no separator is required. + /// public string Separator { get; set; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs b/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs index 3a0c30a..a8b89e7 100644 --- a/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs +++ b/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs @@ -1,6 +1,16 @@ namespace YesNt.Interpreter.Runtime; +/// +/// Base class for all classes that host statement handler methods. +/// Subclasses declare methods decorated with or +/// ; the source generator +/// (GeneratedStatementRegistry) discovers these at compile time and wires them up. +/// internal abstract class StatementRuntimeInformation { + /// + /// Gets or sets the runtime state for the current execution context. + /// Injected by the generated registry before any handler is invoked. + /// public RuntimeInformation RuntimeInfo { get; set; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 02a3f65..462918a 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -10,16 +10,45 @@ using YesNt.Interpreter.Utilities; namespace YesNt.Interpreter.Runtime; +/// +/// The main entry point for executing YesNt scripts. +/// +/// +/// Running a script file: +/// +/// var interpreter = new YesNtInterpreter(); +/// interpreter.Execute("path/to/script.ynt"); +/// +/// Running script lines in memory with a custom statement: +/// +/// var interpreter = new YesNtInterpreter(); +/// interpreter.AddStatement("log", SearchMode.StartOfLine, SpaceAround.End, args => +/// Console.WriteLine($"[LOG] {args}")); +/// interpreter.Execute(new List<string> { "log hello world" }); +/// +/// public class YesNtInterpreter { + /// + /// Raised after each line is executed in debug mode. The argument is + /// when execution ends (either normally or due to an error), allowing callers to detect completion. + /// public event Action OnLineExecuted; + /// + /// Raised in debug mode whenever the script produces output (e.g. via print_line). + /// In non-debug mode output is written directly to . + /// public event Action OnDebugOutput; private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); private Dictionary> statements; private readonly List> staticStatements; + /// + /// Gets a read-only snapshot of all currently registered statements. + /// Useful for building syntax highlighters or documentation tools. + /// public ReadOnlyCollection StatementInformation { get @@ -41,6 +70,16 @@ public class YesNtInterpreter } } + /// + /// Registers a custom statement using a pre-built . + /// If a statement with the same attribute key already exists it will be replaced. + /// The statement list is re-sorted by priority after insertion. + /// + /// The attribute describing the keyword, search mode, and priority. + /// + /// The delegate invoked when the statement matches. Receives the argument text + /// (the part of the line after the keyword, unless is set). + /// public void AddStatement(StatementAttribute attribute, Action handler) { statements[attribute] = handler; @@ -50,16 +89,34 @@ public class YesNtInterpreter .ToDictionary(x => x.Key, x => x.Value); } + /// + /// Registers a custom statement without a syntax-highlight colour. + /// + /// The keyword that identifies this statement in source code. + /// Where in the line the keyword is matched. + /// Which sides of the keyword require a surrounding space. + /// The delegate invoked when the statement matches. public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action handler) { AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler); } + /// + /// Registers a custom statement with a syntax-highlight colour. + /// + /// The keyword that identifies this statement in source code. + /// Where in the line the keyword is matched. + /// Which sides of the keyword require a surrounding space. + /// The colour used for syntax highlighting in the code editor. + /// The delegate invoked when the statement matches. public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action handler) { AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler); } + /// + /// Initialises a new and registers all built-in statements. + /// public YesNtInterpreter() { GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements); @@ -68,11 +125,23 @@ public class YesNtInterpreter runtimeInfo.OnLineExecuted += e => OnLineExecuted?.Invoke(e); } + /// + /// Requests a graceful stop of the currently executing script. + /// The interpreter will terminate at the next line boundary. + /// public void Stop() { runtimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true); } + /// + /// Executes a YesNt script file. + /// + /// The path to the .ynt script file. + /// + /// When , output is routed through instead of + /// and line-execution events are raised via . + /// public void Execute(string path, bool isDebugMode = false) { runtimeInfo.Reset(); @@ -83,6 +152,14 @@ public class YesNtInterpreter } } + /// + /// Executes a YesNt script supplied as an in-memory list of lines. + /// + /// The script lines to execute. + /// + /// When , output is routed through and + /// line-execution events are raised via . + /// public void Execute(List lines, bool isDebugMode = false) { runtimeInfo.Reset(); diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs index a13fa91..427731a 100644 --- a/YesNt.Interpreter/Utilities/Evaluator.cs +++ b/YesNt.Interpreter/Utilities/Evaluator.cs @@ -4,8 +4,19 @@ using System.Text.RegularExpressions; namespace YesNt.Interpreter.Utilities; +/// +/// Provides expression evaluation used by conditional and arithmetic statements. +/// internal static partial class Evaluator { + /// + /// Evaluates a boolean condition string such as a == b, x > 3, or true. + /// + /// The condition expression, which may contain safe-string encoded values. + /// + /// or if the condition could be evaluated; + /// if the expression is not a recognised condition form (treated as an error by callers). + /// public static bool? EvaluateCondition(string input) { if (input.ToLower().FromSafeString().Trim() == "true") @@ -68,6 +79,13 @@ internal static partial class Evaluator return null; } + /// + /// Evaluates a numeric arithmetic expression string and returns the result as a string. + /// Supports +, -, *, and / operators. + /// Adjacent sign characters (++, --, -+, +-) are normalised before evaluation. + /// + /// The arithmetic expression to evaluate. + /// The result as a culture-invariant numeric string, or "NaN" if evaluation failed. public static string Calculate(string input) { input = PlusPlusRegex().Replace(input, "+"); diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs index 28b401a..9430c88 100644 --- a/YesNt.Interpreter/Utilities/FixedProcess.cs +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -11,6 +11,14 @@ public delegate void DataReceivedEventHandler(object sender, DataReceivedEventAr internal delegate void UserCallBack(string data); +/// +/// A workaround replacement for that fixes a buffering +/// issue in / : +/// 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. +/// flushes whatever is in the read buffer immediately, enabling real-time +/// output forwarding for interactive child processes. +/// public class FixedProcess : Process { public new event DataReceivedEventHandler OutputDataReceived; diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index a28f1ea..71d13f7 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -6,10 +6,27 @@ using System.Text; namespace YesNt.Interpreter.Utilities; +/// +/// Extension methods for string manipulation used throughout the interpreter. +/// +/// +/// +/// 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 tilde-prefixed three-letter codes +/// (e.g. space → ~spc, newline → ~nli). The mapping is defined in +/// . Use to encode and +/// to decode. +/// +/// public static class StringExtensions { private static readonly Dictionary reverseReplacementRules; + /// + /// 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. + /// public static Dictionary ReplacementRules { get; } = new() { {"~", "~til" }, @@ -34,6 +51,13 @@ public static class StringExtensions reverseReplacementRules = ReplacementRules.ToDictionary(x => x.Value, x => x.Key); } + /// + /// 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. + /// + /// The plain string to encode. + /// The safe-string encoded representation. public static string ToSafeString(this string input) { StringBuilder output = new StringBuilder(); @@ -45,28 +69,53 @@ public static class StringExtensions return ReplaceOnce(output.ToString(), ReplacementRules); } + /// + /// Decodes a safe-string back to its original plain-text form. + /// + /// A safe-string encoded string. + /// The decoded plain string. public static string FromSafeString(this string input) { return ReplaceOnce(input.Replace("\v", string.Empty), reverseReplacementRules); } + /// + /// Tries to parse the string as a , first decoding safe-string encoding + /// and normalising decimal separators (comma → period). + /// + /// The string to parse (may be safe-string encoded). + /// When this method returns, contains the parsed value if successful. + /// if parsing succeeded; otherwise . public static bool ToStandardizedNumber(this string input, out double result) { return double.TryParse(input.FromSafeString().Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out result); } + /// Replaces only the first occurrence of in the string. + /// The source string. + /// The substring to find. + /// The replacement value. + /// A new string with the first occurrence replaced. 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); } + /// Replaces only the last occurrence of in the string. + /// The source string. + /// The substring to find. + /// The replacement value. + /// A new string with the last occurrence replaced. 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); } + /// Counts the number of trailing whitespace characters in the string. + /// The source string. + /// The number of whitespace characters at the end of the string. public static int WhiteSpaceAtEnd(this string input) { int count = 0;