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