From c7aba542b35675742b1bf54796eda4987698ea4d Mon Sep 17 00:00:00 2001 From: Mitchell Paulus Date: Tue, 17 Sep 2024 09:45:57 -0500 Subject: [PATCH] Implementing standard input redirection --- mshell/Program.cs | 70 ++++++++++++++++++++++---- mshell/tests/stdin_for_test.txt | 2 + mshell/tests/stdin_redirect.msh | 7 +++ mshell/tests/stdin_redirect.msh.stdout | 2 + 4 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 mshell/tests/stdin_for_test.txt create mode 100644 mshell/tests/stdin_redirect.msh create mode 100644 mshell/tests/stdin_redirect.msh.stdout diff --git a/mshell/Program.cs b/mshell/Program.cs index a41921f..395af66 100755 --- a/mshell/Program.cs +++ b/mshell/Program.cs @@ -838,6 +838,35 @@ public EvalResult Evaluate(List tokens, Stack stack, Exe } } + else if (t.TokenType == TokenType.LESSTHAN) + { + // This can either be normal comparison for numbers, or it's a redirect on a list. + index++; + if (stack.Count < 2) return FailWithMessage($"'{t.RawText}' operator requires at least two objects on the stack. Found {stack.Count} object.\n"); + + var arg1 = stack.Pop(); + var arg2 = stack.Pop(); + + if (arg1.IsNumeric() && arg2.IsNumeric()) + { + _push(new MShellBool(arg2.FloatNumeric() < arg1.FloatNumeric()), stack); + } + else if (arg1.TryPickString(out var s) && arg2.TryPickList(out var list)) + { + list.StandardInputFile = s.Content; + // Push the list back on the stack + _push(list, stack); + } + else if (arg1.TryPickString(out s) && arg2.TryPickQuotation(out var quotation)) + { + quotation.StandardInputFile = s.Content; + _push(quotation, stack); + } + else + { + return FailWithMessage($"Currently only implemented redirection for '{t.RawText}' operator or numerics.\n"); + } + } else if (t.TokenType == TokenType.PIPE) { index++; @@ -867,6 +896,8 @@ public EvalResult Evaluate(List tokens, Stack stack, Exe if (o.TryPickQuotation(out var quotation)) { var evalResult = Evaluate(quotation.Tokens, stack, quotation.Context); + if (quotation.Context.StandardInput is not null) quotation.Context.StandardInput.Dispose(); + if (!evalResult.Success) return evalResult; if (evalResult.BreakNum != -1) return evalResult; } @@ -901,6 +932,7 @@ private void ExecuteQuotation(MShellQuotation q) public (EvalResult, int) RunProcess(MShellList list, ExecuteContext context) { + if (list.Items.Any(o => !o.IsCommandLineable())) { var badTypes = list.Items.Where(o => !o.IsCommandLineable()); @@ -918,7 +950,7 @@ private void ExecuteQuotation(MShellQuotation q) FileName = arguments[0], UseShellExecute = false, RedirectStandardError = false, - RedirectStandardInput = list.StandardInputFile is not null, + RedirectStandardInput = list.StandardInputFile is not null || context.StandardInput is not null, RedirectStandardOutput = list.StandardOutFile is not null || context.StandardOutput is not null, CreateNoWindow = true, }; @@ -954,8 +986,14 @@ private void ExecuteQuotation(MShellQuotation q) if (list.StandardInputFile is not null) { - using StreamWriter w = p.StandardInput; - w.Write(File.ReadAllBytes(list.StandardInputFile)); + using FileStream s = new FileStream(list.StandardInputFile, FileMode.Open); + s.CopyTo(p.StandardInput.BaseStream); + p.StandardInput.Close(); + } + else if (context.StandardInput is not null) + { + context.StandardInput.CopyTo(p.StandardInput.BaseStream); + p.StandardInput.Close(); } p.WaitForExit(); @@ -1003,7 +1041,7 @@ private void ExecuteQuotation(MShellQuotation q) var firstProcessStartInfo = new ProcessStartInfo() { FileName = listItems[0].Items[0].CommandLine(), - RedirectStandardInput = firstList.StandardInputFile is not null, + RedirectStandardInput = firstList.StandardInputFile is not null || context.StandardInput is not null, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, @@ -1054,6 +1092,13 @@ private void ExecuteQuotation(MShellQuotation q) { using FileStream s = new(stdinFile, FileMode.Open); s.CopyTo(processes[0].StandardInput.BaseStream); + processes[0].StandardInput.BaseStream.Close(); + } + else if (context.StandardInput is not null) + { + // using FileStream fs = new(context.StandardInput, FileMode.Open); + context.StandardInput.CopyTo(processes[0].StandardInput.BaseStream); + processes[0].StandardInput.BaseStream.Close(); } for (int i = 0; i < processes.Count - 1; i++) @@ -1497,11 +1542,18 @@ public class MShellQuotation public string? StandardInputFile { get; set; } = null; public string? StandardOutputFile { get; set; }= null; - public ExecuteContext Context => new() + public ExecuteContext Context { - StandardOutput = StandardOutputFile, - StandardInput = StandardInputFile, - }; + get + { + Stream? stdIn = (StandardInputFile is null) ? null : new FileStream(StandardInputFile, FileMode.Open); + return new ExecuteContext + { + StandardOutput = StandardOutputFile, + StandardInput = stdIn, + }; + } + } public MShellQuotation(List tokens, int startIndex, int endIndexExc) { @@ -1606,7 +1658,7 @@ public EvalResult(bool success, int breakNum) } public class ExecuteContext() { - public string? StandardInput { get; set; } = null; + public Stream? StandardInput { get; set; } = null; public string? StandardOutput { get; set; } = null; public override string ToString() => $"stdin: '{StandardInput}'\nstdout: '{StandardOutput}'\n"; diff --git a/mshell/tests/stdin_for_test.txt b/mshell/tests/stdin_for_test.txt new file mode 100644 index 0000000..3bddb2b --- /dev/null +++ b/mshell/tests/stdin_for_test.txt @@ -0,0 +1,2 @@ +Hello, +World! diff --git a/mshell/tests/stdin_redirect.msh b/mshell/tests/stdin_redirect.msh new file mode 100644 index 0000000..80dab6f --- /dev/null +++ b/mshell/tests/stdin_redirect.msh @@ -0,0 +1,7 @@ +[grep "World"] "stdin_for_test.txt" < ; + +( + [grep "Hello"]; + # We shouldn't get a World because the first grep should have consumed all the stdin. + [grep "World"]; +) "stdin_for_test.txt" < x diff --git a/mshell/tests/stdin_redirect.msh.stdout b/mshell/tests/stdin_redirect.msh.stdout new file mode 100644 index 0000000..0c07446 --- /dev/null +++ b/mshell/tests/stdin_redirect.msh.stdout @@ -0,0 +1,2 @@ +World! +Hello,