2021-03-29 14:02:25 +13:00
|
|
|
|
/* Dempsey-Jensen, Brychan, 14299890, Assignment 1, 159.341 */
|
|
|
|
|
/* Parses and interprets a simple programming language line-by-line */
|
|
|
|
|
|
|
|
|
|
using System;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
using System.Collections.Generic;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
2021-03-19 12:31:16 +13:00
|
|
|
|
using System.Threading;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
|
|
|
|
|
namespace Assignment_1
|
|
|
|
|
{
|
|
|
|
|
class Program
|
|
|
|
|
{
|
2021-03-17 17:11:18 +13:00
|
|
|
|
/// <summary>
|
2021-03-29 14:02:25 +13:00
|
|
|
|
/// Flags to set symbol properties
|
2021-03-17 17:11:18 +13:00
|
|
|
|
/// </summary>
|
2021-03-12 11:00:29 +13:00
|
|
|
|
[Flags]
|
2021-03-11 16:18:05 +13:00
|
|
|
|
enum VariableFlags
|
|
|
|
|
{
|
|
|
|
|
Empty = 0,
|
2021-03-11 16:39:15 +13:00
|
|
|
|
Reserved = 1,
|
2021-03-15 20:58:52 +13:00
|
|
|
|
NoPrint = 2,
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Static = 4
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
|
|
|
|
// Max display width for tables etc.
|
2021-03-29 14:02:25 +13:00
|
|
|
|
static readonly int ConsoleWidthLimit = 80;
|
2021-03-15 15:03:36 +13:00
|
|
|
|
|
2021-03-11 12:56:25 +13:00
|
|
|
|
static void Main(string[] args)
|
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Console.WriteLine(CenterString("┌──────────────────────────────────────────┐", ConsoleWidthLimit));
|
|
|
|
|
Console.WriteLine(CenterString("│ 159.341 2021 Semester 1, Assignment 1 │", ConsoleWidthLimit));
|
|
|
|
|
Console.WriteLine(CenterString("│ Submitted by Brychan Dempsey, 14299890 │", ConsoleWidthLimit));
|
|
|
|
|
Console.WriteLine(CenterString("└──────────────────────────────────────────┘", ConsoleWidthLimit));
|
|
|
|
|
bool loadedFromFile = false;
|
2021-03-19 12:42:38 +13:00
|
|
|
|
bool exit = false;
|
|
|
|
|
while (!exit)
|
2021-03-17 18:24:31 +13:00
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
MemoryStream sourceStream = new MemoryStream(1024);
|
|
|
|
|
Parser parser = new Parser();
|
2021-03-19 12:42:38 +13:00
|
|
|
|
bool dynamicInput = false;
|
|
|
|
|
// From https://stackoverflow.com/questions/3453220/how-to-detect-if-console-in-stdin-has-been-redirected
|
2021-03-29 14:02:25 +13:00
|
|
|
|
// Reading from pipes is equivalent to reading user input, but the input is redirected
|
|
|
|
|
if (Console.IsInputRedirected || loadedFromFile)
|
2021-03-19 12:42:38 +13:00
|
|
|
|
{
|
|
|
|
|
// To simplify reading, we read all input bytes from the piped input to the stream.
|
2021-03-29 16:24:21 +13:00
|
|
|
|
// 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
|
2021-03-19 12:42:38 +13:00
|
|
|
|
sourceStream.Write(Encoding.UTF8.GetBytes(Console.In.ReadToEnd()));
|
|
|
|
|
Console.In.Dispose();
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Console.SetIn(new StreamReader(Console.OpenStandardInput()));
|
|
|
|
|
Console.OpenStandardInput();
|
2021-03-19 12:42:38 +13:00
|
|
|
|
sourceStream.Position = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sourceStream.Position = 0;
|
|
|
|
|
dynamicInput = true;
|
|
|
|
|
}
|
|
|
|
|
parser.StartParsing(sourceStream, dynamicInput);
|
|
|
|
|
Console.WriteLine(Environment.NewLine + new string('─', 40));
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Console.WriteLine("\nProgram Parsed Successfully!");
|
|
|
|
|
|
|
|
|
|
if (Console.IsInputRedirected)
|
2021-03-19 12:42:38 +13:00
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Thread.Sleep(3000);
|
2021-03-19 12:42:38 +13:00
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
}
|
2021-03-29 10:05:48 +13:00
|
|
|
|
ConsoleKeyInfo ck = new ConsoleKeyInfo();
|
2021-03-24 12:50:33 +13:00
|
|
|
|
while (ck.Key != ConsoleKey.Y && ck.Key != ConsoleKey.N)
|
2021-03-19 12:42:38 +13:00
|
|
|
|
{
|
2021-03-24 12:50:33 +13:00
|
|
|
|
Console.WriteLine("\nWould you like to parse another program? Y/n:");
|
|
|
|
|
ck = Console.ReadKey(true);
|
2021-03-23 11:26:38 +13:00
|
|
|
|
}
|
|
|
|
|
if (ck.Key == ConsoleKey.N)
|
|
|
|
|
{
|
|
|
|
|
exit = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
ck = new ConsoleKeyInfo();
|
2021-03-24 12:50:33 +13:00
|
|
|
|
while (ck.Key != ConsoleKey.Y && ck.Key != ConsoleKey.N)
|
2021-03-23 11:26:38 +13:00
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
Console.WriteLine("\nWould you like to pipe data from a source file? Y/n:");
|
2021-03-24 12:50:33 +13:00
|
|
|
|
ck = Console.ReadKey(true);
|
2021-03-23 11:26:38 +13:00
|
|
|
|
}
|
|
|
|
|
if (ck.Key == ConsoleKey.N)
|
|
|
|
|
{
|
|
|
|
|
// Set the input to standard input stream
|
|
|
|
|
Console.SetIn(Console.In);
|
2021-03-29 14:02:25 +13:00
|
|
|
|
loadedFromFile = false;
|
2021-03-23 11:26:38 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Enter the source path:");
|
|
|
|
|
string sourcePath = Console.ReadLine();
|
|
|
|
|
if (File.Exists(sourcePath))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Console.SetIn(File.OpenText(sourcePath));
|
2021-03-29 14:02:25 +13:00
|
|
|
|
loadedFromFile = true;
|
2021-03-23 11:26:38 +13:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Encountered an error opening the source file: " + e.Message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-19 12:42:38 +13:00
|
|
|
|
}
|
2021-03-17 18:24:31 +13:00
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-19 12:31:16 +13:00
|
|
|
|
|
2021-03-11 12:56:25 +13:00
|
|
|
|
public class Parser
|
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
Dictionary<string, Tuple<string, VariableFlags>> Symbols = new Dictionary<string, Tuple<string, VariableFlags>>()
|
2021-03-19 12:20:01 +13:00
|
|
|
|
{
|
|
|
|
|
{ "SPACE", new Tuple<string, VariableFlags>(" ", VariableFlags.Reserved | VariableFlags.NoPrint) },
|
|
|
|
|
{ "TAB", new Tuple<string, VariableFlags>("\t", VariableFlags.Reserved | VariableFlags.NoPrint) },
|
|
|
|
|
{ "NEWLINE", new Tuple<string, VariableFlags>("\n", VariableFlags.Reserved | VariableFlags.NoPrint) },
|
|
|
|
|
{ "CARRIAGE_RETURN", new Tuple<string, VariableFlags>("\r", VariableFlags.Reserved | VariableFlags.NoPrint) }
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-19 12:31:16 +13:00
|
|
|
|
public enum Statements
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-11 16:18:05 +13:00
|
|
|
|
exit,
|
|
|
|
|
append,
|
|
|
|
|
list,
|
|
|
|
|
print,
|
|
|
|
|
printlength,
|
|
|
|
|
printwords,
|
|
|
|
|
printwordcount,
|
|
|
|
|
set,
|
2021-03-12 12:14:51 +13:00
|
|
|
|
reverse,
|
2021-03-15 13:46:54 +13:00
|
|
|
|
h,
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
public void StartParsing(Stream source, bool dynamicInput = false)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-17 14:25:39 +13:00
|
|
|
|
long initSourceLength = source.Length;
|
2021-03-24 13:40:16 +13:00
|
|
|
|
long lastLinePos = 0;
|
|
|
|
|
long initPos = 0;
|
|
|
|
|
bool cont = false;
|
2021-03-29 15:24:11 +13:00
|
|
|
|
bool isLineFinished = true;
|
2021-03-24 13:40:16 +13:00
|
|
|
|
while (true)
|
2021-03-11 19:22:49 +13:00
|
|
|
|
{
|
2021-03-29 15:24:11 +13:00
|
|
|
|
if (dynamicInput && isLineFinished)
|
2021-03-11 19:22:49 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
lastLinePos = source.Position;
|
2021-03-17 17:27:48 +13:00
|
|
|
|
if (!cont)
|
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
Console.WriteLine("Enter a command: ");
|
2021-03-17 17:27:48 +13:00
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
string s = Console.ReadLine();
|
|
|
|
|
long pos = source.Position;
|
|
|
|
|
source.Write(Encoding.UTF8.GetBytes(s));
|
|
|
|
|
source.Position = pos;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (!cont)
|
|
|
|
|
{
|
|
|
|
|
initPos = source.Position;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
source.Position = initPos;
|
|
|
|
|
}
|
|
|
|
|
SkipWhitespace(source);
|
|
|
|
|
long position = FindNextWord(source, out string word);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Enum.TryParse(typeof(Statements), word, out object statementType))
|
2021-03-11 19:22:49 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
// By turning the result of the command into an action,
|
|
|
|
|
// we can defer processing the final result until the end of this control flow
|
|
|
|
|
Action result = () => { };
|
|
|
|
|
source.Position = position;
|
|
|
|
|
switch ((Statements)statementType)
|
2021-03-15 20:58:52 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
case Statements.exit:
|
|
|
|
|
result = Exit(source, initSourceLength, dynamicInput);
|
|
|
|
|
break;
|
|
|
|
|
case Statements.append:
|
|
|
|
|
result = AppendSet(source);
|
|
|
|
|
break;
|
|
|
|
|
case Statements.list:
|
|
|
|
|
long pos = FindNextWord(source, out string nextWord);
|
|
|
|
|
if (nextWord == "all")
|
2021-03-17 14:25:39 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
source.Position = pos;
|
|
|
|
|
result = List(true);
|
2021-03-17 14:25:39 +13:00
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
else
|
2021-03-19 12:31:16 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
result = List();
|
2021-03-19 12:31:16 +13:00
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
break;
|
|
|
|
|
case Statements.print:
|
|
|
|
|
result = Print(source, 0);
|
|
|
|
|
break;
|
|
|
|
|
case Statements.printlength:
|
|
|
|
|
result = Print(source, 1);
|
|
|
|
|
break;
|
|
|
|
|
case Statements.printwords:
|
|
|
|
|
result = Print(source, 2);
|
|
|
|
|
break;
|
|
|
|
|
case Statements.printwordcount:
|
|
|
|
|
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());
|
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
// Stream ignores this command
|
2021-03-24 13:40:16 +13:00
|
|
|
|
source.Position = initPos;
|
|
|
|
|
source.SetLength(initPos);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (IsNextEoS(source))
|
|
|
|
|
{
|
|
|
|
|
cont = false;
|
|
|
|
|
source.Position++;
|
2021-03-29 15:24:11 +13:00
|
|
|
|
SkipWhitespace(source);
|
|
|
|
|
if (source.Position == source.Length) isLineFinished = true;
|
|
|
|
|
else isLineFinished = false;
|
|
|
|
|
if (dynamicInput && isLineFinished)
|
2021-03-17 17:27:48 +13:00
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
source.WriteByte((byte)'\n'); // put the next statement on a newline so the exported list of commands is clean
|
2021-03-17 17:27:48 +13:00
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
result();
|
|
|
|
|
if (((Statements)statementType).Equals(Statements.exit))
|
2021-03-15 20:58:52 +13:00
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
return;
|
2021-03-15 20:58:52 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-29 15:24:11 +13:00
|
|
|
|
else if (source.Position != lastLinePos)
|
2021-03-15 20:58:52 +13:00
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
// If the semicolon is missing *once*, assume we need more data.
|
2021-03-24 13:40:16 +13:00
|
|
|
|
lastLinePos = source.Position;
|
|
|
|
|
cont = true;
|
|
|
|
|
Console.Write(">");
|
2021-03-15 20:58:52 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
if ((Statements)statementType != Statements.h)
|
|
|
|
|
{
|
|
|
|
|
throw new ParserException("expected a semi-colon", 0, source.Position);
|
|
|
|
|
}
|
2021-03-17 18:24:31 +13:00
|
|
|
|
}
|
2021-03-15 13:46:54 +13:00
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new ParserException("Failed parsing statement", 0, source.Position);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-29 15:24:11 +13:00
|
|
|
|
// Throwing a ParserException will return us to this point immediately. From here, the line is automatically restored,
|
2021-03-24 13:40:16 +13:00
|
|
|
|
// 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)
|
|
|
|
|
{
|
2021-03-29 15:24:11 +13:00
|
|
|
|
isLineFinished = true;
|
2021-03-24 13:40:16 +13:00
|
|
|
|
if (e.Importance > 3)
|
|
|
|
|
{
|
|
|
|
|
throw new ApplicationException("A critical error occurred.");
|
|
|
|
|
}
|
|
|
|
|
if (e.LinePosition > 0)
|
|
|
|
|
{
|
|
|
|
|
WriteDebugLine(initPos, e.LinePosition, e.Message, source);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-29 14:56:39 +13:00
|
|
|
|
WriteDebugLine(0, 0, e.Message, source);
|
2021-03-24 13:40:16 +13:00
|
|
|
|
}
|
|
|
|
|
if (!dynamicInput)
|
|
|
|
|
{
|
|
|
|
|
Environment.Exit(-1);
|
|
|
|
|
}
|
2021-03-11 19:22:49 +13:00
|
|
|
|
}
|
2021-03-17 18:24:31 +13:00
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 12:20:01 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if the next expression in the source meets the requirements of being a key,
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// and optionally verify that key exists. Acoids overidding the value of constants, and
|
|
|
|
|
/// ensures all characters are valid
|
2021-03-19 12:20:01 +13:00
|
|
|
|
/// </summary>
|
2021-03-15 20:58:52 +13:00
|
|
|
|
private string ValidateKey(Stream source, bool checkExist)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-19 12:04:03 +13:00
|
|
|
|
long keyEndPos = FindIdentifier(source, out string key);
|
2021-03-15 20:58:52 +13:00
|
|
|
|
if (keyEndPos < 0 || key.Length == 0)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
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);
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-29 14:02:25 +13:00
|
|
|
|
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));
|
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
int indx = Array.FindIndex(key.ToCharArray(), (c) => (c > 122 || c > 90 && c < 97 && c != '_' || c > 57 && c < 65 || c < 48));
|
2021-03-29 14:02:25 +13:00
|
|
|
|
if (indx > -1)
|
2021-03-15 20:58:52 +13:00
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
throw new ParserException(string.Format("Character \'{0}\' is not valid for an identifier",key[indx]), 0, keyEndPos-key.Length + indx);
|
2021-03-15 20:58:52 +13:00
|
|
|
|
}
|
2021-03-12 11:00:29 +13:00
|
|
|
|
source.Position = keyEndPos;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-15 20:58:52 +13:00
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 12:20:01 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks if the next expression meets the requirements of being a value
|
|
|
|
|
/// </summary>
|
2021-03-15 20:58:52 +13:00
|
|
|
|
private string ValidateValue(Stream source)
|
|
|
|
|
{
|
2021-03-19 12:04:03 +13:00
|
|
|
|
long valuePos = FindExpression(source, out string value);
|
2021-03-12 11:00:29 +13:00
|
|
|
|
if (valuePos < 0)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
throw new ParserException("Could not evaluate expression", 0, source.Position);
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-12 11:00:29 +13:00
|
|
|
|
source.Position = valuePos;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
2021-03-15 20:58:52 +13:00
|
|
|
|
return value;
|
|
|
|
|
}
|
2021-03-19 12:20:01 +13:00
|
|
|
|
|
2021-03-15 20:58:52 +13:00
|
|
|
|
/// <summary>
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// Handles the 'append x y [ + z];' case <br />
|
2021-03-15 20:58:52 +13:00
|
|
|
|
/// And the 'set x y [ + z];' case
|
|
|
|
|
/// </summary>
|
2021-03-19 12:31:16 +13:00
|
|
|
|
Action AppendSet(Stream source, bool appendMode = true)
|
2021-03-15 20:58:52 +13:00
|
|
|
|
{
|
|
|
|
|
string key = ValidateKey(source, appendMode);
|
|
|
|
|
string value = ValidateValue(source);
|
|
|
|
|
if (appendMode)
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
return () => Symbols[key] = new Tuple<string, VariableFlags>(Symbols[key].Item1 + value, Symbols[key].Item2);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
2021-03-15 20:58:52 +13:00
|
|
|
|
else
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-19 12:04:03 +13:00
|
|
|
|
if (Symbols.ContainsKey(key))
|
|
|
|
|
{
|
|
|
|
|
return () => Symbols[key] = new Tuple<string, VariableFlags>(value, Symbols[key].Item2);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return () => Symbols.Add(key, new Tuple<string, VariableFlags>(value, VariableFlags.Empty));
|
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// <summary>
|
2021-03-19 12:12:56 +13:00
|
|
|
|
/// Creates and prints a nicely formatted table of all values
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// </summary>
|
2021-03-15 20:58:52 +13:00
|
|
|
|
/// <param name="printUnprint">List values normally excluded from printing</param>
|
|
|
|
|
Action List(bool printUnprint = false)
|
2021-03-11 16:39:15 +13:00
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
int flagWidth = Math.Max(Enum.GetNames(typeof(VariableFlags)).Length, "Flags".Length);
|
|
|
|
|
int keyWidth = (int)((ConsoleWidthLimit - flagWidth) * 0.2); // 20% - flag width
|
|
|
|
|
int valueWidth = (int)((ConsoleWidthLimit - flagWidth) * 0.8); // 80% - flag width
|
|
|
|
|
|
2021-03-29 10:05:48 +13:00
|
|
|
|
StringBuilder consoleOutput = new StringBuilder();
|
2021-03-15 20:58:52 +13:00
|
|
|
|
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)));
|
2021-03-29 10:07:48 +13:00
|
|
|
|
List<string> eligibleKeys = new List<string>(Symbols.Count);
|
2021-03-15 17:28:20 +13:00
|
|
|
|
foreach (var item in Symbols.Keys)
|
|
|
|
|
{
|
|
|
|
|
if (!Symbols[item].Item2.HasFlag(VariableFlags.NoPrint) || (Symbols[item].Item2.HasFlag(VariableFlags.NoPrint) && printUnprint))
|
2021-03-11 16:39:15 +13:00
|
|
|
|
{
|
2021-03-15 17:28:20 +13:00
|
|
|
|
eligibleKeys.Add(item);
|
2021-03-11 16:39:15 +13:00
|
|
|
|
}
|
2021-03-15 17:28:20 +13:00
|
|
|
|
}
|
|
|
|
|
if (eligibleKeys.Count > 0)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
consoleOutput.Append(string.Format("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤\n"));
|
2021-03-15 17:28:20 +13:00
|
|
|
|
for (int i = 0; i < eligibleKeys.Count; i++)
|
2021-03-11 16:39:15 +13:00
|
|
|
|
{
|
2021-03-19 12:31:16 +13:00
|
|
|
|
string entryFormat = "│{0," + -1 * keyWidth + "}│{1," + -1 * valueWidth + "}│{2," + -1 * flagWidth + "}│\n";
|
2021-03-24 13:40:16 +13:00
|
|
|
|
List<string> keyLines = GetStringLines(eligibleKeys[i], keyWidth);
|
|
|
|
|
List<string> valueLines = GetStringLines(Symbols[eligibleKeys[i]].Item1.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"), valueWidth);
|
2021-03-15 17:28:20 +13:00
|
|
|
|
|
2021-03-24 13:40:16 +13:00
|
|
|
|
for (int j = 0; j < (keyLines.Count > valueLines.Count ? keyLines.Count : valueLines.Count); j++)
|
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
consoleOutput.Append(string.Format(entryFormat, j >= keyLines.Count ? "" : keyLines[j], j >= valueLines.Count ? "" : valueLines[j], j == 0 ? Convert.ToString((byte)Symbols[eligibleKeys[i]].Item2, 2).PadLeft(flagWidth, '0'): ""));
|
2021-03-24 13:40:16 +13:00
|
|
|
|
}
|
2021-03-15 17:28:20 +13:00
|
|
|
|
if (i + 1 < eligibleKeys.Count)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
consoleOutput.Append(string.Format("├" + new string('─', keyWidth) + "┼" + new string('─', valueWidth) + "┼" + new string('─', flagWidth) + "┤\n"));
|
2021-03-15 17:28:20 +13:00
|
|
|
|
}
|
2021-03-11 16:39:15 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-15 20:58:52 +13:00
|
|
|
|
consoleOutput.Append(string.Format("└" + new string('─', keyWidth) + "┴" + new string('─', valueWidth) + "┴" + new string('─', flagWidth) + "┘\n"));
|
|
|
|
|
return () => Console.WriteLine(consoleOutput.ToString());
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Exit Application logic
|
|
|
|
|
/// </summary>
|
2021-03-24 12:50:33 +13:00
|
|
|
|
Action Exit(Stream source, long initialStreamLength, bool isDynamicInput=false)
|
2021-03-11 16:53:20 +13:00
|
|
|
|
{
|
2021-03-29 14:02:25 +13:00
|
|
|
|
void exitAction()
|
2021-03-17 14:25:39 +13:00
|
|
|
|
{
|
2021-03-24 12:50:33 +13:00
|
|
|
|
if (source.Length != initialStreamLength && isDynamicInput)
|
2021-03-17 14:25:39 +13:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Commands list has been modified; would you like to save it to a file?");
|
|
|
|
|
string commandState = "";
|
|
|
|
|
while (commandState.ToLower() != "y" && commandState.ToLower() != "n")
|
|
|
|
|
{
|
|
|
|
|
Console.Write("Y/n: ");
|
|
|
|
|
commandState = Console.ReadLine();
|
|
|
|
|
}
|
|
|
|
|
if (commandState.ToLower() == "y")
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Enter an output file (default {0}):", Environment.CurrentDirectory);
|
|
|
|
|
string path = Console.ReadLine();
|
2021-03-19 12:31:16 +13:00
|
|
|
|
if (path != "")
|
2021-03-17 14:25:39 +13:00
|
|
|
|
{
|
2021-03-19 12:31:16 +13:00
|
|
|
|
path = Path.Combine(Environment.CurrentDirectory, path);
|
|
|
|
|
source.Position = 0;
|
|
|
|
|
using (FileStream fs = File.OpenWrite(path))
|
|
|
|
|
{
|
|
|
|
|
source.CopyTo(fs);
|
|
|
|
|
}
|
|
|
|
|
source.Close();
|
2021-03-17 14:25:39 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-29 14:02:25 +13:00
|
|
|
|
}
|
2021-03-17 14:25:39 +13:00
|
|
|
|
return exitAction;
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2021-03-19 12:31:16 +13:00
|
|
|
|
Action Print(Stream source, int mode = 0)
|
2021-03-11 16:53:20 +13:00
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
StringBuilder outputString = new StringBuilder();
|
2021-03-15 20:58:52 +13:00
|
|
|
|
string expression = ValidateValue(source);
|
2021-03-11 16:53:20 +13:00
|
|
|
|
if (mode == 0)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
outputString.Append(expression + Environment.NewLine);
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
|
|
|
|
else if (mode == 1)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
outputString.Append("Length of the expression is: ");
|
|
|
|
|
outputString.Append(expression.Length + Environment.NewLine);
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
|
|
|
|
else if (mode >= 2)
|
|
|
|
|
{
|
2021-03-19 12:31:16 +13:00
|
|
|
|
|
2021-03-11 16:53:20 +13:00
|
|
|
|
string[] words = expression.Split(' ');
|
|
|
|
|
if (mode == 3)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
outputString.Append("Wordcount is: ");
|
|
|
|
|
outputString.Append(words.Length + Environment.NewLine);
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Words are:");
|
|
|
|
|
foreach (string word in words)
|
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
outputString.Append(word + Environment.NewLine);
|
2021-03-11 16:53:20 +13:00
|
|
|
|
}
|
2021-03-19 12:31:16 +13:00
|
|
|
|
}
|
2021-03-11 17:18:02 +13:00
|
|
|
|
}
|
2021-03-15 20:58:52 +13:00
|
|
|
|
return () => Console.WriteLine(outputString.ToString());
|
2021-03-11 17:18:02 +13:00
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reverses the word-order of the symbol (in-place).
|
|
|
|
|
/// </summary>
|
2021-03-15 20:58:52 +13:00
|
|
|
|
Action Reverse(Stream source)
|
2021-03-11 17:47:38 +13:00
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
string key = ValidateKey(source, true);
|
|
|
|
|
string ToReverse = Symbols[key].Item1;
|
2021-03-11 17:47:38 +13:00
|
|
|
|
string[] words = ToReverse.Split(' ');
|
2021-03-29 10:05:48 +13:00
|
|
|
|
StringBuilder reversed = new StringBuilder();
|
2021-03-19 12:31:16 +13:00
|
|
|
|
for (int i = words.Length - 1; i >= 0; i--)
|
2021-03-11 17:47:38 +13:00
|
|
|
|
{
|
|
|
|
|
reversed.Append(words[i]);
|
|
|
|
|
reversed.Append(' ');
|
|
|
|
|
}
|
2021-03-19 12:31:16 +13:00
|
|
|
|
|
2021-03-15 20:58:52 +13:00
|
|
|
|
return () => Symbols[key] = new Tuple<string, VariableFlags>(reversed.ToString(), Symbols[key].Item2);
|
2021-03-11 17:47:38 +13:00
|
|
|
|
}
|
2021-03-17 14:25:39 +13:00
|
|
|
|
|
2021-03-12 11:29:46 +13:00
|
|
|
|
/// <summary>
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// Writes the debug info to the screen in the form: <br/>
|
|
|
|
|
/// line read from stream (lineStart) to line end <br/>
|
2021-03-12 11:29:46 +13:00
|
|
|
|
/// <whitespace@caratPos> ^ <errorMessage>
|
|
|
|
|
/// </summary>
|
2021-03-17 14:25:39 +13:00
|
|
|
|
static void WriteDebugLine(long lineStart, long caratPos, string errorMessage, Stream source)
|
2021-03-12 11:29:46 +13:00
|
|
|
|
{
|
|
|
|
|
source.Position = lineStart;
|
|
|
|
|
string fullLine = GetNextLine(source);
|
2021-03-19 12:31:16 +13:00
|
|
|
|
string errorMSG = new string(' ', (caratPos - lineStart) >= 0 ? (int)(caratPos - lineStart) : 0) + "^ " + errorMessage;
|
2021-03-12 11:29:46 +13:00
|
|
|
|
Console.WriteLine(fullLine);
|
|
|
|
|
Console.WriteLine(errorMSG);
|
2021-03-24 14:51:17 +13:00
|
|
|
|
source.Position = lineStart;
|
2021-03-12 11:29:46 +13:00
|
|
|
|
source.SetLength(source.Position);
|
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// <summary>
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// Parses & evaluates the expression from the stream, moving the stream to the end of the last value
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// </summary>
|
|
|
|
|
long FindExpression(Stream s, out string expression)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-15 15:03:36 +13:00
|
|
|
|
string result = "";
|
2021-03-24 13:40:16 +13:00
|
|
|
|
bool IsAppendSet = true;
|
2021-03-15 15:03:36 +13:00
|
|
|
|
while (s.Position < s.Length && !IsNextEoS(s))
|
|
|
|
|
{
|
|
|
|
|
if (IsNextEoS(s, '+'))
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-15 15:03:36 +13:00
|
|
|
|
s.Position = FindNextWord(s, out _);
|
2021-03-24 13:40:16 +13:00
|
|
|
|
IsAppendSet = true;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-17 17:11:18 +13:00
|
|
|
|
long val = FindValue(s, out string value);
|
2021-03-15 16:42:37 +13:00
|
|
|
|
if (val == -1)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Could not parse value");
|
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
if (IsAppendSet)
|
|
|
|
|
{
|
|
|
|
|
s.Position = val;
|
|
|
|
|
result += value;
|
|
|
|
|
IsAppendSet = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new ParserException("Append operator not set", 0, s.Position);
|
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
expression = result;
|
2021-03-15 16:42:37 +13:00
|
|
|
|
return s.Position;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// <summary>
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// Checks ahead to see if the next non-whitespace character is the EoS indicator
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>true if the next char is <paramref name="EoSChar"/>, else false</returns>
|
|
|
|
|
static bool IsNextEoS(Stream s, char EoSChar = ';')
|
|
|
|
|
{
|
2021-03-15 16:42:37 +13:00
|
|
|
|
long pos = s.Position;
|
2021-03-15 15:03:36 +13:00
|
|
|
|
char readChar = PeekChar(s);
|
2021-03-15 16:42:37 +13:00
|
|
|
|
while (readChar != 0 && char.IsWhiteSpace(readChar))
|
2021-03-15 15:03:36 +13:00
|
|
|
|
{
|
2021-03-15 16:42:37 +13:00
|
|
|
|
readChar = ReadChar(s);
|
2021-03-15 15:03:36 +13:00
|
|
|
|
}
|
2021-03-15 16:42:37 +13:00
|
|
|
|
s.Position = pos;
|
2021-03-15 15:03:36 +13:00
|
|
|
|
if (readChar == EoSChar) return true;
|
|
|
|
|
else return false;
|
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// <summary>
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// Finds the next value expression in the stream
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// </summary>
|
|
|
|
|
long FindValue(Stream s, out string returnedValue)
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-11 16:18:05 +13:00
|
|
|
|
SkipWhitespace(s);
|
2021-03-15 16:42:37 +13:00
|
|
|
|
char result = PeekChar(s);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
if (result == '\"')
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-15 16:42:37 +13:00
|
|
|
|
// The first char is a ", i.e. the start of a literal - search as if it were a literal.
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return FindLiteral(s, out returnedValue);
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
else
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-17 17:11:18 +13:00
|
|
|
|
long t = FindExistingIdentifier(s, out string keyValue);
|
2021-03-12 11:00:29 +13:00
|
|
|
|
if (!Symbols.ContainsKey(keyValue))
|
|
|
|
|
{
|
2021-03-24 14:51:17 +13:00
|
|
|
|
throw new ParserException("Could not find key: " + keyValue, 0, s.Position);
|
2021-03-12 11:00:29 +13:00
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
returnedValue = Symbols[keyValue].Item1;
|
|
|
|
|
return t;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 14:02:25 +13:00
|
|
|
|
static long FindIdentifier(Stream s, out string returnedKey)
|
2021-03-11 19:22:49 +13:00
|
|
|
|
{
|
|
|
|
|
long wordEnd = FindNextWord(s, out returnedKey);
|
|
|
|
|
return wordEnd;
|
|
|
|
|
}
|
2021-03-29 14:02:25 +13:00
|
|
|
|
static long FindExistingIdentifier(Stream s, out string returnedKey)
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-17 17:11:18 +13:00
|
|
|
|
long wordEnd = FindNextWord(s, out string identifier);
|
2021-03-15 16:42:37 +13:00
|
|
|
|
if (identifier.Length > 1 && identifier.EndsWith(';'))
|
2021-03-12 11:10:24 +13:00
|
|
|
|
{
|
|
|
|
|
// Remove the trailing semicolon from the parse & backtrack the identifier length one spot
|
|
|
|
|
identifier = identifier.TrimEnd(';');
|
|
|
|
|
wordEnd--;
|
2021-03-15 16:42:37 +13:00
|
|
|
|
s.Position--;
|
2021-03-12 11:10:24 +13:00
|
|
|
|
}
|
2021-03-11 19:22:49 +13:00
|
|
|
|
returnedKey = identifier;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return wordEnd;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-15 16:42:37 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the end of the complete literal definition, returning the stream to the original position
|
|
|
|
|
/// </summary>
|
2021-03-29 14:02:25 +13:00
|
|
|
|
static long FindLiteral(Stream s, out string returnedLiteral)
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-15 16:42:37 +13:00
|
|
|
|
long pos = s.Position;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
// Is a literal. Now we must parse until we find the end of the literal
|
2021-03-15 16:42:37 +13:00
|
|
|
|
// Remove the first char, if it is a literal definition.
|
|
|
|
|
if (PeekChar(s) == '\"') ReadChar(s);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
long resultPosition = FindNextOccurance(s, (c, s) =>
|
2021-03-11 12:56:25 +13:00
|
|
|
|
{
|
2021-03-11 16:18:05 +13:00
|
|
|
|
if (c == '\"')
|
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
s.Position--;
|
|
|
|
|
if (PreviousChar(s) == '\\')
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
|
|
|
|
// TODO: handle the \\ escape
|
2021-03-29 16:24:21 +13:00
|
|
|
|
s.Position++;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
s.Position++;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2021-03-24 14:51:17 +13:00
|
|
|
|
}, out string resultLiteral);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
if (resultPosition > -1)
|
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
returnedLiteral = resultLiteral.Replace("\\\"", "\"");
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-03-24 14:51:17 +13:00
|
|
|
|
throw new ParserException("Could not parse the literal", 0, s.Position);
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-15 16:42:37 +13:00
|
|
|
|
s.Position = pos;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return resultPosition;
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the memory stream as a UTF-8 encoded string until the next occurance of '\n' or '\r\n' (consuming, and excluded)
|
|
|
|
|
/// </summary>
|
|
|
|
|
static string GetNextLine(Stream s)
|
|
|
|
|
{
|
2021-03-24 13:40:16 +13:00
|
|
|
|
FindNextOccurance(s, '\n', out string nextLine);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
return nextLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// Finds the end-boundary of the next word in the stream, and returns the stream to the original position
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// </summary>
|
|
|
|
|
static long FindNextWord(Stream s, out string nextWord)
|
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
StringBuilder newWord = new StringBuilder();
|
2021-03-15 16:42:37 +13:00
|
|
|
|
long start = s.Position;
|
|
|
|
|
char currentChar = ReadChar(s);
|
|
|
|
|
while (s.Position < s.Length && char.IsWhiteSpace(currentChar))
|
2021-03-12 12:14:51 +13:00
|
|
|
|
{
|
2021-03-15 16:42:37 +13:00
|
|
|
|
currentChar = ReadChar(s);
|
|
|
|
|
}
|
|
|
|
|
newWord.Append(currentChar);
|
|
|
|
|
// Start a second loop, this time checking we're not a whitespace char
|
|
|
|
|
while (s.Position < s.Length)
|
|
|
|
|
{
|
|
|
|
|
currentChar = ReadChar(s);
|
2021-03-15 20:58:52 +13:00
|
|
|
|
if (char.IsWhiteSpace(currentChar) || currentChar == ';')
|
2021-03-15 16:42:37 +13:00
|
|
|
|
{
|
2021-03-15 20:58:52 +13:00
|
|
|
|
s.Position--;
|
2021-03-15 16:42:37 +13:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
newWord.Append(currentChar);
|
|
|
|
|
}
|
2021-03-12 12:14:51 +13:00
|
|
|
|
}
|
2021-03-15 16:42:37 +13:00
|
|
|
|
nextWord = newWord.ToString();
|
|
|
|
|
|
|
|
|
|
long endPos = s.Position;
|
|
|
|
|
s.Position = start;
|
|
|
|
|
return endPos;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds and returns the position of the next occurance of the Func returning true.
|
|
|
|
|
/// </summary>
|
|
|
|
|
static long FindNextOccurance(Stream s, Func<char, Stream, bool> p, out string result)
|
|
|
|
|
{
|
|
|
|
|
long start = s.Position;
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
bool charFound = false;
|
2021-03-15 15:03:36 +13:00
|
|
|
|
while (s.Position < s.Length && !charFound)
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-15 15:03:36 +13:00
|
|
|
|
char nextChar = ReadChar(s);
|
2021-03-11 19:22:49 +13:00
|
|
|
|
if (nextChar == 0)
|
|
|
|
|
{
|
|
|
|
|
charFound = true;
|
|
|
|
|
}
|
|
|
|
|
else if (p(nextChar, s))
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
|
|
|
|
charFound = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sb.Append(nextChar);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = sb.ToString();
|
|
|
|
|
long newPosition = s.Position;
|
|
|
|
|
s.Position = start;
|
2021-03-11 19:22:49 +13:00
|
|
|
|
return newPosition--;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2021-03-29 16:24:21 +13:00
|
|
|
|
/// Finds the next position of the supplied character
|
2021-03-11 16:18:05 +13:00
|
|
|
|
/// </summary>
|
|
|
|
|
static long FindNextOccurance(Stream s, char c, out string result)
|
|
|
|
|
{
|
|
|
|
|
return FindNextOccurance(s, (streamChar, s) => streamChar == c, out result);
|
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the next UTF-8 encoded character in the stream, and advances the stream by the amount of characters read
|
|
|
|
|
/// </summary>
|
|
|
|
|
static char ReadChar(Stream s)
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
|
|
|
|
// As UTF-8 allows codepoints to span multiple bytes, reading a single byte as a character will not always give the expected
|
|
|
|
|
// value.
|
|
|
|
|
// Fortunately, the standard ASCII table is 7-bits long. The 8th bit is used to determine the character size
|
|
|
|
|
int readAmount = 0;
|
|
|
|
|
int firstChar = s.ReadByte();
|
2021-03-11 19:22:49 +13:00
|
|
|
|
if (firstChar == -1)
|
|
|
|
|
{
|
|
|
|
|
return (char)0;
|
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
if ((firstChar >> 3) == 0x1E) // 11110xxx implies a 4-byte length character
|
|
|
|
|
{
|
|
|
|
|
readAmount = 3;
|
|
|
|
|
}
|
|
|
|
|
else if ((firstChar >> 4) == 0xE) // 1110xxxx, 3-byte
|
|
|
|
|
{
|
|
|
|
|
readAmount = 2;
|
|
|
|
|
}
|
|
|
|
|
else if ((firstChar >> 5) == 0x6) // 110xxxxx, 2-byte
|
|
|
|
|
{
|
|
|
|
|
readAmount = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
byte[] charBytes = new byte[readAmount + 1];
|
|
|
|
|
charBytes[0] = (byte)firstChar;
|
|
|
|
|
for (int i = 1; i < readAmount; i++)
|
|
|
|
|
{
|
|
|
|
|
int nextChar = s.ReadByte();
|
|
|
|
|
if (nextChar >> 6 != 2) throw new Exception("Character is not a valid UTF-8 code point!");
|
|
|
|
|
charBytes[i] = (byte)nextChar;
|
|
|
|
|
}
|
2021-03-11 19:22:49 +13:00
|
|
|
|
s.Position += readAmount;
|
2021-03-11 16:18:05 +13:00
|
|
|
|
string converted = Encoding.UTF8.GetString(charBytes);
|
|
|
|
|
return converted[0];
|
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the next character in the stream, and returns the position to the original position
|
|
|
|
|
/// </summary>
|
|
|
|
|
static char PeekChar(Stream s)
|
|
|
|
|
{
|
|
|
|
|
long curr = s.Position;
|
|
|
|
|
char c = ReadChar(s);
|
|
|
|
|
s.Position = curr;
|
|
|
|
|
return c;
|
|
|
|
|
}
|
2021-03-11 16:18:05 +13:00
|
|
|
|
|
2021-03-15 21:26:19 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the previous char
|
|
|
|
|
/// </summary>
|
|
|
|
|
static char PreviousChar(Stream s)
|
|
|
|
|
{
|
|
|
|
|
Stack<byte> charBytes = new Stack<byte>(4);
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s.Position == 0)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
s.Position--;
|
|
|
|
|
byte read = (byte)s.ReadByte();
|
|
|
|
|
charBytes.Push(read);
|
|
|
|
|
// No longer an UTF-8 extension, last byte is the final
|
|
|
|
|
if (read >> 6 != 2) break;
|
|
|
|
|
}
|
|
|
|
|
string converted = Encoding.UTF8.GetString(charBytes.ToArray());
|
|
|
|
|
return converted[0];
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-15 15:03:36 +13:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Skips whitespace characters
|
|
|
|
|
/// </summary>
|
2021-03-11 16:18:05 +13:00
|
|
|
|
static void SkipWhitespace(Stream s)
|
|
|
|
|
{
|
2021-03-15 15:03:36 +13:00
|
|
|
|
char c = PeekChar(s);
|
|
|
|
|
while (s.Position < s.Length && char.IsWhiteSpace(c))
|
2021-03-11 16:18:05 +13:00
|
|
|
|
{
|
2021-03-29 16:24:21 +13:00
|
|
|
|
ReadChar(s);
|
2021-03-15 15:03:36 +13:00
|
|
|
|
c = PeekChar(s);
|
2021-03-11 16:18:05 +13:00
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
2021-03-12 12:46:00 +13:00
|
|
|
|
|
2021-03-19 12:31:16 +13:00
|
|
|
|
static string CenterString(string source, int totalPadding, char paddingChar = ' ')
|
2021-03-12 12:46:00 +13:00
|
|
|
|
{
|
|
|
|
|
if (source.Length >= totalPadding) return source;
|
|
|
|
|
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);
|
|
|
|
|
string t = "{0," + leftHalfPad + "}{1," + -1 * rightHalfPad + "}";
|
2021-03-15 17:28:20 +13:00
|
|
|
|
string result = string.Format(t, source[..rightHalf], source[rightHalf..]);
|
2021-03-12 12:46:00 +13:00
|
|
|
|
return result;
|
|
|
|
|
}
|
2021-03-24 13:40:16 +13:00
|
|
|
|
|
|
|
|
|
static List<string> GetStringLines(string source, int maxWidth)
|
|
|
|
|
{
|
2021-03-29 10:05:48 +13:00
|
|
|
|
List<string> lines = new List<string>();
|
2021-03-24 15:35:56 +13:00
|
|
|
|
int j = 0;
|
|
|
|
|
while (j < source.Length)
|
2021-03-24 13:40:16 +13:00
|
|
|
|
{
|
2021-03-24 15:35:56 +13:00
|
|
|
|
int max = j + maxWidth <= source.Length ? j + maxWidth : source.Length;
|
|
|
|
|
lines.Add(source[j..max]);
|
|
|
|
|
j = max;
|
2021-03-24 13:40:16 +13:00
|
|
|
|
}
|
|
|
|
|
return lines;
|
|
|
|
|
}
|
2021-03-29 16:24:21 +13:00
|
|
|
|
|
2021-03-15 20:58:52 +13:00
|
|
|
|
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)
|
|
|
|
|
{
|
2021-03-24 14:51:17 +13:00
|
|
|
|
Importance = importance;
|
|
|
|
|
LinePosition = linePos;
|
2021-03-15 20:58:52 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-11 12:56:25 +13:00
|
|
|
|
}
|
|
|
|
|
}
|