Cleaned up plenty of lines.

Added exceptional flow to maintain correct state.
Added EoL/EoS parsing (Closes #3)
This commit is contained in:
Brychan Dempsey 2021-03-15 20:58:52 +13:00
parent 10bd29c296
commit 83c70333f8

View File

@ -4,6 +4,8 @@ using System.Collections;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
namespace Assignment_1 namespace Assignment_1
{ {
@ -14,7 +16,9 @@ namespace Assignment_1
{ {
Empty = 0, Empty = 0,
Reserved = 1, Reserved = 1,
NoPrint = 2 NoPrint = 2,
Static = 4,
Undef = 8
} }
/// <summary> /// <summary>
@ -104,127 +108,183 @@ namespace Assignment_1
SkipWhitespace(source); SkipWhitespace(source);
long initPos = source.Position; long initPos = source.Position;
long position = FindNextWord(source, out string word); long position = FindNextWord(source, out string word);
object statementType; object statementType;
if (Enum.TryParse(typeof(statements), word, out statementType)) try
{ {
source.Position = position; if (Enum.TryParse(typeof(statements), word, out statementType))
switch ((statements)statementType)
{ {
case statements.exit: // By turning the result of the command into an action,
Exit(); // we can defer processing the final result until the end of this control flow
break; // I.e. "I don't know what action to do, but I will need it, when I know where this statement ends"
case statements.append:
Append(source); // In some ways, it makes more sense. The action is determined by the interpreter's result
break; Action result = () => { };
case statements.list: source.Position = position;
List(); switch ((statements)statementType)
break; {
case statements.print: case statements.exit:
Print(source, 0); result = Exit();
break; break;
case statements.printlength: case statements.append:
Print(source, 1); result = AppendSet(source);
break; break;
case statements.printwordcount: case statements.list:
Print(source, 2); result = List();
break; break;
case statements.printwords: case statements.print:
Print(source, 3); result = Print(source, 0);
break; break;
case statements.set: case statements.printlength:
Set(source); result = Print(source, 1);
break; break;
case statements.reverse: case statements.printwordcount:
Reverse(source); result = Print(source, 2);
break; break;
case statements.h: case statements.printwords:
Console.WriteLine("Commands are: "); result = Print(source, 3);
foreach (var item in Enum.GetValues(typeof(statements))) break;
{ case statements.set:
Console.WriteLine("\t{0}", ((statements)item).ToString()); result = AppendSet(source, false);
} break;
break; case statements.reverse:
case statements.writeout: result = Reverse(source);
// Writes the full command history to the stream. break;
Console.WriteLine("Writing input commands to {0}..."); case statements.h:
break; 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, if (e.Importance > 3)
// Ensure stream gets trimmed back to the correct position {
Console.WriteLine("Failed parsing statement"); throw new ApplicationException("A critical error occurred.");
source.Position = initPos; }
source.SetLength(initPos); 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 #region Function Handling
/// <summary>
/// Handles the append x y case. private string ValidateKey(Stream source, bool checkExist)
///
/// </summary>
/// <param name="source"></param>
/// <param name="lineStart"></param>
/// <returns></returns>
bool Append(Stream source, long lineStart = -1)
{ {
if (lineStart == -1)
{
lineStart = GetLineStart(source, "append");
}
string key; string key;
long keyEndPos = FindIdentifier(source, out 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); throw new ParserException("Could not identify object", 0, source.Position);
return false; }
else if (checkExist && !Symbols.ContainsKey(key))
{
throw new ParserException("Key not found", 0, source.Position);
} }
else 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; source.Position = keyEndPos;
} }
return key;
}
private string ValidateValue(Stream source)
{
string value; string value;
long valuePos = FindValue(source, out value); long valuePos = FindExpression(source, out value);
if (valuePos < 0) if (valuePos < 0)
{ {
// Error on finding object throw new ParserException("Could not evaluate expression", 0, source.Position);
WriteDebugLine(lineStart, keyEndPos, "could not evaluate expression", source);
return false;
} }
else else
{ {
source.Position = valuePos; source.Position = valuePos;
} }
string eol; return value;
FindNextWord(source, out eol); }
if (eol.Length == 0 || eol[0] != ';') /// <summary>
/// Handles the 'append x y [ + z];' case &
/// And the 'set x y [ + z];' case
/// </summary>
/// <param name="source"></param>
/// <returns>An Action that will add the key to the dictionary</returns>
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 () => Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
return false;
} }
if (Symbols[key].Item2.HasFlag(VariableFlags.Reserved)) else
{ {
WriteDebugLine(lineStart, keyEndPos - (key.Length + 1), "cannot assign a value to a reserved constant", source); return () => Symbols.Add(key, new Tuple<string, VariableFlags>(value, VariableFlags.Empty));
return false;
} }
Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
return true;
} }
/// <summary> /// <summary>
/// Creates and prints a list of all defined variables /// Creates and prints a list of all defined variables
/// </summary> /// </summary>
void List(bool printUnprint = false) /// <param name="printUnprint">List values normally excluded from printing</param>
Action List(bool printUnprint = false)
{ {
int keyWidth = 10; int keyWidth = 10;
int valueWidth = 50; int valueWidth = 50;
int flagWidth = 9; int flagWidth = 9;
Console.WriteLine("┌" + new string('─', keyWidth) + "┬" + new string('─', valueWidth) + "┬" + new string('─', flagWidth) + "┐"); StringBuilder consoleOutput = new StringBuilder();
Console.WriteLine("│{0}│{1}│{2}│", CenterString("Symbol", keyWidth), CenterString("Value", valueWidth), CenterString("Flags", flagWidth)); 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 // Figure out how many symbols are eligible for printing
List<string> eligibleKeys = new List<string>(Symbols.Count); List<string> eligibleKeys = new List<string>(Symbols.Count);
foreach (var item in Symbols.Keys) foreach (var item in Symbols.Keys)
@ -237,41 +297,39 @@ namespace Assignment_1
// Control printing based on how many keys are available // Control printing based on how many keys are available
if (eligibleKeys.Count > 0) 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++) 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) 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; StringBuilder outputString = new StringBuilder();
long result = FindExpression(source, out expression); string expression = ValidateValue(source);
if (result < 0)
{
// Could not print
return false;
}
if (mode == 0) if (mode == 0)
{ {
Console.WriteLine(expression); outputString.Append(expression + Environment.NewLine);
} }
else if (mode == 1) else if (mode == 1)
{ {
Console.Write("Length of the expression is: "); outputString.Append("Length of the expression is: ");
Console.WriteLine(expression.Length); outputString.Append(expression.Length + Environment.NewLine);
} }
else if (mode >= 2) else if (mode >= 2)
{ {
@ -279,89 +337,34 @@ namespace Assignment_1
string[] words = expression.Split(' '); string[] words = expression.Split(' ');
if (mode == 3) if (mode == 3)
{ {
Console.Write("Wordcount is: "); outputString.Append("Wordcount is: ");
Console.WriteLine(words.Length); outputString.Append(words.Length + Environment.NewLine);
} }
else else
{ {
Console.WriteLine("Words are:"); Console.WriteLine("Words are:");
foreach (string word in words) foreach (string word in words)
{ {
Console.WriteLine(word); outputString.Append(word + Environment.NewLine);
} }
} }
} }
source.Position = result; return () => Console.WriteLine(outputString.ToString());
return true;
} }
bool Set(Stream source, long lineStart=-1) Action Reverse(Stream source)
{ {
if(lineStart == -1) string key = ValidateKey(source, true);
{ string ToReverse = Symbols[key].Item1;
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<string, VariableFlags>(expression, Symbols[identifier].Item2);
}
else
{
Symbols.Add(identifier, new Tuple<string, VariableFlags>(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[] words = ToReverse.Split(' '); string[] words = ToReverse.Split(' ');
StringBuilder reversed = new StringBuilder(); 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(words[i]);
reversed.Append(' '); reversed.Append(' ');
} }
Symbols[identifier] = new Tuple<string, VariableFlags>(reversed.ToString(), Symbols[identifier].Item2);
return true; return () => Symbols[key] = new Tuple<string, VariableFlags>(reversed.ToString(), Symbols[key].Item2);
} }
/// <summary> /// <summary>
/// Writes the debug info to the screen in the form:<br/> /// Writes the debug info to the screen in the form:<br/>
@ -485,8 +488,7 @@ namespace Assignment_1
if (!Symbols.ContainsKey(keyValue)) if (!Symbols.ContainsKey(keyValue))
{ {
returnedValue = ""; throw new ParserException("Could not find key: " + keyValue, 0);
return -1;
} }
returnedValue = Symbols[keyValue].Item1; returnedValue = Symbols[keyValue].Item1;
return t; return t;
@ -551,7 +553,7 @@ namespace Assignment_1
} }
else else
{ {
returnedLiteral = ""; throw new ParserException("Could not parse the literal");
} }
s.Position = pos; s.Position = pos;
return resultPosition; return resultPosition;
@ -596,8 +598,9 @@ namespace Assignment_1
while (s.Position < s.Length) while (s.Position < s.Length)
{ {
currentChar = ReadChar(s); currentChar = ReadChar(s);
if (char.IsWhiteSpace(currentChar)) if (char.IsWhiteSpace(currentChar) || currentChar == ';')
{ {
s.Position--;
break; break;
} }
else else
@ -730,7 +733,6 @@ namespace Assignment_1
static string CenterString(string source, int totalPadding, char paddingChar=' ') static string CenterString(string source, int totalPadding, char paddingChar=' ')
{ {
if (source.Length >= totalPadding) return source; 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 rightHalf = (int)Math.Ceiling(source.Length / 2.0);
int leftHalfPad = (int)Math.Floor(totalPadding / 2.0); int leftHalfPad = (int)Math.Floor(totalPadding / 2.0);
int rightHalfPad = (int)Math.Ceiling(totalPadding / 2.0); int rightHalfPad = (int)Math.Ceiling(totalPadding / 2.0);
@ -739,5 +741,28 @@ namespace Assignment_1
return result; return result;
} }
#endregion #endregion
public class ParserException : Exception
{
/// <summary>
/// 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.
/// </summary>
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)
{
}
}
} }
} }