Cleaned up plenty of lines.
Added exceptional flow to maintain correct state. Added EoL/EoS parsing (Closes #3)
This commit is contained in:
parent
10bd29c296
commit
83c70333f8
@ -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,38 +108,47 @@ namespace Assignment_1
|
||||
SkipWhitespace(source);
|
||||
long initPos = source.Position;
|
||||
long position = FindNextWord(source, out string word);
|
||||
|
||||
object statementType;
|
||||
try
|
||||
{
|
||||
if (Enum.TryParse(typeof(statements), word, out 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:
|
||||
Exit();
|
||||
result = Exit();
|
||||
break;
|
||||
case statements.append:
|
||||
Append(source);
|
||||
result = AppendSet(source);
|
||||
break;
|
||||
case statements.list:
|
||||
List();
|
||||
result = List();
|
||||
break;
|
||||
case statements.print:
|
||||
Print(source, 0);
|
||||
result = Print(source, 0);
|
||||
break;
|
||||
case statements.printlength:
|
||||
Print(source, 1);
|
||||
result = Print(source, 1);
|
||||
break;
|
||||
case statements.printwordcount:
|
||||
Print(source, 2);
|
||||
result = Print(source, 2);
|
||||
break;
|
||||
case statements.printwords:
|
||||
Print(source, 3);
|
||||
result = Print(source, 3);
|
||||
break;
|
||||
case statements.set:
|
||||
Set(source);
|
||||
result = AppendSet(source, false);
|
||||
break;
|
||||
case statements.reverse:
|
||||
Reverse(source);
|
||||
result = Reverse(source);
|
||||
break;
|
||||
case statements.h:
|
||||
Console.WriteLine("Commands are: ");
|
||||
@ -143,12 +156,27 @@ namespace Assignment_1
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -159,72 +187,104 @@ namespace Assignment_1
|
||||
source.SetLength(initPos);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#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] != ';')
|
||||
{
|
||||
WriteDebugLine(lineStart, valuePos, "expected a semicolon", source);
|
||||
return false;
|
||||
return value;
|
||||
}
|
||||
if (Symbols[key].Item2.HasFlag(VariableFlags.Reserved))
|
||||
/// <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)
|
||||
{
|
||||
WriteDebugLine(lineStart, keyEndPos - (key.Length + 1), "cannot assign a value to a reserved constant", source);
|
||||
return false;
|
||||
string key = ValidateKey(source, appendMode);
|
||||
string value = ValidateValue(source);
|
||||
if (appendMode)
|
||||
{
|
||||
return () => Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
|
||||
}
|
||||
Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
|
||||
return true;
|
||||
else
|
||||
{
|
||||
return () => Symbols.Add(key, new Tuple<string, VariableFlags>(value, VariableFlags.Empty));
|
||||
}
|
||||
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return () => Console.WriteLine(outputString.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
source.Position = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user