From 17d4a3672b3e54caad94a573db49f1982c75e8df Mon Sep 17 00:00:00 2001 From: Brychan Dempsey Date: Wed, 24 Mar 2021 13:40:16 +1300 Subject: [PATCH] Cleaned up some code, Fixed append operator requirement (Closes #10) --- Assignment 1/Program.cs | 357 ++++++++++++++++++++-------------------- 1 file changed, 181 insertions(+), 176 deletions(-) diff --git a/Assignment 1/Program.cs b/Assignment 1/Program.cs index 21fb665..cee9eb8 100644 --- a/Assignment 1/Program.cs +++ b/Assignment 1/Program.cs @@ -35,8 +35,6 @@ namespace Assignment_1 '\'' }; - static bool nonStrict = false; - static void Main(string[] args) { Console.WriteLine("┌──────────────────────────────────────────┐"); @@ -50,20 +48,14 @@ namespace Assignment_1 MemoryStream sourceStream = new(1024); Parser parser = new(); bool dynamicInput = false; - /*foreach (var arg in args) - { - if (arg == "-ns") - { - nonStrict = true; - } - }*/ // From https://stackoverflow.com/questions/3453220/how-to-detect-if-console-in-stdin-has-been-redirected // Reading from pipes is equivalent to reading user input, though the input is redirected if (Console.IsInputRedirected) { // To simplify reading, we read all input bytes from the piped input to the stream. - // This is by far not the best way to do it; reading line-by-line would reduce memory space, - // but it allows a simple read into the console + // Not the best way to do it; we don't need to keep any data that has already been read. + // Whilst the stream could be copied excluding already parsed data at each input step, this would rely + // on GC to cleanup afterwards sourceStream.Write(Encoding.UTF8.GetBytes(Console.In.ReadToEnd())); // Dispose will close a piped input, or piped file in further iterations of the program Console.In.Dispose(); @@ -157,174 +149,159 @@ namespace Assignment_1 public void StartParsing(Stream source, bool dynamicInput = false) { long initSourceLength = source.Length; - //if (nonStrict || PeekChar(source) == '{') - if (true) + long lastLinePos = 0; + long initPos = 0; + bool cont = false; + while (true) { - /*if (PeekChar(source) == '{') + if (dynamicInput) { - source.ReadByte(); - }*/ - long lastLinePos = 0; - long initPos = 0; - bool cont = false; - while (true) - { - if (dynamicInput) - { - lastLinePos = source.Position; - if (!cont) - { - Console.WriteLine("Enter a command: "); - } - string s = Console.ReadLine(); - long pos = source.Position; - source.Write(Encoding.UTF8.GetBytes(s)); - source.Position = pos; - - } - // parse the statement or list of statements; - // This is done by reading the next word + lastLinePos = source.Position; if (!cont) { - initPos = source.Position; + Console.WriteLine("Enter a command: "); + } + string s = Console.ReadLine(); + long pos = source.Position; + source.Write(Encoding.UTF8.GetBytes(s)); + source.Position = pos; + + } + // parse the statement or list of statements; + // This is done by reading the next word + if (!cont) + { + initPos = source.Position; + } + else + { + source.Position = initPos; + } + SkipWhitespace(source); + long position = FindNextWord(source, out string word); + try + { + if (Enum.TryParse(typeof(Statements), word, out object statementType)) + { + // By turning the result of the command into an action, + // we can defer processing the final result until the end of this control flow + Action result = () => { }; + source.Position = position; + switch ((Statements)statementType) + { + case Statements.exit: + result = Exit(source, initSourceLength, dynamicInput); + break; + case Statements.append: + result = AppendSet(source); + break; + case Statements.list: + long pos = FindNextWord(source, out string nextWord); + if (nextWord == "all") + { + source.Position = pos; + result = List(true); + } + else + { + result = List(); + } + break; + case Statements.print: + result = Print(source, 0); + break; + case Statements.printlength: + result = Print(source, 1); + break; + case Statements.printwords: + result = Print(source, 2); + break; + case Statements.printwordcount: + result = Print(source, 3); + break; + case Statements.set: + result = AppendSet(source, false); + break; + case Statements.reverse: + result = Reverse(source); + break; + // These are additional helper functions. Thier input gets excluded from the MemoryStream + case Statements.h: + Console.WriteLine("Commands are: "); + foreach (var item in Enum.GetValues(typeof(Statements))) + { + Console.WriteLine("\t{0}", ((Statements)item).ToString()); + } + // Ignore these as actual commands + source.Position = initPos; + source.SetLength(initPos); + break; + case Statements.writeout: + // Writes the full command history to the stream. + Console.WriteLine("Writing input commands to {0}..."); + source.Position = initPos; + source.SetLength(initPos); + break; + } + // Do a check semicolons etc + if (IsNextEoS(source)) + { + // Increment the source pos past the semi-colon + cont = false; + source.Position++; + if (dynamicInput) + { + // Nicely format the output stream, so we may print it cleanly + source.WriteByte((byte)'\n'); + } + result(); + if (((Statements)statementType).Equals(Statements.exit)) + { + return; + } + } + else if (source.Position != lastLinePos - 1) + { + // In the case that we expect some more data, we must keep tabs of our current line, and keep accumulating data until we're finished + lastLinePos = source.Position; + cont = true; + source.WriteByte((byte)' '); + Console.Write(">"); + } + else + { + throw new ParserException("expected a semi-colon", 0, source.Position); + } } else { - source.Position = initPos; - } - SkipWhitespace(source); - long position = FindNextWord(source, out string word); - try - { - if (Enum.TryParse(typeof(Statements), word, out object statementType)) - { - // By turning the result of the command into an action, - // we can defer processing the final result until the end of this control flow - // I.e. "I don't know what action to do, but I will need it, when I know where this statement ends" - - // In some ways, it makes more sense. The action is determined by the interpreter's result - Action result = () => { }; - source.Position = position; - switch ((Statements)statementType) - { - case Statements.exit: - result = Exit(source, initSourceLength, dynamicInput); - break; - case Statements.append: - result = AppendSet(source); - break; - case Statements.list: - long pos = FindNextWord(source, out string nextWord); - if (nextWord == "all") - { - source.Position = pos; - result = List(true); - } - else - { - result = List(); - } - break; - case Statements.print: - result = Print(source, 0); - break; - case Statements.printlength: - result = Print(source, 1); - break; - case Statements.printwords: - result = Print(source, 2); - break; - case Statements.printwordcount: - result = Print(source, 3); - break; - case Statements.set: - result = AppendSet(source, false); - break; - case Statements.reverse: - result = Reverse(source); - break; - // These are additional helper functions. Thier input gets excluded from the MemoryStream - case Statements.h: - Console.WriteLine("Commands are: "); - foreach (var item in Enum.GetValues(typeof(Statements))) - { - Console.WriteLine("\t{0}", ((Statements)item).ToString()); - } - // Ignore these as actual commands - source.Position = initPos; - source.SetLength(initPos); - break; - case Statements.writeout: - // Writes the full command history to the stream. - Console.WriteLine("Writing input commands to {0}..."); - source.Position = initPos; - source.SetLength(initPos); - break; - } - // Do a check semicolons etc - if (IsNextEoS(source)) - { - // Increment the source pos past the semi-colon - cont = false; - source.Position++; - if (dynamicInput) - { - // Nicely format the output stream, so we may print it cleanly - source.WriteByte((byte)'\n'); - } - result(); - if (((Statements)statementType).Equals(Statements.exit)) - { - return; - } - } - else if (source.Position != lastLinePos - 1) - { - // In the case that we expect some more data, we must keep tabs of our current line, and keep accumulating data until we're finished - lastLinePos = source.Position; - cont = true; - source.WriteByte((byte)' '); - Console.Write(">"); - } - else - { - throw new ParserException("expected a semi-colon", 0, source.Position); - } - } - else - { - throw new ParserException("Failed parsing statement", 0, source.Position); - } - } - // Throwing a parserexception will return us to this point immediately. From here, the line is automatically restored, - // and the excepion printed to the console window. - // This means that each function does not need to keep track of our current position in the stream - catch (ParserException e) - { - if (e.Importance > 3) - { - throw new ApplicationException("A critical error occurred."); - } - if (e.LinePosition > 0) - { - WriteDebugLine(initPos, e.LinePosition, e.Message, source); - } - else - { - Console.WriteLine(e.LinePosition + ": " + e.Message); - source.Position = initPos; - source.SetLength(initPos); - } - if (!dynamicInput) - { - Environment.Exit(-1); - } + throw new ParserException("Failed parsing statement", 0, source.Position); + } + } + // Throwing a parserexception will return us to this point immediately. From here, the line is automatically restored, + // and the excepion printed to the console window. + // This means that each function does not need to keep track of our current position in the stream + catch (ParserException e) + { + if (e.Importance > 3) + { + throw new ApplicationException("A critical error occurred."); + } + if (e.LinePosition > 0) + { + WriteDebugLine(initPos, e.LinePosition, e.Message, source); + } + else + { + Console.WriteLine(e.LinePosition + ": " + e.Message); + source.Position = initPos; + source.SetLength(initPos); + } + if (!dynamicInput) + { + Environment.Exit(-1); } } - } - else - { - Console.WriteLine("First read character was not \'{\'. Use the launch flag -ns for non-strict syntax checking"); } } @@ -432,8 +409,14 @@ namespace Assignment_1 for (int i = 0; i < eligibleKeys.Count; i++) { string entryFormat = "│{0," + -1 * keyWidth + "}│{1," + -1 * valueWidth + "}│{2," + -1 * flagWidth + "}│\n"; + List keyLines = GetStringLines(eligibleKeys[i], keyWidth); + List valueLines = GetStringLines(Symbols[eligibleKeys[i]].Item1.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"), valueWidth); - consoleOutput.Append(string.Format(entryFormat, eligibleKeys[i], Symbols[eligibleKeys[i]].Item1.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"), Convert.ToString((byte)Symbols[eligibleKeys[i]].Item2, 2).PadLeft(8, '0'))); + for (int j = 0; j < (keyLines.Count > valueLines.Count ? keyLines.Count : valueLines.Count); j++) + { + consoleOutput.Append(string.Format(entryFormat, j >= keyLines.Count ? "" : keyLines[j], j >= valueLines.Count ? "" : valueLines[j], j == 0 ? Convert.ToString((byte)Symbols[eligibleKeys[i]].Item2, 2).PadLeft(8, '0'): "")); + } + //consoleOutput.Append(string.Format(entryFormat, eligibleKeys[i], Symbols[eligibleKeys[i]].Item1.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"), Convert.ToString((byte)Symbols[eligibleKeys[i]].Item2, 2).PadLeft(8, '0'))); if (i + 1 < eligibleKeys.Count) { consoleOutput.Append(string.Format("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤\n")); @@ -558,11 +541,13 @@ namespace Assignment_1 { string result = ""; // iterate through values until we reach either the end of the stream or the end-of-statement + bool IsAppendSet = true; while (s.Position < s.Length && !IsNextEoS(s)) { if (IsNextEoS(s, '+')) { s.Position = FindNextWord(s, out _); + IsAppendSet = true; } else { @@ -571,8 +556,17 @@ namespace Assignment_1 { Console.WriteLine("Could not parse value"); } - s.Position = val; - result += value; + if (IsAppendSet) + { + s.Position = val; + result += value; + IsAppendSet = false; + } + else + { + throw new ParserException("Append operator not set", 0, s.Position); + } + } } expression = result; @@ -701,8 +695,7 @@ namespace Assignment_1 /// static string GetNextLine(Stream s) { - string nextLine; - FindNextOccurance(s, '\n', out nextLine); + FindNextOccurance(s, '\n', out string nextLine); return nextLine; } @@ -718,7 +711,7 @@ namespace Assignment_1 // Record our current position long start = s.Position; // Check if the character at the current pos is whitespace, if so, keep advancing until it isn't. - // NB: Whitespace includes carriage returns or line feeds, + // NB: Whitespace includes carriage returns and line feeds, // so 'set\r\n // var // "expression"; @@ -900,6 +893,18 @@ namespace Assignment_1 string result = string.Format(t, source[..rightHalf], source[rightHalf..]); return result; } + + static List GetStringLines(string source, int maxWidth) + { + List lines = new(); + for (int i = 0; i < source.Length; i++) + { + int max = i + maxWidth <= source.Length ? i + maxWidth : source.Length; + lines.Add(source[i..max]); + i = max; + } + return lines; + } #endregion public class ParserException : Exception {