From 3c98c2b26f8cb6de708c4299f7e2f1f284d2b558 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 28 Apr 2022 19:58:30 +0200 Subject: [PATCH 01/73] Code structure and speed imporvments --- .../Runtime/RuntimeInformation.cs | 2 +- .../PredifinedVariableStatements.cs | 35 +++++++++++++++---- .../Statements/VariableStatements.cs | 4 ++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 0833eb0..9b5efc1 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -6,7 +6,7 @@ using YesNt.Interpreter.Utilities; namespace YesNt.Interpreter.Runtime { - internal class RuntimeInformation + internal sealed class RuntimeInformation { private RuntimeInformation parentRuntimeInformation; private static int internalTaskId = 0; diff --git a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs index 6974940..6740f44 100644 --- a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs +++ b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs @@ -11,13 +11,34 @@ namespace YesNt.Interpreter.Statements { private readonly Random random = new Random(); - [Statement("%tim", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%time", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetUnixTimestamp(string args) { - while (args.Contains("%tim")) - { - args = args.ReplaceFirstOccurrence("%tim", $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"); - } + args = args.Replace("%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetOperatingSystem(string args) + { + args = args.Replace("%os", Environment.OSVersion.Platform.ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetProcessorArchitecture(string args) + { + args = args.Replace("%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetIsOperatingSystem64Bit(string args) + { + args = args.Replace("%is64", $"{Environment.Is64BitOperatingSystem}"); RuntimeInfo.CurrentLine = args.TrimEnd(); } @@ -25,7 +46,7 @@ namespace YesNt.Interpreter.Statements [Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetPi(string args) { - args = args.Replace("%pi", $"{Math.PI}"); + args = args.Replace("%pi", Math.PI.ToString()); RuntimeInfo.CurrentLine = args.TrimEnd(); } @@ -35,7 +56,7 @@ namespace YesNt.Interpreter.Statements { while (args.Contains("%rnd")) { - args = args.ReplaceFirstOccurrence("%rnd", $"{random.Next(32767, int.MaxValue)}"); + args = args.ReplaceFirstOccurrence("%rnd", random.Next(32767, int.MaxValue).ToString()); } RuntimeInfo.CurrentLine = args.TrimEnd(); diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 8716075..3d2e48a 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -9,6 +9,8 @@ namespace YesNt.Interpreter.Statements { internal class VariableStatements : StatementRuntimeInformation { + private static readonly Regex variableStatementRegex = new Regex(@">[a-zA-Z0-9]+"); + [Statement("<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] public void DefineVariable(string args) { @@ -105,7 +107,7 @@ namespace YesNt.Interpreter.Statements return; } - MatchCollection matches = Regex.Matches(RuntimeInfo.CurrentLine, @">[a-zA-Z0-9]+"); + MatchCollection matches = variableStatementRegex.Matches(RuntimeInfo.CurrentLine); for (int i = 0; i < matches.Count; i++) { From dd6fbf3be773db1dd5d6fe942d7c7ca06189513c Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 24 May 2022 19:49:25 +0200 Subject: [PATCH 02/73] Add system statements ('exc') --- .editorconfig | 3 + YesNt-Interpreter.sln | 16 +- .../Properties/launchSettings.json | 12 + YesNt.CodeEditor/YesNt.CodeEditor.csproj | 1 + .../YesNt.Interpreter.Tests.csproj | 2 + .../Statements/SystemStatements.cs | 111 ++++++ YesNt.Interpreter/Utilities/FixedProcess.cs | 316 ++++++++++++++++++ YesNt.Interpreter/YesNt.Interpreter.csproj | 1 + 8 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 YesNt.CodeEditor/Properties/launchSettings.json create mode 100644 YesNt.Interpreter/Statements/SystemStatements.cs create mode 100644 YesNt.Interpreter/Utilities/FixedProcess.cs diff --git a/.editorconfig b/.editorconfig index 051cab7..0ec1ee4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -64,6 +64,9 @@ dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case +# S1172: Unused method parameters should be removed +dotnet_diagnostic.S1172.severity = none + [*.{cs,vb}] #### Naming styles #### diff --git a/YesNt-Interpreter.sln b/YesNt-Interpreter.sln index ed6df67..0181ad5 100644 --- a/YesNt-Interpreter.sln +++ b/YesNt-Interpreter.sln @@ -12,26 +12,40 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YesNt.Interpreter.Tests", "YesNt.Interpreter.Tests\YesNt.Interpreter.Tests.csproj", "{2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter.Tests", "YesNt.Interpreter.Tests\YesNt.Interpreter.Tests.csproj", "{2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.ActiveCfg = Debug|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.Build.0 = Debug|x64 {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.Build.0 = Release|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.ActiveCfg = Release|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.Build.0 = Release|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.ActiveCfg = Debug|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.Build.0 = Debug|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.ActiveCfg = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.Build.0 = Release|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.ActiveCfg = Release|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.Build.0 = Release|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.ActiveCfg = Debug|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.Build.0 = Debug|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.Build.0 = Release|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.ActiveCfg = Release|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YesNt.CodeEditor/Properties/launchSettings.json b/YesNt.CodeEditor/Properties/launchSettings.json new file mode 100644 index 0000000..161eba8 --- /dev/null +++ b/YesNt.CodeEditor/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "YesNt.CodeEditor": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/YesNt.CodeEditor/YesNt.CodeEditor.csproj b/YesNt.CodeEditor/YesNt.CodeEditor.csproj index 68e1220..16be40b 100644 --- a/YesNt.CodeEditor/YesNt.CodeEditor.csproj +++ b/YesNt.CodeEditor/YesNt.CodeEditor.csproj @@ -3,6 +3,7 @@ Exe net5.0 + AnyCPU;x64 diff --git a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj index cf7cd15..9e6dacd 100644 --- a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj +++ b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj @@ -5,6 +5,8 @@ enable false + + AnyCPU;x64 diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs new file mode 100644 index 0000000..fb48103 --- /dev/null +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -0,0 +1,111 @@ +using System; +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("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Seperator = "|")] + public void ExecuteProgramWithArgs(string input) + { + string[] parts = input.FromSaveString().Split('|'); + parts[0] = parts[0].Trim(); + + string[] functionArgumets = parts[1].Split(','); + + foreach (string argumanet in functionArgumets) + { + RuntimeInfo.InParametersStack.Push(argumanet.Trim()); + } + + try + { + StartProcess(parts[0], string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); + } + catch (FileNotFoundException) + { + RuntimeInfo.Exit($"Cannot find file \"{parts[0]}\".", false); + } + catch (Win32Exception ex) + { + RuntimeInfo.Exit($"Failed to start \"{parts[0]}\". {ex.Message}", false); + } + + //HACK: Clear line to avoid execution from other "exc" statement + RuntimeInfo.CurrentLine = string.Empty; + } + + [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)] + public void ExecuteProgram(string input) + { + try + { + StartProcess(input, string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); + } + catch (FileNotFoundException) + { + RuntimeInfo.Exit($"Cannot find file \"{input}\".", false); + } + catch (Win32Exception ex) + { + RuntimeInfo.Exit($"Failed to start \"{input}\". {ex.Message}", false); + } + } + + private void StartProcess(string name, string args) + { + FixedProcess process = new FixedProcess + { + StartInfo = new ProcessStartInfo() + { + FileName = name, + Arguments = args, + RedirectStandardOutput = true, + RedirectStandardError = true + } + }; + + process.OutputDataReceived += Process_OutputDataReceived; + process.ErrorDataReceived += Process_ErrorDataReceived; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + + RuntimeInfo.InParametersStack.Clear(); + RuntimeInfo.OutParametersStack.Clear(); + RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); + } + + private void Process_ErrorDataReceived(object sender, Utilities.DataReceivedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + RuntimeInfo.OutParametersStack.Push(e.Data); + Console.Write("Error: " + e.Data); + } + + private void Process_OutputDataReceived(object sender, Utilities.DataReceivedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + RuntimeInfo.OutParametersStack.Push(e.Data); + Console.Write(e.Data); + } + } +} \ No newline at end of file diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs new file mode 100644 index 0000000..65a32e2 --- /dev/null +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; + +namespace YesNt.Interpreter.Utilities +{ + internal delegate void UserCallBack(string data); + + public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e); + + public class FixedProcess : Process + { + internal AsyncStreamReader output; + internal AsyncStreamReader error; + + public new event DataReceivedEventHandler OutputDataReceived; + + public new event DataReceivedEventHandler ErrorDataReceived; + + public new void BeginOutputReadLine() + { + Stream baseStream = StandardOutput.BaseStream; + output = new AsyncStreamReader(this, baseStream, new UserCallBack(FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding); + output.BeginReadLine(); + } + + public new void BeginErrorReadLine() + { + Stream baseStream = StandardError.BaseStream; + error = new AsyncStreamReader(this, 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, new object[] + { + 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, new object[] + { + this, + dataReceivedEventArgs + }); + return; + } + errorDataReceived(this, dataReceivedEventArgs); + } + } + } + + internal class AsyncStreamReader : IDisposable + { + internal const int DefaultBufferSize = 1024; + 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 readonly Queue messageQueue; + private StringBuilder sb; + private bool bLastCarriageReturn; + public virtual Encoding CurrentEncoding => encoding; + public virtual Stream BaseStream => stream; + + internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) : this(process, stream, callback, encoding, 1024) + { + } + + internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) + { + Init(process, stream, callback, encoding, bufferSize); + messageQueue = new Queue(); + } + + private void Init(Process process, 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; + } + + public virtual void Close() + { + Dispose(true); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + 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; + } + if (eofEvent != null) + { + eofEvent.Close(); + eofEvent = null; + } + } + + 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; + } + + 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 == '\r' || c == '\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); + } + } + } + } + } + + internal void WaitUtilEOF() + { + if (eofEvent != null) + { + eofEvent.WaitOne(); + eofEvent.Close(); + eofEvent = null; + } + } + } + + public class DataReceivedEventArgs : EventArgs + { + internal string _data; + + /// Gets the line of characters that was written to a redirected output stream. + /// The line that was written by an associated to its redirected or stream. + /// 2 + public string Data => _data; + + internal DataReceivedEventArgs(string data) + { + _data = data; + } + } +} \ No newline at end of file diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index c017137..bd4ef12 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -6,6 +6,7 @@ Exe + AnyCPU;x64 From 533ad23c4da8187ce0a1869d88a738eda59888c8 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 09:29:29 +0100 Subject: [PATCH 03/73] Fix error when loading empty file --- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 1b89015..ce07f9b 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -94,8 +94,10 @@ namespace YesNt.Interpreter.Runtime { runtimeInfo.Reset(); runtimeInfo.IsDebugMode = isDebugMode; - LoadFile(path); - Execute(); + if (LoadFile(path)) + { + Execute(); + } } public void Execute(List lines, bool isDebugMode = false) @@ -205,14 +207,7 @@ namespace YesNt.Interpreter.Runtime statement.Value.Invoke(copyLine); statementFound = true; - if (!leadingWhitespace) - { - runtimeInfo.CurrentLine = runtimeInfo.CurrentLine.Trim(); - } - else - { - runtimeInfo.CurrentLine = runtimeInfo.CurrentLine.TrimEnd(); - } + runtimeInfo.CurrentLine = !leadingWhitespace ? runtimeInfo.CurrentLine.Trim() : runtimeInfo.CurrentLine.TrimEnd(); } else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) { @@ -261,20 +256,28 @@ namespace YesNt.Interpreter.Runtime } } - private void LoadFile(string path) + private bool LoadFile(string path) { if (!File.Exists(path)) { - runtimeInfo.Exit($"File \"{path}\" not found!", true); - return; + Console.WriteLine($"File \"{path}\" not found!"); + return false; } string[] lines = File.ReadAllLines(path); + if (lines.Length <= 0) + { + Console.WriteLine($"File \"{path}\" is empty!"); + return false; + } + for (int i = 0; i < lines.Length; i++) { runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName(path), i)); } + + return true; } } } \ No newline at end of file From 1ced6f393c9d8c93ae5570d9592fb4a139db212f Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:40:51 +0100 Subject: [PATCH 04/73] Improve code formatting --- YesNt.CodeEditor/Editor.cs | 11 +---- YesNt.CodeEditor/InputHandler.cs | 25 +++++------- YesNt.CodeEditor/Program.cs | 12 +----- YesNt.Interpreter.Tests/CodeFowTests.cs | 2 +- YesNt.Interpreter.Tests/YesNtAssert.cs | 4 +- .../Runtime/RuntimeInformation.cs | 40 ++++--------------- .../Statements/SystemStatements.cs | 2 +- .../Statements/VariableStatements.cs | 4 +- YesNt.Interpreter/Utilities/Evaluator.cs | 28 ++----------- YesNt.Interpreter/Utilities/FixedProcess.cs | 18 ++++----- .../Utilities/StringExtentions.cs | 2 +- 11 files changed, 39 insertions(+), 109 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index dee06c7..54c92cb 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -23,7 +23,7 @@ namespace YesNt.CodeEditor { if (File.Exists(path)) { - Load(path); + _ = Load(path); } } @@ -131,14 +131,7 @@ namespace YesNt.CodeEditor if (CurrentPath.Trim() != path.Trim() && loadIfExists) { - if (Load(path)) - { - return true; - } - else - { - return false; - } + return Load(path); } if (string.IsNullOrEmpty(Path.GetExtension(path))) { diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index 59a3024..3b31f46 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -16,7 +16,7 @@ namespace YesNt.CodeEditor { while (Console.KeyAvailable) { - Console.ReadKey(true); + _ = Console.ReadKey(true); } if (textEditor.EditMode == Mode.Edit) { @@ -50,14 +50,7 @@ namespace YesNt.CodeEditor return true; case ConsoleKey.E: - if (textEditor.Lines.Count > textEditor.CursorPosition.Y) - { - textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length; - } - else - { - textEditor.CursorPosition.X = 0; - } + textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0; return true; } } @@ -128,7 +121,7 @@ namespace YesNt.CodeEditor StringBuilder lineBuilder = new StringBuilder(textEditor.Lines[textEditor.CursorPosition.Y]); while (lineBuilder.Length <= textEditor.CursorPosition.X) { - lineBuilder.Append(' '); + _ = lineBuilder.Append(' '); } textEditor.Lines[textEditor.CursorPosition.Y] = lineBuilder.ToString(); @@ -228,7 +221,7 @@ namespace YesNt.CodeEditor break; case "save": - textEditor.Save(input, false); + _ = textEditor.Save(input, false); break; case "run": @@ -240,9 +233,9 @@ namespace YesNt.CodeEditor textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); while (Console.KeyAvailable) { - Console.ReadKey(true); + _ = Console.ReadKey(true); } - Console.ReadKey(); + _ = Console.ReadKey(); WriteStatus(string.Empty); textEditor.EditMode = Mode.Command; } @@ -257,9 +250,9 @@ namespace YesNt.CodeEditor textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); while (Console.KeyAvailable) { - Console.ReadKey(true); + _ = Console.ReadKey(true); } - Console.ReadKey(); + _ = Console.ReadKey(); WriteStatus(string.Empty); textEditor.EditMode = Mode.Command; } @@ -278,7 +271,7 @@ namespace YesNt.CodeEditor try { - textEditor.Load(path); + _ = textEditor.Load(path); } catch (Exception ex) { diff --git a/YesNt.CodeEditor/Program.cs b/YesNt.CodeEditor/Program.cs index d95b4a7..ffa8ec7 100644 --- a/YesNt.CodeEditor/Program.cs +++ b/YesNt.CodeEditor/Program.cs @@ -4,17 +4,7 @@ { private static void Main(string[] args) { - TextEditor textEditor; - - if (args.Length > 0) - { - textEditor = new TextEditor(args[0]); - } - else - { - textEditor = new TextEditor(); - } - + TextEditor textEditor = args.Length > 0 ? new TextEditor(args[0]) : new TextEditor(); textEditor.Run(); } } diff --git a/YesNt.Interpreter.Tests/CodeFowTests.cs b/YesNt.Interpreter.Tests/CodeFowTests.cs index 105b253..23a715a 100644 --- a/YesNt.Interpreter.Tests/CodeFowTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowTests.cs @@ -39,6 +39,6 @@ public class CodeFlowTests public void CalculationsTest() { Assert.Inconclusive(); - YesNtAssert.IsLineEqual("10 * 10 !calc", (20).ToString()); + YesNtAssert.IsLineEqual("10 * 10 !calc", 20.ToString()); } } \ No newline at end of file diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index e7ae5f7..b7200a6 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -28,7 +28,7 @@ internal static class YesNtAssert if (er is null) { - onDone.Set(); + _ = onDone.Set(); } }; @@ -51,7 +51,7 @@ internal static class YesNtAssert yesNtInterpreter.OnLineExecuted += (er) => { debugEventArgs = er ?? debugEventArgs; - onDone.Set(); + _ = onDone.Set(); }; yesNtInterpreter.Execute(lines, true); diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 9b5efc1..8113c6b 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -28,7 +28,7 @@ namespace YesNt.Interpreter.Runtime public bool Stop { get; private set; } = false; public bool StopAllTasks { get; private set; } = false; public bool IsDebugMode { get; set; } = false; - public string CurrentFilePath { get; set; } = string.Empty; + public string WorkingDirectory { get; set; } = string.Empty; public bool IsTask => ParentRuntimeInformation is not null; public int TaskId => IsTask ? taskId : 0; public bool InternalIsInFunction { get; set; } @@ -39,35 +39,9 @@ namespace YesNt.Interpreter.Runtime set => InternalIsInFunction = value; } - public Dictionary Variables - { - get - { - if (FunctionCallStack.Count == 0) - { - return topVariables; - } - else - { - return FunctionCallStack.Peek().Variables; - } - } - } + public Dictionary Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables; - public Dictionary Labels - { - get - { - if (FunctionCallStack.Count == 0) - { - return topLabels; - } - else - { - return FunctionCallStack.Peek().Labels; - } - } - } + public Dictionary Labels => FunctionCallStack.Count == 0 ? topLabels : FunctionCallStack.Peek().Labels; public RuntimeInformation ParentRuntimeInformation { @@ -82,7 +56,7 @@ namespace YesNt.Interpreter.Runtime } } - public bool IsSearching => !string.IsNullOrWhiteSpace(SearchLabel + SearchFunction) || IsInFunction && FunctionCallStack.Count == 0; + public bool IsSearching => !string.IsNullOrWhiteSpace(SearchLabel + SearchFunction) || (IsInFunction && FunctionCallStack.Count == 0); public bool IsLocalSearch { get; set; } private event Action OnExit; @@ -98,7 +72,7 @@ namespace YesNt.Interpreter.Runtime public void WriteLine(string output, bool forceWrite = false) { - if (Stop && !forceWrite || parentRuntimeInformation?.StopAllTasks == true && !forceWrite) + if ((Stop && !forceWrite) || (parentRuntimeInformation?.StopAllTasks == true && !forceWrite)) { return; } @@ -122,7 +96,7 @@ namespace YesNt.Interpreter.Runtime public void Write(string output, bool forceWrite = false) { - if (Stop && !forceWrite || parentRuntimeInformation?.StopAllTasks == true && !forceWrite) + if ((Stop && !forceWrite) || (parentRuntimeInformation?.StopAllTasks == true && !forceWrite)) { return; } @@ -190,7 +164,7 @@ namespace YesNt.Interpreter.Runtime ParentRuntimeInformation = null; SearchLabel = string.Empty; SearchFunction = string.Empty; - CurrentFilePath = string.Empty; + WorkingDirectory = string.Empty; CurrentLine = string.Empty; Stop = false; StopAllTasks = false; diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index fb48103..ac357b7 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -76,7 +76,7 @@ namespace YesNt.Interpreter.Statements process.OutputDataReceived += Process_OutputDataReceived; process.ErrorDataReceived += Process_ErrorDataReceived; - process.Start(); + _ = process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 3d2e48a..06194a2 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -72,11 +72,11 @@ namespace YesNt.Interpreter.Statements if (RuntimeInfo.Variables.ContainsKey(key)) { - RuntimeInfo.Variables.Remove(key); + _ = RuntimeInfo.Variables.Remove(key); } else if (RuntimeInfo.GloablVariables.ContainsKey(key)) { - RuntimeInfo.GloablVariables.Remove(key); + _ = RuntimeInfo.GloablVariables.Remove(key); } else { diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs index 1f0f239..aac7d50 100644 --- a/YesNt.Interpreter/Utilities/Evaluator.cs +++ b/YesNt.Interpreter/Utilities/Evaluator.cs @@ -38,12 +38,7 @@ namespace YesNt.Interpreter.Utilities { bool succ1 = parts[0].ToStandardizedNumber(out double part1); bool succ2 = parts[1].ToStandardizedNumber(out double part2); - if (!succ1 || !succ2) - { - return false; - } - - return part1 >= part2; + return succ1 && succ2 && part1 >= part2; } parts = input.Split("<="); @@ -51,12 +46,7 @@ namespace YesNt.Interpreter.Utilities { bool succ1 = parts[0].ToStandardizedNumber(out double part1); bool succ2 = parts[1].ToStandardizedNumber(out double part2); - if (!succ1 || !succ2) - { - return false; - } - - return part1 <= part2; + return succ1 && succ2 && part1 <= part2; } parts = input.Split(">"); @@ -64,12 +54,7 @@ namespace YesNt.Interpreter.Utilities { bool succ1 = parts[0].ToStandardizedNumber(out double part1); bool succ2 = parts[1].ToStandardizedNumber(out double part2); - if (!succ1 || !succ2) - { - return false; - } - - return part1 > part2; + return succ1 && succ2 && part1 > part2; } parts = input.Split("<"); @@ -77,12 +62,7 @@ namespace YesNt.Interpreter.Utilities { bool succ1 = parts[0].ToStandardizedNumber(out double part1); bool succ2 = parts[1].ToStandardizedNumber(out double part2); - if (!succ1 || !succ2) - { - return false; - } - - return part1 < part2; + return succ1 && succ2 && part1 < part2; } return null; diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs index 65a32e2..9ee542d 100644 --- a/YesNt.Interpreter/Utilities/FixedProcess.cs +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -42,7 +42,7 @@ namespace YesNt.Interpreter.Utilities DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) { - SynchronizingObject.Invoke(outputDataReceived, new object[] + _ = SynchronizingObject.Invoke(outputDataReceived, new object[] { this, dataReceivedEventArgs @@ -61,7 +61,7 @@ namespace YesNt.Interpreter.Utilities DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) { - SynchronizingObject.Invoke(errorDataReceived, new object[] + _ = SynchronizingObject.Invoke(errorDataReceived, new object[] { this, dataReceivedEventArgs @@ -160,7 +160,7 @@ namespace YesNt.Interpreter.Utilities if (sb == null) { sb = new StringBuilder(1024); - stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); + _ = stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); return; } FlushMessageQueue(); @@ -204,13 +204,13 @@ namespace YesNt.Interpreter.Utilities } finally { - eofEvent.Set(); + _ = eofEvent.Set(); } } int chars = decoder.GetChars(byteBuffer, 0, num, charBuffer, 0); - sb.Append(charBuffer, 0, chars); + _ = sb.Append(charBuffer, 0, chars); GetLinesFromStringBuilder(); - stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); + _ = stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); } private void GetLinesFromStringBuilder() @@ -227,7 +227,7 @@ namespace YesNt.Interpreter.Utilities while (i < length) { char c = sb[i]; - if (c == '\r' || c == '\n') + if (c is '\r' or '\n') { if (c == '\r' && i + 1 < length && sb[i + 1] == '\n') { @@ -261,7 +261,7 @@ namespace YesNt.Interpreter.Utilities } if (num < length) { - sb.Remove(0, num); + _ = sb.Remove(0, num); } else { @@ -292,7 +292,7 @@ namespace YesNt.Interpreter.Utilities { if (eofEvent != null) { - eofEvent.WaitOne(); + _ = eofEvent.WaitOne(); eofEvent.Close(); eofEvent = null; } diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index 6c81400..c8e615e 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -11,7 +11,7 @@ namespace YesNt.Interpreter.Utilities StringBuilder output = new StringBuilder(); foreach (char c in input) { - output.Append($"\v{c}\v"); + _ = output.Append($"\v{c}\v"); } return output.ToString(); } From e5398133144b3c06866e57208054cbabf6fece9b Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:54:01 +0100 Subject: [PATCH 05/73] Use correct directory when importing other file --- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 4 ++++ YesNt.Interpreter/Statements/ProcessingStatements.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index ce07f9b..7445996 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -258,6 +258,8 @@ namespace YesNt.Interpreter.Runtime private bool LoadFile(string path) { + path = Path.GetFullPath(path); + if (!File.Exists(path)) { Console.WriteLine($"File \"{path}\" not found!"); @@ -272,6 +274,8 @@ namespace YesNt.Interpreter.Runtime return false; } + runtimeInfo.WorkingDirectory = Path.GetDirectoryName(path); + for (int i = 0; i < lines.Length; i++) { runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName(path), i)); diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 282bcad..5efe797 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -86,6 +86,8 @@ namespace YesNt.Interpreter.Statements [Statement("imp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Import(string path) { + path = Path.Combine(RuntimeInfo.WorkingDirectory, path); + if (string.IsNullOrEmpty(Path.GetExtension(path))) { path = Path.ChangeExtension(path, "ynt"); From 0f7f63ae9a3e74fc6ffe08829e9e09d94f4e842e Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 12:37:32 +0100 Subject: [PATCH 06/73] Improve Regex performance and change .NET version to .NET 7 --- YesNt.CodeEditor/SyntaxHighlighter.cs | 244 +++++++------- YesNt.CodeEditor/YesNt.CodeEditor.csproj | 2 +- .../YesNt.Interpreter.Tests.csproj | 2 +- .../Statements/ProcessingStatements.cs | 188 +++++------ .../Statements/VariableStatements.cs | 192 +++++------ YesNt.Interpreter/Utilities/Evaluator.cs | 300 +++++++++--------- YesNt.Interpreter/Utilities/FixedProcess.cs | 12 +- YesNt.Interpreter/YesNt.Interpreter.csproj | 2 +- 8 files changed, 485 insertions(+), 457 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index d85fc6b..b69420d 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -6,154 +6,168 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.CodeEditor +namespace YesNt.CodeEditor; + +internal partial class SyntaxHighlighter { - internal class SyntaxHighlighter + private readonly ReadOnlyCollection statementInformation; + + public SyntaxHighlighter(ReadOnlyCollection statementInformation) { - private readonly ReadOnlyCollection statementInformation; + this.statementInformation = statementInformation; + } - public SyntaxHighlighter(ReadOnlyCollection statementInformation) + public void Write(string input) + { + input = input.Replace("\0", string.Empty); + + if (input.StartsWith('#')) { - this.statementInformation = statementInformation; + input = AddColorInformation(input, input, ConsoleColor.Gray, SearchMode.Exact); } - - public void Write(string input) + else { - input = input.Replace("\0", string.Empty); - - if (input.StartsWith('#')) + MatchCollection matches = EscapeSequenceRegex().Matches(input); + for (int i = 0; i < matches.Count; i++) { - input = AddColorInformation(input, input, ConsoleColor.Gray, SearchMode.Exact); + input = AddColorInformation(input, matches[i].Value, Console.ForegroundColor, SearchMode.Contains); } - else + + foreach (StatementInformation statement in statementInformation) { - MatchCollection matches = Regex.Matches(input, @"!!."); - for (int i = 0; i < matches.Count; i++) + if (statement.IgnoreSyntaxHighlighting) { - input = AddColorInformation(input, matches[i].Value, Console.ForegroundColor, SearchMode.Contains); + continue; } - foreach (StatementInformation statement in statementInformation) + string name = statement.SpaceAround switch { - if (statement.IgnoreSyntaxHighlighting) + SpaceAround.StartEnd => $" {statement.Name.Trim()} ", + SpaceAround.Start => $" {statement.Name.Trim()}", + SpaceAround.End => $"{statement.Name.Trim()} ", + _ => statement.Name + }; + input = input.TrimEnd(); + if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) + { + if (statement.Seperator is not null && input.Contains(statement.Seperator)) + { + input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + } + else if (statement.Seperator is not null) { continue; } - - string name = statement.SpaceAround switch - { - SpaceAround.StartEnd => $" {statement.Name.Trim()} ", - SpaceAround.Start => $" {statement.Name.Trim()}", - SpaceAround.End => $"{statement.Name.Trim()} ", - _ => statement.Name - }; - input = input.TrimEnd(); - if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) - { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) - { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); - } - else if (statement.Seperator is not null) - { - continue; - } - input = AddColorInformation(input, input[..name.Length], statement.Color, statement.SearchMode); - } - if (statement.SearchMode == SearchMode.Contains && $" {input} ".Contains(name)) - { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) - { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); - } - else if (statement.Seperator is not null) - { - continue; - } - input = AddColorInformation($"{input} ", $"{input} ".Substring($"{input} ".IndexOf(name), name.Length), statement.Color, statement.SearchMode); - } - if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) - { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) - { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); - } - else if (statement.Seperator is not null) - { - continue; - } - input = AddColorInformation(input, input[^name.Length..], statement.Color, statement.SearchMode); - } - if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) - { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) - { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); - } - else if (statement.Seperator is not null) - { - continue; - } - input = AddColorInformation(input, input, statement.Color, statement.SearchMode); - } + input = AddColorInformation(input, input[..name.Length], statement.Color, statement.SearchMode); } - - matches = Regex.Matches(input, @">[a-zA-Z0-9]+"); - for (int i = 0; i < matches.Count; i++) + if (statement.SearchMode == SearchMode.Contains && $" {input} ".Contains(name)) { - input = AddColorInformation(input, matches[i].Value, ConsoleColor.Cyan, SearchMode.Contains); + if (statement.Seperator is not null && input.Contains(statement.Seperator)) + { + input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + } + else if (statement.Seperator is not null) + { + continue; + } + input = AddColorInformation($"{input} ", $"{input} ".Substring($"{input} ".IndexOf(name), name.Length), statement.Color, statement.SearchMode); } - - matches = Regex.Matches(input, @"^<[a-zA-Z0-9]+"); - for (int i = 0; i < matches.Count; i++) + if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) { - input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkCyan, SearchMode.StartOfLine); + if (statement.Seperator is not null && input.Contains(statement.Seperator)) + { + input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + } + else if (statement.Seperator is not null) + { + continue; + } + input = AddColorInformation(input, input[^name.Length..], statement.Color, statement.SearchMode); } - - matches = Regex.Matches(input, @"^!<[a-zA-Z0-9]+"); - for (int i = 0; i < matches.Count; i++) + if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) { - input = AddColorInformation(input, matches[i].Value, ConsoleColor.Blue, SearchMode.StartOfLine); + if (statement.Seperator is not null && input.Contains(statement.Seperator)) + { + input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + } + else if (statement.Seperator is not null) + { + continue; + } + input = AddColorInformation(input, input, statement.Color, statement.SearchMode); } } - foreach (string part in input.Split("\0")) + matches = VariableRegex().Matches(input); + for (int i = 0; i < matches.Count; i++) { - ConsoleColor consoleColor; - string messagePart = part; + input = AddColorInformation(input, matches[i].Value, ConsoleColor.Cyan, SearchMode.Contains); + } - string stringColor = Regex.Match(messagePart, "(?<=(\\r))(.*)(?=\\r)").Value; - bool succ = int.TryParse(stringColor, out int colorIndex); - if (succ && colorIndex >= 0 && colorIndex < 16) - { - messagePart = messagePart.Replace($"\r{stringColor}\r", string.Empty); - consoleColor = (ConsoleColor)colorIndex; - } - else - { - consoleColor = ConsoleColor.White; - } + matches = VariableDeclarationRegex().Matches(input); + for (int i = 0; i < matches.Count; i++) + { + input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkCyan, SearchMode.StartOfLine); + } - if (consoleColor == Console.BackgroundColor) - { - consoleColor = ConsoleColor.White; - } - Console.ForegroundColor = consoleColor; - Console.Write(messagePart); + matches = GlobalVariableDeclarationRegex().Matches(input); + for (int i = 0; i < matches.Count; i++) + { + input = AddColorInformation(input, matches[i].Value, ConsoleColor.Blue, SearchMode.StartOfLine); } - Console.ForegroundColor = ConsoleColor.Gray; } - private static string AddColorInformation(string originalString, string value, ConsoleColor color, SearchMode searchMode) + foreach (string part in input.Split("\0")) { - int spacesAtEnd = value.WhiteSpaceAtEnd(); - string reult = searchMode switch + ConsoleColor consoleColor; + string messagePart = part; + + string stringColor = StringColorRegex().Match(messagePart).Value; + bool succ = int.TryParse(stringColor, out int colorIndex); + if (succ && colorIndex >= 0 && colorIndex < 16) { - SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), - SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), - _ => originalString.Replace(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)) - }; - return reult; + messagePart = messagePart.Replace($"\r{stringColor}\r", string.Empty); + consoleColor = (ConsoleColor)colorIndex; + } + else + { + consoleColor = ConsoleColor.White; + } + + if (consoleColor == Console.BackgroundColor) + { + consoleColor = ConsoleColor.White; + } + Console.ForegroundColor = consoleColor; + Console.Write(messagePart); } + Console.ForegroundColor = ConsoleColor.Gray; } + + private static string AddColorInformation(string originalString, string value, ConsoleColor color, SearchMode searchMode) + { + int spacesAtEnd = value.WhiteSpaceAtEnd(); + string reult = searchMode switch + { + SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), + SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), + _ => originalString.Replace(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)) + }; + return reult; + } + + [GeneratedRegex("!!.")] + private static partial Regex EscapeSequenceRegex(); + + [GeneratedRegex("^<[a-zA-Z0-9]+")] + private static partial Regex VariableDeclarationRegex(); + + [GeneratedRegex("^!<[a-zA-Z0-9]+")] + private static partial Regex GlobalVariableDeclarationRegex(); + + [GeneratedRegex(">[a-zA-Z0-9]+")] + private static partial Regex VariableRegex(); + + [GeneratedRegex("(?<=(\\r))(.*)(?=\\r)")] + private static partial Regex StringColorRegex(); } \ No newline at end of file diff --git a/YesNt.CodeEditor/YesNt.CodeEditor.csproj b/YesNt.CodeEditor/YesNt.CodeEditor.csproj index 16be40b..dd3b5de 100644 --- a/YesNt.CodeEditor/YesNt.CodeEditor.csproj +++ b/YesNt.CodeEditor/YesNt.CodeEditor.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net7.0 AnyCPU;x64 diff --git a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj index 9e6dacd..5172e5e 100644 --- a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj +++ b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 enable false diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 5efe797..2d80026 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -9,112 +9,112 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal partial class ProcessingStatements : StatementRuntimeInformation { - internal class ProcessingStatements : StatementRuntimeInformation + [Statement("!calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)] + public void Calculate(string args) { - private static readonly Regex calculationRegex = new Regex(@"[0-9*+().,^%/-]+[0-9*+ ().,^%/-]+[0-9*+().,^%/-]+"); + MatchCollection matches = CalculationRegex().Matches(args.FromSaveString()); - [Statement("!calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)] - public void Calculate(string args) + for (int i = 0; i < matches.Count; i++) { - MatchCollection matches = calculationRegex.Matches(args.FromSaveString()); - - for (int i = 0; i < matches.Count; i++) + string res = Evaluator.Calculate(matches[i].Value); + if (res is null) { - string res = Evaluator.Calculate(matches[i].Value); - if (res is null) + RuntimeInfo.Exit("Invalid operation", true); + return; + } + args = args.FromSaveString().Replace(matches[i].Value, res); + } + + RuntimeInfo.CurrentLine = args; + } + + [Statement("!eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] + public void Evaluate(string args) + { + RuntimeInfo.CurrentLine = args.FromSaveString(); + } + + [Statement("!!", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)] + public void DontEvaluate(string args) + { + int index; + while ((index = args.IndexOf("!!")) != -1) + { + args = args.Remove(index, 2); + if (index < args.Length) + { + char charToEscape = args[index]; + args = args.Remove(index, 1); + args = args.Insert(index, charToEscape.ToString().ToSaveString()); + } + } + RuntimeInfo.CurrentLine = args; + } + + [Statement("!task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] + public void RunTask(string line) + { + int lineNumer = RuntimeInfo.LineNumber; + List lines = RuntimeInfo.Lines.GetRange(0, RuntimeInfo.Lines.Count); + + Line oldLine = lines[lineNumer]; + + lines[lineNumer] = new Line(line, oldLine.FileName, oldLine.LineNumber); + _ = Task.Run(() => + { + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + interpreter.Execute(lines, RuntimeInfo.GloablVariables, lineNumer, RuntimeInfo); + }); + + RuntimeInfo.CurrentLine = string.Empty; + } + + [Statement("slp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] + public void Sleep(string args) + { + _ = int.TryParse(args, out int millisecondsTimeout); + ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); + } + + [Statement("imp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] + public void Import(string path) + { + 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.Exit("Invalid operation", true); - return; + RuntimeInfo.Lines.Insert(RuntimeInfo.LineNumber + i, new Line(lines[i], Path.GetFileName(path), i)); } - args = args.FromSaveString().Replace(matches[i].Value, res); + RuntimeInfo.LineNumber--; } - - RuntimeInfo.CurrentLine = args; - } - - [Statement("!eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] - public void Evaluate(string args) - { - RuntimeInfo.CurrentLine = args.FromSaveString(); - } - - [Statement("!!", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)] - public void DontEvaluate(string args) - { - int index; - while ((index = args.IndexOf("!!")) != -1) + catch { - args = args.Remove(index, 2); - if (index < args.Length) - { - char charToEscape = args[index]; - args = args.Remove(index, 1); - args = args.Insert(index, charToEscape.ToString().ToSaveString()); - } + RuntimeInfo.Exit($"Could not load file \"{path}\"", true); } - RuntimeInfo.CurrentLine = args; } - - [Statement("!task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] - public void RunTask(string line) + else { - int lineNumer = RuntimeInfo.LineNumber; - List lines = RuntimeInfo.Lines.GetRange(0, RuntimeInfo.Lines.Count); - - Line oldLine = lines[lineNumer]; - - lines[lineNumer] = new Line(line, oldLine.FileName, oldLine.LineNumber); - _ = Task.Run(() => - { - YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); - interpreter.Execute(lines, RuntimeInfo.GloablVariables, lineNumer, RuntimeInfo); - }); - - RuntimeInfo.CurrentLine = string.Empty; - } - - [Statement("slp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] - public void Sleep(string args) - { - _ = int.TryParse(args, out int millisecondsTimeout); - ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); - } - - [Statement("imp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] - public void Import(string path) - { - 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.LineNumber--; - } - catch - { - RuntimeInfo.Exit($"Could not load file \"{path}\"", true); - } - } - else - { - RuntimeInfo.Exit($"Could not find file \"{path}\"", true); - } + RuntimeInfo.Exit($"Could not find file \"{path}\"", true); } } + + [GeneratedRegex("[0-9*+().,^%/-]+[0-9*+ ().,^%/-]+[0-9*+().,^%/-]+")] + private static partial Regex CalculationRegex(); } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 06194a2..98eb57d 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -5,119 +5,119 @@ using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal partial class VariableStatements : StatementRuntimeInformation { - internal class VariableStatements : StatementRuntimeInformation + [Statement("<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] + public void DefineVariable(string args) { - private static readonly Regex variableStatementRegex = new Regex(@">[a-zA-Z0-9]+"); - - [Statement("<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] - public void DefineVariable(string args) + string[] parts = args.Split('='); + if (parts.Length == 2) { - string[] parts = args.Split('='); - if (parts.Length == 2) + string key = parts[0].Trim(); + if (key.Contains(' ')) { - string key = parts[0].Trim(); - if (key.Contains(' ')) - { - RuntimeInfo.Exit("Invalid Syntax", true); - } - - if (RuntimeInfo.Variables.ContainsKey(key)) - { - RuntimeInfo.Variables[key] = parts[1].Trim(); - } - else - { - RuntimeInfo.Variables.Add(key, parts[1].Trim()); - } + RuntimeInfo.Exit("Invalid Syntax", true); } - else - { - RuntimeInfo.Exit("Invalid syntax", true); - } - } - - [Statement("!<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] - public void DefineGlobalVariable(string args) - { - string[] parts = args.Split('='); - if (parts.Length == 2) - { - string key = parts[0].Trim(); - if (key.Contains(' ')) - { - RuntimeInfo.Exit("Invalid Syntax", true); - } - - if (RuntimeInfo.GloablVariables.ContainsKey(key)) - { - RuntimeInfo.GloablVariables[key] = parts[1].Trim(); - } - else - { - RuntimeInfo.GloablVariables.Add(key, parts[1].Trim()); - } - } - else - { - RuntimeInfo.Exit("Invalid syntax", true); - } - } - - [Statement("del", 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.GloablVariables.ContainsKey(key)) - { - _ = RuntimeInfo.GloablVariables.Remove(key); + RuntimeInfo.Variables[key] = parts[1].Trim(); } else { - RuntimeInfo.Exit($"Variable \"{key}\" not found", true); + RuntimeInfo.Variables.Add(key, parts[1].Trim()); } } - - [Statement(">", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest)] - public void ReadVariable(string _) + else { - if (!RuntimeInfo.CurrentLine.Contains('>')) + RuntimeInfo.Exit("Invalid syntax", true); + } + } + + [Statement("!<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] + public void DefineGlobalVariable(string args) + { + string[] parts = args.Split('='); + if (parts.Length == 2) + { + string key = parts[0].Trim(); + if (key.Contains(' ')) { + RuntimeInfo.Exit("Invalid Syntax", true); + } + + if (RuntimeInfo.GloablVariables.ContainsKey(key)) + { + RuntimeInfo.GloablVariables[key] = parts[1].Trim(); + } + else + { + RuntimeInfo.GloablVariables.Add(key, parts[1].Trim()); + } + } + else + { + RuntimeInfo.Exit("Invalid syntax", true); + } + } + + [Statement("del", 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.GloablVariables.ContainsKey(key)) + { + _ = RuntimeInfo.GloablVariables.Remove(key); + } + else + { + RuntimeInfo.Exit($"Variable \"{key}\" not found", true); + } + } + + [Statement(">", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest)] + public void ReadVariable(string _) + { + if (!RuntimeInfo.CurrentLine.Contains('>')) + { + return; + } + + foreach (KeyValuePair variable in RuntimeInfo.Variables) + { + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); + } + + foreach (KeyValuePair variable in RuntimeInfo.GloablVariables) + { + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); + } + + if (RuntimeInfo.IsSearching) + { + return; + } + + MatchCollection matches = VariableStatementRegex().Matches(RuntimeInfo.CurrentLine); + + for (int i = 0; i < matches.Count; i++) + { + string varName = matches[i].Value.Replace(">", string.Empty); + if (!RuntimeInfo.Variables.ContainsKey(varName) && !RuntimeInfo.GloablVariables.ContainsKey(varName)) + { + RuntimeInfo.Exit($"Variable \"{varName}\" not found", true); return; } - - foreach (KeyValuePair variable in RuntimeInfo.Variables) - { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); - } - - foreach (KeyValuePair variable in RuntimeInfo.GloablVariables) - { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); - } - - if (RuntimeInfo.IsSearching) - { - return; - } - - MatchCollection matches = variableStatementRegex.Matches(RuntimeInfo.CurrentLine); - - for (int i = 0; i < matches.Count; i++) - { - string varName = matches[i].Value.Replace(">", string.Empty); - if (!RuntimeInfo.Variables.ContainsKey(varName) && !RuntimeInfo.GloablVariables.ContainsKey(varName)) - { - RuntimeInfo.Exit($"Variable \"{varName}\" not found", true); - return; - } - } } } + + [GeneratedRegex(">[a-zA-Z0-9]+")] + private static partial Regex VariableStatementRegex(); } \ No newline at end of file diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs index aac7d50..ecd39ff 100644 --- a/YesNt.Interpreter/Utilities/Evaluator.cs +++ b/YesNt.Interpreter/Utilities/Evaluator.cs @@ -2,177 +2,191 @@ using System.Linq; using System.Text.RegularExpressions; -namespace YesNt.Interpreter.Utilities +namespace YesNt.Interpreter.Utilities; + +internal static partial class Evaluator { - internal static class Evaluator + public static bool? EvaluateCondition(string input) { - public static bool? EvaluateCondition(string input) + if (input.ToLower().FromSaveString().Trim() == "true") { - if (input.ToLower().FromSaveString().Trim() == "true") - { - return true; - } - else if (input.ToLower().FromSaveString().Trim() == "false") - { - return false; - } + return true; + } + else if (input.ToLower().FromSaveString().Trim() == "false") + { + return false; + } - string[] parts = input.Split("=="); - if (parts.Length == 2) - { - string part1 = parts[0].FromSaveString().Trim(); - string part2 = parts[1].FromSaveString().Trim(); - return part1 == part2; - } + string[] parts = input.Split("=="); + if (parts.Length == 2) + { + string part1 = parts[0].FromSaveString().Trim(); + string part2 = parts[1].FromSaveString().Trim(); + return part1 == part2; + } - parts = input.Split("!="); - if (parts.Length == 2) - { - string part1 = parts[0].FromSaveString().Trim(); - string part2 = parts[1].FromSaveString().Trim(); - return part1 != part2; - } + parts = input.Split("!="); + if (parts.Length == 2) + { + string part1 = parts[0].FromSaveString().Trim(); + string part2 = parts[1].FromSaveString().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; + } - 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; + } + + public static string Calculate(string input) + { + input = PlusPlusRegex().Replace(input, "+"); + input = MinusMinusRegex().Replace(input, "+"); + input = MinusPlusRegex().Replace(input, "-"); + input = PlusMinusRegex().Replace(input, "-"); + + string yes = Calculate(input, '+'); + return yes; + } + + private static string Calculate(string input, char op) + { + if (input is null) + { return null; } - public static string Calculate(string input) - { - input = Regex.Replace(input, @"(\+ +\+)+", "+"); - input = Regex.Replace(input, @"(\- +\-)+", "+"); - input = Regex.Replace(input, @"(\- +\+)+", "-"); - input = Regex.Replace(input, @"(\+ +\-)+", "-"); + input = input.FromSaveString(); - string yes = Calculate(input, '+'); - return yes; + 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 = Calculate(calc); + input = input.Replace(matches[i].Value, ret); + } + matches = ParenthesesRegex().Matches(input); } - private static string Calculate(string input, char op) + string[] parts = input.Split(op); + + //Weird fix + if (parts.Length >= 2 && string.IsNullOrWhiteSpace(parts[0])) { - if (input is null) + 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 + { + '+' => Calculate(part, '-'), + '-' => Calculate(part, '*'), + '*' => Calculate(part, '/'), + '/' => Calculate(part, '%'), + '%' => Calculate(part, '^'), + _ => part + }; + + if (part is null) { return null; } - input = input.FromSaveString(); - - MatchCollection matches = Regex.Matches(input, @"\(([^()]+)\)"); - while (matches.Count > 0) + if (part.ToStandardizedNumber(out double num)) { - for (int i = 0; i < matches.Count; i++) + if (double.IsNaN(number)) { - string calc = matches[i].Value.Substring(1, matches[i].Length - 2); - string ret = Calculate(calc); - input = input.Replace(matches[i].Value, ret); - } - matches = Regex.Matches(input, @"\(([^()]+)\)"); - } - - string[] parts = input.Split(op); - - //Weird fix - 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 - { - '+' => Calculate(part, '-'), - '-' => Calculate(part, '*'), - '*' => Calculate(part, '/'), - '/' => Calculate(part, '%'), - '%' => Calculate(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; - } - } + number = num; } else { - return null; + 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; + } } } - - return number.ToString(System.Globalization.CultureInfo.InvariantCulture); + 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(); } \ No newline at end of file diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs index 9ee542d..46ee530 100644 --- a/YesNt.Interpreter/Utilities/FixedProcess.cs +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -23,14 +23,14 @@ namespace YesNt.Interpreter.Utilities public new void BeginOutputReadLine() { Stream baseStream = StandardOutput.BaseStream; - output = new AsyncStreamReader(this, baseStream, new UserCallBack(FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding); + output = new AsyncStreamReader(baseStream, new UserCallBack(FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding); output.BeginReadLine(); } public new void BeginErrorReadLine() { Stream baseStream = StandardError.BaseStream; - error = new AsyncStreamReader(this, baseStream, new UserCallBack(FixedErrorReadNotifyUser), StandardError.CurrentEncoding); + error = new AsyncStreamReader(baseStream, new UserCallBack(FixedErrorReadNotifyUser), StandardError.CurrentEncoding); error.BeginReadLine(); } @@ -90,17 +90,17 @@ namespace YesNt.Interpreter.Utilities public virtual Encoding CurrentEncoding => encoding; public virtual Stream BaseStream => stream; - internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) : this(process, stream, callback, encoding, 1024) + internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding) : this(stream, callback, encoding, 1024) { } - internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) + internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) { - Init(process, stream, callback, encoding, bufferSize); + Init(stream, callback, encoding, bufferSize); messageQueue = new Queue(); } - private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) + private void Init(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) { this.stream = stream; this.encoding = encoding; diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index bd4ef12..3b71ea2 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -1,7 +1,7 @@  - net5.0 + net7.0 YesNt.Interpreter Exe From 9d22381085a585382634be7c041929ba18dd16a7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 12:58:05 +0100 Subject: [PATCH 07/73] Rename SaveString to SafeString --- .../Runtime/RuntimeInformation.cs | 12 +-- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 4 +- .../Statements/ConsoleStatements.cs | 4 +- .../Statements/ProcessingStatements.cs | 8 +- .../Statements/SystemStatements.cs | 2 +- YesNt.Interpreter/Utilities/Evaluator.cs | 14 ++-- .../Utilities/StringExtentions.cs | 83 +++++++++---------- 7 files changed, 63 insertions(+), 64 deletions(-) diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 8113c6b..2e70a69 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -81,16 +81,16 @@ namespace YesNt.Interpreter.Runtime { if (IsTask) { - parentRuntimeInformation!.WriteLine(output.FromSaveString(), forceWrite); + parentRuntimeInformation!.WriteLine(output.FromSafeString(), forceWrite); } else { - OnDebugOutput?.Invoke(output.FromSaveString() + Environment.NewLine); + OnDebugOutput?.Invoke(output.FromSafeString() + Environment.NewLine); } } else { - Console.WriteLine(output.FromSaveString()); + Console.WriteLine(output.FromSafeString()); } } @@ -105,16 +105,16 @@ namespace YesNt.Interpreter.Runtime { if (IsTask) { - parentRuntimeInformation!.Write(output.FromSaveString(), forceWrite); + parentRuntimeInformation!.Write(output.FromSafeString(), forceWrite); } else { - OnDebugOutput?.Invoke(output.FromSaveString()); + OnDebugOutput?.Invoke(output.FromSafeString()); } } else { - Console.Write(output.FromSaveString()); + Console.Write(output.FromSafeString()); } } diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 7445996..67940ae 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -148,7 +148,7 @@ namespace YesNt.Interpreter.Runtime DebugEventArgs debugEventArgs = new DebugEventArgs() { LineNumber = runtimeInfo.LineNumber + 1, - OriginalLine = runtimeInfo.CurrentLine.FromSaveString(), + OriginalLine = runtimeInfo.CurrentLine.FromSafeString(), IsTask = runtimeInfo.IsTask, TaskId = runtimeInfo.TaskId }; @@ -229,7 +229,7 @@ namespace YesNt.Interpreter.Runtime } if (runtimeInfo.IsDebugMode && notSearchingLabel) { - debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSaveString(); + debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString(); runtimeInfo.LineExecuted(debugEventArgs); } } diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index 1d07072..574df89 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -34,7 +34,7 @@ namespace YesNt.Interpreter.Statements RuntimeInfo.Exit("Terminated by external process", true); return; } - args = args.ReplaceFirstOccurrence("%crl ", input.ToSaveString() + " "); + args = args.ReplaceFirstOccurrence("%crl ", input.ToSafeString() + " "); } RuntimeInfo.CurrentLine = args.TrimEnd(); } @@ -46,7 +46,7 @@ namespace YesNt.Interpreter.Statements while (args.Contains("%cr ")) { string input = ConsoleExtentions.ReadKey(RuntimeInfo).ToString(); - args = args.ReplaceFirstOccurrence("%cr ", input.ToSaveString() + " "); + args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); } RuntimeInfo.CurrentLine = args.TrimEnd(); } diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 2d80026..8c77aa8 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -16,7 +16,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [Statement("!calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)] public void Calculate(string args) { - MatchCollection matches = CalculationRegex().Matches(args.FromSaveString()); + MatchCollection matches = CalculationRegex().Matches(args.FromSafeString()); for (int i = 0; i < matches.Count; i++) { @@ -26,7 +26,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation RuntimeInfo.Exit("Invalid operation", true); return; } - args = args.FromSaveString().Replace(matches[i].Value, res); + args = args.FromSafeString().Replace(matches[i].Value, res); } RuntimeInfo.CurrentLine = args; @@ -35,7 +35,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [Statement("!eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] public void Evaluate(string args) { - RuntimeInfo.CurrentLine = args.FromSaveString(); + RuntimeInfo.CurrentLine = args.FromSafeString(); } [Statement("!!", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)] @@ -49,7 +49,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation { char charToEscape = args[index]; args = args.Remove(index, 1); - args = args.Insert(index, charToEscape.ToString().ToSaveString()); + args = args.Insert(index, charToEscape.ToString().ToSafeString()); } } RuntimeInfo.CurrentLine = args; diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index ac357b7..d1a3ee4 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -16,7 +16,7 @@ namespace YesNt.Interpreter.Statements [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Seperator = "|")] public void ExecuteProgramWithArgs(string input) { - string[] parts = input.FromSaveString().Split('|'); + string[] parts = input.FromSafeString().Split('|'); parts[0] = parts[0].Trim(); string[] functionArgumets = parts[1].Split(','); diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs index ecd39ff..a13fa91 100644 --- a/YesNt.Interpreter/Utilities/Evaluator.cs +++ b/YesNt.Interpreter/Utilities/Evaluator.cs @@ -8,11 +8,11 @@ internal static partial class Evaluator { public static bool? EvaluateCondition(string input) { - if (input.ToLower().FromSaveString().Trim() == "true") + if (input.ToLower().FromSafeString().Trim() == "true") { return true; } - else if (input.ToLower().FromSaveString().Trim() == "false") + else if (input.ToLower().FromSafeString().Trim() == "false") { return false; } @@ -20,16 +20,16 @@ internal static partial class Evaluator string[] parts = input.Split("=="); if (parts.Length == 2) { - string part1 = parts[0].FromSaveString().Trim(); - string part2 = parts[1].FromSaveString().Trim(); + string part1 = parts[0].FromSafeString().Trim(); + string part2 = parts[1].FromSafeString().Trim(); return part1 == part2; } parts = input.Split("!="); if (parts.Length == 2) { - string part1 = parts[0].FromSaveString().Trim(); - string part2 = parts[1].FromSaveString().Trim(); + string part1 = parts[0].FromSafeString().Trim(); + string part2 = parts[1].FromSafeString().Trim(); return part1 != part2; } @@ -86,7 +86,7 @@ internal static partial class Evaluator return null; } - input = input.FromSaveString(); + input = input.FromSafeString(); MatchCollection matches = ParenthesesRegex().Matches(input); while (matches.Count > 0) diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index c8e615e..c7a674a 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -2,52 +2,51 @@ using System.Globalization; using System.Text; -namespace YesNt.Interpreter.Utilities +namespace YesNt.Interpreter.Utilities; + +public static class StringExtentions { - public static class StringExtentions + public static string ToSafeString(this string input) { - public static string ToSaveString(this string input) + StringBuilder output = new StringBuilder(); + foreach (char c in input) { - StringBuilder output = new StringBuilder(); - foreach (char c in input) - { - _ = output.Append($"\v{c}\v"); - } - return output.ToString(); + _ = output.Append($"\v{c}\v"); + } + return output.ToString(); + } + + public static string FromSafeString(this string input) + { + return input.Replace("\v", ""); + } + + public static bool ToStandardizedNumber(this string input, out double result) + { + return double.TryParse(input.FromSafeString().Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out result); + } + + 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); + } + + 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); + } + + public static int WhiteSpaceAtEnd(this string input) + { + int count = 0; + int index = input.Length - 1; + while (index >= 0 && char.IsWhiteSpace(input[index--])) + { + count++; } - public static string FromSaveString(this string input) - { - return input.Replace("\v", ""); - } - - public static bool ToStandardizedNumber(this string input, out double result) - { - return double.TryParse(input.FromSaveString().Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out result); - } - - 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); - } - - 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); - } - - 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; - } + return count; } } \ No newline at end of file From ef1f731adc886f3eb0bb3f00f8eef9b2138da374 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 14:07:11 +0100 Subject: [PATCH 08/73] Fix exc command --- YesNt.Interpreter/Statements/SystemStatements.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index d1a3ee4..cdf66ec 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -62,6 +62,8 @@ namespace YesNt.Interpreter.Statements private void StartProcess(string name, string args) { + RuntimeInfo.OutParametersStack.Clear(); + FixedProcess process = new FixedProcess { StartInfo = new ProcessStartInfo() @@ -82,7 +84,7 @@ namespace YesNt.Interpreter.Statements process.WaitForExit(); RuntimeInfo.InParametersStack.Clear(); - RuntimeInfo.OutParametersStack.Clear(); + RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); } From 2e023f1a4ece5f32bbdfc0d644724abde7a37f26 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 17 Nov 2022 14:07:29 +0100 Subject: [PATCH 09/73] Add empty cwl command --- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 425 +++++++++--------- .../Statements/ConsoleStatements.cs | 83 ++-- 2 files changed, 256 insertions(+), 252 deletions(-) diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 67940ae..81974a0 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -9,279 +9,278 @@ using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Runtime +namespace YesNt.Interpreter.Runtime; + +public class YesNtInterpreter { - public class YesNtInterpreter + private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); + private Dictionary> statements = new(); + private List> staticStatements = new(); + + public ReadOnlyCollection StatementInformation { - private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); - private Dictionary> statements = new(); - private List> staticStatements = new(); - - public ReadOnlyCollection StatementInformation + get { - get + List informations = statements.Select(s => { - List informations = statements.Select(s => + return new StatementInformation() { - return new StatementInformation() - { - Name = s.Key.Name, - SearchMode = s.Key.SearchMode, - SpaceAround = s.Key.SpaceAround, - Color = s.Key.Color, - IgnoreSyntaxHighlighting = s.Key.IgnoreSyntaxHighlighting, - Seperator = s.Key.Seperator - }; - }).ToList(); + Name = s.Key.Name, + SearchMode = s.Key.SearchMode, + SpaceAround = s.Key.SpaceAround, + Color = s.Key.Color, + IgnoreSyntaxHighlighting = s.Key.IgnoreSyntaxHighlighting, + Seperator = s.Key.Seperator + }; + }).ToList(); - return new ReadOnlyCollection(informations); - } + return new ReadOnlyCollection(informations); } + } - public event Action OnLineExecuted; + public event Action OnLineExecuted; - public event Action OnDebugOutput; + public event Action OnDebugOutput; - public void Stop() + public void Stop() + { + runtimeInfo.Exit("Terminated by external process", true); + } + + public void Initialize() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + Type[] types = assembly.GetTypes(); + + IEnumerable statementRuntimeInfos = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); + + statements.Clear(); + + foreach (Type type in statementRuntimeInfos) { - runtimeInfo.Exit("Terminated by external process", true); - } + object statementInfo = Activator.CreateInstance(type); - public void Initialize() - { - Assembly assembly = Assembly.GetExecutingAssembly(); - Type[] types = assembly.GetTypes(); + MethodInfo[] methodInfos = statementInfo.GetType().GetMethods(); - IEnumerable statementRuntimeInfos = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); + StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; + statementRuntimeInfo.RuntimeInfo = runtimeInfo; - statements.Clear(); - - foreach (Type type in statementRuntimeInfos) + foreach (MethodInfo methodInfo in methodInfos) { - object statementInfo = Activator.CreateInstance(type); - - MethodInfo[] methodInfos = statementInfo.GetType().GetMethods(); - - StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; - statementRuntimeInfo.RuntimeInfo = runtimeInfo; - - foreach (MethodInfo methodInfo in methodInfos) + StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); + if (statementAttribute is not null) { - StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); - if (statementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - statements.Add(statementAttribute, method); - } + Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; + statements.Add(statementAttribute, method); + } - StaticStatementAttribute staticStatementAttribute = methodInfo.GetCustomAttribute(); - if (staticStatementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - staticStatements.Add(new(staticStatementAttribute, method)); - } + StaticStatementAttribute staticStatementAttribute = methodInfo.GetCustomAttribute(); + if (staticStatementAttribute is not null) + { + Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; + staticStatements.Add(new(staticStatementAttribute, method)); } } - - statements = statements.OrderBy(s => s.Key.Priority).ToDictionary(x => x.Key, x => x.Value); - staticStatements = staticStatements.OrderBy(s => s.Key.Priority).ToList(); - - runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); - runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); } - public void Execute(string path, bool isDebugMode = false) + statements = statements.OrderBy(s => s.Key.Priority).ToDictionary(x => x.Key, x => x.Value); + staticStatements = staticStatements.OrderBy(s => s.Key.Priority).ToList(); + + runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); + runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); + } + + public void Execute(string path, bool isDebugMode = false) + { + runtimeInfo.Reset(); + runtimeInfo.IsDebugMode = isDebugMode; + if (LoadFile(path)) { - runtimeInfo.Reset(); - runtimeInfo.IsDebugMode = isDebugMode; - if (LoadFile(path)) - { - Execute(); - } - } - - public void Execute(List lines, bool isDebugMode = false) - { - runtimeInfo.Reset(); - runtimeInfo.IsDebugMode = isDebugMode; - - for (int i = 0; i < lines.Count; i++) - { - runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName("#Memory#"), i)); - } - Execute(); } + } - internal void Execute(List lines, Dictionary gloablVariables, int startLine, RuntimeInformation parentRuntimeInformation) + public void Execute(List lines, bool isDebugMode = false) + { + runtimeInfo.Reset(); + runtimeInfo.IsDebugMode = isDebugMode; + + for (int i = 0; i < lines.Count; i++) { - runtimeInfo.Reset(); - runtimeInfo.IsDebugMode = parentRuntimeInformation.IsDebugMode; - runtimeInfo.Lines = lines; - runtimeInfo.LineNumber = startLine; - runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation; - runtimeInfo.GloablVariables = gloablVariables; - if (parentRuntimeInformation.StopAllTasks) - { - runtimeInfo.Exit($"Parent task was terminated!", parentRuntimeInformation.StopAllTasks); - return; - } - Execute(); + runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName("#Memory#"), i)); } - private void Execute() + Execute(); + } + + internal void Execute(List lines, Dictionary gloablVariables, int startLine, RuntimeInformation parentRuntimeInformation) + { + runtimeInfo.Reset(); + runtimeInfo.IsDebugMode = parentRuntimeInformation.IsDebugMode; + runtimeInfo.Lines = lines; + runtimeInfo.LineNumber = startLine; + runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation; + runtimeInfo.GloablVariables = gloablVariables; + if (parentRuntimeInformation.StopAllTasks) { - for (; runtimeInfo.LineNumber < runtimeInfo.Lines.Count; runtimeInfo.LineNumber++) + runtimeInfo.Exit($"Parent task was terminated!", parentRuntimeInformation.StopAllTasks); + return; + } + Execute(); + } + + private void Execute() + { + for (; runtimeInfo.LineNumber < runtimeInfo.Lines.Count; runtimeInfo.LineNumber++) + { + if (runtimeInfo.Stop) { + break; + } + + runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].Content.TrimEnd().Replace("\r", string.Empty); + + if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#')) + { + continue; + } + + DebugEventArgs debugEventArgs = new DebugEventArgs() + { + LineNumber = runtimeInfo.LineNumber + 1, + OriginalLine = runtimeInfo.CurrentLine.FromSafeString(), + IsTask = runtimeInfo.IsTask, + TaskId = runtimeInfo.TaskId + }; + + foreach (KeyValuePair staticStatement in staticStatements) + { + StaticStatementAttribute staticStatementAttribute = staticStatement.Key; + if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching) + { + continue; + } + + staticStatement.Value.Invoke(); + } + + bool statementFound = false; + bool notSearchingLabel = !runtimeInfo.IsSearching; + + foreach (KeyValuePair> statement in statements) + { + StatementAttribute statementAttribute = statement.Key; + + if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching) + { + statementFound = true; + continue; + } + if (runtimeInfo.Stop) { break; } - runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].Content.TrimEnd().Replace("\r", string.Empty); - - if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#')) + string name = statementAttribute.SpaceAround switch { - continue; - } - - DebugEventArgs debugEventArgs = new DebugEventArgs() - { - LineNumber = runtimeInfo.LineNumber + 1, - OriginalLine = runtimeInfo.CurrentLine.FromSafeString(), - IsTask = runtimeInfo.IsTask, - TaskId = runtimeInfo.TaskId + SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ", + SpaceAround.Start => $" {statementAttribute.Name.Trim()}", + SpaceAround.End => $"{statementAttribute.Name.Trim()} ", + _ => statementAttribute.Name }; - foreach (KeyValuePair staticStatement in staticStatements) + if (statementAttribute.Seperator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Seperator)) { - StaticStatementAttribute staticStatementAttribute = staticStatement.Key; - if (!staticStatementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching) - { - continue; - } - - staticStatement.Value.Invoke(); - } - - bool statementFound = false; - bool notSearchingLabel = !runtimeInfo.IsSearching; - - foreach (KeyValuePair> statement in statements) - { - StatementAttribute statementAttribute = statement.Key; - - if (!statementAttribute.ExecuteInSearchMode && runtimeInfo.IsSearching) + if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name)) { + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(0, name.Length); + statement.Value.Invoke(copyLine); statementFound = true; - continue; } - - if (runtimeInfo.Stop) + else if (statementAttribute.SearchMode == SearchMode.Contains && $" {runtimeInfo.CurrentLine} ".Contains(name)) { - break; + bool leadingWhitespace = runtimeInfo.CurrentLine.StartsWith(' '); + + runtimeInfo.CurrentLine = $" {runtimeInfo.CurrentLine} "; + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty); + statement.Value.Invoke(copyLine); + statementFound = true; + + runtimeInfo.CurrentLine = !leadingWhitespace ? runtimeInfo.CurrentLine.Trim() : runtimeInfo.CurrentLine.TrimEnd(); } - - string name = statementAttribute.SpaceAround switch + else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) { - SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ", - SpaceAround.Start => $" {statementAttribute.Name.Trim()}", - SpaceAround.End => $"{statementAttribute.Name.Trim()} ", - _ => statementAttribute.Name - }; - - if (statementAttribute.Seperator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Seperator)) + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(runtimeInfo.CurrentLine.Length - name.Length); + statement.Value.Invoke(copyLine); + statementFound = true; + } + else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name)) { - if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name)) - { - string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(0, name.Length); - statement.Value.Invoke(copyLine); - statementFound = true; - } - else if (statementAttribute.SearchMode == SearchMode.Contains && $" {runtimeInfo.CurrentLine} ".Contains(name)) - { - bool leadingWhitespace = runtimeInfo.CurrentLine.StartsWith(' '); - - runtimeInfo.CurrentLine = $" {runtimeInfo.CurrentLine} "; - string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty); - statement.Value.Invoke(copyLine); - statementFound = true; - - runtimeInfo.CurrentLine = !leadingWhitespace ? runtimeInfo.CurrentLine.Trim() : runtimeInfo.CurrentLine.TrimEnd(); - } - else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) - { - string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(runtimeInfo.CurrentLine.Length - name.Length); - statement.Value.Invoke(copyLine); - statementFound = true; - } - else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name)) - { - statement.Value.Invoke(runtimeInfo.CurrentLine); - statementFound = true; - } + statement.Value.Invoke(runtimeInfo.CurrentLine); + statementFound = true; } - } - - if (!statementFound) - { - runtimeInfo.Exit("Invalid statement", true); - } - if (runtimeInfo.IsDebugMode && notSearchingLabel) - { - debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString(); - runtimeInfo.LineExecuted(debugEventArgs); } } - if (!runtimeInfo.Stop) + if (!statementFound) { - if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel)) - { - runtimeInfo.Exit($"Label \"{runtimeInfo.SearchLabel}\" not found", true); - } - else if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchFunction)) - { - runtimeInfo.Exit($"Function \"{runtimeInfo.SearchFunction}\" not found", true); - } - else - { - runtimeInfo.Exit("End of file", false); - } - - if (runtimeInfo.IsDebugMode) - { - runtimeInfo.LineExecuted(null); - } + runtimeInfo.Exit("Invalid statement", true); + } + if (runtimeInfo.IsDebugMode && notSearchingLabel) + { + debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSafeString(); + runtimeInfo.LineExecuted(debugEventArgs); } } - private bool LoadFile(string path) + if (!runtimeInfo.Stop) { - path = Path.GetFullPath(path); - - if (!File.Exists(path)) + if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel)) { - Console.WriteLine($"File \"{path}\" not found!"); - return false; + runtimeInfo.Exit($"Label \"{runtimeInfo.SearchLabel}\" not found", true); + } + else if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchFunction)) + { + runtimeInfo.Exit($"Function \"{runtimeInfo.SearchFunction}\" not found", true); + } + else + { + runtimeInfo.Exit("End of file", false); } - string[] lines = File.ReadAllLines(path); - - if (lines.Length <= 0) + if (runtimeInfo.IsDebugMode) { - Console.WriteLine($"File \"{path}\" is empty!"); - return false; + runtimeInfo.LineExecuted(null); } - - runtimeInfo.WorkingDirectory = Path.GetDirectoryName(path); - - for (int i = 0; i < lines.Length; i++) - { - runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName(path), i)); - } - - return true; } } + + private bool LoadFile(string path) + { + path = Path.GetFullPath(path); + + if (!File.Exists(path)) + { + Console.WriteLine($"File \"{path}\" not found!"); + return false; + } + + string[] lines = File.ReadAllLines(path); + + if (lines.Length <= 0) + { + Console.WriteLine($"File \"{path}\" is empty!"); + return false; + } + + runtimeInfo.WorkingDirectory = Path.GetDirectoryName(path); + + for (int i = 0; i < lines.Length; i++) + { + runtimeInfo.Lines.Add(new Line(lines[i], Path.GetFileName(path), i)); + } + + return true; + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index 574df89..d0bd322 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -6,56 +6,61 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal class ConsoleStatements : StatementRuntimeInformation { - internal class ConsoleStatements : StatementRuntimeInformation + [Statement("cwl", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + public void WriteLineEmpty(string _) { - [Statement("cwl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] - public void WriteLine(string args) - { - RuntimeInfo.WriteLine(args); - } + RuntimeInfo.WriteLine(string.Empty); + } - [Statement("cw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] - public void Write(string args) - { - RuntimeInfo.Write(args); - } + [Statement("cwl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + public void WriteLine(string args) + { + RuntimeInfo.WriteLine(args); + } - [Statement("%crl", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void ReadLine(string args) + [Statement("cw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + public void Write(string args) + { + RuntimeInfo.Write(args); + } + + [Statement("%crl", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void ReadLine(string args) + { + args += " "; + while (args.Contains("%crl ")) { - args += " "; - while (args.Contains("%crl ")) + string input = Console.ReadLine(); + if (input is null) { - string input = Console.ReadLine(); - if (input is null) - { - RuntimeInfo.Exit("Terminated by external process", true); - return; - } - args = args.ReplaceFirstOccurrence("%crl ", input.ToSafeString() + " "); + RuntimeInfo.Exit("Terminated by external process", true); + return; } - RuntimeInfo.CurrentLine = args.TrimEnd(); + args = args.ReplaceFirstOccurrence("%crl ", input.ToSafeString() + " "); } + RuntimeInfo.CurrentLine = args.TrimEnd(); + } - [Statement("%cr", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void ReadKey(string args) + [Statement("%cr", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void ReadKey(string args) + { + args += " "; + while (args.Contains("%cr ")) { - args += " "; - while (args.Contains("%cr ")) - { - string input = ConsoleExtentions.ReadKey(RuntimeInfo).ToString(); - args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); - } - RuntimeInfo.CurrentLine = args.TrimEnd(); + string input = ConsoleExtentions.ReadKey(RuntimeInfo).ToString(); + args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); } + RuntimeInfo.CurrentLine = args.TrimEnd(); + } - [Statement("cls", 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(); - } + [Statement("cls", 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(); } } \ No newline at end of file From 79df5c9b97f36c7fc701118a94b0a421d8f3336f Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:15:38 +0200 Subject: [PATCH 10/73] Many improvements and bug fixes --- YesNt.CodeEditor/SyntaxHighlighter.cs | 37 ++- YesNt.Interpreter/Runtime/FunctionScope.cs | 27 +- .../Runtime/RuntimeInformation.cs | 296 +++++++++--------- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 28 +- .../Statements/ConsoleStatements.cs | 8 +- .../Statements/ProcessingStatements.cs | 9 + .../Statements/VariableStatements.cs | 28 +- .../Utilities/StringExtentions.cs | 8 +- 8 files changed, 228 insertions(+), 213 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index b69420d..7ef4d22 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -17,6 +17,18 @@ internal partial class SyntaxHighlighter this.statementInformation = statementInformation; } + public static string Base64Encode(string plainText) + { + byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + + public static string Base64Decode(string base64EncodedData) + { + byte[] base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + public void Write(string input) { input = input.Replace("\0", string.Empty); @@ -30,7 +42,7 @@ internal partial class SyntaxHighlighter MatchCollection matches = EscapeSequenceRegex().Matches(input); for (int i = 0; i < matches.Count; i++) { - input = AddColorInformation(input, matches[i].Value, Console.ForegroundColor, SearchMode.Contains); + input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkYellow, SearchMode.Contains); } foreach (StatementInformation statement in statementInformation) @@ -45,9 +57,9 @@ internal partial class SyntaxHighlighter SpaceAround.StartEnd => $" {statement.Name.Trim()} ", SpaceAround.Start => $" {statement.Name.Trim()}", SpaceAround.End => $"{statement.Name.Trim()} ", - _ => statement.Name + _ => statement.Name.Trim() }; - input = input.TrimEnd(); + input = input.TrimEnd(' '); if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) { if (statement.Seperator is not null && input.Contains(statement.Seperator)) @@ -60,7 +72,7 @@ internal partial class SyntaxHighlighter } input = AddColorInformation(input, input[..name.Length], statement.Color, statement.SearchMode); } - if (statement.SearchMode == SearchMode.Contains && $" {input} ".Contains(name)) + else if (statement.SearchMode == SearchMode.Contains && input.Contains(name)) { if (statement.Seperator is not null && input.Contains(statement.Seperator)) { @@ -70,9 +82,9 @@ internal partial class SyntaxHighlighter { continue; } - input = AddColorInformation($"{input} ", $"{input} ".Substring($"{input} ".IndexOf(name), name.Length), statement.Color, statement.SearchMode); + input = AddColorInformation(input, input.Substring(input.IndexOf(name), name.Length), statement.Color, statement.SearchMode); } - if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) + else if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) { if (statement.Seperator is not null && input.Contains(statement.Seperator)) { @@ -84,7 +96,7 @@ internal partial class SyntaxHighlighter } input = AddColorInformation(input, input[^name.Length..], statement.Color, statement.SearchMode); } - if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) + else if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) { if (statement.Seperator is not null && input.Contains(statement.Seperator)) { @@ -126,7 +138,7 @@ internal partial class SyntaxHighlighter bool succ = int.TryParse(stringColor, out int colorIndex); if (succ && colorIndex >= 0 && colorIndex < 16) { - messagePart = messagePart.Replace($"\r{stringColor}\r", string.Empty); + messagePart = Base64Decode(messagePart.Replace($"\r{stringColor}\r", string.Empty)); consoleColor = (ConsoleColor)colorIndex; } else @@ -147,11 +159,14 @@ internal partial class SyntaxHighlighter private static string AddColorInformation(string originalString, string value, ConsoleColor color, SearchMode searchMode) { int spacesAtEnd = value.WhiteSpaceAtEnd(); + + string base64Value = Base64Encode(value.TrimEnd()); + string reult = searchMode switch { - SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), - SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), - _ => originalString.Replace(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)) + SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), + SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), + _ => originalString.Replace(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)) }; return reult; } diff --git a/YesNt.Interpreter/Runtime/FunctionScope.cs b/YesNt.Interpreter/Runtime/FunctionScope.cs index ed9acf9..9b58e0c 100644 --- a/YesNt.Interpreter/Runtime/FunctionScope.cs +++ b/YesNt.Interpreter/Runtime/FunctionScope.cs @@ -1,19 +1,18 @@ using System.Collections.Generic; -namespace YesNt.Interpreter.Runtime -{ - internal class FunctionScope - { - public int CallerLine { get; } - public Dictionary Variables { get; } = new(); - public Dictionary Labels { get; } = new(); - public Stack Arguemtns { get; } - public Stack Results { get; } = new(); +namespace YesNt.Interpreter.Runtime; - public FunctionScope(int callerLine, Stack arguemtns) - { - CallerLine = callerLine; - Arguemtns = arguemtns; - } +internal class FunctionScope +{ + public int CallerLine { get; } + public Dictionary Variables { get; } = new(); + public Dictionary Labels { get; } = new(); + public Stack Arguemtns { get; } + public Stack Results { get; } = new(); + + public FunctionScope(int callerLine, Stack arguemtns) + { + CallerLine = callerLine; + Arguemtns = arguemtns; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 2e70a69..c04e697 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -1,181 +1,177 @@ using System; using System.Collections.Generic; -using System.Linq; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Runtime +namespace YesNt.Interpreter.Runtime; + +internal sealed class RuntimeInformation { - internal sealed class RuntimeInformation + public event Action OnDebugOutput; + + public event Action OnLineExecuted; + + private event Action OnExit; + + private static int internalTaskId = 0; + private readonly Dictionary topVariables = new(); + private readonly Dictionary topLabels = new(); + private RuntimeInformation parentRuntimeInformation; + private int taskId = 0; + public Dictionary GloablVariables { get; set; } = new(); + public Dictionary Functions { get; } = new(); + public Stack FunctionCallStack { get; } = new(); + public Stack InParametersStack { get; } = new(); + public Stack OutParametersStack { get; set; } = new(); + public List Lines { get; set; } = new(); + 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 => IsTask ? taskId : 0; + public bool InternalIsInFunction { get; set; } + + public bool IsInFunction { - private RuntimeInformation parentRuntimeInformation; - private static int internalTaskId = 0; - private int taskId = 0; + get => InternalIsInFunction || FunctionCallStack.Count > 0; + set => InternalIsInFunction = value; + } - private readonly Dictionary topVariables = new(); - private readonly Dictionary topLabels = new(); + public Dictionary Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables; - public Dictionary GloablVariables { get; set; } = new(); - public Dictionary Functions { get; } = new(); - public Stack FunctionCallStack { get; } = new(); - public Stack InParametersStack { get; } = new(); - public Stack OutParametersStack { get; set; } = new(); - public List Lines { get; set; } = new(); - 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 => IsTask ? taskId : 0; - public bool InternalIsInFunction { get; set; } + public Dictionary Labels => FunctionCallStack.Count == 0 ? topLabels : FunctionCallStack.Peek().Labels; - public bool IsInFunction + public RuntimeInformation ParentRuntimeInformation + { + get => parentRuntimeInformation; + set { - get => InternalIsInFunction || FunctionCallStack.Count > 0; - set => InternalIsInFunction = value; - } - - public Dictionary Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables; - - public Dictionary Labels => FunctionCallStack.Count == 0 ? topLabels : FunctionCallStack.Peek().Labels; - - public RuntimeInformation ParentRuntimeInformation - { - get => parentRuntimeInformation; - set + parentRuntimeInformation = value; + if (parentRuntimeInformation is not null) { - parentRuntimeInformation = value; - if (parentRuntimeInformation is not null) - { - parentRuntimeInformation.OnExit += ParentRuntimeInformation_OnExit; - } + parentRuntimeInformation.OnExit += ParentRuntimeInformation_OnExit; } } + } - public bool IsSearching => !string.IsNullOrWhiteSpace(SearchLabel + SearchFunction) || (IsInFunction && FunctionCallStack.Count == 0); - public bool IsLocalSearch { get; set; } + public bool IsSearching => !string.IsNullOrWhiteSpace(SearchLabel + SearchFunction) || (IsInFunction && FunctionCallStack.Count == 0); + public bool IsLocalSearch { get; set; } - private event Action OnExit; - - public event Action OnDebugOutput; - - public event Action OnLineExecuted; - - private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks) + public void WriteLine(string output, bool forceWrite = false) + { + if ((Stop && !forceWrite) || (parentRuntimeInformation?.StopAllTasks == true && !forceWrite)) { - Exit($"Terminated by parent task", stopAllTasks); + return; } - 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) - { - 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; - Line stackLine = (ParentRuntimeInformation?.Lines ?? Lines).ElementAt(stackLineNumber); - WriteLine($" at line {stackLine.LineNumber + 1} in the file \"{stackLine.FileName}\"", true); - } - - Stop = true; - } - if (stopAllTasks && !StopAllTasks) - { - StopAllTasks = true; - OnExit?.Invoke(message, StopAllTasks); - parentRuntimeInformation?.Exit("Terminated by child task", true); - } - } - - public void LineExecuted(DebugEventArgs debugEventArgs) + if (IsDebugMode) { if (IsTask) { - parentRuntimeInformation.LineExecuted(debugEventArgs); + parentRuntimeInformation!.WriteLine(output.FromSafeString(), forceWrite); } else { - OnLineExecuted?.Invoke(debugEventArgs); + OnDebugOutput?.Invoke(output.FromSafeString() + Environment.NewLine); } } - - public void Reset() + else { - topVariables.Clear(); - Lines.Clear(); - GloablVariables.Clear(); - Labels.Clear(); - Functions.Clear(); - FunctionCallStack.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; - taskId = internalTaskId + 1; -#pragma warning disable S2696 // Instance members should not write to "static" fields - internalTaskId++; -#pragma warning restore S2696 // Instance members should not write to "static" fields + 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) + { + 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; + Line stackLine = (ParentRuntimeInformation?.Lines ?? Lines)[stackLineNumber]; + WriteLine($" at line {stackLine.LineNumber + 1} in the file \"{stackLine.FileName}\"", true); + } + + Stop = true; + } + if (stopAllTasks && !StopAllTasks) + { + StopAllTasks = true; + OnExit?.Invoke(message, StopAllTasks); + parentRuntimeInformation?.Exit("Terminated by child task", true); + } + } + + public void LineExecuted(DebugEventArgs debugEventArgs) + { + if (IsTask) + { + parentRuntimeInformation.LineExecuted(debugEventArgs); + } + else + { + OnLineExecuted?.Invoke(debugEventArgs); + } + } + + public void Reset() + { + topVariables.Clear(); + Lines.Clear(); + GloablVariables.Clear(); + Labels.Clear(); + Functions.Clear(); + FunctionCallStack.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; + taskId = internalTaskId + 1; +#pragma warning disable S2696 // Instance members should not write to "static" fields + internalTaskId++; +#pragma warning restore S2696 // Instance members should not write to "static" fields + } + + private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks) + { + Exit($"Terminated by parent task", stopAllTasks); + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 81974a0..d11659a 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -13,6 +13,10 @@ namespace YesNt.Interpreter.Runtime; public class YesNtInterpreter { + public event Action OnLineExecuted; + + public event Action OnDebugOutput; + private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); private Dictionary> statements = new(); private List> staticStatements = new(); @@ -38,10 +42,6 @@ public class YesNtInterpreter } } - public event Action OnLineExecuted; - - public event Action OnDebugOutput; - public void Stop() { runtimeInfo.Exit("Terminated by external process", true); @@ -83,8 +83,13 @@ public class YesNtInterpreter } } - statements = statements.OrderBy(s => s.Key.Priority).ToDictionary(x => x.Key, x => x.Value); - staticStatements = staticStatements.OrderBy(s => s.Key.Priority).ToList(); + statements = statements + .OrderBy(s => s.Key.Priority) + .ThenByDescending(s => s.Key.Name.Length) + .ToDictionary(x => x.Key, x => x.Value); + staticStatements = staticStatements + .OrderBy(s => s.Key.Priority) + .ToList(); runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); @@ -138,7 +143,7 @@ public class YesNtInterpreter break; } - runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].Content.TrimEnd().Replace("\r", string.Empty); + runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].Content.Trim(' ').Replace("\r", string.Empty); if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine) || runtimeInfo.CurrentLine.StartsWith('#')) { @@ -187,7 +192,7 @@ public class YesNtInterpreter SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ", SpaceAround.Start => $" {statementAttribute.Name.Trim()}", SpaceAround.End => $"{statementAttribute.Name.Trim()} ", - _ => statementAttribute.Name + _ => statementAttribute.Name.Trim() }; if (statementAttribute.Seperator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Seperator)) @@ -198,16 +203,11 @@ public class YesNtInterpreter statement.Value.Invoke(copyLine); statementFound = true; } - else if (statementAttribute.SearchMode == SearchMode.Contains && $" {runtimeInfo.CurrentLine} ".Contains(name)) + else if (statementAttribute.SearchMode == SearchMode.Contains && runtimeInfo.CurrentLine.Contains(name)) { - bool leadingWhitespace = runtimeInfo.CurrentLine.StartsWith(' '); - - runtimeInfo.CurrentLine = $" {runtimeInfo.CurrentLine} "; string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty); statement.Value.Invoke(copyLine); statementFound = true; - - runtimeInfo.CurrentLine = !leadingWhitespace ? runtimeInfo.CurrentLine.Trim() : runtimeInfo.CurrentLine.TrimEnd(); } else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) { diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index d0bd322..406db42 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -28,11 +28,11 @@ internal class ConsoleStatements : StatementRuntimeInformation RuntimeInfo.Write(args); } - [Statement("%crl", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%crl", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] public void ReadLine(string args) { args += " "; - while (args.Contains("%crl ")) + while (args.Contains("%crl")) { string input = Console.ReadLine(); if (input is null) @@ -45,11 +45,11 @@ internal class ConsoleStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%cr", SearchMode.Contains, SpaceAround.End, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%cr", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] public void ReadKey(string args) { args += " "; - while (args.Contains("%cr ")) + while (args.Contains("%cr")) { string input = ConsoleExtentions.ReadKey(RuntimeInfo).ToString(); args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 8c77aa8..0b5bd26 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -81,6 +81,15 @@ internal partial class ProcessingStatements : StatementRuntimeInformation ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); } + [Statement("len", 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("imp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Import(string path) { diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 98eb57d..490512d 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; @@ -90,27 +89,20 @@ internal partial class VariableStatements : StatementRuntimeInformation return; } - foreach (KeyValuePair variable in RuntimeInfo.Variables) - { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); - } - - foreach (KeyValuePair variable in RuntimeInfo.GloablVariables) - { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); - } - - if (RuntimeInfo.IsSearching) - { - return; - } - MatchCollection matches = VariableStatementRegex().Matches(RuntimeInfo.CurrentLine); for (int i = 0; i < matches.Count; i++) { string varName = matches[i].Value.Replace(">", string.Empty); - if (!RuntimeInfo.Variables.ContainsKey(varName) && !RuntimeInfo.GloablVariables.ContainsKey(varName)) + if (RuntimeInfo.Variables.TryGetValue(varName, out string value)) + { + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); + } + else if (RuntimeInfo.GloablVariables.TryGetValue(varName, out value)) + { + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); + } + else if (!RuntimeInfo.IsSearching) { RuntimeInfo.Exit($"Variable \"{varName}\" not found", true); return; diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index c7a674a..06efe5f 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -13,12 +13,16 @@ public static class StringExtentions { _ = output.Append($"\v{c}\v"); } - return output.ToString(); + return output + .ToString() + .Replace(' ', '~'); } public static string FromSafeString(this string input) { - return input.Replace("\v", ""); + return input + .Replace("\v", "") + .Replace('~', ' '); } public static bool ToStandardizedNumber(this string input, out double result) From ce3715c1b5482bd92fee5b0b8abf78a9fe1f472b Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:17:28 +0200 Subject: [PATCH 11/73] Reformatting and reimperilment escape codes --- YesNt.CodeEditor/Editor.cs | 446 +++++++------- YesNt.CodeEditor/InputHandler.cs | 552 +++++++++--------- YesNt.CodeEditor/Program.cs | 15 +- YesNt.CodeEditor/SyntaxHighlighter.cs | 13 +- YesNt.Interpreter.Tests/CodeFowTests.cs | 3 +- YesNt.Interpreter.Tests/YesNtAssert.cs | 22 + .../Statements/ProcessingStatements.cs | 27 +- .../Statements/VariableStatements.cs | 5 + .../Utilities/StringExtentions.cs | 51 +- 9 files changed, 591 insertions(+), 543 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index 54c92cb..7c8c08b 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -4,265 +4,263 @@ using System.IO; using YesNt.Interpreter.Runtime; -namespace YesNt.CodeEditor +namespace YesNt.CodeEditor; + +internal class TextEditor { - internal class TextEditor + private readonly InputHandler inputHandler; + private readonly SyntaxHighlighter syntaxHighlighter; + private readonly List debugOutput = new(); + + private readonly Point oldSize = new Point(0, 0); + public YesNtInterpreter YesNtInterpreter { get; } = new(); + public int LineOffset { get; set; } = 0; + public List Lines { get; } = new(); + public Point CursorPosition { get; } = new(0, 0); + public Mode EditMode { get; set; } = Mode.Command; + public string CurrentPath { get; set; } = string.Empty; + + public TextEditor(string path) : this() { - private readonly InputHandler inputHandler; - private readonly SyntaxHighlighter syntaxHighlighter; - private readonly List debugOutput = new(); - - public YesNtInterpreter YesNtInterpreter { get; } = new(); - public int LineOffset { get; set; } = 0; - public List Lines { get; } = new(); - public Point CursorPosition { get; } = new(0, 0); - public Mode EditMode { get; set; } = Mode.Command; - public string CurrentPath { get; set; } = string.Empty; - - public TextEditor(string path) : this() + if (File.Exists(path)) { - if (File.Exists(path)) - { - _ = Load(path); - } + _ = Load(path); + } + } + + public TextEditor() + { + YesNtInterpreter.Initialize(); + YesNtInterpreter.OnDebugOutput += YesNtInterpreter_OnDebugOutput; + YesNtInterpreter.OnLineExecuted += YesNtInterpreter_OnLineExecuted; + syntaxHighlighter = new(YesNtInterpreter.StatementInformation); + inputHandler = new InputHandler(this); + Console.CancelKeyPress += Console_CancelKeyPress; + } + + public void Run() + { + Console.Clear(); + + Display(true); + do + { + Display(false); + } while (inputHandler.HandleInput()); + + Console.Clear(); + } + + public void Display(bool drawAll) + { + Console.CursorVisible = false; + Console.ForegroundColor = ConsoleColor.Gray; + Console.BackgroundColor = ConsoleColor.Black; + + Console.SetCursorPosition(0, 0); + + if (SizeChanged()) + { + drawAll = true; + InputHandler.WriteStatus(string.Empty); } - public TextEditor() + if (LineOffset < 0 || CursorPosition.Y < 0 || CursorPosition.Y < 0) { - YesNtInterpreter.Initialize(); - YesNtInterpreter.OnDebugOutput += YesNtInterpreter_OnDebugOutput; - YesNtInterpreter.OnLineExecuted += YesNtInterpreter_OnLineExecuted; - syntaxHighlighter = new(YesNtInterpreter.StatementInformation); - inputHandler = new InputHandler(this); - Console.CancelKeyPress += Console_CancelKeyPress; + LineOffset = 0; + CursorPosition.Y = 0; + CursorPosition.X = 0; } - public void Run() + for (int i = LineOffset; i < Console.WindowHeight + LineOffset - 2; i++) { - Console.Clear(); + Console.SetCursorPosition(0, i - LineOffset); - Display(true); - do + string lineCountString = $"{i + 1}".PadRight(GetSpacing(), ' ') + "| "; + if (i < Lines.Count) { - Display(false); - } while (inputHandler.HandleInput()); - - Console.Clear(); - } - - public void Display(bool drawAll) - { - Console.CursorVisible = false; - Console.ForegroundColor = ConsoleColor.Gray; - Console.BackgroundColor = ConsoleColor.Black; - - Console.SetCursorPosition(0, 0); - - if (SizeChanged()) - { - drawAll = true; - InputHandler.WriteStatus(string.Empty); - } - - if (LineOffset < 0 || CursorPosition.Y < 0 || CursorPosition.Y < 0) - { - LineOffset = 0; - CursorPosition.Y = 0; - CursorPosition.X = 0; - } - - for (int i = LineOffset; i < Console.WindowHeight + LineOffset - 2; i++) - { - Console.SetCursorPosition(0, i - LineOffset); - - string lineCountString = $"{i + 1}".PadRight(GetSpacing(), ' ') + "| "; - if (i < Lines.Count) + if (CursorPosition.Y == i || drawAll) { - if (CursorPosition.Y == i || drawAll) - { - Console.Write(lineCountString); - string printLine = $"{Lines[i][..Math.Min(Lines[i].Length, Console.WindowWidth)]}".TrimEnd(); - syntaxHighlighter.Write(printLine); - Console.Write(new string(' ', Math.Max(Console.WindowWidth - lineCountString.Length - printLine.Length, 0))); - } - } - else if (drawAll || CursorPosition.Y == i) - { - Console.Write(lineCountString + new string(' ', Console.WindowWidth - lineCountString.Length)); + Console.Write(lineCountString); + string printLine = $"{Lines[i][..Math.Min(Lines[i].Length, Console.WindowWidth)]}".TrimEnd(); + syntaxHighlighter.Write(printLine); + Console.Write(new string(' ', Math.Max(Console.WindowWidth - lineCountString.Length - printLine.Length, 0))); } } - - Console.SetCursorPosition(0, Console.WindowHeight - 3); - Console.Write(new string('-', Console.WindowWidth)); - Console.SetCursorPosition(0, Console.WindowHeight - 2); - Console.Write(">>>" + new string(' ', Console.WindowWidth - 3)); - - Console.SetCursorPosition(Math.Min(CursorPosition.X + GetSpacing() + 2, Console.WindowWidth - 1), CursorPosition.Y - LineOffset); - - Console.CursorVisible = true; + else if (drawAll || CursorPosition.Y == i) + { + Console.Write(lineCountString + new string(' ', Console.WindowWidth - lineCountString.Length)); + } } - public bool Load(string path) + Console.SetCursorPosition(0, Console.WindowHeight - 3); + Console.Write(new string('-', Console.WindowWidth)); + Console.SetCursorPosition(0, Console.WindowHeight - 2); + Console.Write(">>>" + new string(' ', Console.WindowWidth - 3)); + + Console.SetCursorPosition(Math.Min(CursorPosition.X + GetSpacing() + 2, Console.WindowWidth - 1), CursorPosition.Y - LineOffset); + + Console.CursorVisible = true; + } + + public bool Load(string path) + { + if (string.IsNullOrEmpty(Path.GetExtension(path))) { + path = Path.ChangeExtension(path, "ynt"); + } + + if (!File.Exists(path)) + { + InputHandler.WriteStatus("File does not exist!"); + return false; + } + + Lines.Clear(); + Lines.AddRange(File.ReadAllLines(path)); + CurrentPath = path; + InputHandler.WriteStatus("File Loaded!"); + return true; + } + + public bool Save(string input, bool loadIfExists) + { + string path; + if (input.Split(' ').Length == 2) + { + path = input.Split(' ')[1]; + + if (CurrentPath.Trim() != path.Trim() && loadIfExists) + { + return Load(path); + } if (string.IsNullOrEmpty(Path.GetExtension(path))) { path = Path.ChangeExtension(path, "ynt"); } - - if (!File.Exists(path)) - { - InputHandler.WriteStatus("File does not exist!"); - return false; - } - - Lines.Clear(); - Lines.AddRange(File.ReadAllLines(path)); CurrentPath = path; - InputHandler.WriteStatus("File Loaded!"); + } + else if (!string.IsNullOrWhiteSpace(CurrentPath)) + { + path = CurrentPath; + } + else if (input.Split(' ').Length > 2) + { + InputHandler.WriteStatus("Invalid arguments!"); + return false; + } + else + { + InputHandler.WriteStatus("File path is empty! (Save the file before you can use this command)"); + return false; + } + + if (string.IsNullOrEmpty(Path.GetExtension(path))) + { + path = Path.ChangeExtension(path, "ynt"); + } + + while (Lines.Count > 0 && string.IsNullOrWhiteSpace(Lines[^1])) + { + Lines.RemoveAt(Lines.Count - 1); + } + + try + { + File.WriteAllLines(path, Lines); + InputHandler.WriteStatus("File Saved!"); return true; } - - public bool Save(string input, bool loadIfExists) + catch (Exception ex) { - string path; - if (input.Split(' ').Length == 2) - { - path = input.Split(' ')[1]; - - if (CurrentPath.Trim() != path.Trim() && loadIfExists) - { - return Load(path); - } - if (string.IsNullOrEmpty(Path.GetExtension(path))) - { - path = Path.ChangeExtension(path, "ynt"); - } - CurrentPath = path; - } - else if (!string.IsNullOrWhiteSpace(CurrentPath)) - { - path = CurrentPath; - } - else if (input.Split(' ').Length > 2) - { - InputHandler.WriteStatus("Invalid arguments!"); - return false; - } - else - { - InputHandler.WriteStatus("File path is empty! (Save the file before you can use this command)"); - return false; - } - - if (string.IsNullOrEmpty(Path.GetExtension(path))) - { - path = Path.ChangeExtension(path, "ynt"); - } - - while (Lines.Count > 0 && string.IsNullOrWhiteSpace(Lines[^1])) - { - Lines.RemoveAt(Lines.Count - 1); - } - - try - { - File.WriteAllLines(path, Lines); - InputHandler.WriteStatus("File Saved!"); - return true; - } - catch (Exception ex) - { - InputHandler.WriteStatus(ex.Message); - return false; - } - } - - private void YesNtInterpreter_OnDebugOutput(string output) - { - debugOutput.Add(output); - } - - private void YesNtInterpreter_OnLineExecuted(Interpreter.Runtime.DebugEventArgs e) - { - lock (Console.Out) - { - if (e is not null) - { - Console.ForegroundColor = ConsoleColor.Magenta; - - string sharedString = (Console.CursorLeft != 0) ? Environment.NewLine : string.Empty; - sharedString += (e.IsTask ? $"[Task: {e.TaskId}]" : string.Empty) + $"[{e.LineNumber}]"; - - if (e.OriginalLine == e.CurrentLine) - { - Console.WriteLine($"{sharedString}[{e.CurrentLine}] ==>"); - } - else - { - Console.WriteLine($"{sharedString}[{e.OriginalLine}] => [{e.CurrentLine}] ==>"); - } - Console.ForegroundColor = ConsoleColor.Gray; - } - string[] outputs = debugOutput.ToArray(); - debugOutput.Clear(); - - foreach (string output in outputs) - { - Console.Write(output); - } - } - } - - private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) - { - e.Cancel = true; - switch (EditMode) - { - case Mode.Debug: - YesNtInterpreter.Stop(); - EditMode = Mode.Command; - break; - } - } - - public int GetSpacing() - { - int padding = (Console.WindowHeight + LineOffset - 3).ToString().Length; - padding = Math.Max(padding, Lines.Count.ToString().Length); - padding += 1; - return padding; - } - - private readonly Point oldSize = new Point(0, 0); - - private bool SizeChanged() - { - if (oldSize.X != Console.WindowWidth || oldSize.Y != Console.WindowHeight) - { - oldSize.X = Console.WindowWidth; - oldSize.Y = Console.WindowHeight; - return true; - } + InputHandler.WriteStatus(ex.Message); return false; } } - internal class Point + public int GetSpacing() { - public int X { get; set; } - public int Y { get; set; } + int padding = (Console.WindowHeight + LineOffset - 3).ToString().Length; + padding = Math.Max(padding, Lines.Count.ToString().Length); + padding += 1; + return padding; + } - public Point(int x, int y) + private void YesNtInterpreter_OnDebugOutput(string output) + { + debugOutput.Add(output); + } + + private void YesNtInterpreter_OnLineExecuted(Interpreter.Runtime.DebugEventArgs e) + { + lock (Console.Out) { - X = x; - Y = y; + if (e is not null) + { + Console.ForegroundColor = ConsoleColor.Magenta; + + string sharedString = (Console.CursorLeft != 0) ? Environment.NewLine : string.Empty; + sharedString += (e.IsTask ? $"[Task: {e.TaskId}]" : string.Empty) + $"[{e.LineNumber}]"; + + if (e.OriginalLine == e.CurrentLine) + { + Console.WriteLine($"{sharedString}[{e.CurrentLine}] ==>"); + } + else + { + Console.WriteLine($"{sharedString}[{e.OriginalLine}] => [{e.CurrentLine}] ==>"); + } + Console.ForegroundColor = ConsoleColor.Gray; + } + string[] outputs = debugOutput.ToArray(); + debugOutput.Clear(); + + foreach (string output in outputs) + { + Console.Write(output); + } } } - internal enum Mode + private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { - Edit, - Command, - Debug + e.Cancel = true; + switch (EditMode) + { + case Mode.Debug: + YesNtInterpreter.Stop(); + EditMode = Mode.Command; + break; + } } + + private bool SizeChanged() + { + if (oldSize.X != Console.WindowWidth || oldSize.Y != Console.WindowHeight) + { + oldSize.X = Console.WindowWidth; + oldSize.Y = Console.WindowHeight; + return true; + } + return false; + } +} + +internal class Point +{ + public int X { get; set; } + public int Y { get; set; } + + public Point(int x, int y) + { + X = x; + Y = y; + } +} + +internal enum Mode +{ + Edit, + Command, + Debug } \ No newline at end of file diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index 3b31f46..7a6f6b1 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -1,313 +1,317 @@ using System; using System.Text; -namespace YesNt.CodeEditor +namespace YesNt.CodeEditor; + +internal class InputHandler { - internal class InputHandler + private readonly TextEditor textEditor; + + public InputHandler(TextEditor textEditor) { - private readonly TextEditor textEditor; + this.textEditor = textEditor; + } - public InputHandler(TextEditor textEditor) + public bool HandleInput() + { + while (Console.KeyAvailable) { - this.textEditor = textEditor; + _ = Console.ReadKey(true); } - - public bool HandleInput() + if (textEditor.EditMode == Mode.Edit) { - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - if (textEditor.EditMode == Mode.Edit) - { - ConsoleKeyInfo keyInfo = Console.ReadKey(true); + ConsoleKeyInfo keyInfo = Console.ReadKey(true); - if ((ConsoleModifiers.Alt & keyInfo.Modifiers) == ConsoleModifiers.Alt) + if ((ConsoleModifiers.Alt & keyInfo.Modifiers) == ConsoleModifiers.Alt) + { + switch (keyInfo.Key) { - switch (keyInfo.Key) - { - case ConsoleKey.C: - textEditor.EditMode = Mode.Command; - return true; + case ConsoleKey.C: + textEditor.EditMode = Mode.Command; + return true; - case ConsoleKey.B: - int position = textEditor.Lines.Count; - textEditor.CursorPosition.Y = position - 1; - textEditor.LineOffset = Math.Max(position - Console.WindowHeight + 3, 0); + case ConsoleKey.B: + int position = textEditor.Lines.Count; + textEditor.CursorPosition.Y = position - 1; + textEditor.LineOffset = Math.Max(position - Console.WindowHeight + 3, 0); - textEditor.Display(true); - return true; + textEditor.Display(true); + return true; - case ConsoleKey.T: - textEditor.CursorPosition.Y = 0; - textEditor.LineOffset = 0; + case ConsoleKey.T: + textEditor.CursorPosition.Y = 0; + textEditor.LineOffset = 0; - textEditor.Display(true); - return true; + textEditor.Display(true); + return true; - case ConsoleKey.S: - textEditor.CursorPosition.X = 0; - return true; + case ConsoleKey.S: + textEditor.CursorPosition.X = 0; + return true; - case ConsoleKey.E: - textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0; - return true; - } + case ConsoleKey.E: + textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0; + return true; } - if (keyInfo.Key == ConsoleKey.DownArrow) + } + if (keyInfo.Key == ConsoleKey.DownArrow) + { + textEditor.CursorPosition.Y++; + if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3) { - textEditor.CursorPosition.Y++; - if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3) + textEditor.LineOffset++; + textEditor.Display(true); + } + } + else if (keyInfo.Key == ConsoleKey.UpArrow) + { + if (textEditor.CursorPosition.Y > 0) + { + textEditor.CursorPosition.Y--; + if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0) { - textEditor.LineOffset++; + textEditor.LineOffset--; textEditor.Display(true); } } - else if (keyInfo.Key == ConsoleKey.UpArrow) + } + else if (keyInfo.Key == ConsoleKey.LeftArrow) + { + if (textEditor.CursorPosition.X > 0) { - if (textEditor.CursorPosition.Y > 0) - { - textEditor.CursorPosition.Y--; - if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0) - { - textEditor.LineOffset--; - textEditor.Display(true); - } - } - } - else if (keyInfo.Key == ConsoleKey.LeftArrow) - { - if (textEditor.CursorPosition.X > 0) - { - textEditor.CursorPosition.X--; - } - } - else if (keyInfo.Key == ConsoleKey.RightArrow) - { - if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth) - { - textEditor.CursorPosition.X++; - } - } - else if (keyInfo.Key == ConsoleKey.Enter) - { - while (textEditor.Lines.Count <= textEditor.CursorPosition.Y) - { - textEditor.Lines.Add(""); - } - textEditor.Lines.Insert(textEditor.CursorPosition.Y, ""); - - string line = textEditor.Lines[textEditor.CursorPosition.Y + 1]; - - textEditor.Lines[textEditor.CursorPosition.Y] = line[..Math.Min(textEditor.CursorPosition.X, line.Length)]; - textEditor.Lines[textEditor.CursorPosition.Y + 1] = line[Math.Min(textEditor.CursorPosition.X, line.Length)..]; - - textEditor.CursorPosition.X = 0; - textEditor.CursorPosition.Y++; - - if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3) - { - textEditor.LineOffset++; - } - textEditor.Display(true); - } - else - { - while (textEditor.Lines.Count <= textEditor.CursorPosition.Y) - { - textEditor.Lines.Add(""); - } - - StringBuilder lineBuilder = new StringBuilder(textEditor.Lines[textEditor.CursorPosition.Y]); - while (lineBuilder.Length <= textEditor.CursorPosition.X) - { - _ = lineBuilder.Append(' '); - } - - textEditor.Lines[textEditor.CursorPosition.Y] = lineBuilder.ToString(); - - if (keyInfo.Key == ConsoleKey.Backspace) - { - if (textEditor.CursorPosition.X > 0) - { - if (textEditor.Lines[textEditor.CursorPosition.Y][textEditor.CursorPosition.X - 1] == ' ' && textEditor.CursorPosition.X > textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length) - { - textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd(); - textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].Length; - } - else - { - textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Remove(textEditor.CursorPosition.X - 1, 1); - textEditor.CursorPosition.X--; - } - } - else - { - if (textEditor.CursorPosition.Y > 0) - { - if (string.IsNullOrWhiteSpace(textEditor.Lines[textEditor.CursorPosition.Y - 1])) - { - textEditor.Lines.RemoveAt(--textEditor.CursorPosition.Y); - } - else - { - textEditor.CursorPosition.Y--; - textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length; - textEditor.Lines[textEditor.CursorPosition.Y] += textEditor.Lines[textEditor.CursorPosition.Y + 1]; - textEditor.Lines.RemoveAt(textEditor.CursorPosition.Y + 1); - } - if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0) - { - textEditor.LineOffset--; - } - textEditor.Display(true); - } - } - - while (textEditor.Lines.Count > 0 && string.IsNullOrWhiteSpace(textEditor.Lines[^1])) - { - textEditor.Lines.RemoveAt(textEditor.Lines.Count - 1); - } - } - else if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth) - { - char input = keyInfo.KeyChar; - if (!char.IsControl(input)) - { - textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Insert(textEditor.CursorPosition.X, input.ToString()); - textEditor.CursorPosition.X++; - } - } + textEditor.CursorPosition.X--; } } - else if (textEditor.EditMode == Mode.Command) + else if (keyInfo.Key == ConsoleKey.RightArrow) { - Console.SetCursorPosition(3, Console.WindowHeight - 2); - - string input = Console.ReadLine() ?? string.Empty; - string command = input.Split(' ')[0].Trim(); - string path; - - Console.CursorVisible = false; - - switch (command) + if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth) { - case "edit": - WriteStatus(string.Empty); - textEditor.EditMode = Mode.Edit; - break; + textEditor.CursorPosition.X++; + } + } + else if (keyInfo.Key == ConsoleKey.Enter) + { + while (textEditor.Lines.Count <= textEditor.CursorPosition.Y) + { + textEditor.Lines.Add(""); + } + textEditor.Lines.Insert(textEditor.CursorPosition.Y, ""); - case "line": - WriteStatus(string.Empty); + string line = textEditor.Lines[textEditor.CursorPosition.Y + 1]; - bool success = false; - int lineNumber = 0; - if (input.Split(' ').Length == 2) - { - success = int.TryParse(input.Split(' ')[1], out lineNumber); - } + textEditor.Lines[textEditor.CursorPosition.Y] = line[..Math.Min(textEditor.CursorPosition.X, line.Length)]; + textEditor.Lines[textEditor.CursorPosition.Y + 1] = line[Math.Min(textEditor.CursorPosition.X, line.Length)..]; - if (success && lineNumber > 0) - { - textEditor.CursorPosition.Y = lineNumber - 1; - textEditor.CursorPosition.X = 0; - textEditor.LineOffset = lineNumber - 1; - textEditor.EditMode = Mode.Edit; - } - else - { - WriteStatus("Invalid line number!"); - } - break; + textEditor.CursorPosition.X = 0; + textEditor.CursorPosition.Y++; - case "save": - _ = textEditor.Save(input, false); - break; - - case "run": - if (textEditor.Save(input, true)) - { - textEditor.EditMode = Mode.Debug; - Console.Clear(); - Console.CursorVisible = true; - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - _ = Console.ReadKey(); - WriteStatus(string.Empty); - textEditor.EditMode = Mode.Command; - } - break; - - case "debug": - if (textEditor.Save(input, true)) - { - textEditor.EditMode = Mode.Debug; - Console.Clear(); - Console.CursorVisible = true; - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - _ = Console.ReadKey(); - WriteStatus(string.Empty); - textEditor.EditMode = Mode.Command; - } - break; - - case "load": - if (input.Split(' ').Length == 2) - { - path = input.Split(' ')[1]; - } - else - { - WriteStatus("Invalid arguments!"); - break; - } - - try - { - _ = textEditor.Load(path); - } - catch (Exception ex) - { - WriteStatus(ex.Message); - } - textEditor.LineOffset = 0; - textEditor.CursorPosition.X = 0; - textEditor.CursorPosition.Y = 0; - break; - - case "new": - - textEditor.LineOffset = 0; - textEditor.CursorPosition.X = 0; - textEditor.CursorPosition.Y = 0; - textEditor.CurrentPath = string.Empty; - textEditor.Lines.Clear(); - WriteStatus(string.Empty); - break; - - case "exit": - return false; - - default: - WriteStatus("Command not found!"); - break; + if (textEditor.CursorPosition.Y - textEditor.LineOffset >= Console.WindowHeight - 3) + { + textEditor.LineOffset++; } textEditor.Display(true); } - return true; - } + else + { + while (textEditor.Lines.Count <= textEditor.CursorPosition.Y) + { + textEditor.Lines.Add(""); + } - internal static void WriteStatus(string input) - { - Console.SetCursorPosition(0, Console.WindowHeight - 1); - Console.Write(input + new string(' ', Console.WindowWidth - input.Length - 1)); + StringBuilder lineBuilder = new StringBuilder(textEditor.Lines[textEditor.CursorPosition.Y]); + while (lineBuilder.Length <= textEditor.CursorPosition.X) + { + _ = lineBuilder.Append(' '); + } + + textEditor.Lines[textEditor.CursorPosition.Y] = lineBuilder.ToString(); + + if (keyInfo.Key is ConsoleKey.Backspace or ConsoleKey.Delete) + { + if (keyInfo.Key == ConsoleKey.Delete) + { + textEditor.CursorPosition.X++; + } + + if (textEditor.CursorPosition.X > 0) + { + if (textEditor.Lines[textEditor.CursorPosition.Y][textEditor.CursorPosition.X - 1] == ' ' && textEditor.CursorPosition.X > textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length) + { + textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd(); + textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].Length; + } + else + { + textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Remove(textEditor.CursorPosition.X - 1, 1); + textEditor.CursorPosition.X--; + } + } + else + { + if (textEditor.CursorPosition.Y > 0) + { + if (string.IsNullOrWhiteSpace(textEditor.Lines[textEditor.CursorPosition.Y - 1])) + { + textEditor.Lines.RemoveAt(--textEditor.CursorPosition.Y); + } + else + { + textEditor.CursorPosition.Y--; + textEditor.CursorPosition.X = textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length; + textEditor.Lines[textEditor.CursorPosition.Y] += textEditor.Lines[textEditor.CursorPosition.Y + 1]; + textEditor.Lines.RemoveAt(textEditor.CursorPosition.Y + 1); + } + if (textEditor.CursorPosition.Y - textEditor.LineOffset < 0 && textEditor.LineOffset > 0) + { + textEditor.LineOffset--; + } + textEditor.Display(true); + } + } + + while (textEditor.Lines.Count > 0 && string.IsNullOrWhiteSpace(textEditor.Lines[^1])) + { + textEditor.Lines.RemoveAt(textEditor.Lines.Count - 1); + } + } + else if (textEditor.CursorPosition.X + textEditor.GetSpacing() + 3 < Console.WindowWidth) + { + char input = keyInfo.KeyChar; + if (!char.IsControl(input)) + { + textEditor.Lines[textEditor.CursorPosition.Y] = textEditor.Lines[textEditor.CursorPosition.Y].Insert(textEditor.CursorPosition.X, input.ToString()); + textEditor.CursorPosition.X++; + } + } + } } + else if (textEditor.EditMode == Mode.Command) + { + Console.SetCursorPosition(3, Console.WindowHeight - 2); + + string input = Console.ReadLine() ?? string.Empty; + string command = input.Split(' ')[0].Trim(); + string path; + + Console.CursorVisible = false; + + switch (command) + { + case "edit": + WriteStatus(string.Empty); + textEditor.EditMode = Mode.Edit; + break; + + case "line": + WriteStatus(string.Empty); + + bool success = false; + int lineNumber = 0; + if (input.Split(' ').Length == 2) + { + success = int.TryParse(input.Split(' ')[1], out lineNumber); + } + + if (success && lineNumber > 0) + { + textEditor.CursorPosition.Y = lineNumber - 1; + textEditor.CursorPosition.X = 0; + textEditor.LineOffset = lineNumber - 1; + textEditor.EditMode = Mode.Edit; + } + else + { + WriteStatus("Invalid line number!"); + } + break; + + case "save": + _ = textEditor.Save(input, false); + break; + + case "run": + if (textEditor.Save(input, true)) + { + textEditor.EditMode = Mode.Debug; + Console.Clear(); + Console.CursorVisible = true; + textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); + while (Console.KeyAvailable) + { + _ = Console.ReadKey(true); + } + _ = Console.ReadKey(); + WriteStatus(string.Empty); + textEditor.EditMode = Mode.Command; + } + break; + + case "debug": + if (textEditor.Save(input, true)) + { + textEditor.EditMode = Mode.Debug; + Console.Clear(); + Console.CursorVisible = true; + textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); + while (Console.KeyAvailable) + { + _ = Console.ReadKey(true); + } + _ = Console.ReadKey(); + WriteStatus(string.Empty); + textEditor.EditMode = Mode.Command; + } + break; + + case "load": + if (input.Split(' ').Length == 2) + { + path = input.Split(' ')[1]; + } + else + { + WriteStatus("Invalid arguments!"); + break; + } + + try + { + _ = textEditor.Load(path); + } + catch (Exception ex) + { + WriteStatus(ex.Message); + } + textEditor.LineOffset = 0; + textEditor.CursorPosition.X = 0; + textEditor.CursorPosition.Y = 0; + break; + + case "new": + + textEditor.LineOffset = 0; + textEditor.CursorPosition.X = 0; + textEditor.CursorPosition.Y = 0; + textEditor.CurrentPath = string.Empty; + textEditor.Lines.Clear(); + WriteStatus(string.Empty); + break; + + case "exit": + return false; + + default: + WriteStatus("Command not found!"); + break; + } + textEditor.Display(true); + } + return true; + } + + internal static void WriteStatus(string input) + { + Console.SetCursorPosition(0, Console.WindowHeight - 1); + Console.Write(input + new string(' ', Console.WindowWidth - input.Length - 1)); } } \ No newline at end of file diff --git a/YesNt.CodeEditor/Program.cs b/YesNt.CodeEditor/Program.cs index ffa8ec7..5b5517b 100644 --- a/YesNt.CodeEditor/Program.cs +++ b/YesNt.CodeEditor/Program.cs @@ -1,11 +1,4 @@ -namespace YesNt.CodeEditor -{ - internal static class Program - { - private static void Main(string[] args) - { - TextEditor textEditor = args.Length > 0 ? new TextEditor(args[0]) : new TextEditor(); - textEditor.Run(); - } - } -} \ No newline at end of file +using YesNt.CodeEditor; + +TextEditor textEditor = args.Length > 0 ? new TextEditor(args[0]) : new TextEditor(); +textEditor.Run(); \ No newline at end of file diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 7ef4d22..5e8a869 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Text.RegularExpressions; using YesNt.Interpreter.Enums; @@ -11,10 +12,12 @@ namespace YesNt.CodeEditor; internal partial class SyntaxHighlighter { private readonly ReadOnlyCollection statementInformation; + private readonly string[] replacementValues; public SyntaxHighlighter(ReadOnlyCollection statementInformation) { this.statementInformation = statementInformation; + replacementValues = StringExtentions.ReplacementRules.Values.ToArray(); } public static string Base64Encode(string plainText) @@ -39,10 +42,9 @@ internal partial class SyntaxHighlighter } else { - MatchCollection matches = EscapeSequenceRegex().Matches(input); - for (int i = 0; i < matches.Count; i++) + for (int i = 0; i < replacementValues.Length; i++) { - input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkYellow, SearchMode.Contains); + input = AddColorInformation(input, replacementValues[i], ConsoleColor.Blue, SearchMode.Contains); } foreach (StatementInformation statement in statementInformation) @@ -110,7 +112,7 @@ internal partial class SyntaxHighlighter } } - matches = VariableRegex().Matches(input); + MatchCollection matches = VariableRegex().Matches(input); for (int i = 0; i < matches.Count; i++) { input = AddColorInformation(input, matches[i].Value, ConsoleColor.Cyan, SearchMode.Contains); @@ -171,9 +173,6 @@ internal partial class SyntaxHighlighter return reult; } - [GeneratedRegex("!!.")] - private static partial Regex EscapeSequenceRegex(); - [GeneratedRegex("^<[a-zA-Z0-9]+")] private static partial Regex VariableDeclarationRegex(); diff --git a/YesNt.Interpreter.Tests/CodeFowTests.cs b/YesNt.Interpreter.Tests/CodeFowTests.cs index 23a715a..438116b 100644 --- a/YesNt.Interpreter.Tests/CodeFowTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowTests.cs @@ -38,7 +38,6 @@ public class CodeFlowTests [TestMethod] public void CalculationsTest() { - Assert.Inconclusive(); - YesNtAssert.IsLineEqual("10 * 10 !calc", 20.ToString()); + YesNtAssert.IsLineEqual("10 * 10 !calc", 100.ToString()); } } \ No newline at end of file diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index b7200a6..b3aa128 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -60,4 +60,26 @@ internal static class YesNtAssert Assert.AreEqual(expected, debugEventArgs.CurrentLine); } + + public static void IsLineNotEqual(string line, string expected, int timeout = 1000) + { + AutoResetEvent onDone = new AutoResetEvent(false); + List lines = new List() + { + line + }; + + DebugEventArgs debugEventArgs = new DebugEventArgs(); + yesNtInterpreter.OnLineExecuted += (er) => + { + debugEventArgs = er ?? debugEventArgs; + _ = onDone.Set(); + }; + + yesNtInterpreter.Execute(lines, true); + + _ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout)); + + Assert.AreNotEqual(expected, debugEventArgs.CurrentLine); + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 0b5bd26..54cebf4 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -38,23 +38,6 @@ internal partial class ProcessingStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = args.FromSafeString(); } - [Statement("!!", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkYellow, Priority = Priority.PreProcessing, KeepStatementInArgs = true)] - public void DontEvaluate(string args) - { - int index; - while ((index = args.IndexOf("!!")) != -1) - { - args = args.Remove(index, 2); - if (index < args.Length) - { - char charToEscape = args[index]; - args = args.Remove(index, 1); - args = args.Insert(index, charToEscape.ToString().ToSafeString()); - } - } - RuntimeInfo.CurrentLine = args; - } - [Statement("!task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] public void RunTask(string line) { @@ -77,8 +60,14 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [Statement("slp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Sleep(string args) { - _ = int.TryParse(args, out int millisecondsTimeout); - ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); + if (int.TryParse(args, out int millisecondsTimeout)) + { + ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); + } + else + { + RuntimeInfo.Exit($"\"{args}\" is not a valid time-out value", true); + } } [Statement("len", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 490512d..5fd4c5d 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -91,6 +91,11 @@ internal partial class VariableStatements : StatementRuntimeInformation MatchCollection matches = VariableStatementRegex().Matches(RuntimeInfo.CurrentLine); + if (matches.Count <= 0) + { + RuntimeInfo.Exit("Invalid syntax", true); + } + for (int i = 0; i < matches.Count; i++) { string varName = matches[i].Value.Replace(">", string.Empty); diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index 06efe5f..ca042b8 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -1,11 +1,34 @@ using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Linq; using System.Text; namespace YesNt.Interpreter.Utilities; public static class StringExtentions { + private static readonly Dictionary reverseReplacementRules; + + public static Dictionary ReplacementRules { get; } = new() + { + {"~", "~til" }, + {" ", "~spc" }, + {"%", "~per" }, + {"<", "~let" }, + {">", "~grt" }, + {",", "~com" }, + {"!", "~exm" }, + {"|", "~pip" } + }; + + [SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "Doesn't work because it throws a TypeInitializationException")] + static StringExtentions() + { + reverseReplacementRules = ReplacementRules.ToDictionary(x => x.Value, x => x.Key); + } + public static string ToSafeString(this string input) { StringBuilder output = new StringBuilder(); @@ -13,16 +36,32 @@ public static class StringExtentions { _ = output.Append($"\v{c}\v"); } - return output - .ToString() - .Replace(' ', '~'); + + return ReplaceOnce(output.ToString(), ReplacementRules); } public static string FromSafeString(this string input) { - return input - .Replace("\v", "") - .Replace('~', ' '); + return ReplaceOnce(input.Replace("\v", ""), reverseReplacementRules); + } + + public static string ReplaceOnce(string input, Dictionary replacementRules) + { + IEnumerable> matches = replacementRules.Where(rule => input.Contains(rule.Key)); + if (!matches.Any()) + { + return input; + } + + KeyValuePair match = matches.First(); + int startIndex = input.IndexOf(match.Key); + int endIndex = startIndex + match.Key.Length; + + string before = ReplaceOnce(input[..startIndex], replacementRules); + string replaced = match.Value; + string after = ReplaceOnce(input[endIndex..], replacementRules); + + return before + replaced + after; } public static bool ToStandardizedNumber(this string input, out double result) From 1ac6d7790b03b527a8de05a8e72da2127737907c Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:43:53 +0200 Subject: [PATCH 12/73] Automatically redraw console window when size chnages --- YesNt.CodeEditor/Editor.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index 7c8c08b..d88677b 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Timers; using YesNt.Interpreter.Runtime; @@ -36,6 +37,22 @@ internal class TextEditor syntaxHighlighter = new(YesNtInterpreter.StatementInformation); inputHandler = new InputHandler(this); Console.CancelKeyPress += Console_CancelKeyPress; + + Timer timer = new Timer(500); + timer.Elapsed += (s, e) => + { + if (SizeChanged() && EditMode != Mode.Debug) + { + Display(true); + + if (EditMode == Mode.Command) + { + InputHandler.WriteStatus(string.Empty); + Console.SetCursorPosition(3, Console.WindowHeight - 2); + } + } + }; + timer.Start(); } public void Run() From 84afe70c96a885d71cdc42c0bf1cf14e9cd24240 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:27:36 +0200 Subject: [PATCH 13/73] Fix crash when resizing and cursor is off screen --- YesNt.CodeEditor/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index d88677b..7637538 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -38,7 +38,7 @@ internal class TextEditor inputHandler = new InputHandler(this); Console.CancelKeyPress += Console_CancelKeyPress; - Timer timer = new Timer(500); + Timer timer = new Timer(100); timer.Elapsed += (s, e) => { if (SizeChanged() && EditMode != Mode.Debug) @@ -115,7 +115,7 @@ internal class TextEditor Console.SetCursorPosition(0, Console.WindowHeight - 2); Console.Write(">>>" + new string(' ', Console.WindowWidth - 3)); - Console.SetCursorPosition(Math.Min(CursorPosition.X + GetSpacing() + 2, Console.WindowWidth - 1), CursorPosition.Y - LineOffset); + Console.SetCursorPosition(Math.Min(CursorPosition.X + GetSpacing() + 2, Console.WindowWidth - 1), Math.Min(CursorPosition.Y - LineOffset, Console.WindowHeight - 4)); Console.CursorVisible = true; } From 7078ed5637e99057e24bc1eb72971b93202d95dc Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:44:49 +0200 Subject: [PATCH 14/73] Upgrade to .NET 8 and code cleanup --- .editorconfig | 3 + YesNt.CodeEditor/Editor.cs | 16 +- YesNt.CodeEditor/GlobalSuppressions.cs | 2 +- YesNt.CodeEditor/InputHandler.cs | 9 +- .../Properties/launchSettings.json | 18 +- YesNt.CodeEditor/SyntaxHighlighter.cs | 40 +- YesNt.CodeEditor/YesNt.CodeEditor.csproj | 2 +- YesNt.Interpreter.Tests/CodeFowTests.cs | 12 +- .../YesNt.Interpreter.Tests.csproj | 2 +- YesNt.Interpreter.Tests/YesNtAssert.cs | 12 +- .../Attributes/StatementAttribute.cs | 53 +- .../Attributes/StaticStatementAttribute.cs | 13 +- YesNt.Interpreter/Enums/Priority.cs | 21 +- YesNt.Interpreter/Enums/SearchMode.cs | 15 +- YesNt.Interpreter/Enums/SpaceAround.cs | 15 +- YesNt.Interpreter/Program.cs | 25 +- .../Properties/launchSettings.json | 20 +- YesNt.Interpreter/Runtime/DebugEventArgs.cs | 17 +- YesNt.Interpreter/Runtime/FunctionScope.cs | 16 +- YesNt.Interpreter/Runtime/Line.cs | 22 +- .../Runtime/RuntimeInformation.cs | 12 +- .../Runtime/StatementInformation.cs | 19 +- .../Runtime/StatementRuntimeInfo.cs | 9 +- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 16 +- .../Statements/CodeFlowStatements.cs | 361 ++++++----- .../Statements/FunctionStatements.cs | 345 ++++++----- .../PredifinedVariableStatements.cs | 99 ++- .../Statements/ProcessingStatements.cs | 8 +- .../Statements/SystemStatements.cs | 171 +++--- .../Statements/VariableStatements.cs | 12 +- YesNt.Interpreter/Utilities/FixedProcess.cs | 577 +++++++++--------- .../Utilities/StringExtentions.cs | 4 +- YesNt.Interpreter/YesNt.Interpreter.csproj | 2 +- 33 files changed, 960 insertions(+), 1008 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0ec1ee4..4b19b40 100644 --- a/.editorconfig +++ b/.editorconfig @@ -114,3 +114,6 @@ dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = none diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index 7637538..11e1eb5 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -11,12 +11,12 @@ internal class TextEditor { private readonly InputHandler inputHandler; private readonly SyntaxHighlighter syntaxHighlighter; - private readonly List debugOutput = new(); + private readonly List debugOutput = []; private readonly Point oldSize = new Point(0, 0); public YesNtInterpreter YesNtInterpreter { get; } = new(); public int LineOffset { get; set; } = 0; - public List Lines { get; } = new(); + public List Lines { get; } = []; public Point CursorPosition { get; } = new(0, 0); public Mode EditMode { get; set; } = Mode.Command; public string CurrentPath { get; set; } = string.Empty; @@ -263,16 +263,10 @@ internal class TextEditor } } -internal class Point +internal class Point(int x, int y) { - public int X { get; set; } - public int Y { get; set; } - - public Point(int x, int y) - { - X = x; - Y = y; - } + public int X { get; set; } = x; + public int Y { get; set; } = y; } internal enum Mode diff --git a/YesNt.CodeEditor/GlobalSuppressions.cs b/YesNt.CodeEditor/GlobalSuppressions.cs index 3e5f094..05f5be2 100644 --- a/YesNt.CodeEditor/GlobalSuppressions.cs +++ b/YesNt.CodeEditor/GlobalSuppressions.cs @@ -5,4 +5,4 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Critical Code Smell", "S3998:Threads should not lock on objects with weak identity", Justification = "", Scope = "member", Target = "~M:YesNt.CodeEditor.TextEditor.YesNtInterpreter_OnLineExecuted(YesNt.Interpreter.Runtime.DebugEventArgs)")] +[assembly: SuppressMessage("Critical Code Smell", "S3998:Threads should not lock on objects with weak identity", Justification = "", Scope = "member", Target = "~M:YesNt.CodeEditor.TextEditor.YesNtInterpreter_OnLineExecuted(YesNt.Interpreter.Runtime.DebugEventArgs)")] \ No newline at end of file diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index 7a6f6b1..924e5b2 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -3,14 +3,9 @@ using System.Text; namespace YesNt.CodeEditor; -internal class InputHandler +internal class InputHandler(TextEditor textEditor) { - private readonly TextEditor textEditor; - - public InputHandler(TextEditor textEditor) - { - this.textEditor = textEditor; - } + private readonly TextEditor textEditor = textEditor; public bool HandleInput() { diff --git a/YesNt.CodeEditor/Properties/launchSettings.json b/YesNt.CodeEditor/Properties/launchSettings.json index 161eba8..d1da8c5 100644 --- a/YesNt.CodeEditor/Properties/launchSettings.json +++ b/YesNt.CodeEditor/Properties/launchSettings.json @@ -1,12 +1,12 @@ { - "profiles": { - "YesNt.CodeEditor": { - "commandName": "Project" - }, - "WSL": { - "commandName": "WSL2", - "environmentVariables": {}, - "distributionName": "" + "profiles": { + "YesNt.CodeEditor": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } } - } } \ No newline at end of file diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 5e8a869..12c664e 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -9,16 +9,10 @@ using YesNt.Interpreter.Utilities; namespace YesNt.CodeEditor; -internal partial class SyntaxHighlighter +internal partial class SyntaxHighlighter(ReadOnlyCollection statementInformation) { - private readonly ReadOnlyCollection statementInformation; - private readonly string[] replacementValues; - - public SyntaxHighlighter(ReadOnlyCollection statementInformation) - { - this.statementInformation = statementInformation; - replacementValues = StringExtentions.ReplacementRules.Values.ToArray(); - } + private readonly ReadOnlyCollection statementInformation = statementInformation; + private readonly string[] replacementValues = StringExtensions.ReplacementRules.Values.ToArray(); public static string Base64Encode(string plainText) { @@ -64,11 +58,11 @@ internal partial class SyntaxHighlighter input = input.TrimEnd(' '); if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) + if (statement.Separator is not null && input.Contains(statement.Separator)) { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine); } - else if (statement.Seperator is not null) + else if (statement.Separator is not null) { continue; } @@ -76,11 +70,11 @@ internal partial class SyntaxHighlighter } else if (statement.SearchMode == SearchMode.Contains && input.Contains(name)) { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) + if (statement.Separator is not null && input.Contains(statement.Separator)) { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine); } - else if (statement.Seperator is not null) + else if (statement.Separator is not null) { continue; } @@ -88,11 +82,11 @@ internal partial class SyntaxHighlighter } else if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) + if (statement.Separator is not null && input.Contains(statement.Separator)) { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine); } - else if (statement.Seperator is not null) + else if (statement.Separator is not null) { continue; } @@ -100,11 +94,11 @@ internal partial class SyntaxHighlighter } else if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) { - if (statement.Seperator is not null && input.Contains(statement.Seperator)) + if (statement.Separator is not null && input.Contains(statement.Separator)) { - input = AddColorInformation(input, statement.Seperator, statement.Color, SearchMode.StartOfLine); + input = AddColorInformation(input, statement.Separator, statement.Color, SearchMode.StartOfLine); } - else if (statement.Seperator is not null) + else if (statement.Separator is not null) { continue; } @@ -164,13 +158,13 @@ internal partial class SyntaxHighlighter string base64Value = Base64Encode(value.TrimEnd()); - string reult = searchMode switch + string result = searchMode switch { SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), _ => originalString.Replace(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)) }; - return reult; + return result; } [GeneratedRegex("^<[a-zA-Z0-9]+")] diff --git a/YesNt.CodeEditor/YesNt.CodeEditor.csproj b/YesNt.CodeEditor/YesNt.CodeEditor.csproj index dd3b5de..b7ba18b 100644 --- a/YesNt.CodeEditor/YesNt.CodeEditor.csproj +++ b/YesNt.CodeEditor/YesNt.CodeEditor.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 AnyCPU;x64 diff --git a/YesNt.Interpreter.Tests/CodeFowTests.cs b/YesNt.Interpreter.Tests/CodeFowTests.cs index 438116b..66444e5 100644 --- a/YesNt.Interpreter.Tests/CodeFowTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowTests.cs @@ -10,28 +10,28 @@ public class CodeFlowTests [TestMethod] public void FunctionTest() { - List lines = new List() - { + List lines = + [ "cal yes", "fnc yes", "!result" - }; + ]; YesNtAssert.IsLastLineEqual(lines, "1"); } [TestMethod] public void LabelsTest() { - List lines = new List() - { + List lines = + [ "result" - }; + ]; YesNtAssert.IsLastLineEqual(lines, "1"); } diff --git a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj index 5172e5e..3090b90 100644 --- a/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj +++ b/YesNt.Interpreter.Tests/YesNt.Interpreter.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable false diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index b3aa128..66638d9 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -42,10 +42,10 @@ internal static class YesNtAssert public static void IsLineEqual(string line, string expected, int timeout = 1000) { AutoResetEvent onDone = new AutoResetEvent(false); - List lines = new List() - { + List lines = + [ line - }; + ]; DebugEventArgs debugEventArgs = new DebugEventArgs(); yesNtInterpreter.OnLineExecuted += (er) => @@ -64,10 +64,10 @@ internal static class YesNtAssert public static void IsLineNotEqual(string line, string expected, int timeout = 1000) { AutoResetEvent onDone = new AutoResetEvent(false); - List lines = new List() - { + List lines = + [ line - }; + ]; DebugEventArgs debugEventArgs = new DebugEventArgs(); yesNtInterpreter.OnLineExecuted += (er) => diff --git a/YesNt.Interpreter/Attributes/StatementAttribute.cs b/YesNt.Interpreter/Attributes/StatementAttribute.cs index 9b55c9b..999c9e8 100644 --- a/YesNt.Interpreter/Attributes/StatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StatementAttribute.cs @@ -2,35 +2,34 @@ using YesNt.Interpreter.Enums; -namespace YesNt.Interpreter.Attributes +namespace YesNt.Interpreter.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +internal class StatementAttribute : Attribute { - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - internal class StatementAttribute : Attribute + public string Name { get; } + public SearchMode SearchMode { get; } + public SpaceAround SpaceAround { get; } + public ConsoleColor Color { get; set; } + public Priority Priority { get; set; } = Priority.Normal; + public bool ExecuteInSearchMode { get; set; } + public bool KeepStatementInArgs { get; set; } + public bool IgnoreSyntaxHighlighting { get; } + public string Separator { get; set; } + + internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color) { - public string Name { get; } - public SearchMode SearchMode { get; } - public SpaceAround SpaceAround { get; } - public ConsoleColor Color { get; set; } - public Priority Priority { get; set; } = Priority.Normal; - public bool ExecuteInSearchMode { get; set; } - public bool KeepStatementInArgs { get; set; } - public bool IgnoreSyntaxHighlighting { get; } - public string Seperator { get; set; } + Name = name; + SearchMode = searchMode; + SpaceAround = spaceAround; + Color = color; + } - internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color) - { - Name = name; - SearchMode = searchMode; - SpaceAround = spaceAround; - Color = color; - } - - internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) - { - Name = name; - SearchMode = searchMode; - SpaceAround = spaceAround; - IgnoreSyntaxHighlighting = true; - } + internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) + { + Name = name; + SearchMode = searchMode; + SpaceAround = spaceAround; + IgnoreSyntaxHighlighting = true; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs index 28bf029..f0c5a5a 100644 --- a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs @@ -2,12 +2,11 @@ using YesNt.Interpreter.Enums; -namespace YesNt.Interpreter.Attributes +namespace YesNt.Interpreter.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +internal class StaticStatementAttribute : Attribute { - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - internal class StaticStatementAttribute : Attribute - { - public bool ExecuteInSearchMode { get; set; } - public Priority Priority { get; set; } = Priority.Normal; - } + public bool ExecuteInSearchMode { get; set; } + 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 a65051a..acb372b 100644 --- a/YesNt.Interpreter/Enums/Priority.cs +++ b/YesNt.Interpreter/Enums/Priority.cs @@ -1,13 +1,12 @@ -namespace YesNt.Interpreter.Enums +namespace YesNt.Interpreter.Enums; + +internal enum Priority { - internal enum Priority - { - PreProcessing, - Highest, - VeryHigh, - High, - Normal, - Low, - VeryLow - } + PreProcessing, + Highest, + VeryHigh, + High, + Normal, + Low, + VeryLow } \ No newline at end of file diff --git a/YesNt.Interpreter/Enums/SearchMode.cs b/YesNt.Interpreter/Enums/SearchMode.cs index 0e083b8..0226142 100644 --- a/YesNt.Interpreter/Enums/SearchMode.cs +++ b/YesNt.Interpreter/Enums/SearchMode.cs @@ -1,10 +1,9 @@ -namespace YesNt.Interpreter.Enums +namespace YesNt.Interpreter.Enums; + +public enum SearchMode { - public enum SearchMode - { - StartOfLine, - EndOfLine, - Contains, - Exact - } + StartOfLine, + EndOfLine, + Contains, + Exact } \ No newline at end of file diff --git a/YesNt.Interpreter/Enums/SpaceAround.cs b/YesNt.Interpreter/Enums/SpaceAround.cs index c16fd85..0ee0990 100644 --- a/YesNt.Interpreter/Enums/SpaceAround.cs +++ b/YesNt.Interpreter/Enums/SpaceAround.cs @@ -1,10 +1,9 @@ -namespace YesNt.Interpreter.Enums +namespace YesNt.Interpreter.Enums; + +public enum SpaceAround { - public enum SpaceAround - { - StartEnd, - Start, - End, - None - } + StartEnd, + Start, + End, + None } \ No newline at end of file diff --git a/YesNt.Interpreter/Program.cs b/YesNt.Interpreter/Program.cs index 70ce45e..ccbba14 100644 --- a/YesNt.Interpreter/Program.cs +++ b/YesNt.Interpreter/Program.cs @@ -2,22 +2,13 @@ using YesNt.Interpreter.Runtime; -namespace YesNt.Interpreter +if (args.Length == 1) { - internal static class Program - { - private static void Main(string[] args) - { - if (args.Length == 1) - { - YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); - interpreter.Execute(args[0]); - } - else - { - Console.WriteLine("No path specified!"); - } - } - } + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + interpreter.Execute(args[0]); +} +else +{ + Console.WriteLine("No path specified!"); } \ No newline at end of file diff --git a/YesNt.Interpreter/Properties/launchSettings.json b/YesNt.Interpreter/Properties/launchSettings.json index f380490..60b7039 100644 --- a/YesNt.Interpreter/Properties/launchSettings.json +++ b/YesNt.Interpreter/Properties/launchSettings.json @@ -1,13 +1,13 @@ { - "profiles": { - "YesNt-Interpreter": { - "commandName": "Project", - "commandLineArgs": "code.ynt" - }, - "WSL": { - "commandName": "WSL2", - "environmentVariables": {}, - "distributionName": "" + "profiles": { + "YesNt-Interpreter": { + "commandName": "Project", + "commandLineArgs": "code.ynt" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } } - } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/DebugEventArgs.cs b/YesNt.Interpreter/Runtime/DebugEventArgs.cs index 798d860..0f2d2fe 100644 --- a/YesNt.Interpreter/Runtime/DebugEventArgs.cs +++ b/YesNt.Interpreter/Runtime/DebugEventArgs.cs @@ -1,13 +1,12 @@ using System; -namespace YesNt.Interpreter.Runtime +namespace YesNt.Interpreter.Runtime; + +public class DebugEventArgs : EventArgs { - public class DebugEventArgs : EventArgs - { - public int LineNumber { get; internal set; } - public string CurrentLine { get; internal set; } - public string OriginalLine { get; internal set; } - public int TaskId { get; internal set; } - public bool IsTask { get; internal set; } - } + public int LineNumber { get; internal set; } + public string CurrentLine { get; internal set; } + public string OriginalLine { get; internal set; } + public int TaskId { get; internal set; } + public bool IsTask { get; internal set; } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/FunctionScope.cs b/YesNt.Interpreter/Runtime/FunctionScope.cs index 9b58e0c..cbae505 100644 --- a/YesNt.Interpreter/Runtime/FunctionScope.cs +++ b/YesNt.Interpreter/Runtime/FunctionScope.cs @@ -2,17 +2,11 @@ namespace YesNt.Interpreter.Runtime; -internal class FunctionScope +internal class FunctionScope(int callerLine, Stack arguments) { - public int CallerLine { get; } - public Dictionary Variables { get; } = new(); - public Dictionary Labels { get; } = new(); - public Stack Arguemtns { get; } + public int CallerLine { get; } = callerLine; + public Dictionary Variables { get; } = []; + public Dictionary Labels { get; } = []; + public Stack Arguments { get; } = arguments; public Stack Results { get; } = new(); - - public FunctionScope(int callerLine, Stack arguemtns) - { - CallerLine = callerLine; - Arguemtns = arguemtns; - } } \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/Line.cs b/YesNt.Interpreter/Runtime/Line.cs index 4243dac..7f3447d 100644 --- a/YesNt.Interpreter/Runtime/Line.cs +++ b/YesNt.Interpreter/Runtime/Line.cs @@ -1,16 +1,10 @@ -namespace YesNt.Interpreter.Runtime -{ - internal class Line - { - public Line(string content, string fileName, int lineNumber) - { - Content = content; - FileName = fileName; - LineNumber = lineNumber; - } +namespace YesNt.Interpreter.Runtime; - public string Content { get; set; } - public string FileName { get; set; } - public int LineNumber { get; set; } - } +internal class Line(string content, string fileName, int lineNumber) +{ + public string Content { get; set; } = content; + + public string FileName { get; set; } = fileName; + + 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 c04e697..0eb4676 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -14,16 +14,16 @@ internal sealed class RuntimeInformation private event Action OnExit; private static int internalTaskId = 0; - private readonly Dictionary topVariables = new(); - private readonly Dictionary topLabels = new(); + private readonly Dictionary topVariables = []; + private readonly Dictionary topLabels = []; private RuntimeInformation parentRuntimeInformation; private int taskId = 0; - public Dictionary GloablVariables { get; set; } = new(); - public Dictionary Functions { get; } = new(); + public Dictionary GlobalVariables { get; set; } = []; + public Dictionary Functions { get; } = []; public Stack FunctionCallStack { get; } = new(); public Stack InParametersStack { get; } = new(); public Stack OutParametersStack { get; set; } = new(); - public List Lines { get; set; } = new(); + public List Lines { get; set; } = []; public string CurrentLine { get; set; } = string.Empty; public string SearchLabel { get; set; } = string.Empty; public string SearchFunction { get; set; } = string.Empty; @@ -149,7 +149,7 @@ internal sealed class RuntimeInformation { topVariables.Clear(); Lines.Clear(); - GloablVariables.Clear(); + GlobalVariables.Clear(); Labels.Clear(); Functions.Clear(); FunctionCallStack.Clear(); diff --git a/YesNt.Interpreter/Runtime/StatementInformation.cs b/YesNt.Interpreter/Runtime/StatementInformation.cs index ed9489c..ab7e9a0 100644 --- a/YesNt.Interpreter/Runtime/StatementInformation.cs +++ b/YesNt.Interpreter/Runtime/StatementInformation.cs @@ -2,15 +2,14 @@ using YesNt.Interpreter.Enums; -namespace YesNt.Interpreter.Runtime +namespace YesNt.Interpreter.Runtime; + +public class StatementInformation { - public class StatementInformation - { - public string Name { get; internal set; } - public SearchMode SearchMode { get; internal set; } - public SpaceAround SpaceAround { get; internal set; } - public ConsoleColor Color { get; internal set; } - public bool IgnoreSyntaxHighlighting { get; internal set; } - public string Seperator { get; set; } - } + public string Name { get; internal set; } + public SearchMode SearchMode { get; internal set; } + public SpaceAround SpaceAround { get; internal set; } + public ConsoleColor Color { get; internal set; } + public bool IgnoreSyntaxHighlighting { get; internal set; } + 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 a0aaf2f..3a0c30a 100644 --- a/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs +++ b/YesNt.Interpreter/Runtime/StatementRuntimeInfo.cs @@ -1,7 +1,6 @@ -namespace YesNt.Interpreter.Runtime +namespace YesNt.Interpreter.Runtime; + +internal abstract class StatementRuntimeInformation { - internal abstract class StatementRuntimeInformation - { - public RuntimeInformation RuntimeInfo { get; set; } - } + 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 d11659a..fdfd80d 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -18,14 +18,14 @@ public class YesNtInterpreter public event Action OnDebugOutput; private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); - private Dictionary> statements = new(); - private List> staticStatements = new(); + private Dictionary> statements = []; + private List> staticStatements = []; public ReadOnlyCollection StatementInformation { get { - List informations = statements.Select(s => + List information = statements.Select(s => { return new StatementInformation() { @@ -34,11 +34,11 @@ public class YesNtInterpreter SpaceAround = s.Key.SpaceAround, Color = s.Key.Color, IgnoreSyntaxHighlighting = s.Key.IgnoreSyntaxHighlighting, - Seperator = s.Key.Seperator + Separator = s.Key.Separator }; }).ToList(); - return new ReadOnlyCollection(informations); + return new ReadOnlyCollection(information); } } @@ -118,14 +118,14 @@ public class YesNtInterpreter Execute(); } - internal void Execute(List lines, Dictionary gloablVariables, int startLine, RuntimeInformation parentRuntimeInformation) + internal void Execute(List lines, Dictionary globalVariables, int startLine, RuntimeInformation parentRuntimeInformation) { runtimeInfo.Reset(); runtimeInfo.IsDebugMode = parentRuntimeInformation.IsDebugMode; runtimeInfo.Lines = lines; runtimeInfo.LineNumber = startLine; runtimeInfo.ParentRuntimeInformation = parentRuntimeInformation; - runtimeInfo.GloablVariables = gloablVariables; + runtimeInfo.GlobalVariables = globalVariables; if (parentRuntimeInformation.StopAllTasks) { runtimeInfo.Exit($"Parent task was terminated!", parentRuntimeInformation.StopAllTasks); @@ -195,7 +195,7 @@ public class YesNtInterpreter _ => statementAttribute.Name.Trim() }; - if (statementAttribute.Seperator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Seperator)) + if (statementAttribute.Separator is null || runtimeInfo.CurrentLine.Contains(statementAttribute.Separator)) { if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name)) { diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 61bafcf..d1ccdd6 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -7,192 +7,191 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal class CodeFlowStatements : StatementRuntimeInformation { - internal class CodeFlowStatements : StatementRuntimeInformation + [Statement("jmp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow)] + public void Jump(string args) { - [Statement("jmp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow)] - public void Jump(string args) - { - string key = args.Trim(); + string key = args.Trim(); - if (RuntimeInfo.Labels.ContainsKey(key)) - { - RuntimeInfo.LineNumber = RuntimeInfo.Labels[key]; - } - else - { - RuntimeInfo.SearchLabel = key; - RuntimeInfo.IsLocalSearch = RuntimeInfo.IsInFunction; - } + if (RuntimeInfo.Labels.TryGetValue(key, out int value)) + { + RuntimeInfo.LineNumber = value; } - - [Statement("jif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Seperator = "|")] - public void JumpIf(string args) + else { - string[] parts = args.Split('|'); - if (parts.Length != 2) - { - RuntimeInfo.Exit("Invalid syntax", true); - return; - } - - string key = parts[0].Trim(); - string condition = parts[1].Trim(); - - bool? result = Evaluator.EvaluateCondition(condition); - - if (result is null) - { - RuntimeInfo.Exit("Invalid operation", true); - return; - } - - if (result == false) - { - return; - } - - if (RuntimeInfo.Labels.ContainsKey(key)) - { - RuntimeInfo.LineNumber = RuntimeInfo.Labels[key]; - } - else - { - RuntimeInfo.SearchLabel = key; - RuntimeInfo.IsLocalSearch = RuntimeInfo.IsInFunction; - } - } - - [Statement("lbl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true)] - public void FindLabel(string args) - { - string key = args.Trim(); - if (RuntimeInfo.Labels.ContainsKey(key)) - { - RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber; - } - else - { - RuntimeInfo.Labels.Add(key, RuntimeInfo.LineNumber); - } - - if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key) - { - RuntimeInfo.SearchLabel = string.Empty; - RuntimeInfo.IsLocalSearch = false; - } - } - - [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow)] - public void Call(string args) - { - string key = args.Trim(); - - RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack.Reverse()))); - RuntimeInfo.InParametersStack.Clear(); - - if (RuntimeInfo.Functions.ContainsKey(key)) - { - RuntimeInfo.LineNumber = RuntimeInfo.Functions[key]; - } - else - { - RuntimeInfo.SearchFunction = key; - } - } - - [Statement("cif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow, Seperator = "|")] - public void CallIf(string args) - { - string[] parts = args.Split('|'); - if (parts.Length != 2) - { - RuntimeInfo.Exit("Invalid syntax", true); - return; - } - - string key = parts[0].Trim(); - string condition = parts[1].Trim(); - - bool? result = Evaluator.EvaluateCondition(condition); - - if (result is null) - { - RuntimeInfo.Exit("Invalid operation", true); - return; - } - - if (result == false) - { - return; - } - - RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack))); - RuntimeInfo.InParametersStack.Clear(); - - if (RuntimeInfo.Functions.ContainsKey(key)) - { - RuntimeInfo.LineNumber = RuntimeInfo.Functions[key]; - } - else - { - RuntimeInfo.SearchFunction = key; - } - } - - [Statement("end", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] - public void End(string _) - { - if (RuntimeInfo.IsSearching) - { - RuntimeInfo.IsInFunction = false; - if (RuntimeInfo.IsLocalSearch) - { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); - } - - return; - } - else - { - RuntimeInfo.IsInFunction = false; - } - - RuntimeInfo.Exit("Planned termination by code", false); - } - - [Statement("trm", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] - public void Terminate(string _) - { - if (RuntimeInfo.IsSearching) - { - RuntimeInfo.IsInFunction = false; - if (RuntimeInfo.IsLocalSearch) - { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); - } - - return; - } - else - { - RuntimeInfo.IsInFunction = false; - } - - RuntimeInfo.Exit("Planned termination by code. Canceling all tasks", true); - } - - [Statement("trw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] - public void Throw(string message) - { - RuntimeInfo.Exit(message, true); - } - - [Statement("err", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] - public void Error(string message) - { - RuntimeInfo.Exit(message, false); + RuntimeInfo.SearchLabel = key; + RuntimeInfo.IsLocalSearch = RuntimeInfo.IsInFunction; } } + + [Statement("jif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = "|")] + public void JumpIf(string args) + { + string[] parts = args.Split('|'); + if (parts.Length != 2) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string key = parts[0].Trim(); + string condition = parts[1].Trim(); + + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", 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("lbl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true)] + public void FindLabel(string args) + { + string key = args.Trim(); + if (RuntimeInfo.Labels.ContainsKey(key)) + { + RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber; + } + else + { + RuntimeInfo.Labels.Add(key, RuntimeInfo.LineNumber); + } + + if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key) + { + RuntimeInfo.SearchLabel = string.Empty; + RuntimeInfo.IsLocalSearch = false; + } + } + + [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow)] + public void Call(string args) + { + string key = args.Trim(); + + RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack.Reverse()))); + RuntimeInfo.InParametersStack.Clear(); + + if (RuntimeInfo.Functions.TryGetValue(key, out int value)) + { + RuntimeInfo.LineNumber = value; + } + else + { + RuntimeInfo.SearchFunction = key; + } + } + + [Statement("cif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow, Separator = "|")] + public void CallIf(string args) + { + string[] parts = args.Split('|'); + if (parts.Length != 2) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string key = parts[0].Trim(); + string condition = parts[1].Trim(); + + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", true); + return; + } + + if (result == false) + { + return; + } + + RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack))); + RuntimeInfo.InParametersStack.Clear(); + + if (RuntimeInfo.Functions.TryGetValue(key, out int value)) + { + RuntimeInfo.LineNumber = value; + } + else + { + RuntimeInfo.SearchFunction = key; + } + } + + [Statement("end", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] + public void End(string _) + { + if (RuntimeInfo.IsSearching) + { + RuntimeInfo.IsInFunction = false; + if (RuntimeInfo.IsLocalSearch) + { + RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + } + + return; + } + else + { + RuntimeInfo.IsInFunction = false; + } + + RuntimeInfo.Exit("Planned termination by code", false); + } + + [Statement("trm", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] + public void Terminate(string _) + { + if (RuntimeInfo.IsSearching) + { + RuntimeInfo.IsInFunction = false; + if (RuntimeInfo.IsLocalSearch) + { + RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + } + + return; + } + else + { + RuntimeInfo.IsInFunction = false; + } + + RuntimeInfo.Exit("Planned termination by code. Canceling all tasks", true); + } + + [Statement("trw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] + public void Throw(string message) + { + RuntimeInfo.Exit(message, true); + } + + [Statement("err", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] + public void Error(string message) + { + RuntimeInfo.Exit(message, false); + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs index 23d67a6..de923c2 100644 --- a/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -5,196 +5,195 @@ using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal class FunctionStatements : StatementRuntimeInformation { - internal class FunctionStatements : StatementRuntimeInformation + [Statement("fnc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + public void FindFunction(string args) { - [Statement("fnc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] - public void FindFunction(string args) + if (RuntimeInfo.InternalIsInFunction) { - if (RuntimeInfo.InternalIsInFunction) - { - RuntimeInfo.Exit("Nested functions are not allowed", true); - return; - } - - string key = args.Trim(); - if (RuntimeInfo.Functions.ContainsKey(key)) - { - RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber; - } - else - { - RuntimeInfo.Functions.Add(key, RuntimeInfo.LineNumber); - } - - if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchFunction) && RuntimeInfo.SearchFunction == key) - { - RuntimeInfo.SearchFunction = string.Empty; - } - - RuntimeInfo.IsInFunction = true; + RuntimeInfo.Exit("Nested functions are not allowed", true); + return; } - [Statement("in", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] - public void AddInParameter(string args) + string key = args.Trim(); + if (RuntimeInfo.Functions.ContainsKey(key)) { - RuntimeInfo.InParametersStack.Push(args); + RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber; + } + else + { + RuntimeInfo.Functions.Add(key, RuntimeInfo.LineNumber); } - [Statement("out", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] - public void GetOutParameter(string args) + if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchFunction) && RuntimeInfo.SearchFunction == key) { - if (RuntimeInfo.OutParametersStack.Count == 0) - { - RuntimeInfo.Exit("No out argument in stack", true); - return; - } - - if (RuntimeInfo.Variables.ContainsKey(args)) - { - RuntimeInfo.Variables[args] = RuntimeInfo.OutParametersStack.Pop(); - } - else - { - RuntimeInfo.Variables.Add(args, RuntimeInfo.OutParametersStack.Pop()); - } + RuntimeInfo.SearchFunction = string.Empty; } - [Statement("%iso", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void CheckIfOutParameterAvalible(string args) - { - args += " "; - args = args.Replace("%iso", (RuntimeInfo.OutParametersStack.Count > 0).ToString()); + RuntimeInfo.IsInFunction = true; + } - RuntimeInfo.CurrentLine = args.TrimEnd(); + [Statement("in", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + public void AddInParameter(string args) + { + RuntimeInfo.InParametersStack.Push(args); + } + + [Statement("out", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + public void GetOutParameter(string args) + { + if (RuntimeInfo.OutParametersStack.Count == 0) + { + RuntimeInfo.Exit("No out argument in stack", true); + return; } - [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.Low, Seperator = "|")] - public void Call(string args) + if (RuntimeInfo.Variables.ContainsKey(args)) { - string[] parts = args.Split('|'); - if (parts.Length != 2) - { - RuntimeInfo.Exit("Invalid syntax", true); - return; - } - - string key = parts[0].Trim(); - string[] functionArgumets = parts[1].Split(','); - - foreach (string argumanet in functionArgumets) - { - RuntimeInfo.InParametersStack.Push(argumanet.Trim()); - } - - RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack))); - RuntimeInfo.InParametersStack.Clear(); - RuntimeInfo.CurrentLine = string.Empty; - - if (RuntimeInfo.Functions.ContainsKey(key)) - { - RuntimeInfo.LineNumber = RuntimeInfo.Functions[key]; - } - else - { - RuntimeInfo.SearchFunction = key; - } + RuntimeInfo.Variables[args] = RuntimeInfo.OutParametersStack.Pop(); } - - [Statement("get", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] - public void GetInParameter(string args) + else { - if (!RuntimeInfo.IsInFunction) - { - RuntimeInfo.Exit("Statement not allowed outside of function", true); - return; - } - - if (RuntimeInfo.FunctionCallStack.Peek().Arguemtns.Count == 0) - { - RuntimeInfo.Exit("No in argument in stack", true); - return; - } - - if (RuntimeInfo.Variables.ContainsKey(args)) - { - RuntimeInfo.Variables[args] = RuntimeInfo.FunctionCallStack.Peek().Arguemtns.Pop(); - } - else - { - RuntimeInfo.Variables.Add(args, RuntimeInfo.FunctionCallStack.Peek().Arguemtns.Pop()); - } - } - - [Statement("%isi", SearchMode.Contains, SpaceAround.End, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void CheckIfInParameterAvalible(string args) - { - if (!RuntimeInfo.IsInFunction) - { - RuntimeInfo.Exit("Statement not allowed outside of function", true); - return; - } - - args += " "; - args = args.Replace("%isi", (RuntimeInfo.FunctionCallStack.Peek().Arguemtns.Count > 0).ToString()); - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } - - [Statement("put", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] - public void AddOutParameter(string args) - { - if (!RuntimeInfo.IsInFunction) - { - RuntimeInfo.Exit("Statement not allowed outside of function", true); - return; - } - - RuntimeInfo.FunctionCallStack.Peek().Results.Push(args); - } - - [Statement("ret", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] - public void Return(string _) - { - if (!RuntimeInfo.IsInFunction) - { - RuntimeInfo.Exit("Statement not allowed outside of function", true); - return; - } - - if (RuntimeInfo.IsSearching) - { - RuntimeInfo.IsInFunction = false; - - if (RuntimeInfo.IsLocalSearch) - { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); - } - return; - } - else - { - RuntimeInfo.IsInFunction = false; - } - - if (RuntimeInfo.FunctionCallStack.Count > 0) - { - FunctionScope functionScope = RuntimeInfo.FunctionCallStack.Pop(); - - RuntimeInfo.OutParametersStack = new Stack(functionScope.Results); - RuntimeInfo.LineNumber = functionScope.CallerLine; - } - else - { - RuntimeInfo.Exit("No function in stack", true); - } - } - - [Statement("ccs", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red)] - public void ClearCallStack(string _) - { - RuntimeInfo.FunctionCallStack.Clear(); + RuntimeInfo.Variables.Add(args, RuntimeInfo.OutParametersStack.Pop()); } } + + [Statement("%iso", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void CheckIfOutParameterAvailable(string args) + { + args += " "; + args = args.Replace("%iso", (RuntimeInfo.OutParametersStack.Count > 0).ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.Low, Separator = "|")] + public void Call(string args) + { + string[] parts = args.Split('|'); + if (parts.Length != 2) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string key = parts[0].Trim(); + string[] functionArguments = parts[1].Split(','); + + foreach (string argument in functionArguments) + { + RuntimeInfo.InParametersStack.Push(argument.Trim()); + } + + RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(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("get", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + public void GetInParameter(string args) + { + if (!RuntimeInfo.IsInFunction) + { + RuntimeInfo.Exit("Statement not allowed outside of function", true); + return; + } + + if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0) + { + RuntimeInfo.Exit("No in argument in stack", true); + return; + } + + if (RuntimeInfo.Variables.ContainsKey(args)) + { + RuntimeInfo.Variables[args] = RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop(); + } + else + { + RuntimeInfo.Variables.Add(args, RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop()); + } + } + + [Statement("%isi", SearchMode.Contains, SpaceAround.End, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void CheckIfInParameterAvailable(string args) + { + if (!RuntimeInfo.IsInFunction) + { + RuntimeInfo.Exit("Statement not allowed outside of function", true); + return; + } + + args += " "; + args = args.Replace("%isi", (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count > 0).ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("put", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + public void AddOutParameter(string args) + { + if (!RuntimeInfo.IsInFunction) + { + RuntimeInfo.Exit("Statement not allowed outside of function", true); + return; + } + + RuntimeInfo.FunctionCallStack.Peek().Results.Push(args); + } + + [Statement("ret", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + public void Return(string _) + { + if (!RuntimeInfo.IsInFunction) + { + RuntimeInfo.Exit("Statement not allowed outside of function", true); + return; + } + + if (RuntimeInfo.IsSearching) + { + RuntimeInfo.IsInFunction = false; + + if (RuntimeInfo.IsLocalSearch) + { + RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + } + return; + } + else + { + RuntimeInfo.IsInFunction = false; + } + + if (RuntimeInfo.FunctionCallStack.Count > 0) + { + FunctionScope functionScope = RuntimeInfo.FunctionCallStack.Pop(); + + RuntimeInfo.OutParametersStack = new Stack(functionScope.Results); + RuntimeInfo.LineNumber = functionScope.CallerLine; + } + else + { + RuntimeInfo.Exit("No function in stack", true); + } + } + + [Statement("ccs", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red)] + public void ClearCallStack(string _) + { + RuntimeInfo.FunctionCallStack.Clear(); + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs index 6740f44..e79276f 100644 --- a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs +++ b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs @@ -5,61 +5,60 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal class PredefinedVariableStatements : StatementRuntimeInformation { - internal class PredifinedVariableStatements : 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) { - private readonly Random random = new Random(); + args = args.Replace("%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); - [Statement("%time", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetUnixTimestamp(string args) + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetOperatingSystem(string args) + { + args = args.Replace("%os", Environment.OSVersion.Platform.ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetProcessorArchitecture(string args) + { + args = args.Replace("%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetIsOperatingSystem64Bit(string args) + { + args = args.Replace("%is64", $"{Environment.Is64BitOperatingSystem}"); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetPi(string args) + { + args = args.Replace("%pi", Math.PI.ToString()); + + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%rnd", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void GetRandom(string args) + { + while (args.Contains("%rnd")) { - args = args.Replace("%time", DateTimeOffset.Now.ToUnixTimeSeconds().ToString()); - - RuntimeInfo.CurrentLine = args.TrimEnd(); + args = args.ReplaceFirstOccurrence("%rnd", random.Next(32767, int.MaxValue).ToString()); } - [Statement("%os", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetOperatingSystem(string args) - { - args = args.Replace("%os", Environment.OSVersion.Platform.ToString()); - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } - - [Statement("%cpu", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetProcessorArchitecture(string args) - { - args = args.Replace("%cpu", System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString()); - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } - - [Statement("%is64", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetIsOperatingSystem64Bit(string args) - { - args = args.Replace("%is64", $"{Environment.Is64BitOperatingSystem}"); - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } - - [Statement("%pi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetPi(string args) - { - args = args.Replace("%pi", Math.PI.ToString()); - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } - - [Statement("%rnd", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] - public void GetRandom(string args) - { - while (args.Contains("%rnd")) - { - args = args.ReplaceFirstOccurrence("%rnd", random.Next(32767, int.MaxValue).ToString()); - } - - RuntimeInfo.CurrentLine = args.TrimEnd(); - } + RuntimeInfo.CurrentLine = args.TrimEnd(); } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 54cebf4..e606aea 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -41,17 +41,17 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [Statement("!task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] public void RunTask(string line) { - int lineNumer = RuntimeInfo.LineNumber; + int lineNumber = RuntimeInfo.LineNumber; List lines = RuntimeInfo.Lines.GetRange(0, RuntimeInfo.Lines.Count); - Line oldLine = lines[lineNumer]; + Line oldLine = lines[lineNumber]; - lines[lineNumer] = new Line(line, oldLine.FileName, oldLine.LineNumber); + lines[lineNumber] = new Line(line, oldLine.FileName, oldLine.LineNumber); _ = Task.Run(() => { YesNtInterpreter interpreter = new YesNtInterpreter(); interpreter.Initialize(); - interpreter.Execute(lines, RuntimeInfo.GloablVariables, lineNumer, RuntimeInfo); + interpreter.Execute(lines, RuntimeInfo.GlobalVariables, lineNumber, RuntimeInfo); }); RuntimeInfo.CurrentLine = string.Empty; diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index cdf66ec..c8d1e32 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -9,105 +9,104 @@ using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; using YesNt.Interpreter.Utilities; -namespace YesNt.Interpreter.Statements +namespace YesNt.Interpreter.Statements; + +internal class SystemStatements : StatementRuntimeInformation { - internal class SystemStatements : StatementRuntimeInformation + [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Separator = "|")] + public void ExecuteProgramWithArgs(string input) { - [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Seperator = "|")] - public void ExecuteProgramWithArgs(string input) + string[] parts = input.FromSafeString().Split('|'); + parts[0] = parts[0].Trim(); + + string[] functionArguments = parts[1].Split(','); + + foreach (string argument in functionArguments) { - string[] parts = input.FromSafeString().Split('|'); - parts[0] = parts[0].Trim(); - - string[] functionArgumets = parts[1].Split(','); - - foreach (string argumanet in functionArgumets) - { - RuntimeInfo.InParametersStack.Push(argumanet.Trim()); - } - - try - { - StartProcess(parts[0], string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); - } - catch (FileNotFoundException) - { - RuntimeInfo.Exit($"Cannot find file \"{parts[0]}\".", false); - } - catch (Win32Exception ex) - { - RuntimeInfo.Exit($"Failed to start \"{parts[0]}\". {ex.Message}", false); - } - - //HACK: Clear line to avoid execution from other "exc" statement - RuntimeInfo.CurrentLine = string.Empty; + RuntimeInfo.InParametersStack.Push(argument.Trim()); } - [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)] - public void ExecuteProgram(string input) + try { - try - { - StartProcess(input, string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); - } - catch (FileNotFoundException) - { - RuntimeInfo.Exit($"Cannot find file \"{input}\".", false); - } - catch (Win32Exception ex) - { - RuntimeInfo.Exit($"Failed to start \"{input}\". {ex.Message}", false); - } + StartProcess(parts[0], string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); + } + catch (FileNotFoundException) + { + RuntimeInfo.Exit($"Cannot find file \"{parts[0]}\".", false); + } + catch (Win32Exception ex) + { + RuntimeInfo.Exit($"Failed to start \"{parts[0]}\". {ex.Message}", false); } - private void StartProcess(string name, string args) + //HACK: Clear line to avoid execution from other "exc" statement + RuntimeInfo.CurrentLine = string.Empty; + } + + [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)] + public void ExecuteProgram(string input) + { + try { - RuntimeInfo.OutParametersStack.Clear(); - - FixedProcess process = new FixedProcess - { - StartInfo = new ProcessStartInfo() - { - FileName = name, - Arguments = args, - RedirectStandardOutput = true, - RedirectStandardError = true - } - }; - - process.OutputDataReceived += Process_OutputDataReceived; - process.ErrorDataReceived += Process_ErrorDataReceived; - - _ = process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - process.WaitForExit(); - - RuntimeInfo.InParametersStack.Clear(); - - RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); + StartProcess(input, string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); } - - private void Process_ErrorDataReceived(object sender, Utilities.DataReceivedEventArgs e) + catch (FileNotFoundException) { - if (string.IsNullOrWhiteSpace(e.Data)) - { - return; - } - - RuntimeInfo.OutParametersStack.Push(e.Data); - Console.Write("Error: " + e.Data); + RuntimeInfo.Exit($"Cannot find file \"{input}\".", false); } - - private void Process_OutputDataReceived(object sender, Utilities.DataReceivedEventArgs e) + catch (Win32Exception ex) { - if (string.IsNullOrWhiteSpace(e.Data)) - { - return; - } - - RuntimeInfo.OutParametersStack.Push(e.Data); - Console.Write(e.Data); + RuntimeInfo.Exit($"Failed to start \"{input}\". {ex.Message}", false); } } + + private void StartProcess(string name, string args) + { + RuntimeInfo.OutParametersStack.Clear(); + + FixedProcess process = new FixedProcess + { + StartInfo = new ProcessStartInfo() + { + FileName = name, + Arguments = args, + RedirectStandardOutput = true, + RedirectStandardError = true + } + }; + + process.OutputDataReceived += Process_OutputDataReceived; + process.ErrorDataReceived += Process_ErrorDataReceived; + + _ = process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + + RuntimeInfo.InParametersStack.Clear(); + + RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); + } + + private void Process_ErrorDataReceived(object sender, Utilities.DataReceivedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + RuntimeInfo.OutParametersStack.Push(e.Data); + Console.Write("Error: " + e.Data); + } + + private void Process_OutputDataReceived(object sender, Utilities.DataReceivedEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + RuntimeInfo.OutParametersStack.Push(e.Data); + Console.Write(e.Data); + } } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 5fd4c5d..dba0c93 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -47,13 +47,13 @@ internal partial class VariableStatements : StatementRuntimeInformation RuntimeInfo.Exit("Invalid Syntax", true); } - if (RuntimeInfo.GloablVariables.ContainsKey(key)) + if (RuntimeInfo.GlobalVariables.ContainsKey(key)) { - RuntimeInfo.GloablVariables[key] = parts[1].Trim(); + RuntimeInfo.GlobalVariables[key] = parts[1].Trim(); } else { - RuntimeInfo.GloablVariables.Add(key, parts[1].Trim()); + RuntimeInfo.GlobalVariables.Add(key, parts[1].Trim()); } } else @@ -71,9 +71,9 @@ internal partial class VariableStatements : StatementRuntimeInformation { _ = RuntimeInfo.Variables.Remove(key); } - else if (RuntimeInfo.GloablVariables.ContainsKey(key)) + else if (RuntimeInfo.GlobalVariables.ContainsKey(key)) { - _ = RuntimeInfo.GloablVariables.Remove(key); + _ = RuntimeInfo.GlobalVariables.Remove(key); } else { @@ -103,7 +103,7 @@ internal partial class VariableStatements : StatementRuntimeInformation { RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); } - else if (RuntimeInfo.GloablVariables.TryGetValue(varName, out value)) + else if (RuntimeInfo.GlobalVariables.TryGetValue(varName, out value)) { RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); } diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs index 46ee530..1117d27 100644 --- a/YesNt.Interpreter/Utilities/FixedProcess.cs +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -5,312 +5,311 @@ using System.IO; using System.Text; using System.Threading; -namespace YesNt.Interpreter.Utilities +namespace YesNt.Interpreter.Utilities; + +public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e); + +internal delegate void UserCallBack(string data); + +public class FixedProcess : Process { - internal delegate void UserCallBack(string data); + public new event DataReceivedEventHandler OutputDataReceived; - public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e); + public new event DataReceivedEventHandler ErrorDataReceived; - public class FixedProcess : Process + internal AsyncStreamReader output; + internal AsyncStreamReader error; + + public new void BeginOutputReadLine() { - internal AsyncStreamReader output; - internal AsyncStreamReader error; - - public new event DataReceivedEventHandler OutputDataReceived; - - public new event DataReceivedEventHandler ErrorDataReceived; - - 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, new object[] - { - 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, new object[] - { - this, - dataReceivedEventArgs - }); - return; - } - errorDataReceived(this, dataReceivedEventArgs); - } - } + Stream baseStream = StandardOutput.BaseStream; + output = new AsyncStreamReader(baseStream, new UserCallBack(FixedOutputReadNotifyUser), StandardOutput.CurrentEncoding); + output.BeginReadLine(); } - internal class AsyncStreamReader : IDisposable + public new void BeginErrorReadLine() { - internal const int DefaultBufferSize = 1024; - 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 readonly Queue messageQueue; - private StringBuilder sb; - private bool bLastCarriageReturn; - public virtual Encoding CurrentEncoding => encoding; - public virtual Stream BaseStream => stream; + Stream baseStream = StandardError.BaseStream; + error = new AsyncStreamReader(baseStream, new UserCallBack(FixedErrorReadNotifyUser), StandardError.CurrentEncoding); + error.BeginReadLine(); + } - internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding) : this(stream, callback, encoding, 1024) + internal void FixedOutputReadNotifyUser(string data) + { + DataReceivedEventHandler outputDataReceived = OutputDataReceived; + if (outputDataReceived != null) { - } - - internal AsyncStreamReader(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) - { - Init(stream, callback, encoding, bufferSize); - messageQueue = new Queue(); - } - - 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) + DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); + if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) { - 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; - } - - public virtual void Close() - { - Dispose(true); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - 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; - } - if (eofEvent != null) - { - eofEvent.Close(); - eofEvent = null; - } - } - - internal void BeginReadLine() - { - if (cancelOperation) - { - cancelOperation = false; - } - if (sb == null) - { - sb = new StringBuilder(1024); - _ = stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); + _ = SynchronizingObject.Invoke(outputDataReceived, + [ + this, + dataReceivedEventArgs + ]); return; } - FlushMessageQueue(); - } - - internal void CancelOperation() - { - cancelOperation = true; - } - - 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); - } - } - } - } - } - - internal void WaitUtilEOF() - { - if (eofEvent != null) - { - _ = eofEvent.WaitOne(); - eofEvent.Close(); - eofEvent = null; - } + outputDataReceived(this, dataReceivedEventArgs); } } - public class DataReceivedEventArgs : EventArgs + internal void FixedErrorReadNotifyUser(string data) { - internal string _data; - - /// Gets the line of characters that was written to a redirected output stream. - /// The line that was written by an associated to its redirected or stream. - /// 2 - public string Data => _data; - - internal DataReceivedEventArgs(string data) + DataReceivedEventHandler errorDataReceived = ErrorDataReceived; + if (errorDataReceived != null) { - _data = data; + DataReceivedEventArgs dataReceivedEventArgs = new DataReceivedEventArgs(data); + if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) + { + _ = SynchronizingObject.Invoke(errorDataReceived, + [ + this, + dataReceivedEventArgs + ]); + return; + } + errorDataReceived(this, dataReceivedEventArgs); + } + } +} + +public class DataReceivedEventArgs : EventArgs +{ + internal string _data; + + /// Gets the line of characters that was written to a redirected output stream. + /// The line that was written by an associated to its redirected or stream. + /// 2 + 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; + } + if (eofEvent != 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); + } + } + } } } } \ No newline at end of file diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index ca042b8..0feb116 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -7,7 +7,7 @@ using System.Text; namespace YesNt.Interpreter.Utilities; -public static class StringExtentions +public static class StringExtensions { private static readonly Dictionary reverseReplacementRules; @@ -24,7 +24,7 @@ public static class StringExtentions }; [SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "Doesn't work because it throws a TypeInitializationException")] - static StringExtentions() + static StringExtensions() { reverseReplacementRules = ReplacementRules.ToDictionary(x => x.Value, x => x.Key); } diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index 3b71ea2..1093ec0 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 YesNt.Interpreter Exe From c3bc324519ab55e4b949ab01dabbfae130529d46 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:17:59 +0200 Subject: [PATCH 15/73] Reset function in and out parameters stack --- YesNt.Interpreter/Runtime/RuntimeInformation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 0eb4676..719e010 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -153,6 +153,8 @@ internal sealed class RuntimeInformation Labels.Clear(); Functions.Clear(); FunctionCallStack.Clear(); + InParametersStack.Clear(); + OutParametersStack.Clear(); ParentRuntimeInformation = null; SearchLabel = string.Empty; SearchFunction = string.Empty; From 7e357884f53b31076526c68bc964215268fc9491 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 7 Aug 2024 21:19:45 +0200 Subject: [PATCH 16/73] `get` and `out` statements can now be placed anywhere and function arguments are now always in correct order --- .../Statements/CodeFlowStatements.cs | 3 +- .../Statements/FunctionStatements.cs | 49 +++++++++---------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index d1ccdd6..7ae96a9 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; @@ -89,7 +88,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation { string key = args.Trim(); - RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack.Reverse()))); + RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack))); RuntimeInfo.InParametersStack.Clear(); if (RuntimeInfo.Functions.TryGetValue(key, out int value)) diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs index de923c2..4fef510 100644 --- a/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; using YesNt.Interpreter.Runtime; +using YesNt.Interpreter.Utilities; namespace YesNt.Interpreter.Statements; @@ -42,29 +43,26 @@ internal class FunctionStatements : StatementRuntimeInformation RuntimeInfo.InParametersStack.Push(args); } - [Statement("out", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + [Statement("%out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetOutParameter(string args) { - if (RuntimeInfo.OutParametersStack.Count == 0) + while (args.Contains("%out")) { - RuntimeInfo.Exit("No out argument in stack", true); - return; + if (RuntimeInfo.OutParametersStack.Count == 0) + { + RuntimeInfo.Exit("No out argument in stack", true); + return; + } + + args = args.ReplaceFirstOccurrence("%out", RuntimeInfo.OutParametersStack.Pop()); } - if (RuntimeInfo.Variables.ContainsKey(args)) - { - RuntimeInfo.Variables[args] = RuntimeInfo.OutParametersStack.Pop(); - } - else - { - RuntimeInfo.Variables.Add(args, RuntimeInfo.OutParametersStack.Pop()); - } + RuntimeInfo.CurrentLine = args.TrimEnd(); } [Statement("%iso", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void CheckIfOutParameterAvailable(string args) { - args += " "; args = args.Replace("%iso", (RuntimeInfo.OutParametersStack.Count > 0).ToString()); RuntimeInfo.CurrentLine = args.TrimEnd(); @@ -102,7 +100,7 @@ internal class FunctionStatements : StatementRuntimeInformation } } - [Statement("get", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + [Statement("%get", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetInParameter(string args) { if (!RuntimeInfo.IsInFunction) @@ -111,23 +109,21 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0) + while (args.Contains("%get")) { - RuntimeInfo.Exit("No in argument in stack", true); - return; + if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0) + { + RuntimeInfo.Exit("No in argument in stack", true); + return; + } + + args = args.ReplaceFirstOccurrence("%get", RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop()); } - if (RuntimeInfo.Variables.ContainsKey(args)) - { - RuntimeInfo.Variables[args] = RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop(); - } - else - { - RuntimeInfo.Variables.Add(args, RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop()); - } + RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%isi", SearchMode.Contains, SpaceAround.End, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%isi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void CheckIfInParameterAvailable(string args) { if (!RuntimeInfo.IsInFunction) @@ -136,7 +132,6 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - args += " "; args = args.Replace("%isi", (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count > 0).ToString()); RuntimeInfo.CurrentLine = args.TrimEnd(); From cf274e1db9b5f6895ed9eb91f643bef8fe8e7680 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 7 Aug 2024 23:31:19 +0200 Subject: [PATCH 17/73] Add more escape codes --- .../Utilities/StringExtentions.cs | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index 0feb116..49d00f9 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -20,7 +20,14 @@ public static class StringExtensions {">", "~grt" }, {",", "~com" }, {"!", "~exm" }, - {"|", "~pip" } + {"|", "~pip" }, + {"\n","~nli" }, + {"\r","~ret" }, + {"\t","~tab" }, + {"\b","~bac" }, + {"\f","~for" }, + {"\a","~ale" }, + {"", "~emp" }, }; [SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "Doesn't work because it throws a TypeInitializationException")] @@ -42,26 +49,7 @@ public static class StringExtensions public static string FromSafeString(this string input) { - return ReplaceOnce(input.Replace("\v", ""), reverseReplacementRules); - } - - public static string ReplaceOnce(string input, Dictionary replacementRules) - { - IEnumerable> matches = replacementRules.Where(rule => input.Contains(rule.Key)); - if (!matches.Any()) - { - return input; - } - - KeyValuePair match = matches.First(); - int startIndex = input.IndexOf(match.Key); - int endIndex = startIndex + match.Key.Length; - - string before = ReplaceOnce(input[..startIndex], replacementRules); - string replaced = match.Value; - string after = ReplaceOnce(input[endIndex..], replacementRules); - - return before + replaced + after; + return ReplaceOnce(input.Replace("\v", string.Empty), reverseReplacementRules); } public static bool ToStandardizedNumber(this string input, out double result) @@ -92,4 +80,24 @@ public static class StringExtensions return count; } + + private static string ReplaceOnce(string input, Dictionary replacementRules) + { + // ~emp/string.Empty is a special case, it is used to represent empty strings and won't work with the normal rules because an empty string always matches and causes an infinite loop 3 letter abbreviation + IEnumerable> matches = replacementRules.Where(rule => rule.Key != string.Empty && input.Contains(rule.Key)); + if (!matches.Any()) + { + return input; + } + + KeyValuePair match = matches.First(); + int startIndex = input.IndexOf(match.Key); + int endIndex = startIndex + match.Key.Length; + + string before = ReplaceOnce(input[..startIndex], replacementRules); + string replaced = match.Value; + string after = ReplaceOnce(input[endIndex..], replacementRules); + + return before + replaced + after; + } } \ No newline at end of file From ec2f29479628cb5b8f6b721073ddb007482c4624 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 7 Aug 2024 23:31:53 +0200 Subject: [PATCH 18/73] Fix exec statement and syntax highlighter --- YesNt.CodeEditor/SyntaxHighlighter.cs | 10 ++-- .../Statements/SystemStatements.cs | 52 ++++++++++--------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 12c664e..5aaf33f 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -134,7 +134,7 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection= 0 && colorIndex < 16) { - messagePart = Base64Decode(messagePart.Replace($"\r{stringColor}\r", string.Empty)); + messagePart = Base64Decode(messagePart.Replace($"\v{stringColor}\v", string.Empty)); consoleColor = (ConsoleColor)colorIndex; } else @@ -160,9 +160,9 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), - SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)), - _ => originalString.Replace(value, $"\0\r{(int)color}\r{base64Value}\0" + new string(' ', spacesAtEnd)) + SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\v{(int)color}\v{base64Value}\0" + new string(' ', spacesAtEnd)), + SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\v{(int)color}\v{base64Value}\0" + new string(' ', spacesAtEnd)), + _ => originalString.Replace(value, $"\0\v{(int)color}\v{base64Value}\0" + new string(' ', spacesAtEnd)) }; return result; } @@ -176,6 +176,6 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection[a-zA-Z0-9]+")] private static partial Regex VariableRegex(); - [GeneratedRegex("(?<=(\\r))(.*)(?=\\r)")] + [GeneratedRegex("(?<=(\\v))(.*)(?=\\v)")] private static partial Regex StringColorRegex(); } \ No newline at end of file diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index c8d1e32..c5b603a 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -60,10 +61,34 @@ internal class SystemStatements : StatementRuntimeInformation } } + private static void Process_ErrorDataReceived(Utilities.DataReceivedEventArgs e, Stack outputStack) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + outputStack.Push(e.Data.ToSafeString()); + Console.Write("Error: " + e.Data); + } + + private static void Process_OutputDataReceived(Utilities.DataReceivedEventArgs e, Stack outputStack) + { + if (string.IsNullOrWhiteSpace(e.Data)) + { + return; + } + + outputStack.Push(e.Data.ToSafeString()); + Console.Write(e.Data); + } + private void StartProcess(string name, string args) { RuntimeInfo.OutParametersStack.Clear(); + Stack outputStack = new Stack(); + FixedProcess process = new FixedProcess { StartInfo = new ProcessStartInfo() @@ -75,8 +100,8 @@ internal class SystemStatements : StatementRuntimeInformation } }; - process.OutputDataReceived += Process_OutputDataReceived; - process.ErrorDataReceived += Process_ErrorDataReceived; + process.OutputDataReceived += (s, e) => Process_OutputDataReceived(e, outputStack); + process.ErrorDataReceived += (s, e) => Process_ErrorDataReceived(e, outputStack); _ = process.Start(); process.BeginOutputReadLine(); @@ -85,28 +110,7 @@ internal class SystemStatements : StatementRuntimeInformation RuntimeInfo.InParametersStack.Clear(); + RuntimeInfo.OutParametersStack = new(outputStack); RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); } - - private void Process_ErrorDataReceived(object sender, Utilities.DataReceivedEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.Data)) - { - return; - } - - RuntimeInfo.OutParametersStack.Push(e.Data); - Console.Write("Error: " + e.Data); - } - - private void Process_OutputDataReceived(object sender, Utilities.DataReceivedEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.Data)) - { - return; - } - - RuntimeInfo.OutParametersStack.Push(e.Data); - Console.Write(e.Data); - } } \ No newline at end of file From 9a6f1d780608480fc4eae089e6c3fd4b1660c008 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:57:40 +0200 Subject: [PATCH 19/73] Escape invisible character is debug mode --- YesNt.CodeEditor/Editor.cs | 11 +++-- YesNt.CodeEditor/YesNt.CodeEditor.csproj | 4 ++ YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 8 +-- .../Statements/ConsoleStatements.cs | 2 +- .../Statements/ProcessingStatements.cs | 2 +- .../Utilities/ConsoleExtentions.cs | 49 +++++++++---------- 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index 11e1eb5..f3f7698 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -203,12 +203,17 @@ internal class TextEditor return padding; } + private static string ToLiteral(string input) + { + return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(input, false); + } + private void YesNtInterpreter_OnDebugOutput(string output) { debugOutput.Add(output); } - private void YesNtInterpreter_OnLineExecuted(Interpreter.Runtime.DebugEventArgs e) + private void YesNtInterpreter_OnLineExecuted(DebugEventArgs e) { lock (Console.Out) { @@ -221,11 +226,11 @@ internal class TextEditor if (e.OriginalLine == e.CurrentLine) { - Console.WriteLine($"{sharedString}[{e.CurrentLine}] ==>"); + Console.WriteLine($"{sharedString}[{ToLiteral(e.CurrentLine)}] ==>"); } else { - Console.WriteLine($"{sharedString}[{e.OriginalLine}] => [{e.CurrentLine}] ==>"); + Console.WriteLine($"{sharedString}[{ToLiteral(e.OriginalLine)}] => [{ToLiteral(e.CurrentLine)}] ==>"); } Console.ForegroundColor = ConsoleColor.Gray; } diff --git a/YesNt.CodeEditor/YesNt.CodeEditor.csproj b/YesNt.CodeEditor/YesNt.CodeEditor.csproj index b7ba18b..5b45dec 100644 --- a/YesNt.CodeEditor/YesNt.CodeEditor.csproj +++ b/YesNt.CodeEditor/YesNt.CodeEditor.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index fdfd80d..0559a7c 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -52,20 +52,20 @@ public class YesNtInterpreter Assembly assembly = Assembly.GetExecutingAssembly(); Type[] types = assembly.GetTypes(); - IEnumerable statementRuntimeInfos = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); + IEnumerable allStatementRuntimeInfo = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); statements.Clear(); - foreach (Type type in statementRuntimeInfos) + foreach (Type type in allStatementRuntimeInfo) { object statementInfo = Activator.CreateInstance(type); - MethodInfo[] methodInfos = statementInfo.GetType().GetMethods(); + MethodInfo[] allMethodInfo = statementInfo.GetType().GetMethods(); StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; statementRuntimeInfo.RuntimeInfo = runtimeInfo; - foreach (MethodInfo methodInfo in methodInfos) + foreach (MethodInfo methodInfo in allMethodInfo) { StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); if (statementAttribute is not null) diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index 406db42..b7f1fdb 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -51,7 +51,7 @@ internal class ConsoleStatements : StatementRuntimeInformation args += " "; while (args.Contains("%cr")) { - string input = ConsoleExtentions.ReadKey(RuntimeInfo).ToString(); + string input = ConsoleExtensions.ReadKey(RuntimeInfo).ToString(); args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); } RuntimeInfo.CurrentLine = args.TrimEnd(); diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index e606aea..60af01f 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -62,7 +62,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation { if (int.TryParse(args, out int millisecondsTimeout)) { - ConsoleExtentions.Sleep(millisecondsTimeout, RuntimeInfo); + ConsoleExtensions.Sleep(millisecondsTimeout, RuntimeInfo); } else { diff --git a/YesNt.Interpreter/Utilities/ConsoleExtentions.cs b/YesNt.Interpreter/Utilities/ConsoleExtentions.cs index a394935..f401e52 100644 --- a/YesNt.Interpreter/Utilities/ConsoleExtentions.cs +++ b/YesNt.Interpreter/Utilities/ConsoleExtentions.cs @@ -4,35 +4,34 @@ using System.Threading; using YesNt.Interpreter.Runtime; -namespace YesNt.Interpreter.Utilities -{ - internal static class ConsoleExtentions - { - public static char ReadKey(RuntimeInformation runtimeInformation) - { - while (!runtimeInformation.Stop) - { - if (Console.KeyAvailable) - { - return Console.ReadKey().KeyChar; - } - Thread.Sleep(10); - } - return ' '; - } +namespace YesNt.Interpreter.Utilities; - public static void Sleep(int millisecondsTimeout, RuntimeInformation runtimeInformation) +internal static class ConsoleExtensions +{ + public static char ReadKey(RuntimeInformation runtimeInformation) + { + while (!runtimeInformation.Stop) { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - while (!runtimeInformation.Stop) + if (Console.KeyAvailable) { - if (stopwatch.ElapsedMilliseconds > millisecondsTimeout) - { - return; - } - Thread.Sleep(10); + 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); } } } \ No newline at end of file From 1df0d348415c28632cfeececa133b44e8ccafa6a Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:57:55 +0200 Subject: [PATCH 20/73] Add more unit test --- ...eFowTests.cs => CodeFowStatementsTests.cs} | 6 --- .../ProcessingStatementsTests.cs | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) rename YesNt.Interpreter.Tests/{CodeFowTests.cs => CodeFowStatementsTests.cs} (84%) create mode 100644 YesNt.Interpreter.Tests/ProcessingStatementsTests.cs diff --git a/YesNt.Interpreter.Tests/CodeFowTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs similarity index 84% rename from YesNt.Interpreter.Tests/CodeFowTests.cs rename to YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index 66444e5..8944543 100644 --- a/YesNt.Interpreter.Tests/CodeFowTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -34,10 +34,4 @@ public class CodeFlowTests ]; YesNtAssert.IsLastLineEqual(lines, "1"); } - - [TestMethod] - public void CalculationsTest() - { - YesNtAssert.IsLineEqual("10 * 10 !calc", 100.ToString()); - } } \ No newline at end of file diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs new file mode 100644 index 0000000..e445b55 --- /dev/null +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class ProcessingStatementsTests +{ + [TestMethod] + public void MultiplicationTest() + { + YesNtAssert.IsLineEqual("10 * 10 !calc", "100"); + } + + [TestMethod] + public void DivisionTest() + { + YesNtAssert.IsLineEqual("90 / 4 !calc", "22.5"); + } + + [TestMethod] + public void AdditionTest() + { + YesNtAssert.IsLineEqual("10 + 10 !calc", "20"); + } + + [TestMethod] + public void SubtractionTest() + { + YesNtAssert.IsLineEqual("10 - 10 !calc", "0"); + } + + [TestMethod] + public void ModulusTest() + { + YesNtAssert.IsLineEqual("10 % 3 !calc", "1"); + } + + [TestMethod] + public void ExponentiationTest() + { + YesNtAssert.IsLineEqual("2 ^ 3 !calc", "8"); + } +} \ No newline at end of file From 11200cbff872c6d35d2c0bb510f487b01bc81036 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:14:01 +0100 Subject: [PATCH 21/73] Don't throw exception when line that contains > contains no variable found --- YesNt.Interpreter/Statements/VariableStatements.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index dba0c93..21ea74c 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -93,7 +93,7 @@ internal partial class VariableStatements : StatementRuntimeInformation if (matches.Count <= 0) { - RuntimeInfo.Exit("Invalid syntax", true); + return; } for (int i = 0; i < matches.Count; i++) From 9d43f1373f903cd3961864ce317e3e9fe7edb0c9 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:04:00 +0100 Subject: [PATCH 22/73] Make syntax more readable --- README.md | 17 +++- SYNTAX_V2.md | 93 +++++++++++++++++++ YesNt.CodeEditor/SyntaxHighlighter.cs | 8 +- .../CodeFowStatementsTests.cs | 22 ++--- .../ProcessingStatementsTests.cs | 14 +-- .../Statements/CodeFlowStatements.cs | 43 +++++---- .../Statements/ConsoleStatements.cs | 22 ++--- .../Statements/FunctionStatements.cs | 39 ++++---- .../PredifinedVariableStatements.cs | 8 +- .../Statements/ProcessingStatements.cs | 14 +-- .../Statements/SystemStatements.cs | 14 +-- .../Statements/VariableStatements.cs | 20 ++-- 12 files changed, 216 insertions(+), 98 deletions(-) create mode 100644 SYNTAX_V2.md diff --git a/README.md b/README.md index e840c92..5a83f33 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,19 @@ > YesNt is a imperative and interpreted language inspired by the Assembly language. -Check out the [Wiki](https://github.com/Stone-Red-Code/YesNt-Interpreter/wiki) (Work in progress) +## Syntax + +Current language syntax is documented in [SYNTAX_V2.md](SYNTAX_V2.md). + +Example: + +```ynt +let name = world +print_line Hello ${name} +``` + +## Run + +```bash +dotnet run --project YesNt.Interpreter -- path/to/script.ynt +``` diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md new file mode 100644 index 0000000..ff18b42 --- /dev/null +++ b/SYNTAX_V2.md @@ -0,0 +1,93 @@ +# YesNt v2 Syntax + +This document describes the current, word-based YesNt syntax. + +## Goals + +- Keep current semantics and execution model. +- Replace terse mnemonics and symbols with readable words. +- Preserve line-based scripting. +- Keep the language line-based and lightweight. + +## v2 Core Rules + +- Statements are line-based. +- `# ...` remains a comment. +- Variable interpolation inside text uses `${name}`. +- Function and label declarations are block markers with a trailing `:`. +- Conditions retain current evaluator expressions (for example: `a == b`, `x > 5`, `10 + 2 == 12`). + +## Quick Example + +v1: + +```ynt +name +ret +cal greet +``` + +v2: + +```ynt +let name = world +func greet: +print_line Hello ${name} +return +call greet +``` + +## Full v1 -> v2 Mapping + +| Area | v1 Syntax | v2 Syntax | Notes | +|---|---|---|---| +| Variables | `x` | `${x}` | Variable read/interpolation token. | +| Console | `cwl` | `print_line` | Empty line print. | +| Console | `cwl text` | `print_line text` | Print with newline. | +| Console | `cw text` | `print text` | Print without newline. | +| Console | `%crl` | `%read_line` | Inline token, inserts user line input. | +| Console | `%cr` | `%read_key` | Inline token, inserts user key input. | +| Console | `cls` | `clear` | Clear console. | +| Code flow | `lbl name` | `label name:` | Label declaration/target. | +| Code flow | `jmp name` | `goto name` | Unconditional jump. | +| Code flow | `jif name \| cond` | `if cond goto name` | Conditional jump. | +| Functions | `fnc name` | `func name:` | Function declaration. | +| Functions | `cal name` | `call name` | Function call without args. | +| Functions | `cal name \| a,b,c` | `call name with a, b, c` | Function call with args. | +| Functions | `in value` | `push_in value` | Push in-arg onto input stack. | +| Functions | `%get` | `%in` | Inline token, pop current call input arg. | +| Functions | `%isi` | `%has_in` | Inline token, bool if input arg exists. | +| Functions | `put value` | `push_out value` | Push out-arg in function. | +| Functions | `%out` | `%out` | Keep token name for familiarity. | +| Functions | `%iso` | `%has_out` | Inline token, bool if out arg exists. | +| Functions | `ret` | `return` | Return from function. | +| Functions | `ccs` | `clear_call_stack` | Clear call stack. | +| Condition-call | `cif name \| cond` | `if cond call name` | Conditional function call. | +| Termination | `end` | `exit` | Planned termination. | +| Termination | `trm` | `abort_all` | Planned termination + cancel tasks. | +| Errors | `trw message` | `throw message` | Error termination. | +| Errors | `err message` | `error message` | Non-fatal/runtime message end state. | +| Processing | `expr !calc` | `expr calc` | Evaluate arithmetic fragments. | +| Processing | `text !eval` | `text eval` | Decode safe string literals. | +| Processing | `line !task` | `line task` | Run line in background task runtime. | +| Processing | `slp ms` | `sleep ms` | Sleep with runtime-aware cancellation. | +| Processing | `len text` | `length text` | Push text length to out stack. | +| Processing | `imp file` | `import file` | Inline include of another `.ynt` file. | +| System | `exc prog` | `exec prog` | Execute process with in-stack args. | +| System | `exc prog \| a,b,c` | `exec prog with a, b, c` | Execute process with explicit args. | +| Predefined | `%time` | `%time` | Unix timestamp token. | +| Predefined | `%os` | `%os` | OS platform token. | +| Predefined | `%cpu` | `%cpu` | Processor architecture token. | +| Predefined | `%is64` | `%is64` | 64-bit OS bool token. | +| Predefined | `%pi` | `%pi` | PI token. | +| Predefined | `%rnd` | `%rand` | Random number token. | + +## Notes + +- `%out` is intentionally kept as `%out`. +- Postfix operations are `calc`, `eval`, and `task`. diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 5aaf33f..a368796 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -167,15 +167,15 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection[a-zA-Z0-9]+")] + [GeneratedRegex("\\$\\{[a-zA-Z0-9]+\\}")] private static partial Regex VariableRegex(); [GeneratedRegex("(?<=(\\v))(.*)(?=\\v)")] private static partial Regex StringColorRegex(); -} \ No newline at end of file +} diff --git a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index 8944543..0bddd5b 100644 --- a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -12,11 +12,11 @@ public class CodeFlowTests { List lines = [ - "cal yes", - "fnc yes", - "!result" + "call yes", + "func yes:", + "global result = 1", + "return", + "${result}" ]; YesNtAssert.IsLastLineEqual(lines, "1"); } @@ -26,12 +26,12 @@ public class CodeFlowTests { List lines = [ - "result" + "let result = 1", + "goto yes", + "let result = 0", + "label yes:", + "${result}" ]; YesNtAssert.IsLastLineEqual(lines, "1"); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs index e445b55..fd68646 100644 --- a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -8,36 +8,36 @@ public class ProcessingStatementsTests [TestMethod] public void MultiplicationTest() { - YesNtAssert.IsLineEqual("10 * 10 !calc", "100"); + YesNtAssert.IsLineEqual("10 * 10 calc", "100"); } [TestMethod] public void DivisionTest() { - YesNtAssert.IsLineEqual("90 / 4 !calc", "22.5"); + YesNtAssert.IsLineEqual("90 / 4 calc", "22.5"); } [TestMethod] public void AdditionTest() { - YesNtAssert.IsLineEqual("10 + 10 !calc", "20"); + YesNtAssert.IsLineEqual("10 + 10 calc", "20"); } [TestMethod] public void SubtractionTest() { - YesNtAssert.IsLineEqual("10 - 10 !calc", "0"); + YesNtAssert.IsLineEqual("10 - 10 calc", "0"); } [TestMethod] public void ModulusTest() { - YesNtAssert.IsLineEqual("10 % 3 !calc", "1"); + YesNtAssert.IsLineEqual("10 % 3 calc", "1"); } [TestMethod] public void ExponentiationTest() { - YesNtAssert.IsLineEqual("2 ^ 3 !calc", "8"); + YesNtAssert.IsLineEqual("2 ^ 3 calc", "8"); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 7ae96a9..91fc087 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -10,10 +10,10 @@ namespace YesNt.Interpreter.Statements; internal class CodeFlowStatements : StatementRuntimeInformation { - [Statement("jmp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow)] + [Statement("goto", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow)] public void Jump(string args) { - string key = args.Trim(); + string key = NormalizeBlockName(args); if (RuntimeInfo.Labels.TryGetValue(key, out int value)) { @@ -26,18 +26,18 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("jif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = "|")] + [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = " goto ")] public void JumpIf(string args) { - string[] parts = args.Split('|'); + string[] parts = args.Split(" goto ", 2, StringSplitOptions.None); if (parts.Length != 2) { RuntimeInfo.Exit("Invalid syntax", true); return; } - string key = parts[0].Trim(); - string condition = parts[1].Trim(); + string condition = parts[0].Trim(); + string key = NormalizeBlockName(parts[1]); bool? result = Evaluator.EvaluateCondition(condition); @@ -63,10 +63,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("lbl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true)] + [Statement("label", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true)] public void FindLabel(string args) { - string key = args.Trim(); + string key = NormalizeBlockName(args); if (RuntimeInfo.Labels.ContainsKey(key)) { RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber; @@ -83,10 +83,10 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow)] + [Statement("call", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow)] public void Call(string args) { - string key = args.Trim(); + string key = NormalizeBlockName(args); RuntimeInfo.FunctionCallStack.Push(new FunctionScope(RuntimeInfo.LineNumber, new Stack(RuntimeInfo.InParametersStack))); RuntimeInfo.InParametersStack.Clear(); @@ -101,18 +101,18 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("cif", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow, Separator = "|")] + [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.VeryLow, Separator = " call ")] public void CallIf(string args) { - string[] parts = args.Split('|'); + string[] parts = args.Split(" call ", 2, StringSplitOptions.None); if (parts.Length != 2) { RuntimeInfo.Exit("Invalid syntax", true); return; } - string key = parts[0].Trim(); - string condition = parts[1].Trim(); + string condition = parts[0].Trim(); + string key = NormalizeBlockName(parts[1]); bool? result = Evaluator.EvaluateCondition(condition); @@ -140,7 +140,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("end", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] + [Statement("exit", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] public void End(string _) { if (RuntimeInfo.IsSearching) @@ -161,7 +161,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.Exit("Planned termination by code", false); } - [Statement("trm", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] + [Statement("abort_all", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] public void Terminate(string _) { if (RuntimeInfo.IsSearching) @@ -182,15 +182,20 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.Exit("Planned termination by code. Canceling all tasks", true); } - [Statement("trw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] + [Statement("throw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] public void Throw(string message) { RuntimeInfo.Exit(message, true); } - [Statement("err", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] + [Statement("error", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] public void Error(string message) { RuntimeInfo.Exit(message, false); } -} \ No newline at end of file + + private static string NormalizeBlockName(string value) + { + return value.Trim().TrimEnd(':').Trim(); + } +} diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index b7f1fdb..50264b4 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -10,29 +10,29 @@ namespace YesNt.Interpreter.Statements; internal class ConsoleStatements : StatementRuntimeInformation { - [Statement("cwl", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + [Statement("print_line", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] public void WriteLineEmpty(string _) { RuntimeInfo.WriteLine(string.Empty); } - [Statement("cwl", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + [Statement("print_line", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] public void WriteLine(string args) { RuntimeInfo.WriteLine(args); } - [Statement("cw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] + [Statement("print", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkGreen, Priority = Priority.VeryLow)] public void Write(string args) { RuntimeInfo.Write(args); } - [Statement("%crl", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%read_line", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] public void ReadLine(string args) { args += " "; - while (args.Contains("%crl")) + while (args.Contains("%read_line")) { string input = Console.ReadLine(); if (input is null) @@ -40,27 +40,27 @@ internal class ConsoleStatements : StatementRuntimeInformation RuntimeInfo.Exit("Terminated by external process", true); return; } - args = args.ReplaceFirstOccurrence("%crl ", input.ToSafeString() + " "); + args = args.ReplaceFirstOccurrence("%read_line ", input.ToSafeString() + " "); } RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%cr", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%read_key", SearchMode.Contains, SpaceAround.None, ConsoleColor.DarkGreen, KeepStatementInArgs = true, Priority = Priority.Highest)] public void ReadKey(string args) { args += " "; - while (args.Contains("%cr")) + while (args.Contains("%read_key")) { string input = ConsoleExtensions.ReadKey(RuntimeInfo).ToString(); - args = args.ReplaceFirstOccurrence("%cr ", input.ToSafeString() + " "); + args = args.ReplaceFirstOccurrence("%read_key ", input.ToSafeString() + " "); } RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("cls", SearchMode.Exact, SpaceAround.None, ConsoleColor.Magenta)] + [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(); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs index 4fef510..7e50ec4 100644 --- a/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -10,7 +10,7 @@ namespace YesNt.Interpreter.Statements; internal class FunctionStatements : StatementRuntimeInformation { - [Statement("fnc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + [Statement("func", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] public void FindFunction(string args) { if (RuntimeInfo.InternalIsInFunction) @@ -19,7 +19,7 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - string key = args.Trim(); + string key = NormalizeBlockName(args); if (RuntimeInfo.Functions.ContainsKey(key)) { RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber; @@ -37,7 +37,7 @@ internal class FunctionStatements : StatementRuntimeInformation RuntimeInfo.IsInFunction = true; } - [Statement("in", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + [Statement("push_in", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] public void AddInParameter(string args) { RuntimeInfo.InParametersStack.Push(args); @@ -60,25 +60,25 @@ internal class FunctionStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%iso", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%has_out", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void CheckIfOutParameterAvailable(string args) { - args = args.Replace("%iso", (RuntimeInfo.OutParametersStack.Count > 0).ToString()); + args = args.Replace("%has_out", (RuntimeInfo.OutParametersStack.Count > 0).ToString()); RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("cal", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.Low, Separator = "|")] + [Statement("call", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, Priority = Priority.Low, Separator = " with ")] public void Call(string args) { - string[] parts = args.Split('|'); + string[] parts = args.Split(" with ", 2, StringSplitOptions.None); if (parts.Length != 2) { RuntimeInfo.Exit("Invalid syntax", true); return; } - string key = parts[0].Trim(); + string key = NormalizeBlockName(parts[0]); string[] functionArguments = parts[1].Split(','); foreach (string argument in functionArguments) @@ -100,7 +100,7 @@ internal class FunctionStatements : StatementRuntimeInformation } } - [Statement("%get", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetInParameter(string args) { if (!RuntimeInfo.IsInFunction) @@ -109,7 +109,7 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - while (args.Contains("%get")) + while (args.Contains("%in")) { if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0) { @@ -117,13 +117,13 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - args = args.ReplaceFirstOccurrence("%get", RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop()); + args = args.ReplaceFirstOccurrence("%in", RuntimeInfo.FunctionCallStack.Peek().Arguments.Pop()); } RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%isi", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%has_in", SearchMode.Contains, SpaceAround.None, ConsoleColor.Yellow, KeepStatementInArgs = true, Priority = Priority.Highest)] public void CheckIfInParameterAvailable(string args) { if (!RuntimeInfo.IsInFunction) @@ -132,12 +132,12 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - args = args.Replace("%isi", (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count > 0).ToString()); + args = args.Replace("%has_in", (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count > 0).ToString()); RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("put", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] + [Statement("push_out", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Yellow)] public void AddOutParameter(string args) { if (!RuntimeInfo.IsInFunction) @@ -149,7 +149,7 @@ internal class FunctionStatements : StatementRuntimeInformation RuntimeInfo.FunctionCallStack.Peek().Results.Push(args); } - [Statement("ret", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + [Statement("return", SearchMode.Exact, SpaceAround.None, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] public void Return(string _) { if (!RuntimeInfo.IsInFunction) @@ -186,9 +186,14 @@ internal class FunctionStatements : StatementRuntimeInformation } } - [Statement("ccs", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red)] + [Statement("clear_call_stack", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red)] public void ClearCallStack(string _) { RuntimeInfo.FunctionCallStack.Clear(); } -} \ No newline at end of file + + private static string NormalizeBlockName(string value) + { + return value.Trim().TrimEnd(':').Trim(); + } +} diff --git a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs index e79276f..4036f94 100644 --- a/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs +++ b/YesNt.Interpreter/Statements/PredifinedVariableStatements.cs @@ -51,14 +51,14 @@ internal class PredefinedVariableStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = args.TrimEnd(); } - [Statement("%rnd", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] + [Statement("%rand", SearchMode.Contains, SpaceAround.None, ConsoleColor.Blue, KeepStatementInArgs = true, Priority = Priority.Highest)] public void GetRandom(string args) { - while (args.Contains("%rnd")) + while (args.Contains("%rand")) { - args = args.ReplaceFirstOccurrence("%rnd", random.Next(32767, int.MaxValue).ToString()); + args = args.ReplaceFirstOccurrence("%rand", random.Next(32767, int.MaxValue).ToString()); } RuntimeInfo.CurrentLine = args.TrimEnd(); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 60af01f..aa1e963 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -13,7 +13,7 @@ namespace YesNt.Interpreter.Statements; internal partial class ProcessingStatements : StatementRuntimeInformation { - [Statement("!calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)] + [Statement("calc", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.High)] public void Calculate(string args) { MatchCollection matches = CalculationRegex().Matches(args.FromSafeString()); @@ -32,13 +32,13 @@ internal partial class ProcessingStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = args; } - [Statement("!eval", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] + [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)] + [Statement("task", SearchMode.EndOfLine, SpaceAround.Start, ConsoleColor.DarkYellow, Priority = Priority.VeryHigh)] public void RunTask(string line) { int lineNumber = RuntimeInfo.LineNumber; @@ -57,7 +57,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation RuntimeInfo.CurrentLine = string.Empty; } - [Statement("slp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] + [Statement("sleep", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Sleep(string args) { if (int.TryParse(args, out int millisecondsTimeout)) @@ -70,7 +70,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation } } - [Statement("len", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] + [Statement("length", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Length(string args) { RuntimeInfo.InParametersStack.Clear(); @@ -79,7 +79,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation RuntimeInfo.OutParametersStack.Push(args.FromSafeString().Length.ToString()); } - [Statement("imp", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] + [Statement("import", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta)] public void Import(string path) { path = Path.Combine(RuntimeInfo.WorkingDirectory, path); @@ -115,4 +115,4 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [GeneratedRegex("[0-9*+().,^%/-]+[0-9*+ ().,^%/-]+[0-9*+().,^%/-]+")] private static partial Regex CalculationRegex(); -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index c5b603a..a5d148d 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -14,10 +14,10 @@ namespace YesNt.Interpreter.Statements; internal class SystemStatements : StatementRuntimeInformation { - [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Separator = "|")] + [Statement("exec", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.Low, Separator = " with ")] public void ExecuteProgramWithArgs(string input) { - string[] parts = input.FromSafeString().Split('|'); + string[] parts = input.FromSafeString().Split(" with ", 2, StringSplitOptions.None); parts[0] = parts[0].Trim(); string[] functionArguments = parts[1].Split(','); @@ -29,7 +29,7 @@ internal class SystemStatements : StatementRuntimeInformation try { - StartProcess(parts[0], string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); + StartProcess(parts[0], string.Join(" ", RuntimeInfo.InParametersStack.Reverse())); } catch (FileNotFoundException) { @@ -40,16 +40,16 @@ internal class SystemStatements : StatementRuntimeInformation RuntimeInfo.Exit($"Failed to start \"{parts[0]}\". {ex.Message}", false); } - //HACK: Clear line to avoid execution from other "exc" statement + // HACK: Clear line to avoid execution from another "exec" statement. RuntimeInfo.CurrentLine = string.Empty; } - [Statement("exc", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)] + [Statement("exec", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Magenta, Priority = Priority.VeryLow)] public void ExecuteProgram(string input) { try { - StartProcess(input, string.Join(string.Empty, RuntimeInfo.InParametersStack.Reverse())); + StartProcess(input, string.Join(" ", RuntimeInfo.InParametersStack.Reverse())); } catch (FileNotFoundException) { @@ -113,4 +113,4 @@ internal class SystemStatements : StatementRuntimeInformation RuntimeInfo.OutParametersStack = new(outputStack); RuntimeInfo.OutParametersStack.Push(process.ExitCode.ToString()); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 21ea74c..9cfde6d 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -8,7 +8,7 @@ namespace YesNt.Interpreter.Statements; internal partial class VariableStatements : StatementRuntimeInformation { - [Statement("<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] + [Statement("let", SearchMode.StartOfLine, SpaceAround.End, Priority = Priority.VeryLow)] public void DefineVariable(string args) { string[] parts = args.Split('='); @@ -35,7 +35,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } } - [Statement("!<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow)] + [Statement("global", SearchMode.StartOfLine, SpaceAround.End, Priority = Priority.VeryLow)] public void DefineGlobalVariable(string args) { string[] parts = args.Split('='); @@ -62,7 +62,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } } - [Statement("del", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.Red, Priority = Priority.VeryLow)] + [Statement("delete", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.Red, Priority = Priority.VeryLow)] public void DeleteVariable(string args) { string key = args.Trim(); @@ -81,10 +81,10 @@ internal partial class VariableStatements : StatementRuntimeInformation } } - [Statement(">", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest)] + [Statement("${", SearchMode.Contains, SpaceAround.None, Priority = Priority.Highest, Separator = "}")] public void ReadVariable(string _) { - if (!RuntimeInfo.CurrentLine.Contains('>')) + if (!RuntimeInfo.CurrentLine.Contains("${")) { return; } @@ -98,14 +98,14 @@ internal partial class VariableStatements : StatementRuntimeInformation for (int i = 0; i < matches.Count; i++) { - string varName = matches[i].Value.Replace(">", string.Empty); + string varName = matches[i].Groups[1].Value; if (RuntimeInfo.Variables.TryGetValue(varName, out string value)) { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace(matches[i].Value, value); } else if (RuntimeInfo.GlobalVariables.TryGetValue(varName, out value)) { - RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{varName}", value); + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace(matches[i].Value, value); } else if (!RuntimeInfo.IsSearching) { @@ -115,6 +115,6 @@ internal partial class VariableStatements : StatementRuntimeInformation } } - [GeneratedRegex(">[a-zA-Z0-9]+")] + [GeneratedRegex("\\$\\{([a-zA-Z0-9]+)\\}")] private static partial Regex VariableStatementRegex(); -} \ No newline at end of file +} From 299558df70e6f1c851f9b2e094dad1511a3a3888 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:28:04 +0100 Subject: [PATCH 23/73] Add support for block-style if/else and while loop statements with tests --- SYNTAX_V2.md | 32 ++- .../CodeFowStatementsTests.cs | 102 ++++++++ .../Statements/CodeFlowStatements.cs | 227 ++++++++++++++++++ 3 files changed, 349 insertions(+), 12 deletions(-) diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index ff18b42..ab96d1d 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -19,18 +19,6 @@ This document describes the current, word-based YesNt syntax. ## Quick Example -v1: - -```ynt -name -ret -cal greet -``` - -v2: - ```ynt let name = world func greet: @@ -39,6 +27,26 @@ return call greet ``` +## Block Conditionals + +```ynt +if 10 > 5: +print_line yes +else: +print_line no +end_if +``` + +## While Loops + +```ynt +let i = 3 +while ${i} > 0: +print_line ${i} +let i = ${i} - 1 calc +end_while +``` + ## Full v1 -> v2 Mapping | Area | v1 Syntax | v2 Syntax | Notes | diff --git a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index 0bddd5b..6bd75b9 100644 --- a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -34,4 +34,106 @@ public class CodeFlowTests ]; YesNtAssert.IsLastLineEqual(lines, "1"); } + + [TestMethod] + public void IfBlockTrueTest() + { + List lines = + [ + "let result = low", + "if 6 > 5:", + "let result = high", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "high"); + } + + [TestMethod] + public void IfElseFalseBranchTest() + { + List lines = + [ + "let result = low", + "if 6 < 5:", + "let result = high", + "else:", + "let result = medium", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "medium"); + } + + [TestMethod] + public void NestedIfElseTest() + { + List lines = + [ + "let result = 0", + "if 1 == 1:", + "if 2 == 3:", + "let result = 1", + "else:", + "let result = 2", + "end_if", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "2"); + } + + [TestMethod] + public void WhileLoopTest() + { + List lines = + [ + "let i = 3", + "while ${i} > 0:", + "let i = ${i} - 1 calc", + "end_while", + "${i}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void WhileSkipBodyWhenFalseTest() + { + List lines = + [ + "let i = 0", + "while ${i} > 0:", + "let i = 99", + "end_while", + "${i}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void NestedWhileLoopTest() + { + List lines = + [ + "let outer = 2", + "let count = 0", + "while ${outer} > 0:", + "let inner = 2", + "while ${inner} > 0:", + "let count = ${count} + 1 calc", + "let inner = ${inner} - 1 calc", + "end_while", + "let outer = ${outer} - 1 calc", + "end_while", + "${count}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "4"); + } } diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 91fc087..38aee20 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -140,6 +140,105 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } + [Statement("if", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")] + public void IfBlock(string args) + { + args = args.Trim(); + if (!args.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string condition = args[..^1].Trim(); + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", true); + return; + } + + if (result == true) + { + return; + } + + (int targetLine, _) = FindElseOrEndIf(RuntimeInfo.LineNumber); + if (targetLine < 0) + { + RuntimeInfo.Exit("No matching end_if found", true); + return; + } + + RuntimeInfo.LineNumber = targetLine; + } + + [Statement("else:", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void Else(string _) + { + int targetLine = FindEndIf(RuntimeInfo.LineNumber); + if (targetLine < 0) + { + RuntimeInfo.Exit("No matching end_if found", true); + return; + } + + RuntimeInfo.LineNumber = targetLine; + } + + [Statement("end_if", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void EndIf(string _) + { + } + + [Statement("while", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, Priority = Priority.VeryLow, Separator = ":")] + public void While(string args) + { + args = args.Trim(); + if (!args.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + + string condition = args[..^1].Trim(); + bool? result = Evaluator.EvaluateCondition(condition); + + if (result is null) + { + RuntimeInfo.Exit("Invalid operation", true); + return; + } + + if (result == true) + { + return; + } + + int endWhileLine = FindEndWhile(RuntimeInfo.LineNumber); + if (endWhileLine < 0) + { + RuntimeInfo.Exit("No matching end_while found", true); + return; + } + + RuntimeInfo.LineNumber = endWhileLine; + } + + [Statement("end_while", SearchMode.Exact, SpaceAround.None, ConsoleColor.Green)] + public void EndWhile(string _) + { + int whileLine = FindWhile(RuntimeInfo.LineNumber); + if (whileLine < 0) + { + RuntimeInfo.Exit("No matching while found", true); + return; + } + + RuntimeInfo.LineNumber = whileLine - 1; + } + [Statement("exit", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] public void End(string _) { @@ -198,4 +297,132 @@ internal class CodeFlowStatements : StatementRuntimeInformation { return value.Trim().TrimEnd(':').Trim(); } + + private (int TargetLine, bool IsElse) FindElseOrEndIf(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsIfStart(line)) + { + depth++; + continue; + } + + if (line == "end_if") + { + if (depth == 0) + { + return (i, false); + } + + depth--; + continue; + } + + if (line == "else:" && depth == 0) + { + return (i, true); + } + } + + return (-1, false); + } + + private int FindEndIf(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsIfStart(line)) + { + depth++; + continue; + } + + if (line == "end_if") + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private static bool IsIfStart(string line) + { + return line.StartsWith("if ", StringComparison.Ordinal) && line.EndsWith(':'); + } + + private int FindEndWhile(int currentLine) + { + int depth = 0; + + for (int i = currentLine + 1; i < RuntimeInfo.Lines.Count; i++) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (IsWhileStart(line)) + { + depth++; + continue; + } + + if (line == "end_while") + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private int FindWhile(int currentLine) + { + int depth = 0; + + for (int i = currentLine - 1; i >= 0; i--) + { + string line = RuntimeInfo.Lines[i].Content.Trim().Replace("\r", string.Empty); + + if (line == "end_while") + { + depth++; + continue; + } + + if (IsWhileStart(line)) + { + if (depth == 0) + { + return i; + } + + depth--; + } + } + + return -1; + } + + private static bool IsWhileStart(string line) + { + return line.StartsWith("while ", StringComparison.Ordinal) && line.EndsWith(':'); + } } From 317bca84d310770798cadcfc198743015ccb4014 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:33:47 +0100 Subject: [PATCH 24/73] Enforce label and function statements to end with ':' and update syntax checks --- .../Statements/CodeFlowStatements.cs | 21 +++++++++++++++---- .../Statements/FunctionStatements.cs | 17 +++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 38aee20..1000b84 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -63,10 +63,23 @@ internal class CodeFlowStatements : StatementRuntimeInformation } } - [Statement("label", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true)] + [Statement("label", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Green, ExecuteInSearchMode = true, Separator = ":")] public void FindLabel(string args) { - string key = NormalizeBlockName(args); + string labelDeclaration = args.Trim(); + if (!labelDeclaration.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + return; + } + + string key = NormalizeBlockName(labelDeclaration); + if (string.IsNullOrWhiteSpace(key)) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + if (RuntimeInfo.Labels.ContainsKey(key)) { RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber; @@ -146,7 +159,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation args = args.Trim(); if (!args.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); return; } @@ -198,7 +211,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation args = args.Trim(); if (!args.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); return; } diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs index 7e50ec4..d975a35 100644 --- a/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -10,7 +10,7 @@ namespace YesNt.Interpreter.Statements; internal class FunctionStatements : StatementRuntimeInformation { - [Statement("func", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true)] + [Statement("func", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkYellow, ExecuteInSearchMode = true, Separator = ":")] public void FindFunction(string args) { if (RuntimeInfo.InternalIsInFunction) @@ -19,7 +19,20 @@ internal class FunctionStatements : StatementRuntimeInformation return; } - string key = NormalizeBlockName(args); + string functionDeclaration = args.Trim(); + if (!functionDeclaration.EndsWith(':')) + { + RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + return; + } + + string key = NormalizeBlockName(functionDeclaration); + if (string.IsNullOrWhiteSpace(key)) + { + RuntimeInfo.Exit("Invalid syntax", true); + return; + } + if (RuntimeInfo.Functions.ContainsKey(key)) { RuntimeInfo.Functions[key] = RuntimeInfo.LineNumber; From 36d8735fbbd25cd70c3bf6a3d97cc2e1ab33ad14 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:48:04 +0100 Subject: [PATCH 25/73] Remove manual variable declaration highlighting from SyntaxHighlighter and add color to VariableStatements --- YesNt.CodeEditor/SyntaxHighlighter.cs | 18 ------------------ .../Statements/VariableStatements.cs | 4 ++-- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index a368796..57b9bd2 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -111,18 +111,6 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection Date: Wed, 4 Mar 2026 14:49:54 +0100 Subject: [PATCH 26/73] Add new tests for console, function, system, variable, and code flow statements --- .../CodeFlowStatementsTests.cs | 80 ++++++++++ .../CodeFowStatementsTests.cs | 132 ++++++++++++++++ .../ConsoleStatementsTests.cs | 110 +++++++++++++ .../FunctionStatementsTests.cs | 147 ++++++++++++++++++ .../PredefinedVariableStatementsTests.cs | 107 +++++++++++++ .../ProcessingStatementsTests.cs | 100 +++++++++++- .../SystemStatementsTests.cs | 48 ++++++ .../VariableStatementsTests.cs | 104 +++++++++++++ YesNt.Interpreter.Tests/YesNtAssert.cs | 127 ++++++++------- 9 files changed, 899 insertions(+), 56 deletions(-) create mode 100644 YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs create mode 100644 YesNt.Interpreter.Tests/ConsoleStatementsTests.cs create mode 100644 YesNt.Interpreter.Tests/FunctionStatementsTests.cs create mode 100644 YesNt.Interpreter.Tests/PredefinedVariableStatementsTests.cs create mode 100644 YesNt.Interpreter.Tests/SystemStatementsTests.cs create mode 100644 YesNt.Interpreter.Tests/VariableStatementsTests.cs diff --git a/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs new file mode 100644 index 0000000..6674ac3 --- /dev/null +++ b/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs @@ -0,0 +1,80 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class CodeFlowStatementsTests +{ + [TestMethod] + public void ExitStopsExecutionTest() + { + List lines = + [ + "let result = before", + "exit", + "let result = after", + "${result}" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Planned termination by code"); + } + + [TestMethod] + public void AbortAllStopsExecutionTest() + { + List lines = + [ + "abort_all", + "let result = after", + "${result}" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Canceling all tasks"); + } + + [TestMethod] + public void ThrowTerminatesWithErrorFlagTest() + { + List lines = + [ + "throw bad" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "with the message: bad"); + } + + [TestMethod] + public void ErrorTerminatesWithMessageTest() + { + List lines = + [ + "error soft" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "with the message: soft"); + } + + [TestMethod] + public void MissingLabelFailsTest() + { + List lines = + [ + "goto nowhere" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Label \"nowhere\" not found"); + } + + [TestMethod] + public void MissingFunctionFailsTest() + { + List lines = + [ + "call nowhere" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Function \"nowhere\" not found"); + } +} diff --git a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index 6bd75b9..c21063b 100644 --- a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -136,4 +136,136 @@ public class CodeFlowTests YesNtAssert.IsLastLineEqual(lines, "4"); } + + [TestMethod] + public void IfElseTrueSkipsElseBranchTest() + { + List lines = + [ + "let result = 0", + "if 2 > 1:", + "let result = 1", + "else:", + "let result = 2", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "1"); + } + + [TestMethod] + public void IfWithoutElseFalseSkipsBodyTest() + { + List lines = + [ + "let result = 5", + "if 1 == 2:", + "let result = 1", + "end_if", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "5"); + } + + [TestMethod] + public void IfGotoTrueTest() + { + List lines = + [ + "let result = 0", + "if 1 == 1 goto done", + "let result = 2", + "label done:", + "let result = ${result} + 1 calc", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "1"); + } + + [TestMethod] + public void IfCallTrueTest() + { + List lines = + [ + "func set_result:", + "global result = ok", + "return", + "if 1 == 1 call set_result", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "ok"); + } + + [TestMethod] + public void LabelWithoutColonFailsTest() + { + List lines = + [ + "label loop" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void FunctionWithoutColonFailsTest() + { + List lines = + [ + "func missing" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void MissingEndIfFailsTest() + { + List lines = + [ + "if 1 == 2:", + "let result = 1" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found"); + } + + [TestMethod] + public void ElseWithoutIfFailsTest() + { + List lines = + [ + "else:" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found"); + } + + [TestMethod] + public void MissingEndWhileFailsTest() + { + List lines = + [ + "let i = 0", + "while ${i} > 1:", + "let i = ${i} + 1 calc" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "No matching end_while found"); + } + + [TestMethod] + public void EndWhileWithoutWhileFailsTest() + { + List lines = + [ + "end_while" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "No matching while found"); + } } diff --git a/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs new file mode 100644 index 0000000..2e789ba --- /dev/null +++ b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs @@ -0,0 +1,110 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using YesNt.Interpreter.Runtime; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class ConsoleStatementsTests +{ + private static readonly object ConsoleLock = new object(); + + [TestMethod] + public void PrintLineWritesOutputTest() + { + List lines = + [ + "print_line hello" + ]; + + YesNtAssert.ContainsDebugOutput(lines, "hello"); + } + + [TestMethod] + public void PrintWritesOutputTest() + { + List lines = + [ + "print hello" + ]; + + YesNtAssert.ContainsDebugOutput(lines, "hello"); + } + + [TestMethod] + public void ClearThrowsInNonInteractiveConsoleTest() + { + List lines = + [ + "clear" + ]; + + _ = Assert.ThrowsException(() => YesNtAssert.GetLastLine(lines)); + } + + [TestMethod] + public void ReadLineReplacesTokenTest() + { + lock (ConsoleLock) + { + TextReader originalIn = Console.In; + + try + { + Console.SetIn(new StringReader("typed value" + Environment.NewLine)); + + List lines = + [ + "let value = %read_line", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "typed value"); + } + finally + { + Console.SetIn(originalIn); + } + } + } + + [TestMethod] + public void ReadKeyCanBeInterruptedByStopTest() + { + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + + AutoResetEvent onDone = new AutoResetEvent(false); + StringBuilder output = new StringBuilder(); + + interpreter.OnDebugOutput += (s) => _ = output.Append(s); + interpreter.OnLineExecuted += (e) => + { + if (e is null) + { + _ = onDone.Set(); + } + }; + + List lines = + [ + "let value = %read_key" + ]; + + _ = Task.Run(() => interpreter.Execute(lines, true)); + + Thread.Sleep(100); + interpreter.Stop(); + + _ = onDone.WaitOne(TimeSpan.FromSeconds(3)); + + StringAssert.Contains(output.ToString(), "Terminated by external process"); + } +} diff --git a/YesNt.Interpreter.Tests/FunctionStatementsTests.cs b/YesNt.Interpreter.Tests/FunctionStatementsTests.cs new file mode 100644 index 0000000..7203cf6 --- /dev/null +++ b/YesNt.Interpreter.Tests/FunctionStatementsTests.cs @@ -0,0 +1,147 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class FunctionStatementsTests +{ + [TestMethod] + public void FunctionCallWithInParameterTest() + { + List lines = + [ + "goto main", + "func echo:", + "global result = %in", + "return", + "label main:", + "call echo with hello", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello"); + } + + [TestMethod] + public void HasInAndHasOutTokensTest() + { + List lines = + [ + "goto main", + "func probe:", + "global hasInBefore = %has_in", + "let consume = %in", + "global hasInAfter = %has_in", + "push_out ${hasInBefore}", + "push_out ${hasInAfter}", + "return", + "label main:", + "call probe with x", + "let hasOut = %has_out", + "${hasOut}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "True"); + } + + [TestMethod] + public void OutParameterReadTest() + { + List lines = + [ + "goto main", + "func make:", + "push_out out_value", + "return", + "label main:", + "call make with anything", + "let value = %out", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "out_value"); + } + + [TestMethod] + public void OutParameterWithoutValueFailsTest() + { + List lines = + [ + "let x = %out" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "No out argument in stack"); + } + + [TestMethod] + public void InParameterOutsideFunctionFailsTest() + { + List lines = + [ + "let x = %in" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); + } + + [TestMethod] + public void ReturnOutsideFunctionFailsTest() + { + List lines = + [ + "return" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); + } + + [TestMethod] + public void PushOutOutsideFunctionFailsTest() + { + List lines = + [ + "push_out value" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); + } + + [TestMethod] + public void FunctionWithoutColonFailsTest() + { + List lines = + [ + "func missing" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void NestedFunctionDefinitionFailsTest() + { + List lines = + [ + "func outer:", + "func inner:", + "return" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Nested functions are not allowed"); + } + + [TestMethod] + public void ClearCallStackRunsTest() + { + List lines = + [ + "clear_call_stack", + "let result = ok", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "ok"); + } +} diff --git a/YesNt.Interpreter.Tests/PredefinedVariableStatementsTests.cs b/YesNt.Interpreter.Tests/PredefinedVariableStatementsTests.cs new file mode 100644 index 0000000..cf4b6c9 --- /dev/null +++ b/YesNt.Interpreter.Tests/PredefinedVariableStatementsTests.cs @@ -0,0 +1,107 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class PredefinedVariableStatementsTests +{ + [TestMethod] + public void TimeTokenProducesUnixTimestampTest() + { + List lines = + [ + "%time" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsNotNull(value); + Assert.IsTrue(long.TryParse(value, out long parsed)); + + long now = DateTimeOffset.Now.ToUnixTimeSeconds(); + Assert.IsTrue(Math.Abs(now - parsed) < 10); + } + + [TestMethod] + public void OsTokenProducesValueTest() + { + List lines = + [ + "%os" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsFalse(string.IsNullOrWhiteSpace(value)); + } + + [TestMethod] + public void CpuTokenProducesValueTest() + { + List lines = + [ + "%cpu" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsFalse(string.IsNullOrWhiteSpace(value)); + } + + [TestMethod] + public void Is64TokenProducesBooleanTest() + { + List lines = + [ + "%is64" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.AreEqual(Environment.Is64BitOperatingSystem.ToString(), value); + } + + [TestMethod] + public void PiTokenProducesPiTest() + { + List lines = + [ + "%pi" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsNotNull(value); + Assert.IsTrue(double.TryParse(value, out double parsed)); + Assert.IsTrue(Math.Abs(parsed - Math.PI) < 0.001d); + } + + [TestMethod] + public void RandTokenProducesIntegerTest() + { + List lines = + [ + "%rand" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsNotNull(value); + Assert.IsTrue(int.TryParse(value, out int parsed)); + Assert.IsTrue(parsed >= 32767); + } + + [TestMethod] + public void MultipleRandTokensAreReplacedTest() + { + List lines = + [ + "%rand %rand" + ]; + + string? value = YesNtAssert.GetLastLine(lines); + Assert.IsNotNull(value); + + string[] parts = value.Split(' ', StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(2, parts.Length); + Assert.IsTrue(int.TryParse(parts[0], out _)); + Assert.IsTrue(int.TryParse(parts[1], out _)); + } +} diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs index fd68646..bbfd2ef 100644 --- a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -1,4 +1,8 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; +using System.Collections.Generic; +using System.IO; namespace YesNt.Interpreter.Tests; @@ -40,4 +44,98 @@ public class ProcessingStatementsTests { YesNtAssert.IsLineEqual("2 ^ 3 calc", "8"); } + + [TestMethod] + public void EvalDecodesSafeStringTest() + { + YesNtAssert.IsLineEqual("hello~nliworld eval", "hello\nworld"); + } + + [TestMethod] + public void SleepInvalidValueFailsTest() + { + List lines = + [ + "sleep nope" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "\"nope\" is not a valid time-out value"); + } + + [TestMethod] + public void SleepRunsAndContinuesTest() + { + List lines = + [ + "sleep 5", + "let result = ok", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "ok"); + } + + [TestMethod] + public void LengthPushesOutParameterTest() + { + List lines = + [ + "length hello", + "let value = %out", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "5"); + } + + [TestMethod] + public void ImportLoadsScriptTest() + { + string tempFile = Path.Combine(Path.GetTempPath(), $"yesnt-import-{Guid.NewGuid():N}.ynt"); + + try + { + File.WriteAllText(tempFile, "let imported = yes"); + + List lines = + [ + $"import {tempFile}", + "${imported}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "yes"); + } + finally + { + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + } + + [TestMethod] + public void ImportMissingFileFailsTest() + { + List lines = + [ + $"import {Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))}.ynt" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Could not find file"); + } + + [TestMethod] + public void TaskCanUpdateGlobalVariableTest() + { + List lines = + [ + "global result = 0", + "global result = 1 task", + "sleep 50", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "1", timeout: 3000); + } } diff --git a/YesNt.Interpreter.Tests/SystemStatementsTests.cs b/YesNt.Interpreter.Tests/SystemStatementsTests.cs new file mode 100644 index 0000000..441eefc --- /dev/null +++ b/YesNt.Interpreter.Tests/SystemStatementsTests.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class SystemStatementsTests +{ + [TestMethod] + public void ExecWithArgsRunsProcessTest() + { + List lines = + [ + "exec cmd with /c,echo yesnt", + "let exitCode = %out", + "${exitCode}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void ExecWithInStackArgsRunsProcessTest() + { + List lines = + [ + "push_in /c", + "push_in echo yesnt", + "exec cmd", + "let exitCode = %out", + "${exitCode}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void ExecInvalidProgramFailsTest() + { + List lines = + [ + "exec does_not_exist_abc_xyz" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Failed to start \"does_not_exist_abc_xyz\""); + } +} diff --git a/YesNt.Interpreter.Tests/VariableStatementsTests.cs b/YesNt.Interpreter.Tests/VariableStatementsTests.cs new file mode 100644 index 0000000..260e796 --- /dev/null +++ b/YesNt.Interpreter.Tests/VariableStatementsTests.cs @@ -0,0 +1,104 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class VariableStatementsTests +{ + [TestMethod] + public void LetAndReadVariableTest() + { + List lines = + [ + "let value = hi", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hi"); + } + + [TestMethod] + public void GlobalVariableReadTest() + { + List lines = + [ + "global value = hi", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hi"); + } + + [TestMethod] + public void LocalVariableOverridesGlobalTest() + { + List lines = + [ + "global value = global", + "let value = local", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "local"); + } + + [TestMethod] + public void DeleteLocalVariableTest() + { + List lines = + [ + "let value = a", + "delete value", + "global value = b", + "${value}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "b"); + } + + [TestMethod] + public void DeleteMissingVariableFailsTest() + { + List lines = + [ + "delete missing" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found"); + } + + [TestMethod] + public void LetInvalidSyntaxFailsTest() + { + List lines = + [ + "let a" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void LetInvalidNameFailsTest() + { + List lines = + [ + "let a b = 1" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid Syntax"); + } + + [TestMethod] + public void MissingVariableReferenceFailsTest() + { + List lines = + [ + "${missing}" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found"); + } +} diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index 66638d9..25c86f3 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -1,7 +1,9 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; using System.Threading; using YesNt.Interpreter.Runtime; @@ -10,76 +12,91 @@ namespace YesNt.Interpreter.Tests; internal static class YesNtAssert { - private static readonly YesNtInterpreter yesNtInterpreter = new YesNtInterpreter(); - - static YesNtAssert() - { - yesNtInterpreter.Initialize(); - } - public static void IsLastLineEqual(List lines, string expected, int timeout = 1000) { + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout); + Assert.AreEqual(expected, debugEventArgs?.CurrentLine); + } + + public static void IsLineEqual(string line, string expected, int timeout = 1000) + { + List lines = + [ + line + ]; + + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout); + Assert.AreEqual(expected, debugEventArgs?.CurrentLine); + } + + public static void IsLineNotEqual(string line, string expected, int timeout = 1000) + { + List lines = + [ + line + ]; + + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout); + Assert.AreNotEqual(expected, debugEventArgs?.CurrentLine); + } + + public static void ContainsTerminationMessage(List lines, string expectedMessageFragment, int timeout = 1000) + { + (_, string debugOutput) = ExecuteAndCapture(lines, timeout); + + StringAssert.Contains(debugOutput, expectedMessageFragment); + } + + public static void ContainsDebugOutput(List lines, string expectedFragment, int timeout = 1000) + { + (_, string debugOutput) = ExecuteAndCapture(lines, timeout); + + StringAssert.Contains(debugOutput, expectedFragment); + } + + public static string? GetLastLine(List lines, int timeout = 1000) + { + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout); + return debugEventArgs?.CurrentLine; + } + + public static void LastLineMatches(List lines, string pattern, int timeout = 1000) + { + string? value = GetLastLine(lines, timeout); + Assert.IsNotNull(value); + StringAssert.Matches(value, new Regex(pattern)); + } + + private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout) + { + YesNtInterpreter yesNtInterpreter = new YesNtInterpreter(); + yesNtInterpreter.Initialize(); + AutoResetEvent onDone = new AutoResetEvent(false); + DebugEventArgs? debugEventArgs = null; + StringBuilder outputBuilder = new StringBuilder(); - DebugEventArgs debugEventArgs = new DebugEventArgs(); yesNtInterpreter.OnLineExecuted += (er) => { - debugEventArgs = er ?? debugEventArgs; - - if (er is null) + if (er is not null) + { + debugEventArgs = er; + } + else { _ = onDone.Set(); } }; - yesNtInterpreter.Execute(lines, true); - - _ = onDone.WaitOne(TimeSpan.FromSeconds(timeout)); - - Assert.AreEqual(expected, debugEventArgs.CurrentLine); - } - - public static void IsLineEqual(string line, string expected, int timeout = 1000) - { - AutoResetEvent onDone = new AutoResetEvent(false); - List lines = - [ - line - ]; - - DebugEventArgs debugEventArgs = new DebugEventArgs(); - yesNtInterpreter.OnLineExecuted += (er) => + yesNtInterpreter.OnDebugOutput += (s) => { - debugEventArgs = er ?? debugEventArgs; - _ = onDone.Set(); + _ = outputBuilder.Append(s); }; yesNtInterpreter.Execute(lines, true); _ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout)); - Assert.AreEqual(expected, debugEventArgs.CurrentLine); + return (debugEventArgs, outputBuilder.ToString()); } - - public static void IsLineNotEqual(string line, string expected, int timeout = 1000) - { - AutoResetEvent onDone = new AutoResetEvent(false); - List lines = - [ - line - ]; - - DebugEventArgs debugEventArgs = new DebugEventArgs(); - yesNtInterpreter.OnLineExecuted += (er) => - { - debugEventArgs = er ?? debugEventArgs; - _ = onDone.Set(); - }; - - yesNtInterpreter.Execute(lines, true); - - _ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout)); - - Assert.AreNotEqual(expected, debugEventArgs.CurrentLine); - } -} \ No newline at end of file +} From d4cc1d1c28aaa0fad76110ec62fb940bbc702e1a Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:55:09 +0100 Subject: [PATCH 27/73] Use var instead of let for variables --- README.md | 3 +- SYNTAX_V2.md | 9 +-- .../CodeFlowStatementsTests.cs | 7 ++- .../CodeFowStatementsTests.cs | 63 ++++++++++--------- .../ConsoleStatementsTests.cs | 5 +- .../FunctionStatementsTests.cs | 13 ++-- .../ProcessingStatementsTests.cs | 7 ++- .../SystemStatementsTests.cs | 5 +- .../VariableStatementsTests.cs | 11 ++-- .../Statements/VariableStatements.cs | 5 +- 10 files changed, 69 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 5a83f33..bf67f69 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Current language syntax is documented in [SYNTAX_V2.md](SYNTAX_V2.md). Example: ```ynt -let name = world +var name = world print_line Hello ${name} ``` @@ -18,3 +18,4 @@ print_line Hello ${name} ```bash dotnet run --project YesNt.Interpreter -- path/to/script.ynt ``` + diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index ab96d1d..dd62b7a 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -20,7 +20,7 @@ This document describes the current, word-based YesNt syntax. ## Quick Example ```ynt -let name = world +var name = world func greet: print_line Hello ${name} return @@ -40,10 +40,10 @@ end_if ## While Loops ```ynt -let i = 3 +var i = 3 while ${i} > 0: print_line ${i} -let i = ${i} - 1 calc +var i = ${i} - 1 calc end_while ``` @@ -51,7 +51,7 @@ end_while | Area | v1 Syntax | v2 Syntax | Notes | |---|---|---|---| -| Variables | `x` | `${x}` | Variable read/interpolation token. | @@ -99,3 +99,4 @@ end_while - `%out` is intentionally kept as `%out`. - Postfix operations are `calc`, `eval`, and `task`. + diff --git a/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs index 6674ac3..0c00e1f 100644 --- a/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFlowStatementsTests.cs @@ -12,9 +12,9 @@ public class CodeFlowStatementsTests { List lines = [ - "let result = before", + "var result = before", "exit", - "let result = after", + "var result = after", "${result}" ]; @@ -27,7 +27,7 @@ public class CodeFlowStatementsTests List lines = [ "abort_all", - "let result = after", + "var result = after", "${result}" ]; @@ -78,3 +78,4 @@ public class CodeFlowStatementsTests YesNtAssert.ContainsTerminationMessage(lines, "Function \"nowhere\" not found"); } } + diff --git a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs index c21063b..a3c0527 100644 --- a/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs +++ b/YesNt.Interpreter.Tests/CodeFowStatementsTests.cs @@ -26,9 +26,9 @@ public class CodeFlowTests { List lines = [ - "let result = 1", + "var result = 1", "goto yes", - "let result = 0", + "var result = 0", "label yes:", "${result}" ]; @@ -40,9 +40,9 @@ public class CodeFlowTests { List lines = [ - "let result = low", + "var result = low", "if 6 > 5:", - "let result = high", + "var result = high", "end_if", "${result}" ]; @@ -55,11 +55,11 @@ public class CodeFlowTests { List lines = [ - "let result = low", + "var result = low", "if 6 < 5:", - "let result = high", + "var result = high", "else:", - "let result = medium", + "var result = medium", "end_if", "${result}" ]; @@ -72,12 +72,12 @@ public class CodeFlowTests { List lines = [ - "let result = 0", + "var result = 0", "if 1 == 1:", "if 2 == 3:", - "let result = 1", + "var result = 1", "else:", - "let result = 2", + "var result = 2", "end_if", "end_if", "${result}" @@ -91,9 +91,9 @@ public class CodeFlowTests { List lines = [ - "let i = 3", + "var i = 3", "while ${i} > 0:", - "let i = ${i} - 1 calc", + "var i = ${i} - 1 calc", "end_while", "${i}" ]; @@ -106,9 +106,9 @@ public class CodeFlowTests { List lines = [ - "let i = 0", + "var i = 0", "while ${i} > 0:", - "let i = 99", + "var i = 99", "end_while", "${i}" ]; @@ -121,15 +121,15 @@ public class CodeFlowTests { List lines = [ - "let outer = 2", - "let count = 0", + "var outer = 2", + "var count = 0", "while ${outer} > 0:", - "let inner = 2", + "var inner = 2", "while ${inner} > 0:", - "let count = ${count} + 1 calc", - "let inner = ${inner} - 1 calc", + "var count = ${count} + 1 calc", + "var inner = ${inner} - 1 calc", "end_while", - "let outer = ${outer} - 1 calc", + "var outer = ${outer} - 1 calc", "end_while", "${count}" ]; @@ -142,11 +142,11 @@ public class CodeFlowTests { List lines = [ - "let result = 0", + "var result = 0", "if 2 > 1:", - "let result = 1", + "var result = 1", "else:", - "let result = 2", + "var result = 2", "end_if", "${result}" ]; @@ -159,9 +159,9 @@ public class CodeFlowTests { List lines = [ - "let result = 5", + "var result = 5", "if 1 == 2:", - "let result = 1", + "var result = 1", "end_if", "${result}" ]; @@ -174,11 +174,11 @@ public class CodeFlowTests { List lines = [ - "let result = 0", + "var result = 0", "if 1 == 1 goto done", - "let result = 2", + "var result = 2", "label done:", - "let result = ${result} + 1 calc", + "var result = ${result} + 1 calc", "${result}" ]; @@ -228,7 +228,7 @@ public class CodeFlowTests List lines = [ "if 1 == 2:", - "let result = 1" + "var result = 1" ]; YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found"); @@ -250,9 +250,9 @@ public class CodeFlowTests { List lines = [ - "let i = 0", + "var i = 0", "while ${i} > 1:", - "let i = ${i} + 1 calc" + "var i = ${i} + 1 calc" ]; YesNtAssert.ContainsTerminationMessage(lines, "No matching end_while found"); @@ -269,3 +269,4 @@ public class CodeFlowTests YesNtAssert.ContainsTerminationMessage(lines, "No matching while found"); } } + diff --git a/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs index 2e789ba..fc076b7 100644 --- a/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs @@ -62,7 +62,7 @@ public class ConsoleStatementsTests List lines = [ - "let value = %read_line", + "var value = %read_line", "${value}" ]; @@ -95,7 +95,7 @@ public class ConsoleStatementsTests List lines = [ - "let value = %read_key" + "var value = %read_key" ]; _ = Task.Run(() => interpreter.Execute(lines, true)); @@ -108,3 +108,4 @@ public class ConsoleStatementsTests StringAssert.Contains(output.ToString(), "Terminated by external process"); } } + diff --git a/YesNt.Interpreter.Tests/FunctionStatementsTests.cs b/YesNt.Interpreter.Tests/FunctionStatementsTests.cs index 7203cf6..aa148b9 100644 --- a/YesNt.Interpreter.Tests/FunctionStatementsTests.cs +++ b/YesNt.Interpreter.Tests/FunctionStatementsTests.cs @@ -32,14 +32,14 @@ public class FunctionStatementsTests "goto main", "func probe:", "global hasInBefore = %has_in", - "let consume = %in", + "var consume = %in", "global hasInAfter = %has_in", "push_out ${hasInBefore}", "push_out ${hasInAfter}", "return", "label main:", "call probe with x", - "let hasOut = %has_out", + "var hasOut = %has_out", "${hasOut}" ]; @@ -57,7 +57,7 @@ public class FunctionStatementsTests "return", "label main:", "call make with anything", - "let value = %out", + "var value = %out", "${value}" ]; @@ -69,7 +69,7 @@ public class FunctionStatementsTests { List lines = [ - "let x = %out" + "var x = %out" ]; YesNtAssert.ContainsTerminationMessage(lines, "No out argument in stack"); @@ -80,7 +80,7 @@ public class FunctionStatementsTests { List lines = [ - "let x = %in" + "var x = %in" ]; YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function"); @@ -138,10 +138,11 @@ public class FunctionStatementsTests List lines = [ "clear_call_stack", - "let result = ok", + "var result = ok", "${result}" ]; YesNtAssert.IsLastLineEqual(lines, "ok"); } } + diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs index bbfd2ef..98a093e 100644 --- a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -68,7 +68,7 @@ public class ProcessingStatementsTests List lines = [ "sleep 5", - "let result = ok", + "var result = ok", "${result}" ]; @@ -81,7 +81,7 @@ public class ProcessingStatementsTests List lines = [ "length hello", - "let value = %out", + "var value = %out", "${value}" ]; @@ -95,7 +95,7 @@ public class ProcessingStatementsTests try { - File.WriteAllText(tempFile, "let imported = yes"); + File.WriteAllText(tempFile, "var imported = yes"); List lines = [ @@ -139,3 +139,4 @@ public class ProcessingStatementsTests YesNtAssert.IsLastLineEqual(lines, "1", timeout: 3000); } } + diff --git a/YesNt.Interpreter.Tests/SystemStatementsTests.cs b/YesNt.Interpreter.Tests/SystemStatementsTests.cs index 441eefc..fa7ec65 100644 --- a/YesNt.Interpreter.Tests/SystemStatementsTests.cs +++ b/YesNt.Interpreter.Tests/SystemStatementsTests.cs @@ -13,7 +13,7 @@ public class SystemStatementsTests List lines = [ "exec cmd with /c,echo yesnt", - "let exitCode = %out", + "var exitCode = %out", "${exitCode}" ]; @@ -28,7 +28,7 @@ public class SystemStatementsTests "push_in /c", "push_in echo yesnt", "exec cmd", - "let exitCode = %out", + "var exitCode = %out", "${exitCode}" ]; @@ -46,3 +46,4 @@ public class SystemStatementsTests YesNtAssert.ContainsTerminationMessage(lines, "Failed to start \"does_not_exist_abc_xyz\""); } } + diff --git a/YesNt.Interpreter.Tests/VariableStatementsTests.cs b/YesNt.Interpreter.Tests/VariableStatementsTests.cs index 260e796..ed1f3ec 100644 --- a/YesNt.Interpreter.Tests/VariableStatementsTests.cs +++ b/YesNt.Interpreter.Tests/VariableStatementsTests.cs @@ -12,7 +12,7 @@ public class VariableStatementsTests { List lines = [ - "let value = hi", + "var value = hi", "${value}" ]; @@ -37,7 +37,7 @@ public class VariableStatementsTests List lines = [ "global value = global", - "let value = local", + "var value = local", "${value}" ]; @@ -49,7 +49,7 @@ public class VariableStatementsTests { List lines = [ - "let value = a", + "var value = a", "delete value", "global value = b", "${value}" @@ -74,7 +74,7 @@ public class VariableStatementsTests { List lines = [ - "let a" + "var a" ]; YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); @@ -85,7 +85,7 @@ public class VariableStatementsTests { List lines = [ - "let a b = 1" + "var a b = 1" ]; YesNtAssert.ContainsTerminationMessage(lines, "Invalid Syntax"); @@ -102,3 +102,4 @@ public class VariableStatementsTests YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found"); } } + diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index 3decc2c..b4cf6d8 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; @@ -8,7 +8,7 @@ namespace YesNt.Interpreter.Statements; internal partial class VariableStatements : StatementRuntimeInformation { - [Statement("let", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")] + [Statement("var", SearchMode.StartOfLine, SpaceAround.End, System.ConsoleColor.DarkBlue, Priority = Priority.VeryLow, Separator = "=")] public void DefineVariable(string args) { string[] parts = args.Split('='); @@ -118,3 +118,4 @@ internal partial class VariableStatements : StatementRuntimeInformation [GeneratedRegex("\\$\\{([a-zA-Z0-9]+)\\}")] private static partial Regex VariableStatementRegex(); } + From 344e9e644e370991463afb142a96ee9ca0a67016 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:06:43 +0100 Subject: [PATCH 28/73] Use source generator instead of reflection --- YesNt-Interpreter.sln | 28 ++ .../StatementRegistryGenerator.cs | 243 ++++++++++++++++++ .../YesNt.Interpreter.Generator.csproj | 13 + YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 45 +--- YesNt.Interpreter/YesNt.Interpreter.csproj | 8 +- 5 files changed, 294 insertions(+), 43 deletions(-) create mode 100644 YesNt.Interpreter.Generator/StatementRegistryGenerator.cs create mode 100644 YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj diff --git a/YesNt-Interpreter.sln b/YesNt-Interpreter.sln index 0181ad5..b2c2492 100644 --- a/YesNt-Interpreter.sln +++ b/YesNt-Interpreter.sln @@ -14,38 +14,66 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter.Tests", "YesNt.Interpreter.Tests\YesNt.Interpreter.Tests.csproj", "{2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YesNt.Interpreter.Generator", "YesNt.Interpreter.Generator\YesNt.Interpreter.Generator.csproj", "{85AAB233-9C4D-4B42-8117-00010958DD1D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.ActiveCfg = Debug|x64 {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.Build.0 = Debug|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x86.Build.0 = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.Build.0 = Release|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.ActiveCfg = Release|x64 {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.Build.0 = Release|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x86.ActiveCfg = Release|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x86.Build.0 = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.Build.0 = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.ActiveCfg = Debug|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.Build.0 = Debug|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x86.ActiveCfg = Debug|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x86.Build.0 = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.ActiveCfg = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.Build.0 = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.ActiveCfg = Release|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.Build.0 = Release|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x86.ActiveCfg = Release|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x86.Build.0 = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.ActiveCfg = Debug|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.Build.0 = Debug|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x86.Build.0 = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.Build.0 = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.ActiveCfg = Release|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.Build.0 = Release|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.ActiveCfg = Release|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x64.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x64.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x86.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x86.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|Any CPU.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x64.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x64.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x86.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs new file mode 100644 index 0000000..a228de1 --- /dev/null +++ b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Microsoft.CodeAnalysis; + +namespace YesNt.Interpreter.Generator; + +[Generator] +public sealed class StatementRegistryGenerator : ISourceGenerator +{ + private const string StatementAttributeName = "YesNt.Interpreter.Attributes.StatementAttribute"; + private const string StaticStatementAttributeName = "YesNt.Interpreter.Attributes.StaticStatementAttribute"; + + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + Compilation compilation = context.Compilation; + + List statementMethods = []; + List staticStatementMethods = []; + + CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods); + + string source = GenerateRegistrySource(statementMethods, staticStatementMethods); + context.AddSource("GeneratedStatementRegistry.g.cs", source); + } + + private static void CollectMethods( + INamespaceSymbol namespaceSymbol, + List statementMethods, + List staticStatementMethods) + { + foreach (INamespaceSymbol childNamespace in namespaceSymbol.GetNamespaceMembers()) + { + CollectMethods(childNamespace, statementMethods, staticStatementMethods); + } + + foreach (INamedTypeSymbol type in namespaceSymbol.GetTypeMembers()) + { + CollectMethods(type, statementMethods, staticStatementMethods); + } + } + + private static void CollectMethods( + INamedTypeSymbol typeSymbol, + List statementMethods, + List staticStatementMethods) + { + foreach (ISymbol member in typeSymbol.GetMembers()) + { + if (member is IMethodSymbol method && method.MethodKind == MethodKind.Ordinary) + { + foreach (AttributeData attribute in method.GetAttributes()) + { + string? attributeName = attribute.AttributeClass?.ToDisplayString(); + if (attributeName == StatementAttributeName) + { + statementMethods.Add(new MethodRegistration(typeSymbol, method, attribute)); + } + else if (attributeName == StaticStatementAttributeName) + { + staticStatementMethods.Add(new MethodRegistration(typeSymbol, method, attribute)); + } + } + } + } + + foreach (INamedTypeSymbol nestedType in typeSymbol.GetTypeMembers()) + { + CollectMethods(nestedType, statementMethods, staticStatementMethods); + } + } + + private static string GenerateRegistrySource( + List statementMethods, + List staticStatementMethods) + { + StringBuilder sb = new StringBuilder(); + + _ = sb.AppendLine("// "); + _ = sb.AppendLine("#nullable enable"); + _ = sb.AppendLine("using System;"); + _ = sb.AppendLine("using System.Collections.Generic;"); + _ = sb.AppendLine("using System.Linq;"); + _ = sb.AppendLine(); + _ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;"); + _ = sb.AppendLine(); + _ = sb.AppendLine("internal static class GeneratedStatementRegistry"); + _ = sb.AppendLine("{"); + _ = sb.AppendLine(" internal static void Register("); + _ = sb.AppendLine(" RuntimeInformation runtimeInfo,"); + _ = sb.AppendLine(" out Dictionary> statements,"); + _ = sb.AppendLine(" out List> staticStatements)"); + _ = sb.AppendLine(" {"); + + List allTypes = statementMethods + .Concat(staticStatementMethods) + .Select(x => x.ContainingType) + .GroupBy(x => x, SymbolEqualityComparer.Default) + .Select(g => g.First()) + .OrderBy(x => x.ToDisplayString()) + .ToList(); + + Dictionary instanceNames = new Dictionary(SymbolEqualityComparer.Default); + int index = 0; + foreach (INamedTypeSymbol type in allTypes) + { + string instanceName = $"instance{index++}"; + instanceNames[type] = instanceName; + _ = sb.AppendLine($" var {instanceName} = new global::{type.ToDisplayString()}();"); + _ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;"); + } + + _ = sb.AppendLine(" var statementEntries = new List>>();"); + + foreach (MethodRegistration method in statementMethods + .OrderBy(x => x.ContainingType.ToDisplayString()) + .ThenBy(x => x.Method.Name)) + { + string instanceName = instanceNames[method.ContainingType]; + string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StatementAttribute", method.Attribute); + _ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));"); + } + + _ = sb.AppendLine(" var staticEntries = new List>();"); + + foreach (MethodRegistration method in staticStatementMethods + .OrderBy(x => x.ContainingType.ToDisplayString()) + .ThenBy(x => x.Method.Name)) + { + string instanceName = instanceNames[method.ContainingType]; + string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StaticStatementAttribute", method.Attribute); + _ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));"); + } + + _ = sb.AppendLine(" statements = statementEntries"); + _ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)"); + _ = sb.AppendLine(" .ThenByDescending(s => s.Key.Name.Length)"); + _ = sb.AppendLine(" .ToDictionary(x => x.Key, x => x.Value);"); + _ = sb.AppendLine(); + _ = sb.AppendLine(" staticStatements = staticEntries"); + _ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)"); + _ = sb.AppendLine(" .ToList();"); + _ = sb.AppendLine(" }"); + _ = sb.AppendLine("}"); + + return sb.ToString(); + } + + private static string BuildAttributeCreation(string attributeTypeName, AttributeData attribute) + { + string ctorArgs = string.Join(", ", + attribute.ConstructorArguments.Select(ToLiteral)); + + string creation = $"new {attributeTypeName}({ctorArgs})"; + + if (attribute.NamedArguments.Length == 0) + { + return creation; + } + + string namedArgs = string.Join(", ", + attribute.NamedArguments.Select(arg => $"{arg.Key} = {ToLiteral(arg.Value)}")); + + return $"{creation} {{ {namedArgs} }}"; + } + + private static string ToLiteral(TypedConstant constant) + { + if (constant.IsNull) + { + return "null!"; + } + + if (constant.Type is null) + { + return "null!"; + } + + if (constant.Kind == TypedConstantKind.Enum) + { + string enumType = $"global::{constant.Type.ToDisplayString()}"; + object value = constant.Value!; + return $"({enumType}){Convert.ToInt64(value)}"; + } + + return constant.Type.SpecialType switch + { + SpecialType.System_String => "\"" + EscapeString((string)constant.Value!) + "\"", + SpecialType.System_Char => "'" + EscapeChar((char)constant.Value!) + "'", + SpecialType.System_Boolean => (bool)constant.Value! ? "true" : "false", + SpecialType.System_Int32 => ((int)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture), + SpecialType.System_Int64 => ((long)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "L", + SpecialType.System_Single => ((float)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "f", + SpecialType.System_Double => ((double)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture), + _ => constant.Value!.ToString() ?? "null!" + }; + } + + private static string EscapeString(string value) + { + return value + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\r", "\\r") + .Replace("\n", "\\n") + .Replace("\t", "\\t"); + } + + private static string EscapeChar(char value) + { + return value switch + { + '\\' => "\\\\", + '\'' => "\\'", + '\r' => "\\r", + '\n' => "\\n", + '\t' => "\\t", + _ => value.ToString() + }; + } + + private sealed class MethodRegistration + { + public MethodRegistration(INamedTypeSymbol containingType, IMethodSymbol method, AttributeData attribute) + { + ContainingType = containingType; + Method = method; + Attribute = attribute; + } + + public INamedTypeSymbol ContainingType { get; } + + public IMethodSymbol Method { get; } + + public AttributeData Attribute { get; } + } +} diff --git a/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj b/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj new file mode 100644 index 0000000..4be3731 --- /dev/null +++ b/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + latest + enable + true + true + + + + + + diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 0559a7c..da6aa9a 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reflection; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; @@ -49,47 +48,9 @@ public class YesNtInterpreter public void Initialize() { - Assembly assembly = Assembly.GetExecutingAssembly(); - Type[] types = assembly.GetTypes(); - - IEnumerable allStatementRuntimeInfo = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); - statements.Clear(); - - foreach (Type type in allStatementRuntimeInfo) - { - object statementInfo = Activator.CreateInstance(type); - - MethodInfo[] allMethodInfo = statementInfo.GetType().GetMethods(); - - StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; - statementRuntimeInfo.RuntimeInfo = runtimeInfo; - - foreach (MethodInfo methodInfo in allMethodInfo) - { - StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); - if (statementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - statements.Add(statementAttribute, method); - } - - StaticStatementAttribute staticStatementAttribute = methodInfo.GetCustomAttribute(); - if (staticStatementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - staticStatements.Add(new(staticStatementAttribute, method)); - } - } - } - - statements = statements - .OrderBy(s => s.Key.Priority) - .ThenByDescending(s => s.Key.Name.Length) - .ToDictionary(x => x.Key, x => x.Value); - staticStatements = staticStatements - .OrderBy(s => s.Key.Priority) - .ToList(); + staticStatements.Clear(); + GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements); runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); @@ -283,4 +244,4 @@ public class YesNtInterpreter return true; } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index 1093ec0..f6e5bb2 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -9,4 +9,10 @@ AnyCPU;x64 + + + + From e99df1e3f2e2259e2b26398e0143d1c448a02a10 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:47:54 +0100 Subject: [PATCH 29/73] Add list statement support with tests for creation, access, and errors --- SYNTAX_V2.md | 11 + .../ListStatementsTests.cs | 161 ++++++++++ .../ProcessingStatementsTests.cs | 4 +- YesNt.Interpreter/Runtime/FunctionScope.cs | 3 +- .../Runtime/RuntimeInformation.cs | 5 +- .../Statements/ListStatements.cs | 274 ++++++++++++++++++ 6 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 YesNt.Interpreter.Tests/ListStatementsTests.cs create mode 100644 YesNt.Interpreter/Statements/ListStatements.cs diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index dd62b7a..690e071 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -47,6 +47,17 @@ var i = ${i} - 1 calc end_while ``` +## Lists + +```ynt +list items new +list items add apple +list items add banana +list items get 1 +var value = %out +print_line ${value} +``` + ## Full v1 -> v2 Mapping | Area | v1 Syntax | v2 Syntax | Notes | diff --git a/YesNt.Interpreter.Tests/ListStatementsTests.cs b/YesNt.Interpreter.Tests/ListStatementsTests.cs new file mode 100644 index 0000000..b5a447d --- /dev/null +++ b/YesNt.Interpreter.Tests/ListStatementsTests.cs @@ -0,0 +1,161 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class ListStatementsTests +{ + [TestMethod] + public void ListCreateAddGetTest() + { + List lines = + [ + "list items new", + "list items add a", + "list items add b", + "list items get 1", + "var result = %out", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "b"); + } + + [TestMethod] + public void ListSetAndInsertTest() + { + List lines = + [ + "list items new", + "list items add a", + "list items add c", + "list items insert 1 b", + "list items set 2 d", + "list items get 2", + "var result = %out", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "d"); + } + + [TestMethod] + public void ListRemoveAndLengthTest() + { + List lines = + [ + "list items new", + "list items add a", + "list items add b", + "list items add c", + "list items remove 1", + "list items length", + "var len = %out", + "${len}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "2"); + } + + [TestMethod] + public void ListClearTest() + { + List lines = + [ + "list items new", + "list items add a", + "list items clear", + "list items length", + "var len = %out", + "${len}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "0"); + } + + [TestMethod] + public void ListDeleteTest() + { + List lines = + [ + "list items new", + "list items delete", + "list items length" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "List \"items\" not found"); + } + + [TestMethod] + public void ListMissingFailsTest() + { + List lines = + [ + "list missing get 0" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "List \"missing\" not found"); + } + + [TestMethod] + public void ListInvalidIndexFailsTest() + { + List lines = + [ + "list items new", + "list items add a", + "list items get 3" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Index 3 out of range"); + } + + [TestMethod] + public void ListInvalidSyntaxFailsTest() + { + List lines = + [ + "list items add" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void ListScopeInsideFunctionTest() + { + List lines = + [ + "goto main", + "func make:", + "list items new", + "list items add x", + "list items get 0", + "push_out %out", + "return", + "label main:", + "call make", + "var result = %out", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "x"); + } + + [TestMethod] + public void ListAddWithSpacesTest() + { + List lines = + [ + "list items new", + "list items add hello~spcworld", + "list items get 0", + "var result = %out eval", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello world"); + } +} diff --git a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs index 98a093e..7bd07a3 100644 --- a/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ProcessingStatementsTests.cs @@ -132,7 +132,9 @@ public class ProcessingStatementsTests [ "global result = 0", "global result = 1 task", - "sleep 50", + "while ${result} == 0:", + "sleep 10", + "end_while", "${result}" ]; diff --git a/YesNt.Interpreter/Runtime/FunctionScope.cs b/YesNt.Interpreter/Runtime/FunctionScope.cs index cbae505..4bea9c6 100644 --- a/YesNt.Interpreter/Runtime/FunctionScope.cs +++ b/YesNt.Interpreter/Runtime/FunctionScope.cs @@ -6,7 +6,8 @@ internal class FunctionScope(int callerLine, Stack arguments) { public int CallerLine { get; } = callerLine; public Dictionary Variables { get; } = []; + public Dictionary> Lists { get; } = []; public Dictionary Labels { get; } = []; public Stack Arguments { get; } = arguments; public Stack Results { get; } = new(); -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 719e010..57d61d6 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -15,6 +15,7 @@ internal sealed class RuntimeInformation private static int internalTaskId = 0; private readonly Dictionary topVariables = []; + private readonly Dictionary> topLists = []; private readonly Dictionary topLabels = []; private RuntimeInformation parentRuntimeInformation; private int taskId = 0; @@ -43,6 +44,7 @@ internal sealed class RuntimeInformation } public Dictionary Variables => FunctionCallStack.Count == 0 ? topVariables : FunctionCallStack.Peek().Variables; + public Dictionary> Lists => FunctionCallStack.Count == 0 ? topLists : FunctionCallStack.Peek().Lists; public Dictionary Labels => FunctionCallStack.Count == 0 ? topLabels : FunctionCallStack.Peek().Labels; @@ -148,6 +150,7 @@ internal sealed class RuntimeInformation public void Reset() { topVariables.Clear(); + topLists.Clear(); Lines.Clear(); GlobalVariables.Clear(); Labels.Clear(); @@ -176,4 +179,4 @@ internal sealed class RuntimeInformation { Exit($"Terminated by parent task", stopAllTasks); } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/Statements/ListStatements.cs b/YesNt.Interpreter/Statements/ListStatements.cs new file mode 100644 index 0000000..bb2e810 --- /dev/null +++ b/YesNt.Interpreter/Statements/ListStatements.cs @@ -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 is null) + { + return; + } + + string name = parts[0]; + if (!RuntimeInfo.Lists.ContainsKey(name)) + { + RuntimeInfo.Lists.Add(name, []); + } + else + { + RuntimeInfo.Lists[name].Clear(); + } + } + + [Statement("list", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.DarkCyan, Separator = " delete")] + public void Delete(string args) + { + string[] parts = SplitTwo(args, " delete"); + if (parts is null) + { + return; + } + + string name = parts[0]; + + if (!RuntimeInfo.Lists.ContainsKey(name)) + { + RuntimeInfo.Exit($"List \"{name}\" not found", 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List list)) + { + return; + } + + if (string.IsNullOrWhiteSpace(parts[1])) + { + RuntimeInfo.Exit("Invalid syntax", 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List list)) + { + return; + } + + string[] indexAndValue = SplitIndexAndValue(parts[1]); + if (indexAndValue is null) + { + 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 is null) + { + return; + } + + if (!TryGetList(parts[0], out List list)) + { + return; + } + + string[] indexAndValue = SplitIndexAndValue(parts[1]); + if (indexAndValue is null) + { + 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("Invalid syntax", true); + return null; + } + + parts[0] = parts[0].Trim(); + parts[1] = parts[1].Trim(); + + if (string.IsNullOrWhiteSpace(parts[0])) + { + RuntimeInfo.Exit("Invalid syntax", true); + return null; + } + + return parts; + } + + private bool TryGetList(string name, out List list) + { + if (!RuntimeInfo.Lists.TryGetValue(name, out list)) + { + RuntimeInfo.Exit($"List \"{name}\" not found", true); + return false; + } + + return true; + } + + private string[] SplitIndexAndValue(string input) + { + string[] parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + { + RuntimeInfo.Exit("Invalid syntax", true); + return null; + } + + 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($"\"{rawIndex}\" is not a valid index", true); + return false; + } + + if (index < 0 || index >= maxExclusive) + { + RuntimeInfo.Exit($"Index {index} out of range", true); + return false; + } + + return true; + } +} From 7f58fb4aaf511aca6f96f1e902734ff535506c11 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:01:23 +0100 Subject: [PATCH 30/73] Add step debug mode and refactor command handling for run/debug --- YesNt.CodeEditor/Editor.cs | 11 +++- YesNt.CodeEditor/InputHandler.cs | 102 +++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 28 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index f3f7698..f2b2973 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -20,6 +20,7 @@ internal class TextEditor public Point CursorPosition { get; } = new(0, 0); public Mode EditMode { get; set; } = Mode.Command; public string CurrentPath { get; set; } = string.Empty; + public bool IsStepDebugMode { get; set; } public TextEditor(string path) : this() { @@ -241,6 +242,14 @@ internal class TextEditor { Console.Write(output); } + + if (IsStepDebugMode && e is not null && !e.IsTask) + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine("[Step] Press any key for next line (Ctrl+C to stop)..."); + Console.ForegroundColor = ConsoleColor.Gray; + _ = Console.ReadKey(true); + } } } @@ -279,4 +288,4 @@ internal enum Mode Edit, Command, Debug -} \ No newline at end of file +} diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index 924e5b2..bf24a9e 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -225,36 +225,18 @@ internal class InputHandler(TextEditor textEditor) break; case "run": - if (textEditor.Save(input, true)) - { - textEditor.EditMode = Mode.Debug; - Console.Clear(); - Console.CursorVisible = true; - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - _ = Console.ReadKey(); - WriteStatus(string.Empty); - textEditor.EditMode = Mode.Command; - } + ExecuteWithDebugScreen(input, false, false); break; case "debug": - if (textEditor.Save(input, true)) + if (TryParseDebugCommand(input, out bool stepMode, out string parsedPath)) { - textEditor.EditMode = Mode.Debug; - Console.Clear(); - Console.CursorVisible = true; - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - _ = Console.ReadKey(); - WriteStatus(string.Empty); - textEditor.EditMode = Mode.Command; + string saveInput = string.IsNullOrWhiteSpace(parsedPath) ? "debug" : $"debug {parsedPath}"; + ExecuteWithDebugScreen(saveInput, true, stepMode); + } + else + { + WriteStatus("Invalid arguments!"); } break; @@ -309,4 +291,70 @@ internal class InputHandler(TextEditor textEditor) Console.SetCursorPosition(0, Console.WindowHeight - 1); Console.Write(input + new string(' ', Console.WindowWidth - input.Length - 1)); } -} \ No newline at end of file + + private static bool TryParseDebugCommand(string input, out bool stepMode, out string path) + { + stepMode = false; + path = string.Empty; + + string[] parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0 || !parts[0].Equals("debug", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + for (int i = 1; i < parts.Length; i++) + { + string token = parts[i]; + if (token.Equals("step", StringComparison.OrdinalIgnoreCase)) + { + if (stepMode) + { + return false; + } + + stepMode = true; + continue; + } + + if (string.IsNullOrWhiteSpace(path)) + { + path = token; + continue; + } + + return false; + } + + return true; + } + + private void ExecuteWithDebugScreen(string saveInput, bool debugMode, bool stepMode) + { + if (textEditor.Save(saveInput, true)) + { + textEditor.EditMode = Mode.Debug; + textEditor.IsStepDebugMode = stepMode; + Console.Clear(); + Console.CursorVisible = true; + + if (debugMode) + { + textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); + } + else + { + textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); + } + + while (Console.KeyAvailable) + { + _ = Console.ReadKey(true); + } + _ = Console.ReadKey(); + WriteStatus(string.Empty); + textEditor.IsStepDebugMode = false; + textEditor.EditMode = Mode.Command; + } + } +} From 86819900a02ad7f8854e9e495e561a22cf794bc7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:21:49 +0100 Subject: [PATCH 31/73] Add string literal parsing with escapes and related tests --- SYNTAX_V2.md | 3 + .../StringLiteralStatementsTests.cs | 71 +++++++++++++++++ .../Statements/StringLiteralStatements.cs | 78 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs create mode 100644 YesNt.Interpreter/Statements/StringLiteralStatements.cs diff --git a/SYNTAX_V2.md b/SYNTAX_V2.md index 690e071..6669145 100644 --- a/SYNTAX_V2.md +++ b/SYNTAX_V2.md @@ -14,6 +14,8 @@ This document describes the current, word-based YesNt syntax. - Statements are line-based. - `# ...` remains a comment. - Variable interpolation inside text uses `${name}`. +- String literals use double quotes (`"..."`) with escapes like `\n`, `\t`, `\"`, `\\`. + Interpolation is not evaluated inside string literals. - Function and label declarations are block markers with a trailing `:`. - Conditions retain current evaluator expressions (for example: `a == b`, `x > 5`, `10 + 2 == 12`). @@ -22,6 +24,7 @@ This document describes the current, word-based YesNt syntax. ```ynt var name = world func greet: +print_line "Hello world" print_line Hello ${name} return call greet diff --git a/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs b/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs new file mode 100644 index 0000000..e6ffcda --- /dev/null +++ b/YesNt.Interpreter.Tests/StringLiteralStatementsTests.cs @@ -0,0 +1,71 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class StringLiteralStatementsTests +{ + [TestMethod] + public void StringLiteralWithSpacesWorksTest() + { + List lines = + [ + "var msg = \"hello world\"", + "${msg}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello world"); + } + + [TestMethod] + public void StringLiteralEscapesWorkTest() + { + List lines = + [ + "var msg = \"a\\n\\t\\\"b\"", + "${msg}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "a\n\t\"b"); + } + + [TestMethod] + public void StringLiteralPreventsVariableInterpolationTest() + { + List lines = + [ + "var x = hidden", + "print_line \"${x}\"" + ]; + + YesNtAssert.ContainsDebugOutput(lines, "${x}"); + } + + [TestMethod] + public void StringLiteralWorksWithListAddTest() + { + List lines = + [ + "list items new", + "list items add \"hello world\"", + "list items get 0", + "var result = %out", + "${result}" + ]; + + YesNtAssert.IsLastLineEqual(lines, "hello world"); + } + + [TestMethod] + public void UnterminatedStringLiteralFailsTest() + { + List lines = + [ + "var msg = \"hello" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid string literal"); + } +} diff --git a/YesNt.Interpreter/Statements/StringLiteralStatements.cs b/YesNt.Interpreter/Statements/StringLiteralStatements.cs new file mode 100644 index 0000000..c2d0971 --- /dev/null +++ b/YesNt.Interpreter/Statements/StringLiteralStatements.cs @@ -0,0 +1,78 @@ +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); + + 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); + } + + if (!closed) + { + RuntimeInfo.Exit("Invalid string literal", 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', + '"' => '"', + '\\' => '\\', + _ => escapeChar + }; + } +} From e47f614e621f15c99657d5bdc9ac4ecf89450ff2 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:40:14 +0100 Subject: [PATCH 32/73] Convert import statement path from safe string before combining paths --- YesNt.Interpreter/Statements/ProcessingStatements.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index aa1e963..bd363f5 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -82,6 +82,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation [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))) From f411aa7433d106bdf061b890b3b91f2dcceeafd7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:50:58 +0100 Subject: [PATCH 33/73] Add string highlighting with DarkYellow and make sure variable coloring gets applied before others --- YesNt.CodeEditor/SyntaxHighlighter.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 57b9bd2..2c8a3e7 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -36,6 +36,19 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection Date: Wed, 4 Mar 2026 18:00:08 +0100 Subject: [PATCH 34/73] Swap variable and string regex order and update highlight colors to prevent highlighting glitches --- YesNt.CodeEditor/SyntaxHighlighter.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 2c8a3e7..7b8daa1 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -36,19 +36,18 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection Date: Wed, 4 Mar 2026 18:12:08 +0100 Subject: [PATCH 35/73] Code cleanup --- YesNt.Interpreter.Generator/StatementRegistryGenerator.cs | 4 ++-- YesNt.Interpreter.Tests/YesNtAssert.cs | 2 +- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 4 ++-- YesNt.Interpreter/Utilities/FixedProcess.cs | 8 +++----- YesNt.Interpreter/Utilities/StringExtentions.cs | 2 -- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs index a228de1..dbf028f 100644 --- a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs +++ b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs @@ -1,10 +1,10 @@ +using Microsoft.CodeAnalysis; + using System; using System.Collections.Generic; using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; - namespace YesNt.Interpreter.Generator; [Generator] diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index 25c86f3..75380e7 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -2,8 +2,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; -using System.Text.RegularExpressions; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using YesNt.Interpreter.Runtime; diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index da6aa9a..28192bd 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -160,7 +160,7 @@ public class YesNtInterpreter { if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name)) { - string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(0, name.Length); + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[name.Length..]; statement.Value.Invoke(copyLine); statementFound = true; } @@ -172,7 +172,7 @@ public class YesNtInterpreter } else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) { - string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(runtimeInfo.CurrentLine.Length - name.Length); + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine[..^name.Length]; statement.Value.Invoke(copyLine); statementFound = true; } diff --git a/YesNt.Interpreter/Utilities/FixedProcess.cs b/YesNt.Interpreter/Utilities/FixedProcess.cs index 1117d27..28b401a 100644 --- a/YesNt.Interpreter/Utilities/FixedProcess.cs +++ b/YesNt.Interpreter/Utilities/FixedProcess.cs @@ -170,11 +170,9 @@ internal class AsyncStreamReader : IDisposable byteBuffer = null; charBuffer = null; } - if (eofEvent != null) - { - eofEvent.Close(); - eofEvent = null; - } + + eofEvent?.Close(); + eofEvent = null; } private void Init(Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) diff --git a/YesNt.Interpreter/Utilities/StringExtentions.cs b/YesNt.Interpreter/Utilities/StringExtentions.cs index 49d00f9..a28f1ea 100644 --- a/YesNt.Interpreter/Utilities/StringExtentions.cs +++ b/YesNt.Interpreter/Utilities/StringExtentions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text; @@ -30,7 +29,6 @@ public static class StringExtensions {"", "~emp" }, }; - [SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "Doesn't work because it throws a TypeInitializationException")] static StringExtensions() { reverseReplacementRules = ReplacementRules.ToDictionary(x => x.Value, x => x.Key); From 8e0abf155a8662c0541662a2f241c431dd374216 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:40:46 +0100 Subject: [PATCH 36/73] Make YesNtInterpreter usable as a library and add AddStatement method to be able to define custom statement --- YesNt-Interpreter.sln | 14 ++++++++++ YesNt.Interpreter.App/Program.cs | 14 ++++++++++ .../YesNt.Interpreter.App.csproj | 13 ++++++++++ .../Attributes/StatementAttribute.cs | 6 ++--- .../Attributes/StaticStatementAttribute.cs | 2 +- YesNt.Interpreter/Enums/Priority.cs | 2 +- YesNt.Interpreter/Program.cs | 26 ++++++++++++------- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 19 ++++++++++++++ YesNt.Interpreter/YesNt.Interpreter.csproj | 2 +- 9 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 YesNt.Interpreter.App/Program.cs create mode 100644 YesNt.Interpreter.App/YesNt.Interpreter.App.csproj diff --git a/YesNt-Interpreter.sln b/YesNt-Interpreter.sln index b2c2492..8e36ce1 100644 --- a/YesNt-Interpreter.sln +++ b/YesNt-Interpreter.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter.Tests", "YesNt.Interpreter.Tests\YesNt.Interpreter.Tests.csproj", "{2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter.App", "YesNt.Interpreter.App\YesNt.Interpreter.App.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YesNt.Interpreter.Generator", "YesNt.Interpreter.Generator\YesNt.Interpreter.Generator.csproj", "{85AAB233-9C4D-4B42-8117-00010958DD1D}" EndProject Global @@ -62,6 +64,18 @@ Global {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.Build.0 = Release|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.ActiveCfg = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/YesNt.Interpreter.App/Program.cs b/YesNt.Interpreter.App/Program.cs new file mode 100644 index 0000000..61311f7 --- /dev/null +++ b/YesNt.Interpreter.App/Program.cs @@ -0,0 +1,14 @@ +using System; + +using YesNt.Interpreter.Runtime; + +if (args.Length == 1) +{ + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + interpreter.Execute(args[0]); +} +else +{ + Console.WriteLine("No path specified!"); +} diff --git a/YesNt.Interpreter.App/YesNt.Interpreter.App.csproj b/YesNt.Interpreter.App/YesNt.Interpreter.App.csproj new file mode 100644 index 0000000..018aab7 --- /dev/null +++ b/YesNt.Interpreter.App/YesNt.Interpreter.App.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + Exe + AnyCPU;x64 + + + + + + + diff --git a/YesNt.Interpreter/Attributes/StatementAttribute.cs b/YesNt.Interpreter/Attributes/StatementAttribute.cs index 999c9e8..b2aabf4 100644 --- a/YesNt.Interpreter/Attributes/StatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StatementAttribute.cs @@ -5,7 +5,7 @@ using YesNt.Interpreter.Enums; namespace YesNt.Interpreter.Attributes; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -internal class StatementAttribute : Attribute +public class StatementAttribute : Attribute { public string Name { get; } public SearchMode SearchMode { get; } @@ -17,7 +17,7 @@ internal class StatementAttribute : Attribute public bool IgnoreSyntaxHighlighting { get; } public string Separator { get; set; } - internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color) + public StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor color) { Name = name; SearchMode = searchMode; @@ -25,7 +25,7 @@ internal class StatementAttribute : Attribute Color = color; } - internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) + public StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) { Name = name; SearchMode = searchMode; diff --git a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs index f0c5a5a..59f80e3 100644 --- a/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs +++ b/YesNt.Interpreter/Attributes/StaticStatementAttribute.cs @@ -5,7 +5,7 @@ using YesNt.Interpreter.Enums; namespace YesNt.Interpreter.Attributes; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -internal class StaticStatementAttribute : Attribute +public class StaticStatementAttribute : Attribute { public bool ExecuteInSearchMode { get; set; } public Priority Priority { get; set; } = Priority.Normal; diff --git a/YesNt.Interpreter/Enums/Priority.cs b/YesNt.Interpreter/Enums/Priority.cs index acb372b..eb8353d 100644 --- a/YesNt.Interpreter/Enums/Priority.cs +++ b/YesNt.Interpreter/Enums/Priority.cs @@ -1,6 +1,6 @@ namespace YesNt.Interpreter.Enums; -internal enum Priority +public enum Priority { PreProcessing, Highest, diff --git a/YesNt.Interpreter/Program.cs b/YesNt.Interpreter/Program.cs index ccbba14..1582b6b 100644 --- a/YesNt.Interpreter/Program.cs +++ b/YesNt.Interpreter/Program.cs @@ -1,14 +1,22 @@ -using System; +using System; using YesNt.Interpreter.Runtime; -if (args.Length == 1) +namespace YesNt.Interpreter; + +internal static class Program { - YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); - interpreter.Execute(args[0]); + internal static void Main(string[] args) + { + if (args.Length == 1) + { + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + interpreter.Execute(args[0]); + } + else + { + Console.WriteLine("No path specified!"); + } + } } -else -{ - Console.WriteLine("No path specified!"); -} \ No newline at end of file diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 28192bd..65b5c47 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -41,6 +41,25 @@ public class YesNtInterpreter } } + public void AddStatement(StatementAttribute attribute, Action handler) + { + statements[attribute] = handler; + statements = statements + .OrderBy(s => s.Key.Priority) + .ThenByDescending(s => s.Key.Name.Length) + .ToDictionary(x => x.Key, x => x.Value); + } + + public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, Action handler) + { + AddStatement(new StatementAttribute(name, searchMode, spaceAround), handler); + } + + public void AddStatement(string name, SearchMode searchMode, SpaceAround spaceAround, ConsoleColor consoleColor, Action handler) + { + AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler); + } + public void Stop() { runtimeInfo.Exit("Terminated by external process", true); diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index f6e5bb2..67e7926 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -4,7 +4,7 @@ net8.0 YesNt.Interpreter - Exe + Library AnyCPU;x64 From b2fdd9c8419b0275356fdbf4cd5c41fc7bfe3a9d Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:41:14 +0100 Subject: [PATCH 37/73] Remove Initialize calls and add AddStatement tests --- YesNt.CodeEditor/Editor.cs | 1 - YesNt.Interpreter.App/Program.cs | 1 - YesNt.Interpreter.Tests/AddStatementTests.cs | 220 ++++++++++++++++++ .../ConsoleStatementsTests.cs | 3 +- YesNt.Interpreter.Tests/YesNtAssert.cs | 23 +- YesNt.Interpreter/Program.cs | 1 - YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 14 +- .../Statements/ProcessingStatements.cs | 3 +- 8 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 YesNt.Interpreter.Tests/AddStatementTests.cs diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index f2b2973..6bd5b21 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -32,7 +32,6 @@ internal class TextEditor public TextEditor() { - YesNtInterpreter.Initialize(); YesNtInterpreter.OnDebugOutput += YesNtInterpreter_OnDebugOutput; YesNtInterpreter.OnLineExecuted += YesNtInterpreter_OnLineExecuted; syntaxHighlighter = new(YesNtInterpreter.StatementInformation); diff --git a/YesNt.Interpreter.App/Program.cs b/YesNt.Interpreter.App/Program.cs index 61311f7..ccd2ec9 100644 --- a/YesNt.Interpreter.App/Program.cs +++ b/YesNt.Interpreter.App/Program.cs @@ -5,7 +5,6 @@ using YesNt.Interpreter.Runtime; if (args.Length == 1) { YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); interpreter.Execute(args[0]); } else diff --git a/YesNt.Interpreter.Tests/AddStatementTests.cs b/YesNt.Interpreter.Tests/AddStatementTests.cs new file mode 100644 index 0000000..6b4f6cd --- /dev/null +++ b/YesNt.Interpreter.Tests/AddStatementTests.cs @@ -0,0 +1,220 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; + +using YesNt.Interpreter.Attributes; +using YesNt.Interpreter.Enums; +using YesNt.Interpreter.Runtime; + +namespace YesNt.Interpreter.Tests; + +[TestClass] +public class AddStatementTests +{ + [TestMethod] + public void AddStatementStartOfLineExecutesHandlerTest() + { + List lines = + [ + "my_command hello" + ]; + + string? capturedArgs = null; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement("my_command", SearchMode.StartOfLine, SpaceAround.End, args => + { + capturedArgs = args; + }); + }); + + Assert.AreEqual("hello", capturedArgs); + } + + [TestMethod] + public void AddStatementConvenienceOverloadHandlerIsCalledTest() + { + List lines = + [ + "custom_cmd world" + ]; + + bool handlerCalled = false; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement("custom_cmd", SearchMode.StartOfLine, SpaceAround.End, _ => + { + handlerCalled = true; + }); + }); + + Assert.IsTrue(handlerCalled); + } + + [TestMethod] + public void AddStatementAttributeOverloadHandlerIsCalledTest() + { + List lines = + [ + "attr_cmd test" + ]; + + bool handlerCalled = false; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + var attr = new StatementAttribute("attr_cmd", SearchMode.StartOfLine, SpaceAround.End); + interpreter.AddStatement(attr, _ => + { + handlerCalled = true; + }); + }); + + Assert.IsTrue(handlerCalled); + } + + [TestMethod] + public void AddStatementExactSearchModeTest() + { + List lines = + [ + "exact_cmd" + ]; + + bool handlerCalled = false; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement("exact_cmd", SearchMode.Exact, SpaceAround.None, _ => + { + handlerCalled = true; + }); + }); + + Assert.IsTrue(handlerCalled); + } + + [TestMethod] + public void AddStatementContainsSearchModeTest() + { + List lines = + [ + "prefix ~mark~ suffix" + ]; + + bool handlerCalled = false; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement(" ~mark~ ", SearchMode.Contains, SpaceAround.None, _ => + { + handlerCalled = true; + }); + }); + + Assert.IsTrue(handlerCalled); + } + + [TestMethod] + public void AddStatementEndOfLineSearchModeTest() + { + List lines = + [ + "some text !end" + ]; + + bool handlerCalled = false; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement(" !end", SearchMode.EndOfLine, SpaceAround.None, _ => + { + handlerCalled = true; + }); + }); + + Assert.IsTrue(handlerCalled); + } + + [TestMethod] + public void AddStatementReceivesCorrectArgsTest() + { + List lines = + [ + "capture_cmd the quick brown fox" + ]; + + string? capturedArgs = null; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement("capture_cmd", SearchMode.StartOfLine, SpaceAround.End, args => + { + capturedArgs = args; + }); + }); + + Assert.AreEqual("the quick brown fox", capturedArgs); + } + + [TestMethod] + public void AddStatementWorksAlongsideBuiltinStatementsTest() + { + List lines = + [ + "custom_log first", + "print_line second" + ]; + + bool customCalled = false; + + YesNtAssert.ContainsDebugOutputWithSetup(lines, "second", interpreter => + { + interpreter.AddStatement("custom_log", SearchMode.StartOfLine, SpaceAround.End, _ => + { + customCalled = true; + }); + }); + + Assert.IsTrue(customCalled); + } + + [TestMethod] + public void AddStatementUnknownStatementFailsTest() + { + List lines = + [ + "unknown_command foo" + ]; + + YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement"); + } + + [TestMethod] + public void AddStatementWithHighPriorityRunsBeforeNormalTest() + { + List lines = + [ + "priority_cmd arg" + ]; + + int callOrder = 0; + int highPriorityOrder = -1; + int normalPriorityOrder = -1; + + YesNtAssert.GetLastLineWithSetup(lines, interpreter => + { + interpreter.AddStatement( + new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.High }, + _ => highPriorityOrder = callOrder++); + + interpreter.AddStatement( + new StatementAttribute("priority_cmd", SearchMode.StartOfLine, SpaceAround.End) { Priority = Priority.Normal }, + _ => normalPriorityOrder = callOrder++); + }); + + Assert.IsTrue(highPriorityOrder < normalPriorityOrder, "High priority statement should execute before Normal priority"); + } +} diff --git a/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs index fc076b7..1f9d6cd 100644 --- a/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs +++ b/YesNt.Interpreter.Tests/ConsoleStatementsTests.cs @@ -79,9 +79,8 @@ public class ConsoleStatementsTests public void ReadKeyCanBeInterruptedByStopTest() { YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); - AutoResetEvent onDone = new AutoResetEvent(false); + AutoResetEvent onDone= new AutoResetEvent(false); StringBuilder output = new StringBuilder(); interpreter.OnDebugOutput += (s) => _ = output.Append(s); diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index 75380e7..09e53a5 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -67,10 +67,31 @@ internal static class YesNtAssert StringAssert.Matches(value, new Regex(pattern)); } + public static void IsLastLineEqualWithSetup(List lines, string expected, Action setup, int timeout = 1000) + { + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout, setup); + Assert.AreEqual(expected, debugEventArgs?.CurrentLine); + } + + public static void ContainsDebugOutputWithSetup(List lines, string expectedFragment, Action setup, int timeout = 1000) + { + (_, string debugOutput) = ExecuteAndCapture(lines, timeout, setup); + StringAssert.Contains(debugOutput, expectedFragment); + } + + public static string? GetLastLineWithSetup(List lines, Action setup, int timeout = 1000) + { + (DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout, setup); + return debugEventArgs?.CurrentLine; + } + private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout) + => ExecuteAndCapture(lines, timeout, setup: null); + + private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout, Action? setup) { YesNtInterpreter yesNtInterpreter = new YesNtInterpreter(); - yesNtInterpreter.Initialize(); + setup?.Invoke(yesNtInterpreter); AutoResetEvent onDone = new AutoResetEvent(false); DebugEventArgs? debugEventArgs = null; diff --git a/YesNt.Interpreter/Program.cs b/YesNt.Interpreter/Program.cs index 1582b6b..330c6f7 100644 --- a/YesNt.Interpreter/Program.cs +++ b/YesNt.Interpreter/Program.cs @@ -11,7 +11,6 @@ internal static class Program if (args.Length == 1) { YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); interpreter.Execute(args[0]); } else diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 65b5c47..c9ec6d3 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -60,21 +60,19 @@ public class YesNtInterpreter AddStatement(new StatementAttribute(name, searchMode, spaceAround, consoleColor), handler); } - public void Stop() + public YesNtInterpreter() { - runtimeInfo.Exit("Terminated by external process", true); - } - - public void Initialize() - { - statements.Clear(); - staticStatements.Clear(); GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements); runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); } + public void Stop() + { + runtimeInfo.Exit("Terminated by external process", true); + } + public void Execute(string path, bool isDebugMode = false) { runtimeInfo.Reset(); diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index bd363f5..31c524c 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -50,8 +50,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation _ = Task.Run(() => { YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Initialize(); - interpreter.Execute(lines, RuntimeInfo.GlobalVariables, lineNumber, RuntimeInfo); + interpreter.Execute(lines, RuntimeInfo.GlobalVariables, lineNumber, RuntimeInfo); }); RuntimeInfo.CurrentLine = string.Empty; From 27f72f141380a04c8a26b61bf57655fec29b0e62 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:56:08 +0100 Subject: [PATCH 38/73] Remove Program.cs and its main entry point from interpreter project --- YesNt.Interpreter/Program.cs | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 YesNt.Interpreter/Program.cs diff --git a/YesNt.Interpreter/Program.cs b/YesNt.Interpreter/Program.cs deleted file mode 100644 index 330c6f7..0000000 --- a/YesNt.Interpreter/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -using YesNt.Interpreter.Runtime; - -namespace YesNt.Interpreter; - -internal static class Program -{ - internal static void Main(string[] args) - { - if (args.Length == 1) - { - YesNtInterpreter interpreter = new YesNtInterpreter(); - interpreter.Execute(args[0]); - } - else - { - Console.WriteLine("No path specified!"); - } - } -} From d00295516c647320b79cffb7849c37a4f1f3ef07 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:59:01 +0100 Subject: [PATCH 39/73] Merge setup parameter into ExecuteAndCapture method with default value --- YesNt.Interpreter.Tests/YesNtAssert.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/YesNt.Interpreter.Tests/YesNtAssert.cs b/YesNt.Interpreter.Tests/YesNtAssert.cs index 09e53a5..422f286 100644 --- a/YesNt.Interpreter.Tests/YesNtAssert.cs +++ b/YesNt.Interpreter.Tests/YesNtAssert.cs @@ -85,10 +85,7 @@ internal static class YesNtAssert return debugEventArgs?.CurrentLine; } - private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout) - => ExecuteAndCapture(lines, timeout, setup: null); - - private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout, Action? setup) + private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List lines, int timeout, Action? setup = null) { YesNtInterpreter yesNtInterpreter = new YesNtInterpreter(); setup?.Invoke(yesNtInterpreter); From 3411bb88de1f5add2cb23791ef43939309da3d86 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:24:18 +0100 Subject: [PATCH 40/73] Refactor list statement methods to use empty array checks over null checks --- .../StatementRegistryGenerator.cs | 15 +++------ .../Statements/ListStatements.cs | 32 +++++++++---------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs index dbf028f..8d296be 100644 --- a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs +++ b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs @@ -225,19 +225,12 @@ public sealed class StatementRegistryGenerator : ISourceGenerator }; } - private sealed class MethodRegistration + private sealed class MethodRegistration(INamedTypeSymbol containingType, IMethodSymbol method, AttributeData attribute) { - public MethodRegistration(INamedTypeSymbol containingType, IMethodSymbol method, AttributeData attribute) - { - ContainingType = containingType; - Method = method; - Attribute = attribute; - } + public INamedTypeSymbol ContainingType { get; } = containingType; - public INamedTypeSymbol ContainingType { get; } + public IMethodSymbol Method { get; } = method; - public IMethodSymbol Method { get; } - - public AttributeData Attribute { get; } + public AttributeData Attribute { get; } = attribute; } } diff --git a/YesNt.Interpreter/Statements/ListStatements.cs b/YesNt.Interpreter/Statements/ListStatements.cs index bb2e810..0159181 100644 --- a/YesNt.Interpreter/Statements/ListStatements.cs +++ b/YesNt.Interpreter/Statements/ListStatements.cs @@ -13,19 +13,19 @@ internal class ListStatements : StatementRuntimeInformation public void Create(string args) { string[] parts = SplitTwo(args, " new"); - if (parts is null) + if (parts.Length == 0) { return; } string name = parts[0]; - if (!RuntimeInfo.Lists.ContainsKey(name)) + if (!RuntimeInfo.Lists.TryGetValue(name, out List value)) { RuntimeInfo.Lists.Add(name, []); } else { - RuntimeInfo.Lists[name].Clear(); + value.Clear(); } } @@ -33,7 +33,7 @@ internal class ListStatements : StatementRuntimeInformation public void Delete(string args) { string[] parts = SplitTwo(args, " delete"); - if (parts is null) + if (parts.Length == 0) { return; } @@ -53,7 +53,7 @@ internal class ListStatements : StatementRuntimeInformation public void Clear(string args) { string[] parts = SplitTwo(args, " clear"); - if (parts is null) + if (parts.Length == 0) { return; } @@ -70,7 +70,7 @@ internal class ListStatements : StatementRuntimeInformation public void Length(string args) { string[] parts = SplitTwo(args, " length"); - if (parts is null) + if (parts.Length == 0) { return; } @@ -88,7 +88,7 @@ internal class ListStatements : StatementRuntimeInformation public void Add(string args) { string[] parts = SplitTwo(args, " add "); - if (parts is null) + if (parts.Length == 0) { return; } @@ -111,7 +111,7 @@ internal class ListStatements : StatementRuntimeInformation public void Get(string args) { string[] parts = SplitTwo(args, " get "); - if (parts is null) + if (parts.Length == 0) { return; } @@ -134,7 +134,7 @@ internal class ListStatements : StatementRuntimeInformation public void Remove(string args) { string[] parts = SplitTwo(args, " remove "); - if (parts is null) + if (parts.Length == 0) { return; } @@ -156,7 +156,7 @@ internal class ListStatements : StatementRuntimeInformation public void Set(string args) { string[] parts = SplitTwo(args, " set "); - if (parts is null) + if (parts.Length == 0) { return; } @@ -167,7 +167,7 @@ internal class ListStatements : StatementRuntimeInformation } string[] indexAndValue = SplitIndexAndValue(parts[1]); - if (indexAndValue is null) + if (indexAndValue.Length == 0) { return; } @@ -184,7 +184,7 @@ internal class ListStatements : StatementRuntimeInformation public void Insert(string args) { string[] parts = SplitTwo(args, " insert "); - if (parts is null) + if (parts.Length == 0) { return; } @@ -195,7 +195,7 @@ internal class ListStatements : StatementRuntimeInformation } string[] indexAndValue = SplitIndexAndValue(parts[1]); - if (indexAndValue is null) + if (indexAndValue.Length == 0) { return; } @@ -214,7 +214,7 @@ internal class ListStatements : StatementRuntimeInformation if (parts.Length != 2) { RuntimeInfo.Exit("Invalid syntax", true); - return null; + return []; } parts[0] = parts[0].Trim(); @@ -223,7 +223,7 @@ internal class ListStatements : StatementRuntimeInformation if (string.IsNullOrWhiteSpace(parts[0])) { RuntimeInfo.Exit("Invalid syntax", true); - return null; + return []; } return parts; @@ -246,7 +246,7 @@ internal class ListStatements : StatementRuntimeInformation if (parts.Length != 2) { RuntimeInfo.Exit("Invalid syntax", true); - return null; + return []; } parts[0] = parts[0].Trim(); From 10d5766873726e6a365d1e97e8355cb90ead85db Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:32:25 +0100 Subject: [PATCH 41/73] Centralize exit messages in ExitMessages class --- .../VariableStatementsTests.cs | 2 +- YesNt.Interpreter/Runtime/ExitMessages.cs | 36 +++++++++++++++++++ .../Runtime/RuntimeInformation.cs | 4 +-- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 12 +++---- .../Statements/CodeFlowStatements.cs | 36 +++++++++---------- .../Statements/ConsoleStatements.cs | 2 +- .../Statements/FunctionStatements.cs | 24 ++++++------- .../Statements/ListStatements.cs | 16 ++++----- .../Statements/ProcessingStatements.cs | 8 ++--- .../Statements/StringLiteralStatements.cs | 2 +- .../Statements/SystemStatements.cs | 8 ++--- .../Statements/VariableStatements.cs | 12 +++---- 12 files changed, 99 insertions(+), 63 deletions(-) create mode 100644 YesNt.Interpreter/Runtime/ExitMessages.cs diff --git a/YesNt.Interpreter.Tests/VariableStatementsTests.cs b/YesNt.Interpreter.Tests/VariableStatementsTests.cs index ed1f3ec..d7e07e3 100644 --- a/YesNt.Interpreter.Tests/VariableStatementsTests.cs +++ b/YesNt.Interpreter.Tests/VariableStatementsTests.cs @@ -88,7 +88,7 @@ public class VariableStatementsTests "var a b = 1" ]; - YesNtAssert.ContainsTerminationMessage(lines, "Invalid Syntax"); + YesNtAssert.ContainsTerminationMessage(lines, "Invalid syntax"); } [TestMethod] diff --git a/YesNt.Interpreter/Runtime/ExitMessages.cs b/YesNt.Interpreter/Runtime/ExitMessages.cs new file mode 100644 index 0000000..a86b289 --- /dev/null +++ b/YesNt.Interpreter/Runtime/ExitMessages.cs @@ -0,0 +1,36 @@ +namespace YesNt.Interpreter.Runtime; + +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) => $"Label \"{label}\" not found"; + internal static string FunctionNotFound(string function) => $"Function \"{function}\" not found"; + internal static string VariableNotFound(string variable) => $"Variable \"{variable}\" not found"; + internal static string ListNotFound(string list) => $"List \"{list}\" not found"; + internal static string InvalidIndex(string rawIndex) => $"\"{rawIndex}\" is not a valid index"; + internal static string IndexOutOfRange(int index) => $"Index {index} out of range"; + internal static string InvalidTimeoutValue(string value) => $"\"{value}\" is not a valid time-out value"; + internal static string CouldNotLoadFile(string path) => $"Could not load file \"{path}\""; + internal static string CouldNotFindFile(string path) => $"Could not find file \"{path}\""; + internal static string CannotFindFile(string path) => $"Cannot find file \"{path}\"."; + internal static string FailedToStart(string program, string message) => $"Failed to start \"{program}\". {message}"; +} diff --git a/YesNt.Interpreter/Runtime/RuntimeInformation.cs b/YesNt.Interpreter/Runtime/RuntimeInformation.cs index 57d61d6..3e9a49c 100644 --- a/YesNt.Interpreter/Runtime/RuntimeInformation.cs +++ b/YesNt.Interpreter/Runtime/RuntimeInformation.cs @@ -131,7 +131,7 @@ internal sealed class RuntimeInformation { StopAllTasks = true; OnExit?.Invoke(message, StopAllTasks); - parentRuntimeInformation?.Exit("Terminated by child task", true); + parentRuntimeInformation?.Exit(ExitMessages.TerminatedByChildTask, true); } } @@ -177,6 +177,6 @@ internal sealed class RuntimeInformation private void ParentRuntimeInformation_OnExit(string exitMessage, bool stopAllTasks) { - Exit($"Terminated by parent task", stopAllTasks); + Exit(ExitMessages.TerminatedByParentTask, stopAllTasks); } } diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index c9ec6d3..932735d 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -70,7 +70,7 @@ public class YesNtInterpreter public void Stop() { - runtimeInfo.Exit("Terminated by external process", true); + runtimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true); } public void Execute(string path, bool isDebugMode = false) @@ -106,7 +106,7 @@ public class YesNtInterpreter runtimeInfo.GlobalVariables = globalVariables; if (parentRuntimeInformation.StopAllTasks) { - runtimeInfo.Exit($"Parent task was terminated!", parentRuntimeInformation.StopAllTasks); + runtimeInfo.Exit(ExitMessages.TerminatedByParentTask, parentRuntimeInformation.StopAllTasks); return; } Execute(); @@ -203,7 +203,7 @@ public class YesNtInterpreter if (!statementFound) { - runtimeInfo.Exit("Invalid statement", true); + runtimeInfo.Exit(ExitMessages.InvalidStatement, true); } if (runtimeInfo.IsDebugMode && notSearchingLabel) { @@ -216,15 +216,15 @@ public class YesNtInterpreter { if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel)) { - runtimeInfo.Exit($"Label \"{runtimeInfo.SearchLabel}\" not found", true); + runtimeInfo.Exit(ExitMessages.LabelNotFound(runtimeInfo.SearchLabel), true); } else if (!string.IsNullOrWhiteSpace(runtimeInfo.SearchFunction)) { - runtimeInfo.Exit($"Function \"{runtimeInfo.SearchFunction}\" not found", true); + runtimeInfo.Exit(ExitMessages.FunctionNotFound(runtimeInfo.SearchFunction), true); } else { - runtimeInfo.Exit("End of file", false); + runtimeInfo.Exit(ExitMessages.EndOfFile, false); } if (runtimeInfo.IsDebugMode) diff --git a/YesNt.Interpreter/Statements/CodeFlowStatements.cs b/YesNt.Interpreter/Statements/CodeFlowStatements.cs index 1000b84..ab77405 100644 --- a/YesNt.Interpreter/Statements/CodeFlowStatements.cs +++ b/YesNt.Interpreter/Statements/CodeFlowStatements.cs @@ -32,7 +32,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation string[] parts = args.Split(" goto ", 2, StringSplitOptions.None); if (parts.Length != 2) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -43,7 +43,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation if (result is null) { - RuntimeInfo.Exit("Invalid operation", true); + RuntimeInfo.Exit(ExitMessages.InvalidOperation, true); return; } @@ -69,14 +69,14 @@ internal class CodeFlowStatements : StatementRuntimeInformation string labelDeclaration = args.Trim(); if (!labelDeclaration.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true); return; } string key = NormalizeBlockName(labelDeclaration); if (string.IsNullOrWhiteSpace(key)) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -120,7 +120,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation string[] parts = args.Split(" call ", 2, StringSplitOptions.None); if (parts.Length != 2) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -131,7 +131,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation if (result is null) { - RuntimeInfo.Exit("Invalid operation", true); + RuntimeInfo.Exit(ExitMessages.InvalidOperation, true); return; } @@ -159,7 +159,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation args = args.Trim(); if (!args.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true); return; } @@ -168,7 +168,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation if (result is null) { - RuntimeInfo.Exit("Invalid operation", true); + RuntimeInfo.Exit(ExitMessages.InvalidOperation, true); return; } @@ -180,7 +180,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation (int targetLine, _) = FindElseOrEndIf(RuntimeInfo.LineNumber); if (targetLine < 0) { - RuntimeInfo.Exit("No matching end_if found", true); + RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true); return; } @@ -193,7 +193,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation int targetLine = FindEndIf(RuntimeInfo.LineNumber); if (targetLine < 0) { - RuntimeInfo.Exit("No matching end_if found", true); + RuntimeInfo.Exit(ExitMessages.NoMatchingEndIf, true); return; } @@ -211,7 +211,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation args = args.Trim(); if (!args.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true); return; } @@ -220,7 +220,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation if (result is null) { - RuntimeInfo.Exit("Invalid operation", true); + RuntimeInfo.Exit(ExitMessages.InvalidOperation, true); return; } @@ -232,7 +232,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation int endWhileLine = FindEndWhile(RuntimeInfo.LineNumber); if (endWhileLine < 0) { - RuntimeInfo.Exit("No matching end_while found", true); + RuntimeInfo.Exit(ExitMessages.NoMatchingEndWhile, true); return; } @@ -245,7 +245,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation int whileLine = FindWhile(RuntimeInfo.LineNumber); if (whileLine < 0) { - RuntimeInfo.Exit("No matching while found", true); + RuntimeInfo.Exit(ExitMessages.NoMatchingWhile, true); return; } @@ -260,7 +260,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.IsInFunction = false; if (RuntimeInfo.IsLocalSearch) { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + RuntimeInfo.Exit(ExitMessages.LabelNotFound(RuntimeInfo.SearchLabel), true); } return; @@ -270,7 +270,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.IsInFunction = false; } - RuntimeInfo.Exit("Planned termination by code", false); + RuntimeInfo.Exit(ExitMessages.PlannedTermination, false); } [Statement("abort_all", SearchMode.Exact, SpaceAround.None, ConsoleColor.Red, ExecuteInSearchMode = true)] @@ -281,7 +281,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.IsInFunction = false; if (RuntimeInfo.IsLocalSearch) { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + RuntimeInfo.Exit(ExitMessages.LabelNotFound(RuntimeInfo.SearchLabel), true); } return; @@ -291,7 +291,7 @@ internal class CodeFlowStatements : StatementRuntimeInformation RuntimeInfo.IsInFunction = false; } - RuntimeInfo.Exit("Planned termination by code. Canceling all tasks", true); + RuntimeInfo.Exit(ExitMessages.PlannedTerminationCancelingTasks, true); } [Statement("throw", SearchMode.StartOfLine, SpaceAround.End, ConsoleColor.Red)] diff --git a/YesNt.Interpreter/Statements/ConsoleStatements.cs b/YesNt.Interpreter/Statements/ConsoleStatements.cs index 50264b4..9751305 100644 --- a/YesNt.Interpreter/Statements/ConsoleStatements.cs +++ b/YesNt.Interpreter/Statements/ConsoleStatements.cs @@ -37,7 +37,7 @@ internal class ConsoleStatements : StatementRuntimeInformation string input = Console.ReadLine(); if (input is null) { - RuntimeInfo.Exit("Terminated by external process", true); + RuntimeInfo.Exit(ExitMessages.TerminatedByExternalProcess, true); return; } args = args.ReplaceFirstOccurrence("%read_line ", input.ToSafeString() + " "); diff --git a/YesNt.Interpreter/Statements/FunctionStatements.cs b/YesNt.Interpreter/Statements/FunctionStatements.cs index d975a35..3c60faf 100644 --- a/YesNt.Interpreter/Statements/FunctionStatements.cs +++ b/YesNt.Interpreter/Statements/FunctionStatements.cs @@ -15,21 +15,21 @@ internal class FunctionStatements : StatementRuntimeInformation { if (RuntimeInfo.InternalIsInFunction) { - RuntimeInfo.Exit("Nested functions are not allowed", true); + RuntimeInfo.Exit(ExitMessages.NestedFunctionsNotAllowed, true); return; } string functionDeclaration = args.Trim(); if (!functionDeclaration.EndsWith(':')) { - RuntimeInfo.Exit("Invalid syntax. Statement must end with ':'", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntaxColonRequired, true); return; } string key = NormalizeBlockName(functionDeclaration); if (string.IsNullOrWhiteSpace(key)) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -63,7 +63,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (RuntimeInfo.OutParametersStack.Count == 0) { - RuntimeInfo.Exit("No out argument in stack", true); + RuntimeInfo.Exit(ExitMessages.NoOutArgumentInStack, true); return; } @@ -87,7 +87,7 @@ internal class FunctionStatements : StatementRuntimeInformation string[] parts = args.Split(" with ", 2, StringSplitOptions.None); if (parts.Length != 2) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -118,7 +118,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (!RuntimeInfo.IsInFunction) { - RuntimeInfo.Exit("Statement not allowed outside of function", true); + RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true); return; } @@ -126,7 +126,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (RuntimeInfo.FunctionCallStack.Peek().Arguments.Count == 0) { - RuntimeInfo.Exit("No in argument in stack", true); + RuntimeInfo.Exit(ExitMessages.NoInArgumentInStack, true); return; } @@ -141,7 +141,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (!RuntimeInfo.IsInFunction) { - RuntimeInfo.Exit("Statement not allowed outside of function", true); + RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true); return; } @@ -155,7 +155,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (!RuntimeInfo.IsInFunction) { - RuntimeInfo.Exit("Statement not allowed outside of function", true); + RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true); return; } @@ -167,7 +167,7 @@ internal class FunctionStatements : StatementRuntimeInformation { if (!RuntimeInfo.IsInFunction) { - RuntimeInfo.Exit("Statement not allowed outside of function", true); + RuntimeInfo.Exit(ExitMessages.StatementNotAllowedOutsideFunction, true); return; } @@ -177,7 +177,7 @@ internal class FunctionStatements : StatementRuntimeInformation if (RuntimeInfo.IsLocalSearch) { - RuntimeInfo.Exit($"Label \"{RuntimeInfo.SearchLabel}\" not found", true); + RuntimeInfo.Exit(ExitMessages.LabelNotFound(RuntimeInfo.SearchLabel), true); } return; } @@ -195,7 +195,7 @@ internal class FunctionStatements : StatementRuntimeInformation } else { - RuntimeInfo.Exit("No function in stack", true); + RuntimeInfo.Exit(ExitMessages.NoFunctionInStack, true); } } diff --git a/YesNt.Interpreter/Statements/ListStatements.cs b/YesNt.Interpreter/Statements/ListStatements.cs index 0159181..ca4b010 100644 --- a/YesNt.Interpreter/Statements/ListStatements.cs +++ b/YesNt.Interpreter/Statements/ListStatements.cs @@ -42,7 +42,7 @@ internal class ListStatements : StatementRuntimeInformation if (!RuntimeInfo.Lists.ContainsKey(name)) { - RuntimeInfo.Exit($"List \"{name}\" not found", true); + RuntimeInfo.Exit(ExitMessages.ListNotFound(name), true); return; } @@ -100,7 +100,7 @@ internal class ListStatements : StatementRuntimeInformation if (string.IsNullOrWhiteSpace(parts[1])) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return; } @@ -213,7 +213,7 @@ internal class ListStatements : StatementRuntimeInformation string[] parts = input.Split(separator, 2, StringSplitOptions.None); if (parts.Length != 2) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return []; } @@ -222,7 +222,7 @@ internal class ListStatements : StatementRuntimeInformation if (string.IsNullOrWhiteSpace(parts[0])) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return []; } @@ -233,7 +233,7 @@ internal class ListStatements : StatementRuntimeInformation { if (!RuntimeInfo.Lists.TryGetValue(name, out list)) { - RuntimeInfo.Exit($"List \"{name}\" not found", true); + RuntimeInfo.Exit(ExitMessages.ListNotFound(name), true); return false; } @@ -245,7 +245,7 @@ internal class ListStatements : StatementRuntimeInformation string[] parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); return []; } @@ -259,13 +259,13 @@ internal class ListStatements : StatementRuntimeInformation bool success = int.TryParse(rawIndex.Trim(), out index); if (!success) { - RuntimeInfo.Exit($"\"{rawIndex}\" is not a valid index", true); + RuntimeInfo.Exit(ExitMessages.InvalidIndex(rawIndex), true); return false; } if (index < 0 || index >= maxExclusive) { - RuntimeInfo.Exit($"Index {index} out of range", true); + RuntimeInfo.Exit(ExitMessages.IndexOutOfRange(index), true); return false; } diff --git a/YesNt.Interpreter/Statements/ProcessingStatements.cs b/YesNt.Interpreter/Statements/ProcessingStatements.cs index 31c524c..3cfd3d9 100644 --- a/YesNt.Interpreter/Statements/ProcessingStatements.cs +++ b/YesNt.Interpreter/Statements/ProcessingStatements.cs @@ -23,7 +23,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation string res = Evaluator.Calculate(matches[i].Value); if (res is null) { - RuntimeInfo.Exit("Invalid operation", true); + RuntimeInfo.Exit(ExitMessages.InvalidOperation, true); return; } args = args.FromSafeString().Replace(matches[i].Value, res); @@ -65,7 +65,7 @@ internal partial class ProcessingStatements : StatementRuntimeInformation } else { - RuntimeInfo.Exit($"\"{args}\" is not a valid time-out value", true); + RuntimeInfo.Exit(ExitMessages.InvalidTimeoutValue(args), true); } } @@ -104,12 +104,12 @@ internal partial class ProcessingStatements : StatementRuntimeInformation } catch { - RuntimeInfo.Exit($"Could not load file \"{path}\"", true); + RuntimeInfo.Exit(ExitMessages.CouldNotLoadFile(path), true); } } else { - RuntimeInfo.Exit($"Could not find file \"{path}\"", true); + RuntimeInfo.Exit(ExitMessages.CouldNotFindFile(path), true); } } diff --git a/YesNt.Interpreter/Statements/StringLiteralStatements.cs b/YesNt.Interpreter/Statements/StringLiteralStatements.cs index c2d0971..e502a30 100644 --- a/YesNt.Interpreter/Statements/StringLiteralStatements.cs +++ b/YesNt.Interpreter/Statements/StringLiteralStatements.cs @@ -53,7 +53,7 @@ internal class StringLiteralStatements : StatementRuntimeInformation if (!closed) { - RuntimeInfo.Exit("Invalid string literal", true); + RuntimeInfo.Exit(ExitMessages.InvalidStringLiteral, true); return; } diff --git a/YesNt.Interpreter/Statements/SystemStatements.cs b/YesNt.Interpreter/Statements/SystemStatements.cs index a5d148d..31ccda0 100644 --- a/YesNt.Interpreter/Statements/SystemStatements.cs +++ b/YesNt.Interpreter/Statements/SystemStatements.cs @@ -33,11 +33,11 @@ internal class SystemStatements : StatementRuntimeInformation } catch (FileNotFoundException) { - RuntimeInfo.Exit($"Cannot find file \"{parts[0]}\".", false); + RuntimeInfo.Exit(ExitMessages.CannotFindFile(parts[0]), false); } catch (Win32Exception ex) { - RuntimeInfo.Exit($"Failed to start \"{parts[0]}\". {ex.Message}", false); + RuntimeInfo.Exit(ExitMessages.FailedToStart(parts[0], ex.Message), false); } // HACK: Clear line to avoid execution from another "exec" statement. @@ -53,11 +53,11 @@ internal class SystemStatements : StatementRuntimeInformation } catch (FileNotFoundException) { - RuntimeInfo.Exit($"Cannot find file \"{input}\".", false); + RuntimeInfo.Exit(ExitMessages.CannotFindFile(input), false); } catch (Win32Exception ex) { - RuntimeInfo.Exit($"Failed to start \"{input}\". {ex.Message}", false); + RuntimeInfo.Exit(ExitMessages.FailedToStart(input, ex.Message), false); } } diff --git a/YesNt.Interpreter/Statements/VariableStatements.cs b/YesNt.Interpreter/Statements/VariableStatements.cs index b4cf6d8..f07f640 100644 --- a/YesNt.Interpreter/Statements/VariableStatements.cs +++ b/YesNt.Interpreter/Statements/VariableStatements.cs @@ -17,7 +17,7 @@ internal partial class VariableStatements : StatementRuntimeInformation string key = parts[0].Trim(); if (key.Contains(' ')) { - RuntimeInfo.Exit("Invalid Syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); } if (RuntimeInfo.Variables.ContainsKey(key)) @@ -31,7 +31,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } else { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); } } @@ -44,7 +44,7 @@ internal partial class VariableStatements : StatementRuntimeInformation string key = parts[0].Trim(); if (key.Contains(' ')) { - RuntimeInfo.Exit("Invalid Syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); } if (RuntimeInfo.GlobalVariables.ContainsKey(key)) @@ -58,7 +58,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } else { - RuntimeInfo.Exit("Invalid syntax", true); + RuntimeInfo.Exit(ExitMessages.InvalidSyntax, true); } } @@ -77,7 +77,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } else { - RuntimeInfo.Exit($"Variable \"{key}\" not found", true); + RuntimeInfo.Exit(ExitMessages.VariableNotFound(key), true); } } @@ -109,7 +109,7 @@ internal partial class VariableStatements : StatementRuntimeInformation } else if (!RuntimeInfo.IsSearching) { - RuntimeInfo.Exit($"Variable \"{varName}\" not found", true); + RuntimeInfo.Exit(ExitMessages.VariableNotFound(varName), true); return; } } From ac78afc12f72662fdc18b9fdc9a7d516a6ddc31b Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:34:45 +0100 Subject: [PATCH 42/73] Fix warnings --- YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 932735d..02a3f65 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -17,8 +17,8 @@ public class YesNtInterpreter public event Action OnDebugOutput; private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); - private Dictionary> statements = []; - private List> staticStatements = []; + private Dictionary> statements; + private readonly List> staticStatements; public ReadOnlyCollection StatementInformation { @@ -65,7 +65,7 @@ public class YesNtInterpreter GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements); runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); - runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); + runtimeInfo.OnLineExecuted += e => OnLineExecuted?.Invoke(e); } public void Stop() From a2f621d3f4fe2c05d50a1386b9db31a35ed4622b Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:44:06 +0100 Subject: [PATCH 43/73] Add XML docs --- .../Attributes/StatementAttribute.cs | 57 ++++++++++++++ .../Attributes/StaticStatementAttribute.cs | 19 +++++ YesNt.Interpreter/Enums/Priority.cs | 16 ++++ YesNt.Interpreter/Enums/SearchMode.cs | 10 +++ YesNt.Interpreter/Enums/SpaceAround.cs | 10 +++ YesNt.Interpreter/Runtime/DebugEventArgs.cs | 21 +++++ YesNt.Interpreter/Runtime/ExitMessages.cs | 4 + YesNt.Interpreter/Runtime/FunctionScope.cs | 15 ++++ YesNt.Interpreter/Runtime/Line.cs | 6 ++ .../Runtime/RuntimeInformation.cs | 7 ++ .../Runtime/StatementInformation.cs | 20 +++++ .../Runtime/StatementRuntimeInfo.cs | 10 +++ YesNt.Interpreter/Runtime/YesNtInterpreter.cs | 77 +++++++++++++++++++ YesNt.Interpreter/Utilities/Evaluator.cs | 18 +++++ YesNt.Interpreter/Utilities/FixedProcess.cs | 8 ++ .../Utilities/StringExtentions.cs | 49 ++++++++++++ 16 files changed, 347 insertions(+) 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; From 163bf677ae1575fe5f7ad499bdb30bd3b9b0bcb0 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:02:40 +0100 Subject: [PATCH 44/73] Add line formatting with auto indentation and fix syntax highlighting in editor --- YesNt.CodeEditor/Editor.cs | 112 ++++++++++++++++++++++++++ YesNt.CodeEditor/InputHandler.cs | 64 +++++++++------ YesNt.CodeEditor/SyntaxHighlighter.cs | 9 ++- 3 files changed, 158 insertions(+), 27 deletions(-) diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs index 6bd5b21..6fead3e 100644 --- a/YesNt.CodeEditor/Editor.cs +++ b/YesNt.CodeEditor/Editor.cs @@ -203,6 +203,118 @@ internal class TextEditor return padding; } + public void FormatLines() + { + const int indentationSize = 4; + List blockStack = []; + + for (int i = 0; i < Lines.Count; i++) + { + string trimmed = Lines[i].Trim(' '); + if (string.IsNullOrWhiteSpace(trimmed)) + { + Lines[i] = string.Empty; + continue; + } + + if (trimmed.StartsWith('#')) + { + Lines[i] = trimmed; + continue; + } + + if (trimmed == "else:") + { + for (int j = blockStack.Count - 1; j >= 0; j--) + { + if (blockStack[j] is "if" or "else") + { + blockStack.RemoveAt(j); + break; + } + } + } + bool isTerminatingStatement = trimmed == "exit" + || trimmed.StartsWith("throw ", StringComparison.Ordinal) + || trimmed.StartsWith("error ", StringComparison.Ordinal); + bool closesFunctionBlock = isTerminatingStatement && blockStack.Count > 0 && blockStack[^1] == "func"; + + if (trimmed == "end_if") + { + for (int j = blockStack.Count - 1; j >= 0; j--) + { + if (blockStack[j] is "if" or "else") + { + blockStack.RemoveAt(j); + break; + } + } + } + else if (trimmed == "end_while") + { + for (int j = blockStack.Count - 1; j >= 0; j--) + { + if (blockStack[j] == "while") + { + blockStack.RemoveAt(j); + break; + } + } + } + else if (trimmed == "return") + { + for (int j = blockStack.Count - 1; j >= 0; j--) + { + if (blockStack[j] == "func") + { + blockStack.RemoveAt(j); + break; + } + } + } + + int lineIndentation = closesFunctionBlock ? Math.Max(0, blockStack.Count - 1) : blockStack.Count; + Lines[i] = new string(' ', lineIndentation * indentationSize) + trimmed; + + if (!isTerminatingStatement && ( + (trimmed.StartsWith("if ", StringComparison.Ordinal) && trimmed.EndsWith(':')) + || (trimmed.StartsWith("while ", StringComparison.Ordinal) && trimmed.EndsWith(':')) + || (trimmed.StartsWith("func ", StringComparison.Ordinal) && trimmed.EndsWith(':')) + || trimmed == "else:")) + { + if (trimmed.StartsWith("if ", StringComparison.Ordinal)) + { + blockStack.Add("if"); + } + else if (trimmed.StartsWith("while ", StringComparison.Ordinal)) + { + blockStack.Add("while"); + } + else if (trimmed.StartsWith("func ", StringComparison.Ordinal)) + { + blockStack.Add("func"); + } + else + { + blockStack.Add("else"); + } + } + + if (isTerminatingStatement) + { + while (blockStack.Count > 0 && blockStack[^1] != "func") + { + blockStack.RemoveAt(blockStack.Count - 1); + } + + if (closesFunctionBlock && blockStack.Count > 0 && blockStack[^1] == "func") + { + blockStack.RemoveAt(blockStack.Count - 1); + } + } + } + } + private static string ToLiteral(string input) { return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(input, false); diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index bf24a9e..5eb365d 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -47,6 +47,12 @@ internal class InputHandler(TextEditor textEditor) case ConsoleKey.E: textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0; return true; + + case ConsoleKey.F: + textEditor.FormatLines(); + textEditor.Display(true); + WriteStatus("Formatted!"); + return true; } } if (keyInfo.Key == ConsoleKey.DownArrow) @@ -274,6 +280,11 @@ internal class InputHandler(TextEditor textEditor) WriteStatus(string.Empty); break; + case "format": + textEditor.FormatLines(); + WriteStatus("Formatted!"); + break; + case "exit": return false; @@ -331,30 +342,37 @@ internal class InputHandler(TextEditor textEditor) private void ExecuteWithDebugScreen(string saveInput, bool debugMode, bool stepMode) { - if (textEditor.Save(saveInput, true)) + string[] parts = saveInput.Split(' ', StringSplitOptions.RemoveEmptyEntries); + bool hasPathArgument = parts.Length > 1; + bool canRunUnsavedBuffer = !hasPathArgument && string.IsNullOrWhiteSpace(textEditor.CurrentPath); + + bool canExecute = canRunUnsavedBuffer || textEditor.Save(saveInput, true); + if (!canExecute) { - textEditor.EditMode = Mode.Debug; - textEditor.IsStepDebugMode = stepMode; - Console.Clear(); - Console.CursorVisible = true; - - if (debugMode) - { - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, true); - } - else - { - textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath); - } - - while (Console.KeyAvailable) - { - _ = Console.ReadKey(true); - } - _ = Console.ReadKey(); - WriteStatus(string.Empty); - textEditor.IsStepDebugMode = false; - textEditor.EditMode = Mode.Command; + return; } + + textEditor.EditMode = Mode.Debug; + textEditor.IsStepDebugMode = stepMode; + Console.Clear(); + Console.CursorVisible = true; + + if (canRunUnsavedBuffer) + { + textEditor.YesNtInterpreter.Execute([.. textEditor.Lines], debugMode); + } + else + { + textEditor.YesNtInterpreter.Execute(textEditor.CurrentPath, debugMode); + } + + while (Console.KeyAvailable) + { + _ = Console.ReadKey(true); + } + _ = Console.ReadKey(); + WriteStatus(string.Empty); + textEditor.IsStepDebugMode = false; + textEditor.EditMode = Mode.Command; } } diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs index 7b8daa1..8db25f7 100644 --- a/YesNt.CodeEditor/SyntaxHighlighter.cs +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -30,7 +30,7 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection statement.Name.Trim() }; input = input.TrimEnd(' '); - if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) + string inputTrim = input.Trim(' '); + if (statement.SearchMode == SearchMode.StartOfLine && inputTrim.StartsWith(name)) { if (statement.Separator is not null && input.Contains(statement.Separator)) { @@ -78,7 +79,7 @@ internal partial class SyntaxHighlighter(ReadOnlyCollection Date: Wed, 4 Mar 2026 23:33:08 +0100 Subject: [PATCH 45/73] Add proper docs --- README.md | 16 +- YesNt.CodeEditor/InputHandler.cs | 12 +- YesNt.Interpreter/Utilities/Evaluator.cs | 3 +- docs/README.md | 47 ++ docs/editor.md | 52 ++ docs/language-reference.md | 811 +++++++++++++++++++++++ docs/library-api.md | 265 ++++++++ 7 files changed, 1198 insertions(+), 8 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/editor.md create mode 100644 docs/language-reference.md create mode 100644 docs/library-api.md diff --git a/README.md b/README.md index bf67f69..59da794 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # YesNt - + > YesNt is a imperative and interpreted language inspired by the Assembly language. -## Syntax +## Documentation -Current language syntax is documented in [SYNTAX_V2.md](SYNTAX_V2.md). +| Document | Description | +| -------------------------------------------------------- | ----------------------------------------- | +| [docs/README.md](docs/README.md) | Getting started | +| [docs/language-reference.md](docs/language-reference.md) | Full language reference | +| [docs/library-api.md](docs/library-api.md) | Embedding the interpreter as a C# library | +| [docs/editor.md](docs/editor.md) | Using the terminal code editor | -Example: +## Quick example ```ynt var name = world @@ -16,6 +21,5 @@ print_line Hello ${name} ## Run ```bash -dotnet run --project YesNt.Interpreter -- path/to/script.ynt +dotnet run --project YesNt.Interpreter.App -- path/to/script.ynt ``` - diff --git a/YesNt.CodeEditor/InputHandler.cs b/YesNt.CodeEditor/InputHandler.cs index 5eb365d..b937493 100644 --- a/YesNt.CodeEditor/InputHandler.cs +++ b/YesNt.CodeEditor/InputHandler.cs @@ -48,6 +48,14 @@ internal class InputHandler(TextEditor textEditor) textEditor.CursorPosition.X = textEditor.Lines.Count > textEditor.CursorPosition.Y ? textEditor.Lines[textEditor.CursorPosition.Y].TrimEnd().Length : 0; return true; + case ConsoleKey.R: + ExecuteWithDebugScreen("run", false, false); + return true; + + case ConsoleKey.D: + ExecuteWithDebugScreen("debug", true, false); + return true; + case ConsoleKey.F: textEditor.FormatLines(); textEditor.Display(true); @@ -352,6 +360,8 @@ internal class InputHandler(TextEditor textEditor) return; } + Mode previousMode = textEditor.EditMode; + textEditor.EditMode = Mode.Debug; textEditor.IsStepDebugMode = stepMode; Console.Clear(); @@ -373,6 +383,6 @@ internal class InputHandler(TextEditor textEditor) _ = Console.ReadKey(); WriteStatus(string.Empty); textEditor.IsStepDebugMode = false; - textEditor.EditMode = Mode.Command; + textEditor.EditMode = previousMode; } } diff --git a/YesNt.Interpreter/Utilities/Evaluator.cs b/YesNt.Interpreter/Utilities/Evaluator.cs index 427731a..339e1b5 100644 --- a/YesNt.Interpreter/Utilities/Evaluator.cs +++ b/YesNt.Interpreter/Utilities/Evaluator.cs @@ -81,7 +81,8 @@ internal static partial class Evaluator /// /// Evaluates a numeric arithmetic expression string and returns the result as a string. - /// Supports +, -, *, and / operators. + /// Supports +, -, *, /, % (modulo), and ^ (power) operators + /// with standard precedence (^ highest, +/- lowest) and parentheses. /// Adjacent sign characters (++, --, -+, +-) are normalised before evaluation. /// /// The arithmetic expression to evaluate. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3076695 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,47 @@ +# YesNt Documentation + +YesNt is a line-based, interpreted scripting language inspired by assembly. +Each line is one statement. There are no multi-line expressions. + +## Guides + +| Document | Description | +| ------------------------------------------- | ---------------------------------------------------- | +| [Language Reference](language-reference.md) | Every statement, token, and operator in the language | +| [Library API](library-api.md) | How to embed the interpreter in a C# project | +| [Editor](editor.md) | How to use the terminal code editor | + +## Quick start + +### Running a script from the command line + +```bash +dotnet run --project YesNt.Interpreter.App -- path/to/script.ynt +``` + +### Hello world + +```ynt +print_line Hello, world! +``` + +### Variables and output + +```ynt +var name = Alice +print_line Hello, ${name}! +``` + +### Functions + +```ynt +func greet: + var msg = Hello, ${name}! + print_line ${msg} +return + +var name = Bob +call greet +``` + +Script files use the `.ynt` extension by convention. diff --git a/docs/editor.md b/docs/editor.md new file mode 100644 index 0000000..0253100 --- /dev/null +++ b/docs/editor.md @@ -0,0 +1,52 @@ +# YesNt Code Editor + +`YesNt.CodeEditor` is a terminal editor for writing, formatting, running, and debugging YesNt scripts. + +## Start the editor + +```bash +dotnet run --project YesNt.CodeEditor -- [optional-path-to-file.ynt] +``` + +If you pass a file path, it is loaded on startup. + +## Modes + +- **Command mode:** enter editor commands in the `>>>` prompt. +- **Edit mode:** direct text editing with keyboard navigation. +- **Debug mode:** script output/debug information while running. + +## Command mode commands + +| Command | Description | +| --------------------- | --------------------------------------------------------- | +| `edit` | Switch to edit mode | +| `line ` | Jump to line `n` and switch to edit mode | +| `save [path]` | Save to current path or a new path | +| `load ` | Load a file | +| `new` | Create new file | +| `format` | Auto-format indentation | +| `run [path]` | Run script | +| `debug [path] [step]` | Run in debug mode (`step` enables step-by-step execution) | +| `exit` | Close the editor | + +## Edit mode controls + +- Arrow keys: move cursor +- Enter: split line +- Backspace/Delete: remove characters/merge lines +- **Alt+C:** return to command mode +- **Alt+T:** jump to top +- **Alt+B:** jump to bottom +- **Alt+S:** jump to start of line +- **Alt+E:** jump to end of line +- **Alt+R:** run script +- **Alt+D:** run debug mode +- **Alt+F:** format current file + +## Formatter behavior (quick summary) + +- Indents `func`, `if`, `else`, and `while` blocks. +- Dedents on `return`, `end_if`, and `end_while`. +- `exit` / `throw` / `error` close active non-function blocks for following lines. +- Comment lines (`# ...`) are kept unindented. diff --git a/docs/language-reference.md b/docs/language-reference.md new file mode 100644 index 0000000..834e207 --- /dev/null +++ b/docs/language-reference.md @@ -0,0 +1,811 @@ +# YesNt Language Reference + +YesNt is a line-based scripting language. Every non-empty, non-comment line is one statement. +Execution proceeds top-to-bottom unless a control-flow statement changes the line counter. + +--- + +## Table of contents + +1. [Basic rules](#basic-rules) +2. [Comments](#comments) +3. [String literals](#string-literals) +4. [Variables](#variables) +5. [Console I/O](#console-io) +6. [Arithmetic](#arithmetic) +7. [Conditions](#conditions) +8. [Control flow](#control-flow) +9. [Functions](#functions) +10. [Lists](#lists) +11. [Processing](#processing) +12. [System](#system) +13. [Predefined tokens](#predefined-tokens) +14. [Termination](#termination) + +--- + +## Basic rules + +- Lines are trimmed of leading and trailing whitespace before execution. +- Blank lines are silently skipped. +- Lines starting with `#` are comments and are silently skipped. +- Variable interpolation uses `${name}` and is evaluated before the statement runs. +- Special characters inside string literals are encoded internally and decoded on output — this is transparent to scripts. + +--- + +## Comments + +```ynt +# This is a comment. +print_line Hello # inline comments are NOT supported — everything after print_line is the argument +``` + +Only whole-line comments (lines whose first non-whitespace character is `#`) are supported. + +--- + +## String literals + +Double-quoted strings protect their content from keyword matching and allow escape sequences. + +```ynt +print_line "Hello, world!" +print_line "Line one\nLine two" +print_line "She said \"hi\"" +``` + +| Escape | Meaning | +| ------ | -------------------- | +| `\n` | Newline | +| `\r` | Carriage return | +| `\t` | Horizontal tab | +| `\"` | Literal double-quote | +| `\\` | Literal backslash | + +Variable interpolation (`${name}`) is **not** evaluated inside string literals — the braces +and content are passed through verbatim. + +```ynt +var x = world +print_line "${x}" # prints the literal text: ${x} +print_line "hello " ${x} # prints: hello world (interpolation outside the literal) +``` + +--- + +## Variables + +### Local variables — `var` + +``` +var = +``` + +Defines or updates a variable scoped to the current function (or the top level if called outside a function). +The value is everything after `=`, trimmed. + +```ynt +var count = 0 +var greeting = Hello, world! +``` + +Variable names may only contain letters and digits (`[a-zA-Z0-9]`). + +### Global variables — `global` + +``` +global = +``` + +Defines or updates a variable that is visible across all function scopes and background tasks. + +```ynt +global total = 100 +``` + +### Reading a variable — `${name}` + +`${name}` is an inline token that is replaced with the variable's value before the statement executes. +It can appear anywhere in a line and multiple occurrences are replaced left to right. +Local variables are checked first; if not found, the global table is checked. + +```ynt +var a = 5 +var b = 10 +print_line ${a} plus ${b} +``` + +### Deleting a variable — `delete` + +``` +delete +``` + +Removes the variable. Local scope is checked first; if not found, the global table is used. +Raises an error if the variable does not exist in either scope. + +```ynt +var temp = scratch +delete temp +``` + +--- + +## Console I/O + +### Print with newline — `print_line` + +``` +print_line +print_line +``` + +Writes `` followed by a newline. With no argument, writes a blank line. + +```ynt +print_line Hello! +print_line +print_line Done. +``` + +### Print without newline — `print` + +``` +print +``` + +Writes `` without a trailing newline. + +```ynt +print Enter your name: +var name = %read_line +print_line Hello, ${name}! +``` + +### Read a line of input — `%read_line` + +`%read_line` is an inline token that is replaced with one line of text read from standard input. + +```ynt +var answer = %read_line +print_line You typed: ${answer} +``` + +### Read a single key — `%read_key` + +`%read_key` is an inline token that is replaced with the single character pressed by the user (no Enter required). + +```ynt +print Press any key... +var key = %read_key +print_line You pressed: ${key} +``` + +### Clear the console — `clear` + +``` +clear +``` + +Clears the console window. + +--- + +## Arithmetic + +Arithmetic is a **postfix** modifier applied at the end of a line with the `calc` keyword. + +``` + calc +``` + +Any numeric sub-expression matching the pattern `number op number [op number …]` is evaluated +and replaced with the result. Supported operators (highest to lowest precedence): + +| Operator | Operation | +| -------- | ----------------------------- | +| `(…)` | Parentheses (evaluated first) | +| `^` | Exponentiation | +| `%` | Modulo | +| `/` | Division | +| `*` | Multiplication | +| `-` | Subtraction | +| `+` | Addition (lowest precedence) | + +Adjacent sign characters (`++`, `--`, `-+`, `+-`) are normalised before evaluation. + +```ynt +var x = 3 +var y = 4 +var sum = ${x} + ${y} calc # 7 +var expr = 2 + 3 * 4 calc # 14 (* before +) +var parens = (2 + 3) * 4 calc # 20 +var power = 2 ^ 10 calc # 1024 +var remainder = 17 % 5 calc # 2 +``` + +--- + +## Conditions + +Conditions are used in `if` and `while` statements. A condition is a string of the form: + +``` + +``` + +| Operator | Meaning | +| -------- | --------------------------------------------- | +| `==` | Equal (string comparison, case-sensitive) | +| `!=` | Not equal (string comparison, case-sensitive) | +| `<` | Less than (numeric) | +| `>` | Greater than (numeric) | +| `<=` | Less than or equal (numeric) | +| `>=` | Greater than or equal (numeric) | + +Numeric comparisons (`<`, `>`, `<=`, `>=`) parse both sides with +culture-invariant decimal rules (`.` or `,` as decimal separator). + +A bare value of `True` or `False` (case-insensitive) is also a valid condition. + +```ynt +var x = 10 +if ${x} > 5: + print_line x is greater than 5 +end_if +``` + +--- + +## Control flow + +### If / else / end_if + +``` +if : + +else: + +end_if +``` + +`else:` is optional. `if` / `else:` / `end_if` blocks can be nested. + +```ynt +var score = 75 +if ${score} >= 60: + print_line Pass +else: + print_line Fail +end_if +``` + +### While loop + +``` +while : + +end_while +``` + +The condition is checked before each iteration. `while` / `end_while` blocks can be nested. + +```ynt +var i = 1 +while ${i} <= 5: + print_line ${i} + var i = ${i} + 1 calc +end_while +``` + +### Labels and goto + +``` +label : +goto +``` + +`label` marks a target. `goto` performs an unconditional jump to that label. +Labels are scoped to the current function; you cannot jump to a label outside the calling function. + +```ynt +label loop: + print_line tick + goto loop +``` + +### Conditional goto + +``` +if goto