rabidus-test/Assets/Plugins/QFSW/Quantum Console/Source/Scripts/QuantumMacros.cs

164 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace QFSW.QC
{
public static class QuantumMacros
{
private class MacroPreprocessor : IQcPreprocessor
{
public int Priority => 1000;
public string Process(string text)
{
if (!text.StartsWith("#define", StringComparison.CurrentCulture))
{
text = ExpandMacros(text);
}
return text;
}
}
private static readonly Dictionary<string, string> _macroTable = new Dictionary<string, string>();
/// <summary>
/// Expands all the macros in the given text.
/// </summary>
/// <returns>The macro expanded text.</returns>
/// <param name="text">The text to expand the macros in.</param>
/// <param name="maximumExpansions">The maximum number of macro expansions that can be performed before an exception is thrown.</param>
public static string ExpandMacros(string text, int maximumExpansions = 1000)
{
if (_macroTable.Count == 0) { return text; }
int expansionCount = 0;
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '#')
{
foreach (KeyValuePair<string, string> macro in _macroTable)
{
int keyLength = macro.Key.Length;
if (i + keyLength < text.Length)
{
if (text.Substring(i + 1, keyLength) == macro.Key)
{
if (expansionCount >= maximumExpansions)
{
throw new ArgumentException($"Maximum macro expansions of {maximumExpansions} was exhausted: infinitely recursive macro is likely.");
}
else
{
string start = text.Substring(0, i);
string end = text.Substring(i + 1 + keyLength);
text = $"{start}{macro.Value}{end}";
expansionCount++;
i--;
}
}
}
}
}
}
return text;
}
[Command("#define")]
[CommandDescription("Adds a macro to the macro table which can then be used in the Quantum Console. If the macro 'name' is added, " +
"then all instances of '#name' will be expanded into the full macro expansion. This allows you to define " +
"shortcuts for various things such as long type names or commonly used command strings.\n\n" +
"Macros may not contain hashtags or whitespace in their name.\n\n" +
"Note: macros will not be expanded when using #define, this is so that defining nested macros is possible.")]
public static void DefineMacro(string macroName, string macroExpansion)
{
macroName = macroName.Trim();
if (macroName.Contains(' ')) { throw new ArgumentException("Macro names cannot contain whitespace."); }
if (macroName.Contains('\n')) { throw new ArgumentException("Macro names cannot contain newlines."); }
if (macroName.Contains('#')) { throw new ArgumentException("Macro names cannot contain hashtags."); }
if (macroName == "define") { throw new ArgumentException("Macros cannot be named define."); }
if (macroExpansion.Contains('\n')) { throw new ArgumentException("Macro names cannot contain newlines."); }
if (macroExpansion.Contains($"#{macroName}")) { throw new ArgumentException("Macros cannot contain themselves within the expansion."); }
if (_macroTable.ContainsKey(macroName)) { _macroTable[macroName] = macroExpansion; }
else { _macroTable.Add(macroName, macroExpansion); }
}
[Command("remove-macro")]
[CommandDescription("Removes the specified macro from the macro table")]
public static void RemoveMacro(string macroName)
{
if (_macroTable.ContainsKey(macroName)) { _macroTable.Remove(macroName); }
else { throw new Exception($"Specified macro #{macroName} as it was not defined."); }
}
[Command("clear-macros")]
[CommandDescription("Clears the macro table")]
public static void ClearMacros() { _macroTable.Clear(); }
[Command("all-macros", "Displays all of the macros currently stored in the macro table")]
private static string GetAllMacros()
{
if (_macroTable.Count == 0) { return "Macro table is empty"; }
else { return $"Macro table:\n{string.Join("\n", _macroTable.Select((x) => $"#{x.Key} = {x.Value}"))}"; }
}
[Command("dump-macros", "Creates a file dump of macro table which can the be loaded to repopulate the table using load-macros")]
[CommandPlatform(Platform.AllPlatforms ^ (Platform.WebGLPlayer))]
public static void DumpMacrosToFile(string filePath)
{
using (StreamWriter dumpFile = new StreamWriter(filePath))
{
foreach (KeyValuePair<string, string> macro in _macroTable)
{
dumpFile.WriteLine($"{macro.Key} {macro.Value}");
}
dumpFile.Flush();
dumpFile.Close();
}
}
[Command("load-macros", "Loads macros from an external file into the macro table")]
[CommandPlatform(Platform.AllPlatforms ^ (Platform.WebGLPlayer))]
public static string LoadMacrosFromFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new ArgumentException($"file at the specified path '{filePath}' did not exist.");
}
using (StreamReader macroFile = new StreamReader(filePath))
{
List<string> messages = new List<string>();
while (!macroFile.EndOfStream)
{
string line = macroFile.ReadLine();
string[] parts = line.Split(" ".ToCharArray(), 2);
if (parts.Length != 2)
{
messages.Add($"'{line}' is not a valid macro definition");
}
try
{
DefineMacro(parts[0], parts[1]);
messages.Add($"#{parts[0]} was successfully defined");
}
catch (Exception e)
{
messages.Add($"#{parts[0]} could not be defined: {e.Message}");
}
}
macroFile.Close();
return string.Join("\n", messages);
}
}
}
}