From 83c70333f8eea1dbdfca362b494601dad78a199a Mon Sep 17 00:00:00 2001 From: Brychan Dempsey Date: Mon, 15 Mar 2021 20:58:52 +1300 Subject: [PATCH] Cleaned up plenty of lines. Added exceptional flow to maintain correct state. Added EoL/EoS parsing (Closes #3) --- Assignment 1/Program.cs | 363 +++++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 169 deletions(-) diff --git a/Assignment 1/Program.cs b/Assignment 1/Program.cs index 1155ad7..d0eff38 100644 --- a/Assignment 1/Program.cs +++ b/Assignment 1/Program.cs @@ -4,6 +4,8 @@ using System.Collections; using System.IO; using System.Text; using System.Collections.ObjectModel; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; namespace Assignment_1 { @@ -14,7 +16,9 @@ namespace Assignment_1 { Empty = 0, Reserved = 1, - NoPrint = 2 + NoPrint = 2, + Static = 4, + Undef = 8 } /// @@ -104,127 +108,183 @@ namespace Assignment_1 SkipWhitespace(source); long initPos = source.Position; long position = FindNextWord(source, out string word); + object statementType; - if (Enum.TryParse(typeof(statements), word, out statementType)) + try { - source.Position = position; - switch ((statements)statementType) + if (Enum.TryParse(typeof(statements), word, out statementType)) { - case statements.exit: - Exit(); - break; - case statements.append: - Append(source); - break; - case statements.list: - List(); - break; - case statements.print: - Print(source, 0); - break; - case statements.printlength: - Print(source, 1); - break; - case statements.printwordcount: - Print(source, 2); - break; - case statements.printwords: - Print(source, 3); - break; - case statements.set: - Set(source); - break; - case statements.reverse: - Reverse(source); - break; - case statements.h: - Console.WriteLine("Commands are: "); - foreach (var item in Enum.GetValues(typeof(statements))) - { - Console.WriteLine("\t{0}", ((statements)item).ToString()); - } - break; - case statements.writeout: - // Writes the full command history to the stream. - Console.WriteLine("Writing input commands to {0}..."); - break; + // 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(); + break; + case statements.append: + result = AppendSet(source); + break; + case statements.list: + result = List(); + break; + case statements.print: + result = Print(source, 0); + break; + case statements.printlength: + result = Print(source, 1); + break; + case statements.printwordcount: + result = Print(source, 2); + break; + case statements.printwords: + result = Print(source, 3); + break; + case statements.set: + result = AppendSet(source, false); + break; + case statements.reverse: + result = Reverse(source); + break; + 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)) + { + result(); + } + else + { + throw new ParserException("expected a semi-colon", 0, source.Position); + } + + } + else + { + // Statement parse failed, + // Ensure stream gets trimmed back to the correct position + Console.WriteLine("Failed parsing statement"); + source.Position = initPos; + source.SetLength(initPos); } } - else + // 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) { - // Statement parse failed, - // Ensure stream gets trimmed back to the correct position - Console.WriteLine("Failed parsing statement"); - source.Position = initPos; - source.SetLength(initPos); + 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); + } + } + } } } #region Function Handling - /// - /// Handles the append x y case. - /// - /// - /// - /// - /// - bool Append(Stream source, long lineStart = -1) + + private string ValidateKey(Stream source, bool checkExist) { - if (lineStart == -1) - { - lineStart = GetLineStart(source, "append"); - } string key; long keyEndPos = FindIdentifier(source, out key); - if (keyEndPos < 0 || !Symbols.ContainsKey(key)) + if (keyEndPos < 0 || key.Length == 0) { - WriteDebugLine(lineStart, lineStart + "append ".Length, "could not identify object", source); - return false; + throw new ParserException("Could not identify object", 0, source.Position); + } + else if (checkExist && !Symbols.ContainsKey(key)) + { + throw new ParserException("Key not found", 0, source.Position); } else { + if (Symbols.ContainsKey(key) && Symbols[key].Item2.HasFlag(VariableFlags.Reserved)) + { + throw new ParserException("Cannot assign a value to a reserved constant", 0, keyEndPos - (key.Length + 1)); + } source.Position = keyEndPos; } + return key; + } + + private string ValidateValue(Stream source) + { string value; - long valuePos = FindValue(source, out value); + long valuePos = FindExpression(source, out value); if (valuePos < 0) { - // Error on finding object - WriteDebugLine(lineStart, keyEndPos, "could not evaluate expression", source); - return false; + throw new ParserException("Could not evaluate expression", 0, source.Position); } else { source.Position = valuePos; } - string eol; - FindNextWord(source, out eol); - if (eol.Length == 0 || eol[0] != ';') + return value; + } + /// + /// Handles the 'append x y [ + z];' case & + /// And the 'set x y [ + z];' case + /// + /// + /// An Action that will add the key to the dictionary + Action AppendSet(Stream source, bool appendMode=true) + { + string key = ValidateKey(source, appendMode); + string value = ValidateValue(source); + if (appendMode) { - WriteDebugLine(lineStart, valuePos, "expected a semicolon", source); - return false; + return () => Symbols[key] = new Tuple(Symbols[key].Item1 + value, Symbols[key].Item2); } - if (Symbols[key].Item2.HasFlag(VariableFlags.Reserved)) + else { - WriteDebugLine(lineStart, keyEndPos - (key.Length + 1), "cannot assign a value to a reserved constant", source); - return false; + return () => Symbols.Add(key, new Tuple(value, VariableFlags.Empty)); } - Symbols[key] = new Tuple(Symbols[key].Item1 + value, Symbols[key].Item2); - return true; + } /// /// Creates and prints a list of all defined variables /// - void List(bool printUnprint = false) + /// List values normally excluded from printing + Action List(bool printUnprint = false) { int keyWidth = 10; int valueWidth = 50; int flagWidth = 9; - Console.WriteLine("┌" + new string('─', keyWidth) + "┬" + new string('─', valueWidth) + "┬" + new string('─', flagWidth) + "┐"); - Console.WriteLine("│{0}│{1}│{2}│", CenterString("Symbol", keyWidth), CenterString("Value", valueWidth), CenterString("Flags", flagWidth)); + StringBuilder consoleOutput = new StringBuilder(); + consoleOutput.Append(string.Format("┌" + new string('─', keyWidth) + "┬" + new string('─', valueWidth) + "┬" + new string('─', flagWidth) + "┐\n")); + consoleOutput.Append(string.Format("│{0}│{1}│{2}│\n", CenterString("Symbol", keyWidth), CenterString("Value", valueWidth), CenterString("Flags", flagWidth))); // Figure out how many symbols are eligible for printing List eligibleKeys = new List(Symbols.Count); foreach (var item in Symbols.Keys) @@ -237,41 +297,39 @@ namespace Assignment_1 // Control printing based on how many keys are available if (eligibleKeys.Count > 0) { - Console.WriteLine("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤"); + consoleOutput.Append(string.Format("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤\n")); for (int i = 0; i < eligibleKeys.Count; i++) { - string entryFormat = "│{0," + -1*keyWidth + "}│{1," + -1*valueWidth + "}│{2," + -1*flagWidth + "}│"; + string entryFormat = "│{0," + -1*keyWidth + "}│{1," + -1*valueWidth + "}│{2," + -1*flagWidth + "}│\n"; - Console.WriteLine(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')); + 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) { - Console.WriteLine("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤"); + consoleOutput.Append(string.Format("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤\n")); } } } - Console.WriteLine("└" + new string('─', keyWidth) + "┴" + new string('─', valueWidth) + "┴" + new string('─', flagWidth) + "┘"); + consoleOutput.Append(string.Format("└" + new string('─', keyWidth) + "┴" + new string('─', valueWidth) + "┴" + new string('─', flagWidth) + "┘\n")); + + return () => Console.WriteLine(consoleOutput.ToString()); } - void Exit() + Action Exit() { - Environment.Exit(0); + // Should do some save command here + return () => Environment.Exit(0); } - bool Print(Stream source, int mode=0) + Action Print(Stream source, int mode=0) { - string expression; - long result = FindExpression(source, out expression); - if (result < 0) - { - // Could not print - return false; - } + StringBuilder outputString = new StringBuilder(); + string expression = ValidateValue(source); if (mode == 0) { - Console.WriteLine(expression); + outputString.Append(expression + Environment.NewLine); } else if (mode == 1) { - Console.Write("Length of the expression is: "); - Console.WriteLine(expression.Length); + outputString.Append("Length of the expression is: "); + outputString.Append(expression.Length + Environment.NewLine); } else if (mode >= 2) { @@ -279,89 +337,34 @@ namespace Assignment_1 string[] words = expression.Split(' '); if (mode == 3) { - Console.Write("Wordcount is: "); - Console.WriteLine(words.Length); + outputString.Append("Wordcount is: "); + outputString.Append(words.Length + Environment.NewLine); } else { Console.WriteLine("Words are:"); foreach (string word in words) { - Console.WriteLine(word); + outputString.Append(word + Environment.NewLine); } - } - + } } - source.Position = result; - return true; + return () => Console.WriteLine(outputString.ToString()); } - bool Set(Stream source, long lineStart=-1) + Action Reverse(Stream source) { - if(lineStart == -1) - { - lineStart = GetLineStart(source, "set"); - } - string identifier; - long identifierEndPos = FindIdentifier(source, out identifier); - if (identifierEndPos < source.Position || identifier.Trim().Length == 0) - { - WriteDebugLine(lineStart, "set ".Length, "expected an identifier", source); - return false; - } - else if (ForbiddenChars.Exists((c) => identifier.Contains(c))) - { - char fbChar = ForbiddenChars.Find((c) => identifier.Contains(c)); - WriteDebugLine(lineStart, "set ".Length, string.Format("character {0} is not valid for an identifier", fbChar), source); - return false; - } - source.Position = identifierEndPos; - string expression; - long expressionEndPos = FindExpression(source, out expression); - if (expressionEndPos < 0) - { - WriteDebugLine(lineStart, identifierEndPos, "failed parsing expression", source); - // Couldn't match expression - return false; - } - if (Symbols.ContainsKey(identifier)) - { - if (Symbols[identifier].Item2.HasFlag(VariableFlags.Reserved)) - { - WriteDebugLine(lineStart, identifierEndPos - identifier.Length, "cannot assign to a reserved constant", source); - return false; - } - Symbols[identifier] = new Tuple(expression, Symbols[identifier].Item2); - } - else - { - Symbols.Add(identifier, new Tuple(expression, VariableFlags.Empty)); - } - source.Position = expressionEndPos; - return true; - } - - bool Reverse(Stream source) - { - string identifier; - long resultPos = FindIdentifier(source, out identifier); - if (resultPos < 0) - { - // Couldn't match an identifier - // If ID Doesn't exist, we should make it - return false; - } - - string ToReverse = Symbols[identifier].Item1; + string key = ValidateKey(source, true); + string ToReverse = Symbols[key].Item1; string[] words = ToReverse.Split(' '); StringBuilder reversed = new StringBuilder(); - for (int i = words.Length-1; i < 0; i--) + for (int i = words.Length-1; i >= 0; i--) { reversed.Append(words[i]); reversed.Append(' '); } - Symbols[identifier] = new Tuple(reversed.ToString(), Symbols[identifier].Item2); - return true; + + return () => Symbols[key] = new Tuple(reversed.ToString(), Symbols[key].Item2); } /// /// Writes the debug info to the screen in the form:
@@ -485,8 +488,7 @@ namespace Assignment_1 if (!Symbols.ContainsKey(keyValue)) { - returnedValue = ""; - return -1; + throw new ParserException("Could not find key: " + keyValue, 0); } returnedValue = Symbols[keyValue].Item1; return t; @@ -551,7 +553,7 @@ namespace Assignment_1 } else { - returnedLiteral = ""; + throw new ParserException("Could not parse the literal"); } s.Position = pos; return resultPosition; @@ -596,8 +598,9 @@ namespace Assignment_1 while (s.Position < s.Length) { currentChar = ReadChar(s); - if (char.IsWhiteSpace(currentChar)) + if (char.IsWhiteSpace(currentChar) || currentChar == ';') { + s.Position--; break; } else @@ -730,7 +733,6 @@ namespace Assignment_1 static string CenterString(string source, int totalPadding, char paddingChar=' ') { if (source.Length >= totalPadding) return source; - int leftHalf = (int)Math.Floor(source.Length / 2.0); int rightHalf = (int)Math.Ceiling(source.Length / 2.0); int leftHalfPad = (int)Math.Floor(totalPadding / 2.0); int rightHalfPad = (int)Math.Ceiling(totalPadding / 2.0); @@ -739,5 +741,28 @@ namespace Assignment_1 return result; } #endregion + public class ParserException : Exception + { + /// + /// Importance is used to signify how the parser should respond to the error. + /// A code of 3 or greater is a critical error; the application will throw the error further up the call and exit. + /// 0 implies the line may be retried. + /// 1 should imply the current block is not valid and should be retried. + /// + public int Importance = 0; + public long LinePosition = -1; + public ParserException(string message, int importance, long linePos) : base(message) + { + + } + public ParserException(string message, int importance) : base(message) + { + + } + public ParserException(string message) : base(message) + { + + } + } } }