Condensed /cleaned up some code
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded

This commit is contained in:
Brychan Dempsey 2021-03-29 16:24:21 +13:00
parent a37aa07ede
commit 340591e1a1
3 changed files with 48 additions and 96 deletions

View File

@ -6,10 +6,6 @@
<RootNamespace>Assignment_1</RootNamespace> <RootNamespace>Assignment_1</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Folder Include="Examples\" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="xcopy &quot;$(ProjectDir)\Examples\*.txt&quot; &quot;$(OutDir)&quot; /Y /I" /> <Exec Command="xcopy &quot;$(ProjectDir)\Examples\*.txt&quot; &quot;$(OutDir)&quot; /Y /I" />
</Target> </Target>

View File

@ -0,0 +1,3 @@
set apple "Apple trees are small.";
reverse apple;
print apple;

View File

@ -22,6 +22,8 @@ namespace Assignment_1
NoPrint = 2, NoPrint = 2,
Static = 4 Static = 4
} }
// Max display width for tables etc.
static readonly int ConsoleWidthLimit = 80; static readonly int ConsoleWidthLimit = 80;
static void Main(string[] args) static void Main(string[] args)
@ -42,7 +44,8 @@ namespace Assignment_1
if (Console.IsInputRedirected || loadedFromFile) if (Console.IsInputRedirected || loadedFromFile)
{ {
// To simplify reading, we read all input bytes from the piped input to the stream. // To simplify reading, we read all input bytes from the piped input to the stream.
// Not the best way to do it; we don't need to keep any data that has already been read and parsed successfully. // Not the best way to do it; we don't need to keep any data that has already been read and parsed successfully,
// and we could avoid copying the supplied stream. But keeps interactivity with the console simple
sourceStream.Write(Encoding.UTF8.GetBytes(Console.In.ReadToEnd())); sourceStream.Write(Encoding.UTF8.GetBytes(Console.In.ReadToEnd()));
Console.In.Dispose(); Console.In.Dispose();
Console.SetIn(new StreamReader(Console.OpenStandardInput())); Console.SetIn(new StreamReader(Console.OpenStandardInput()));
@ -75,7 +78,6 @@ namespace Assignment_1
} }
else else
{ {
// Need the logic to prep the next source stream
ck = new ConsoleKeyInfo(); ck = new ConsoleKeyInfo();
while (ck.Key != ConsoleKey.Y && ck.Key != ConsoleKey.N) while (ck.Key != ConsoleKey.Y && ck.Key != ConsoleKey.N)
{ {
@ -131,7 +133,6 @@ namespace Assignment_1
set, set,
reverse, reverse,
h, h,
writeout
} }
public void StartParsing(Stream source, bool dynamicInput = false) public void StartParsing(Stream source, bool dynamicInput = false)
{ {
@ -155,8 +156,6 @@ namespace Assignment_1
source.Position = pos; source.Position = pos;
} }
// parse the statement or list of statements;
// This is done by reading the next word
if (!cont) if (!cont)
{ {
initPos = source.Position; initPos = source.Position;
@ -213,22 +212,19 @@ namespace Assignment_1
case Statements.reverse: case Statements.reverse:
result = Reverse(source); result = Reverse(source);
break; break;
// These are additional helper functions. Thier input gets excluded from the MemoryStream
case Statements.h: case Statements.h:
Console.WriteLine("Commands are: "); Console.WriteLine("Commands are: ");
foreach (var item in Enum.GetValues(typeof(Statements))) foreach (var item in Enum.GetValues(typeof(Statements)))
{ {
Console.WriteLine("\t{0}", ((Statements)item).ToString()); Console.WriteLine("\t{0}", ((Statements)item).ToString());
} }
// Ignore these as actual commands // Stream ignores this command
source.Position = initPos; source.Position = initPos;
source.SetLength(initPos); source.SetLength(initPos);
break; break;
} }
// Do a check semicolons etc
if (IsNextEoS(source)) if (IsNextEoS(source))
{ {
// Increment the source pos past the semi-colon
cont = false; cont = false;
source.Position++; source.Position++;
SkipWhitespace(source); SkipWhitespace(source);
@ -236,8 +232,7 @@ namespace Assignment_1
else isLineFinished = false; else isLineFinished = false;
if (dynamicInput && isLineFinished) if (dynamicInput && isLineFinished)
{ {
// Nicely format the output stream, so we may print it cleanly source.WriteByte((byte)'\n'); // put the next statement on a newline so the exported list of commands is clean
source.WriteByte((byte)'\n');
} }
result(); result();
if (((Statements)statementType).Equals(Statements.exit)) if (((Statements)statementType).Equals(Statements.exit))
@ -247,17 +242,19 @@ namespace Assignment_1
} }
else if (source.Position != lastLinePos) else if (source.Position != lastLinePos)
{ {
// 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 // If the semicolon is missing *once*, assume we need more data.
lastLinePos = source.Position; lastLinePos = source.Position;
cont = true; cont = true;
source.WriteByte((byte)' ');
Console.Write(">"); Console.Write(">");
} }
else else
{
if ((Statements)statementType != Statements.h)
{ {
throw new ParserException("expected a semi-colon", 0, source.Position); throw new ParserException("expected a semi-colon", 0, source.Position);
} }
} }
}
else else
{ {
throw new ParserException("Failed parsing statement", 0, source.Position); throw new ParserException("Failed parsing statement", 0, source.Position);
@ -289,15 +286,11 @@ namespace Assignment_1
} }
} }
#region Function Handling
/// <summary> /// <summary>
/// Checks if the next expression in the source meets the requirements of being a key, /// Checks if the next expression in the source meets the requirements of being a key,
/// and optionally verify that key exists. /// and optionally verify that key exists. Acoids overidding the value of constants, and
/// Also contracts the key is not reserved or constant /// ensures all characters are valid
/// </summary> /// </summary>
/// <param name="source"></param>
/// <param name="checkExist"></param>
/// <returns></returns>
private string ValidateKey(Stream source, bool checkExist) private string ValidateKey(Stream source, bool checkExist)
{ {
long keyEndPos = FindIdentifier(source, out string key); long keyEndPos = FindIdentifier(source, out string key);
@ -315,7 +308,7 @@ namespace Assignment_1
} }
else else
{ {
int indx = Array.FindIndex(key.ToCharArray(), (c) => (c > 122 || c > 90 && c < 97 && c != '_' || c > 57 && c < 65 || c < 48)); // If the overall result is good, move until one isn't int indx = Array.FindIndex(key.ToCharArray(), (c) => (c > 122 || c > 90 && c < 97 && c != '_' || c > 57 && c < 65 || c < 48));
if (indx > -1) if (indx > -1)
{ {
throw new ParserException(string.Format("Character \'{0}\' is not valid for an identifier",key[indx]), 0, keyEndPos-key.Length + indx); throw new ParserException(string.Format("Character \'{0}\' is not valid for an identifier",key[indx]), 0, keyEndPos-key.Length + indx);
@ -328,8 +321,6 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Checks if the next expression meets the requirements of being a value /// Checks if the next expression meets the requirements of being a value
/// </summary> /// </summary>
/// <param name="source"></param>
/// <returns></returns>
private string ValidateValue(Stream source) private string ValidateValue(Stream source)
{ {
long valuePos = FindExpression(source, out string value); long valuePos = FindExpression(source, out string value);
@ -345,11 +336,9 @@ namespace Assignment_1
} }
/// <summary> /// <summary>
/// Handles the 'append x y [ + z];' case & /// Handles the 'append x y [ + z];' case <br />
/// And the 'set x y [ + z];' case /// And the 'set x y [ + z];' case
/// </summary> /// </summary>
/// <param name="source"></param>
/// <returns>An Action that will add the key to the dictionary</returns>
Action AppendSet(Stream source, bool appendMode = true) Action AppendSet(Stream source, bool appendMode = true)
{ {
string key = ValidateKey(source, appendMode); string key = ValidateKey(source, appendMode);
@ -370,6 +359,7 @@ namespace Assignment_1
} }
} }
} }
/// <summary> /// <summary>
/// Creates and prints a nicely formatted table of all values /// Creates and prints a nicely formatted table of all values
/// </summary> /// </summary>
@ -413,6 +403,9 @@ namespace Assignment_1
consoleOutput.Append(string.Format("└" + new string('─', keyWidth) + "┴" + new string('─', valueWidth) + "┴" + new string('─', flagWidth) + "┘\n")); consoleOutput.Append(string.Format("└" + new string('─', keyWidth) + "┴" + new string('─', valueWidth) + "┴" + new string('─', flagWidth) + "┘\n"));
return () => Console.WriteLine(consoleOutput.ToString()); return () => Console.WriteLine(consoleOutput.ToString());
} }
/// <summary>
/// Exit Application logic
/// </summary>
Action Exit(Stream source, long initialStreamLength, bool isDynamicInput=false) Action Exit(Stream source, long initialStreamLength, bool isDynamicInput=false)
{ {
void exitAction() void exitAction()
@ -445,6 +438,14 @@ namespace Assignment_1
} }
return exitAction; return exitAction;
} }
/// <summary>
/// Prints the expression to the console:<br />
/// 0: print the value <br />
/// 1: print the length <br />
/// 2: print the word count <br />
/// 3: print the words in the value <br />
/// </summary>
Action Print(Stream source, int mode = 0) Action Print(Stream source, int mode = 0)
{ {
StringBuilder outputString = new StringBuilder(); StringBuilder outputString = new StringBuilder();
@ -478,7 +479,9 @@ namespace Assignment_1
} }
return () => Console.WriteLine(outputString.ToString()); return () => Console.WriteLine(outputString.ToString());
} }
/// <summary>
/// Reverses the word-order of the symbol (in-place).
/// </summary>
Action Reverse(Stream source) Action Reverse(Stream source)
{ {
string key = ValidateKey(source, true); string key = ValidateKey(source, true);
@ -495,14 +498,10 @@ namespace Assignment_1
} }
/// <summary> /// <summary>
/// Writes the debug info to the screen in the form:<br/> /// Writes the debug info to the screen in the form: <br/>
/// line read from stream (lineStart) to line end<br/> /// line read from stream (lineStart) to line end <br/>
/// &lt;whitespace@caratPos&gt; ^ &lt;errorMessage&gt; /// &lt;whitespace@caratPos&gt; ^ &lt;errorMessage&gt;
/// </summary> /// </summary>
/// <param name="lineStart"></param>
/// <param name="caratPos"></param>
/// <param name="errorMessage"></param>
/// <param name="source"></param>
static void WriteDebugLine(long lineStart, long caratPos, string errorMessage, Stream source) static void WriteDebugLine(long lineStart, long caratPos, string errorMessage, Stream source)
{ {
source.Position = lineStart; source.Position = lineStart;
@ -513,18 +512,13 @@ namespace Assignment_1
source.Position = lineStart; source.Position = lineStart;
source.SetLength(source.Position); source.SetLength(source.Position);
} }
#endregion
#region Data Handling
/// <summary> /// <summary>
/// Parses & evaluates the expression from the stream, moving the stream to the end of the last value /// Parses & evaluates the expression from the stream, moving the stream to the end of the last value
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="expression"></param>
/// <returns></returns>
long FindExpression(Stream s, out string expression) long FindExpression(Stream s, out string expression)
{ {
string result = ""; string result = "";
// iterate through values until we reach either the end of the stream or the end-of-statement
bool IsAppendSet = true; bool IsAppendSet = true;
while (s.Position < s.Length && !IsNextEoS(s)) while (s.Position < s.Length && !IsNextEoS(s))
{ {
@ -550,17 +544,14 @@ namespace Assignment_1
{ {
throw new ParserException("Append operator not set", 0, s.Position); throw new ParserException("Append operator not set", 0, s.Position);
} }
} }
} }
expression = result; expression = result;
return s.Position; return s.Position;
} }
/// <summary> /// <summary>
/// Checks ahead to see if the next non-whitespace character is the EoS indicator (';') /// Checks ahead to see if the next non-whitespace character is the EoS indicator
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="EoSChar"></param>
/// <returns>true if the next char is <paramref name="EoSChar"/>, else false</returns> /// <returns>true if the next char is <paramref name="EoSChar"/>, else false</returns>
static bool IsNextEoS(Stream s, char EoSChar = ';') static bool IsNextEoS(Stream s, char EoSChar = ';')
{ {
@ -576,11 +567,8 @@ namespace Assignment_1
} }
/// <summary> /// <summary>
/// Finds the next value in the stream /// Finds the next value expression in the stream
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="returnedValue"></param>
/// <returns></returns>
long FindValue(Stream s, out string returnedValue) long FindValue(Stream s, out string returnedValue)
{ {
SkipWhitespace(s); SkipWhitespace(s);
@ -593,16 +581,12 @@ namespace Assignment_1
else else
{ {
long t = FindExistingIdentifier(s, out string keyValue); long t = FindExistingIdentifier(s, out string keyValue);
// Set the key value to result + this read string
//keyValue = result + keyValue;
if (!Symbols.ContainsKey(keyValue)) if (!Symbols.ContainsKey(keyValue))
{ {
throw new ParserException("Could not find key: " + keyValue, 0, s.Position); throw new ParserException("Could not find key: " + keyValue, 0, s.Position);
} }
returnedValue = Symbols[keyValue].Item1; returnedValue = Symbols[keyValue].Item1;
return t; return t;
} }
} }
@ -621,7 +605,6 @@ namespace Assignment_1
wordEnd--; wordEnd--;
s.Position--; s.Position--;
} }
// Lookup the value in the symbol table
returnedKey = identifier; returnedKey = identifier;
return wordEnd; return wordEnd;
} }
@ -629,9 +612,6 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Finds the end of the complete literal definition, returning the stream to the original position /// Finds the end of the complete literal definition, returning the stream to the original position
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="returnedLiteral"></param>
/// <returns></returns>
static long FindLiteral(Stream s, out string returnedLiteral) static long FindLiteral(Stream s, out string returnedLiteral)
{ {
long pos = s.Position; long pos = s.Position;
@ -642,14 +622,16 @@ namespace Assignment_1
{ {
if (c == '\"') if (c == '\"')
{ {
long pos = s.Position--; s.Position--;
if (ReadChar(s) == '\\') if (PreviousChar(s) == '\\')
{ {
// TODO: handle the \\ escape // TODO: handle the \\ escape
s.Position++;
return false; return false;
} }
else else
{ {
s.Position++;
return true; return true;
} }
} }
@ -657,7 +639,7 @@ namespace Assignment_1
}, out string resultLiteral); }, out string resultLiteral);
if (resultPosition > -1) if (resultPosition > -1)
{ {
returnedLiteral = resultLiteral; returnedLiteral = resultLiteral.Replace("\\\"", "\"");
} }
else else
{ {
@ -666,16 +648,11 @@ namespace Assignment_1
s.Position = pos; s.Position = pos;
return resultPosition; return resultPosition;
} }
#endregion
} }
#region HelperFunctions
/// <summary> /// <summary>
/// Reads the memory stream as a UTF-8 encoded string until the next occurance of '\n' or '\r\n' (consuming, and excluded) /// Reads the memory stream as a UTF-8 encoded string until the next occurance of '\n' or '\r\n' (consuming, and excluded)
/// </summary> /// </summary>
/// <param name="s"></param>
/// <returns></returns>
static string GetNextLine(Stream s) static string GetNextLine(Stream s)
{ {
FindNextOccurance(s, '\n', out string nextLine); FindNextOccurance(s, '\n', out string nextLine);
@ -685,26 +662,15 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Finds the end-boundary of the next word in the stream, and returns the stream to the original position /// Finds the end-boundary of the next word in the stream, and returns the stream to the original position
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="nextWord"></param>
/// <returns></returns>
static long FindNextWord(Stream s, out string nextWord) static long FindNextWord(Stream s, out string nextWord)
{ {
StringBuilder newWord = new StringBuilder(); StringBuilder newWord = new StringBuilder();
// Record our current position
long start = s.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 and line feeds,
// so 'set\r\n
// var
// "expression";
// should be valid
char currentChar = ReadChar(s); char currentChar = ReadChar(s);
while (s.Position < s.Length && char.IsWhiteSpace(currentChar)) while (s.Position < s.Length && char.IsWhiteSpace(currentChar))
{ {
currentChar = ReadChar(s); currentChar = ReadChar(s);
} }
// Add the last read value to the SB
newWord.Append(currentChar); newWord.Append(currentChar);
// Start a second loop, this time checking we're not a whitespace char // Start a second loop, this time checking we're not a whitespace char
while (s.Position < s.Length) while (s.Position < s.Length)
@ -731,10 +697,6 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Finds and returns the position of the next occurance of the Func returning true. /// Finds and returns the position of the next occurance of the Func returning true.
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="p">A 'predicate'-like Func</param>
/// <param name="result">Returns the string captured while searching for the next char</param>
/// <returns></returns>
static long FindNextOccurance(Stream s, Func<char, Stream, bool> p, out string result) static long FindNextOccurance(Stream s, Func<char, Stream, bool> p, out string result)
{ {
long start = s.Position; long start = s.Position;
@ -764,21 +726,16 @@ namespace Assignment_1
} }
/// <summary> /// <summary>
/// Finds the next position of the character /// Finds the next position of the supplied character
/// </summary> /// </summary>
/// <param name="s"></param>
/// <param name="c"></param>
/// <param name="result">Captures the string read in searching for the character</param>
/// <returns></returns>
static long FindNextOccurance(Stream s, char c, out string result) static long FindNextOccurance(Stream s, char c, out string result)
{ {
return FindNextOccurance(s, (streamChar, s) => streamChar == c, out result); return FindNextOccurance(s, (streamChar, s) => streamChar == c, out result);
} }
/// <summary> /// <summary>
/// Reads the next UTF-8 encoded character in the stream, and advances the stream by the amount of characters read /// Reads the next UTF-8 encoded character in the stream, and advances the stream by the amount of characters read
/// </summary> /// </summary>
/// <param name="s"></param>
/// <returns></returns>
static char ReadChar(Stream s) static char ReadChar(Stream s)
{ {
// As UTF-8 allows codepoints to span multiple bytes, reading a single byte as a character will not always give the expected // As UTF-8 allows codepoints to span multiple bytes, reading a single byte as a character will not always give the expected
@ -815,11 +772,10 @@ namespace Assignment_1
string converted = Encoding.UTF8.GetString(charBytes); string converted = Encoding.UTF8.GetString(charBytes);
return converted[0]; return converted[0];
} }
/// <summary> /// <summary>
/// Reads the next character in the stream, and returns the position to the original position /// Reads the next character in the stream, and returns the position to the original position
/// </summary> /// </summary>
/// <param name="s"></param>
/// <returns></returns>
static char PeekChar(Stream s) static char PeekChar(Stream s)
{ {
long curr = s.Position; long curr = s.Position;
@ -831,8 +787,6 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Reads the previous char /// Reads the previous char
/// </summary> /// </summary>
/// <param name="s"></param>
/// <returns></returns>
static char PreviousChar(Stream s) static char PreviousChar(Stream s)
{ {
Stack<byte> charBytes = new Stack<byte>(4); Stack<byte> charBytes = new Stack<byte>(4);
@ -855,13 +809,12 @@ namespace Assignment_1
/// <summary> /// <summary>
/// Skips whitespace characters /// Skips whitespace characters
/// </summary> /// </summary>
/// <param name="s"></param>
static void SkipWhitespace(Stream s) static void SkipWhitespace(Stream s)
{ {
char c = PeekChar(s); char c = PeekChar(s);
while (s.Position < s.Length && char.IsWhiteSpace(c)) while (s.Position < s.Length && char.IsWhiteSpace(c))
{ {
ReadChar(s); // move by the size of that character ReadChar(s);
c = PeekChar(s); c = PeekChar(s);
} }
} }
@ -889,7 +842,7 @@ namespace Assignment_1
} }
return lines; return lines;
} }
#endregion
public class ParserException : Exception public class ParserException : Exception
{ {
/// <summary> /// <summary>