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.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
}
/// <summary>
@ -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
/// <summary>
/// Handles the append x y case.
///
/// </summary>
/// <param name="source"></param>
/// <param name="lineStart"></param>
/// <returns></returns>
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;
}
/// <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 false;
return () => Symbols[key] = new Tuple<string, VariableFlags>(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<string, VariableFlags>(value, VariableFlags.Empty));
}
Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
return true;
}
/// <summary>
/// Creates and prints a list of all defined variables
/// </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 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<string> eligibleKeys = new List<string>(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<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 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<string, VariableFlags>(reversed.ToString(), Symbols[identifier].Item2);
return true;
return () => Symbols[key] = new Tuple<string, VariableFlags>(reversed.ToString(), Symbols[key].Item2);
}
/// <summary>
/// Writes the debug info to the screen in the form:<br/>
@ -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
{
/// <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)
{
}
}
}
}