This commit is contained in:
Даниил Заикин
2026-06-17 20:44:53 +03:00
parent 9d153773c2
commit b133e7c656
1880 changed files with 244545 additions and 0 deletions
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC
{
/// <summary>
/// Parser for a single type.
/// Caches results and reuses them if the incoming string has already been parsed.
/// </summary>
/// <typeparam name="T">The type to parse.</typeparam>
public abstract class BasicCachedQcParser<T> : BasicQcParser<T>
{
private readonly Dictionary<string, T> _cacheLookup = new Dictionary<string, T>();
public override object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
if (_cacheLookup.ContainsKey(value))
{
return _cacheLookup[value];
}
T result = (T)base.Parse(value, type, recursiveParser);
_cacheLookup[value] = result;
return result;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfc8ab8be8feaaa4292952550e15979d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Parser for a single type.
/// </summary>
/// <typeparam name="T">The type to parse.</typeparam>
public abstract class BasicQcParser<T> : IQcParser
{
private Func<string, Type, object> _recursiveParser;
public virtual int Priority => 0;
public bool CanParse(Type type)
{
return type == typeof(T);
}
public virtual object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
_recursiveParser = recursiveParser;
return Parse(value);
}
protected object ParseRecursive(string value, Type type)
{
return _recursiveParser(value, type);
}
protected TElement ParseRecursive<TElement>(string value)
{
return (TElement)_recursiveParser(value, typeof(TElement));
}
public abstract T Parse(string value);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ffe8acdee133098408a3ec9eaacbd78e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 223d79a496072b94bb244a4a1f7fc489
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Exception to be thrown by an IQcParser.
/// </summary>
public class ParserException : Exception
{
public ParserException(string message) : base(message) { }
public ParserException(string message, Exception innerException) : base(message, innerException) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bac12a9ecc201a4d9988f4f23166952
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Exception to be thrown by an IQcParser to indicate the input was invalid.
/// </summary>
public class ParserInputException : ParserException
{
public ParserInputException(string message) : base(message) { }
public ParserInputException(string message, Exception innerException) : base(message, innerException) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 559f64f0707530544bf37986bfc4f9c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC
{
/// <summary>
/// Parser for all types that are generic constructions of a single type.
/// Caches results and reuses them if the incoming string has already been parsed.
/// </summary>
public abstract class GenericCachedQcParser : GenericQcParser
{
private readonly Dictionary<(string, Type), object> _cacheLookup = new Dictionary<(string, Type), object>();
public override object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
(string value, Type type) key = (value, type);
if (_cacheLookup.ContainsKey(key))
{
return _cacheLookup[key];
}
object result = base.Parse(value, type, recursiveParser);
_cacheLookup[key] = result;
return result;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 45c1313ec062d864794a8fb903f0217e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,56 @@
using QFSW.QC.Utilities;
using System;
namespace QFSW.QC
{
/// <summary>
/// Parser for all types that are generic constructions of a single type.
/// </summary>
public abstract class GenericQcParser : IQcParser
{
/// <summary>
/// The incomplete generic type of this parser.
/// </summary>
protected abstract Type GenericType { get; }
private Func<string, Type, object> _recursiveParser;
protected GenericQcParser()
{
if (!GenericType.IsGenericType)
{
throw new ArgumentException($"Generic Parsers must use a generic type as their base");
}
if (GenericType.IsConstructedGenericType)
{
throw new ArgumentException($"Generic Parsers must use an incomplete generic type as their base");
}
}
public virtual int Priority => -500;
public bool CanParse(Type type)
{
return type.IsGenericTypeOf(GenericType);
}
public virtual object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
_recursiveParser = recursiveParser;
return Parse(value, type);
}
protected object ParseRecursive(string value, Type type)
{
return _recursiveParser(value, type);
}
protected TElement ParseRecursive<TElement>(string value)
{
return (TElement)_recursiveParser(value, typeof(TElement));
}
public abstract object Parse(string value, Type type);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c81ef7ca4a87a7843b5abee5c22e6ace
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 14f1f27cf493be645ba8db007b704475
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f7627ba6a0fc6d34182bb1aa178b4be5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class AdditionOperatorGrammar : BinaryAndUnaryOperatorGrammar
{
public override int Precedence => 0;
protected override char OperatorToken => '+';
protected override string OperatorMethodName => "op_Addition";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Add;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a0906d67478d174c896a36b20dc10c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
namespace QFSW.QC.Grammar
{
public abstract class BinaryAndUnaryOperatorGrammar : BinaryOperatorGrammar
{
private readonly HashSet<char> _operatorChars = new HashSet<char>()
{
'+',
'-',
'*',
'/',
'&',
'|',
'^',
'=',
'!',
','
};
private readonly HashSet<char> _ignoreChars = new HashSet<char>()
{
' ',
'\0'
};
protected override int GetOperatorPosition(string value)
{
IEnumerable<int> splitPoints = TextProcessing.GetScopedSplitPoints(value, OperatorToken, TextProcessing.DefaultLeftScopers, TextProcessing.DefaultRightScopers);
foreach (int index in splitPoints.Reverse())
{
if (IsValidBinaryOperator(value, index))
{
return index;
}
}
return -1;
}
private bool IsValidBinaryOperator(string value, int position)
{
while (position > 0)
{
char ch = value[--position];
if (_operatorChars.Contains(ch)) { return false; }
if (!_ignoreChars.Contains(ch)) { return true; }
}
return false;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98ed033aecce1204bbfca244d563194b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
using System;
using System.Reflection;
namespace QFSW.QC.Grammar
{
internal class BinaryOperatorData : IBinaryOperator
{
public Type LArg { get; }
public Type RArg { get; }
public Type Ret { get; }
private readonly MethodInfo _method;
public BinaryOperatorData(MethodInfo OperatorMethod)
{
_method = OperatorMethod;
Ret = OperatorMethod.ReturnType;
ParameterInfo[] paramData = _method.GetParameters();
if (paramData.Length != 2)
{
throw new ArgumentException($"Cannot create a binary operator from a method with {paramData.Length} parameters");
}
LArg = paramData[0].ParameterType;
RArg = paramData[1].ParameterType;
}
public object Invoke(object left, object right)
{
return _method.Invoke(null, new[] { left, right });
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e479a8c10bf9fa043959daa459ce97db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using QFSW.QC.Utilities;
namespace QFSW.QC.Grammar
{
public abstract class BinaryOperatorGrammar : IQcGrammarConstruct
{
public abstract int Precedence { get; }
protected abstract char OperatorToken { get; }
protected abstract string OperatorMethodName { get; }
protected abstract Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator { get; }
private Regex _operatorRegex;
private readonly HashSet<Type> _missingOperatorTable = new HashSet<Type>();
private readonly Dictionary<Type, IBinaryOperator> _foundOperatorTable = new Dictionary<Type, IBinaryOperator>();
public bool Match(string value, Type type)
{
if (_missingOperatorTable.Contains(type))
{
return false;
}
if (!IsSyntaxMatch(value))
{
return false;
}
if (_foundOperatorTable.ContainsKey(type))
{
return true;
}
IBinaryOperator operatorData = GetOperatorData(type);
if (operatorData != null)
{
_foundOperatorTable.Add(type, operatorData);
return true;
}
_missingOperatorTable.Add(type);
return false;
}
private bool IsSyntaxMatch(string value)
{
if (_operatorRegex == null)
{
_operatorRegex = new Regex($@"^.+\{OperatorToken}.+$");
}
if (!_operatorRegex.IsMatch(value))
{
return false;
}
int operatorPos = GetOperatorPosition(value);
return operatorPos > 0 && operatorPos < value.Length;
}
private IBinaryOperator GetOperatorData(Type type)
{
if (type.IsPrimitive)
{
#if !UNITY_EDITOR && ENABLE_IL2CPP && !UNITY_2022_2_OR_NEWER
string typeName = QFSW.QC.Utilities.ReflectionExtensions.GetDisplayName(type);
UnityEngine.Debug.LogWarning($"{typeName} {OperatorToken} {typeName} is not supported as IL2CPP does not support dynamic value typed generics before Unity 2022.2");
#else
return GeneratePrimitiveOperator(type);
#endif
}
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static);
BinaryOperatorData[] candidates = methods.Where(x => x.Name == OperatorMethodName)
.Where(x => x.ReturnType == type)
.Where(x => x.GetParameters().Length == 2)
.Select(x => new BinaryOperatorData(x))
.ToArray();
BinaryOperatorData idealCandidate = candidates.FirstOrDefault(x => x.LArg == type && x.RArg == type)
?? candidates.FirstOrDefault(x => x.LArg == type)
?? candidates.FirstOrDefault(x => x.RArg == type)
?? candidates.FirstOrDefault();
return idealCandidate;
}
private IBinaryOperator GeneratePrimitiveOperator(Type type)
{
ParameterExpression leftParam = Expression.Parameter(type, "left");
ParameterExpression rightParam = Expression.Parameter(type, "right");
BinaryExpression body;
try
{
body = PrimitiveExpressionGenerator(leftParam, rightParam);
}
catch (InvalidOperationException)
{
return null;
}
Delegate expr = Expression.Lambda(body, true, leftParam, rightParam).Compile();
return new DynamicBinaryOperator(expr, type, type, type);
}
/// <summary>
/// Get the position of the right-most valid operator token.
/// </summary>
/// <param name="value">The string to find the operator in.</param>
/// <returns>The position of the operator. -1 if none can be found</returns>
protected virtual int GetOperatorPosition(string value)
{
return TextProcessing.GetScopedSplitPoints(value, OperatorToken, TextProcessing.DefaultLeftScopers, TextProcessing.DefaultRightScopers).LastOr(-1);
}
public object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
IBinaryOperator operatorData = _foundOperatorTable[type];
int splitIndex = GetOperatorPosition(value);
string left = value.Substring(0, splitIndex);
string right = value.Substring(splitIndex + 1);
object leftVal = recursiveParser(left, operatorData.LArg);
object rightVal = recursiveParser(right, operatorData.RArg);
try
{
return operatorData.Invoke(leftVal, rightVal);
}
catch (TargetInvocationException e)
{
throw e.InnerException ?? e;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3a6802829bc65049b2c671ce148f547
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class BitwiseAndOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 6;
protected override char OperatorToken => '&';
protected override string OperatorMethodName => "op_bitwiseAnd";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.And;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9296938ddd32064d999e8e7000789ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class BitwiseOrOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 5;
protected override char OperatorToken => '|';
protected override string OperatorMethodName => "op_bitwiseOr";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Or;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a87fe84f3c6c7ba4191321937395bd84
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class DivisionOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 3;
protected override char OperatorToken => '/';
protected override string OperatorMethodName => "op_Division";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Divide;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78358bd367d21fb44b571bf2d2dcd03f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,26 @@
using System;
namespace QFSW.QC.Grammar
{
internal class DynamicBinaryOperator : IBinaryOperator
{
public Type LArg { get; }
public Type RArg { get; }
public Type Ret { get; }
private readonly Delegate _del;
public DynamicBinaryOperator(Delegate del, Type lArg, Type rArg, Type ret)
{
_del = del;
LArg = lArg;
RArg = rArg;
Ret = ret;
}
public object Invoke(object left, object right)
{
return _del.DynamicInvoke(left, right);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18c4d964a275c32438dcb4abc6c1fb4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class ExclusiveOrOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 7;
protected override char OperatorToken => '^';
protected override string OperatorMethodName => "op_ExclusiveOr";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.ExclusiveOr;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8528cd4143f809f459ca13f6415e8a03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
namespace QFSW.QC.Grammar
{
public interface IBinaryOperator
{
Type LArg { get; }
Type RArg { get; }
Type Ret { get; }
object Invoke(object left, object right);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8349fa6e81e457847afbe6291aada5fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class ModulusOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 4;
protected override char OperatorToken => '%';
protected override string OperatorMethodName => "op_Modulus";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Modulo;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fedde2bcbb582294d9c08ba7d622ab14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class MultiplyOperatorGrammar : BinaryOperatorGrammar
{
public override int Precedence => 2;
protected override char OperatorToken => '*';
protected override string OperatorMethodName => "op_Multiply";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Multiply;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00dd2e1128bbc2b4db7fc3c2c1e840e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Linq.Expressions;
namespace QFSW.QC.Grammar
{
public class SubtractionOperatorGrammar : BinaryAndUnaryOperatorGrammar
{
public override int Precedence => 1;
protected override char OperatorToken => '-';
protected override string OperatorMethodName => "op_Subtraction";
protected override Func<Expression, Expression, BinaryExpression> PrimitiveExpressionGenerator => Expression.Subtract;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 48f2484f770bd9744bac9ad2f4f92de4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
using System;
using System.Text.RegularExpressions;
namespace QFSW.QC.Grammar
{
public class BooleanNegationGrammar : IQcGrammarConstruct
{
private readonly Regex _negationRegex = new Regex(@"^!\S+$");
public int Precedence => 0;
public bool Match(string value, Type type)
{
return type == typeof(bool) && _negationRegex.IsMatch(value);
}
public object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
value = value.Substring(1);
return !(bool)recursiveParser(value, type);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0674e06c549290f46914c51adb79b5f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
using QFSW.QC.Utilities;
using System;
using System.Text.RegularExpressions;
namespace QFSW.QC.Grammar
{
public class ExpressionBodyGrammar : IQcGrammarConstruct
{
private readonly Regex _expressionBodyRegex = new Regex(@"^{.+}\??$");
public int Precedence => 0;
public bool Match(string value, Type type)
{
return _expressionBodyRegex.IsMatch(value);
}
public object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
bool nullable = false;
if (value.EndsWith("?"))
{
nullable = true;
value = value.Substring(0, value.Length - 1);
}
value = value.ReduceScope('{', '}');
object result = QuantumConsoleProcessor.InvokeCommand(value);
if (result is null)
{
if (nullable)
{
if (type.IsClass)
{
return result;
}
else
{
throw new ParserInputException($"Expression body {{{value}}} evaluated to null which is incompatible with the expected type '{type.GetDisplayName()}'.");
}
}
else
{
throw new ParserInputException($"Expression body {{{value}}} evaluated to null. If this is intended, please use nullable expression bodies, {{expr}}?");
}
}
else if (result.GetType().IsCastableTo(type, true))
{
return type.Cast(result);
}
else
{
throw new ParserInputException($"Expression body {{{value}}} evaluated to an object of type '{result.GetType().GetDisplayName()}', " +
$"which is incompatible with the expected type '{type.GetDisplayName()}'.");
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4299ec1767412954f9b37800967fd471
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
{
"name": "QFSW.QC.Grammar",
"references": [
"QFSW.QC"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ad10a660d2aec9d449de236f5524cb5c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Creates a Parser for a custom grammar construct that is loaded and used by the QuantumParser.
/// Grammar constructs are tested and used before resorting to IQcParsers for object value parsing.
/// </summary>
public interface IQcGrammarConstruct
{
/// <summary>
/// The precedence of this grammar construct.
/// </summary>
int Precedence { get; }
/// <summary>
/// If the incoming data matches this grammar construct.
/// </summary>
/// <param name="value">The incoming string data.</param>
/// <param name="type">The type to test.</param>
/// <returns>If it matches the grammar defined by this construct.</returns>
bool Match(string value, Type type);
/// <summary>
/// Parses the incoming string to the specified type.
/// </summary>
/// <param name="value">The incoming string data.</param>
/// <param name="type">The type to parse the incoming string to.</param>
/// <param name="recursiveParser">Delegate back to the main parser to allow for recursive parsing of sub elements.</param>
/// <returns>The parsed object.</returns>
object Parse(string value, Type type, Func<string, Type, object> recursiveParser);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c2c98c09a1ef19408e80fec4b2932d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Creates a Parser that is loaded and used by the QuantumParser.
/// </summary>
public interface IQcParser
{
/// <summary>
/// The priority of this parser to resolve multiple parsers covering the same type.
/// </summary>
int Priority { get; }
/// <summary>
/// If this parser can parse to the incoming type.
/// </summary>
/// <param name="type">The type to test.</param>
/// <returns>If it can be parsed.</returns>
bool CanParse(Type type);
/// <summary>
/// Parses the incoming string to the specified type.
/// </summary>
/// <param name="value">The incoming string data.</param>
/// <param name="type">The type to parse the incoming string to.</param>
/// <param name="recursiveParser">Delegate back to the main parser to allow for recursive parsing of sub elements.</param>
/// <returns>The parsed object.</returns>
object Parse(string value, Type type, Func<string, Type, object> recursiveParser);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d796cb1619e0adb48916716da79c9f9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC
{
/// <summary>
/// Parser for all types that are generic constructions of a several types.
/// </summary>
public abstract class MassGenericQcParser : IQcParser
{
/// <summary>
/// The incomplete generic types of this parser.
/// </summary>
protected abstract HashSet<Type> GenericTypes { get; }
private Func<string, Type, object> _recursiveParser;
protected MassGenericQcParser()
{
foreach (Type type in GenericTypes)
{
if (!type.IsGenericType)
{
throw new ArgumentException($"Generic Parsers must use a generic type as their base");
}
if (type.IsConstructedGenericType)
{
throw new ArgumentException($"Generic Parsers must use an incomplete generic type as their base");
}
}
}
public virtual int Priority => -2000;
public bool CanParse(Type type)
{
if (type.IsGenericType)
{
return GenericTypes.Contains(type.GetGenericTypeDefinition());
}
return false;
}
public virtual object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
_recursiveParser = recursiveParser;
return Parse(value, type);
}
protected object ParseRecursive(string value, Type type)
{
return _recursiveParser(value, type);
}
protected TElement ParseRecursive<TElement>(string value)
{
return (TElement)_recursiveParser(value, typeof(TElement));
}
public abstract object Parse(string value, Type type);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d08d4cc71bd3f9f47baf04ed33d3a284
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 819146b9096942b48914d95b8268664a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
using System;
using System.Collections;
namespace QFSW.QC.Parsers
{
public class ArrayParser : IQcParser
{
public int Priority => -100;
public bool CanParse(Type type)
{
return type.IsArray;
}
public object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
Type elementType = type.GetElementType();
string[] valueParts = value.ReduceScope('[', ']').SplitScoped(',');
IList dataArray = Array.CreateInstance(elementType, valueParts.Length);
for (int i = 0; i < valueParts.Length; i++)
{
dataArray[i] = recursiveParser(valueParts[i], elementType);
}
return dataArray;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: debc47c083b042c4c8a295d533a85094
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
namespace QFSW.QC.Parsers
{
public class BoolParser : BasicCachedQcParser<bool>
{
public override bool Parse(string value)
{
value = value.ToLower().Trim();
switch (value)
{
case "true": return true;
case "on": return true;
case "1": return true;
case "yes": return true;
case "false": return false;
case "off": return false;
case "0": return false;
case "no": return false;
default: throw new ParserInputException($"Cannot parse '{value}' to a bool.");
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f610c84a8ec2129489d0f4d00a585b18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace QFSW.QC.Parsers
{
public class CollectionParser : MassGenericQcParser
{
protected override HashSet<Type> GenericTypes { get; } = new HashSet<Type>
{
typeof(List<>),
typeof(Stack<>),
typeof(Queue<>),
typeof(HashSet<>),
typeof(LinkedList<>),
typeof(ConcurrentStack<>),
typeof(ConcurrentQueue<>),
typeof(ConcurrentBag<>)
};
public override object Parse(string value, Type type)
{
Type arrayType = type.GetGenericArguments()[0].MakeArrayType();
object array = ParseRecursive(value, arrayType);
return Activator.CreateInstance(type, array);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2692eedbcfd50e24a8644b39e70351a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class ColorParser : BasicCachedQcParser<Color>
{
private readonly Dictionary<string, Color> _colorLookup;
public ColorParser()
{
_colorLookup = new Dictionary<string, Color>();
PropertyInfo[] colorProperties = typeof(Color).GetProperties(BindingFlags.Static | BindingFlags.Public);
foreach (PropertyInfo prop in colorProperties)
{
if (prop.CanRead && !prop.CanWrite)
{
MethodInfo propReader = prop.GetMethod;
if (propReader.ReturnType == typeof(Color))
{
_colorLookup.Add(prop.Name, (Color)propReader.Invoke(null, Array.Empty<object>()));
}
}
}
}
public override Color Parse(string value)
{
if (_colorLookup.ContainsKey(value.ToLower()))
{
return _colorLookup[value.ToLower()];
}
try
{
if (value.StartsWith("0x"))
{
return ParseHexColor(value);
}
else
{
return ParseRGBAColor(value);
}
}
catch (FormatException e)
{
throw new ParserInputException($"{e.Message}\nThe format must be either of:" +
$"\n - R,G,B" +
$"\n - R,G,B,A" +
$"\n - 0xRRGGBB" +
$"\n - 0xRRGGBBAA" +
$"\n - A preset color such as 'red'", e);
}
}
private Color ParseRGBAColor(string value)
{
string[] colorParts = value.Split(',');
Color parsedColor = Color.white;
int i = 0;
if (colorParts.Length < 3 || colorParts.Length > 4) { throw new FormatException($"Cannot parse '{value}' as a Color."); }
float ParsePart(string part)
{
float val = float.Parse(part);
if (val < 0 || val > 1) { throw new FormatException($"{val} falls outside of the valid [0,1] range for a component of a Color."); }
return val;
}
try
{
for (; i < colorParts.Length; i++)
{
parsedColor[i] = ParsePart(colorParts[i]);
}
return parsedColor;
}
catch (FormatException)
{
throw new FormatException($"Cannot parse '{colorParts[i]}' as part of a Color, it must be numerical and in the valid range [0,1].");
}
}
private Color ParseHexColor(string value)
{
int digitCount = value.Length - 2;
if (digitCount != 6 && digitCount != 8)
{
throw new FormatException("Hex colors must contain either 6 or 8 hex digits.");
}
Color parsedColor = Color.white;
int byteCount = digitCount / 2;
int i = 0;
try
{
for (; i < byteCount; i++)
{
parsedColor[i] = int.Parse(value.Substring(2 * (1 + i), 2), System.Globalization.NumberStyles.HexNumber) / 255f;
}
return parsedColor;
}
catch (FormatException)
{
throw new FormatException($"Cannot parse '{value.Substring(2 * (1 + i), 2)}' as part of a Color as it was invalid hex.");
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31739988d804da941aa363d4a58bfae6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using QFSW.QC.Utilities;
using System;
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class ComponentParser : PolymorphicQcParser<Component>
{
public override Component Parse(string value, Type type)
{
GameObject obj = ParseRecursive<GameObject>(value);
Component objComponent = obj.GetComponent(type);
if (!objComponent)
{
throw new ParserInputException($"No component on the object '{value}' of type {type.GetDisplayName()} existed.");
}
return objComponent;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fbd8b9265a0c3f4a9a3aa55595b889f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
using QFSW.QC.Utilities;
using System;
namespace QFSW.QC.Parsers
{
public class EnumParser : PolymorphicCachedQcParser<Enum>
{
public override Enum Parse(string value, Type type)
{
try
{
return (Enum)Enum.Parse(type, value);
}
catch (Exception e)
{
throw new ParserInputException($"Cannot parse '{value}' to the type '{type.GetDisplayName()}'. To see the supported values, use the command `enum-info {type}`", e);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e211a4d691805e049a06ba8a06ca4f92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC.Parsers
{
public class EnumerableParser : MassGenericQcParser
{
protected override HashSet<Type> GenericTypes { get; } = new HashSet<Type>()
{
typeof(IEnumerable<>),
typeof(ICollection<>),
typeof(IReadOnlyCollection<>),
typeof(IList<>),
typeof(IReadOnlyList<>)
};
public override object Parse(string value, Type type)
{
Type arrayType = type.GetGenericArguments()[0].MakeArrayType();
return ParseRecursive(value, arrayType);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c885a65409b41894ab2688fef15008b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using QFSW.QC.Utilities;
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class GameObjectParser : BasicQcParser<GameObject>
{
public override GameObject Parse(string value)
{
string name = ParseRecursive<string>(value);
GameObject obj = GameObjectExtensions.Find(name, true);
if (!obj)
{
throw new ParserInputException($"Could not find GameObject of name {value}.");
}
return obj;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c5d24ea53ab5c341b04e2b935368e73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
using System;
namespace QFSW.QC.Parsers
{
public class NullableParser : GenericQcParser
{
protected override Type GenericType => typeof(Nullable<>);
public override object Parse(string value, Type type)
{
if (value == "null")
{
return null;
}
Type innerType = type.GetGenericArguments()[0];
return ParseRecursive(value, innerType);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d756a2c71cd39bc469193330c26e8797
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
using QFSW.QC.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace QFSW.QC.Parsers
{
public class PrimitiveParser : IQcParser
{
private readonly HashSet<Type> _primitiveTypes = new HashSet<Type>
{
typeof(int),
typeof(float),
typeof(decimal),
typeof(double),
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(uint),
typeof(short),
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(char)
};
public int Priority => -1000;
public bool CanParse(Type type)
{
return _primitiveTypes.Contains(type);
}
public object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
try
{
return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
throw new ParserInputException($"Cannot parse '{value}' to the type '{type.GetDisplayName()}'.", e);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 030edeaf5882fc4408d3ebb9368779a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
{
"name": "QFSW.QC.Parsers",
"references": [
"QFSW.QC"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a5d04fa5e431fa844be99e9199658b16
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class QuaternionParser : BasicCachedQcParser<Quaternion>
{
public override Quaternion Parse(string value)
{
Vector4 vector = ParseRecursive<Vector4>(value);
return new Quaternion(vector.x, vector.y, vector.z, vector.w);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99e9d99d8fb57ac42a9821372699449b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
namespace QFSW.QC.Parsers
{
public class StringParser : BasicCachedQcParser<string>
{
public override int Priority => int.MaxValue;
public override string Parse(string value)
{
return value
.ReduceScope('"', '"')
.UnescapeText('"');
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 028f88c2880e54c4f883885b9a263c8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC.Parsers
{
public class TupleParser : MassGenericQcParser
{
private const int MaxFlatTupleSize = 8;
protected override HashSet<Type> GenericTypes { get; } = new HashSet<Type>
{
typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>),
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>)
};
public override object Parse(string value, Type type)
{
TextProcessing.ScopedSplitOptions options = TextProcessing.ScopedSplitOptions.Default;
options.MaxCount = MaxFlatTupleSize;
string[] inputParts = value.ReduceScope('(', ')').SplitScoped(',', options);
Type[] elementTypes = type.GetGenericArguments();
if (elementTypes.Length != inputParts.Length)
{
throw new ParserInputException($"Desired tuple type {type} has {elementTypes.Length} elements but input contained {inputParts.Length}.");
}
object[] tupleParts = new object[inputParts.Length];
for (int i = 0; i < tupleParts.Length; i++)
{
tupleParts[i] = ParseRecursive(inputParts[i], elementTypes[i]);
}
return Activator.CreateInstance(type, tupleParts);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6842e4e236f2eaa41bddefbef8aa7276
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using System;
namespace QFSW.QC.Parsers
{
public class TypeParser : BasicCachedQcParser<Type>
{
public override Type Parse(string value)
{
return QuantumParser.ParseType(value);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7be70a556f9e094c8fa7f5dd0c7e81b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class Vector2IntParser : BasicCachedQcParser<Vector2Int>
{
public override Vector2Int Parse(string value)
{
return (Vector2Int)ParseRecursive<Vector3Int>(value);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0288f6e4dbdbb964eb1f0f7bafe6ca37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class Vector2Parser : BasicCachedQcParser<Vector2>
{
public override Vector2 Parse(string value)
{
return ParseRecursive<Vector4>(value);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb12462fc0295f046957d1030894bea6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class Vector3IntParser : BasicCachedQcParser<Vector3Int>
{
public override Vector3Int Parse(string value)
{
string[] vectorParts = value.Split(',');
Vector3Int parsedVector = new Vector3Int();
if (vectorParts.Length < 2 || vectorParts.Length > 3)
{
throw new ParserInputException($"Cannot parse '{value}' as an int vector, the format must be either x,y or x,y,z");
}
int i = 0;
try
{
for (; i < vectorParts.Length; i++)
{
parsedVector[i] = int.Parse(vectorParts[i]);
}
return parsedVector;
}
catch
{
throw new ParserInputException($"Cannot parse '{vectorParts[i]}' as it must be integral.");
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7af974e71f13d6c4f9c3a5291581e815
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class Vector3Parser : BasicCachedQcParser<Vector3>
{
public override Vector3 Parse(string value)
{
return ParseRecursive<Vector4>(value);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca39e94d5f7e12b4cbb7b00f77a8278c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
using UnityEngine;
namespace QFSW.QC.Parsers
{
public class Vector4Parser : BasicCachedQcParser<Vector4>
{
public override Vector4 Parse(string value)
{
string[] vectorParts = value.SplitScoped(',');
Vector4 parsedVector = new Vector4();
if (vectorParts.Length < 2 || vectorParts.Length > 4)
{
throw new ParserInputException($"Cannot parse '{value}' as a vector, the format must be either x,y x,y,z or x,y,z,w.");
}
for (int i = 0; i < vectorParts.Length; i++)
{
parsedVector[i] = ParseRecursive<float>(vectorParts[i]);
}
return parsedVector;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: feb6ca1c0ec89bf4e8bb8ae58737a1f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC
{
/// <summary>
/// Parser for all types inheriting from a single type.
/// Caches results and reuses them if the incoming string has already been parsed.
/// </summary>
/// <typeparam name="T">Base type of the types to parse.</typeparam>
public abstract class PolymorphicCachedQcParser<T> : PolymorphicQcParser<T> where T : class
{
private readonly Dictionary<(string, Type), T> _cacheLookup = new Dictionary<(string, Type), T>();
public override object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
(string value, Type type) key = (value, type);
if (_cacheLookup.ContainsKey(key))
{
return _cacheLookup[key];
}
T result = (T)base.Parse(value, type, recursiveParser);
_cacheLookup[key] = result;
return result;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 512ba3db6b1fb234a878ee64b68bf5c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
using System;
namespace QFSW.QC
{
/// <summary>
/// Parser for all types inheriting from a single type.
/// </summary>
/// <typeparam name="T">Base type of the types to parse.</typeparam>
public abstract class PolymorphicQcParser<T> : IQcParser where T : class
{
private Func<string, Type, object> _recursiveParser;
public virtual int Priority => -1000;
public bool CanParse(Type type)
{
return typeof(T).IsAssignableFrom(type);
}
public virtual object Parse(string value, Type type, Func<string, Type, object> recursiveParser)
{
_recursiveParser = recursiveParser;
return Parse(value, type);
}
protected object ParseRecursive(string value, Type type)
{
return _recursiveParser(value, type);
}
protected TElement ParseRecursive<TElement>(string value)
{
return (TElement)_recursiveParser(value, typeof(TElement));
}
public abstract T Parse(string value, Type type);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c042a38294a50b34e8089e7adbaf76e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,399 @@
using QFSW.QC.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
namespace QFSW.QC
{
/// <summary>
/// Handles parsing values to use as console inputs.
/// </summary>
public class QuantumParser
{
private readonly IQcParser[] _parsers;
private readonly IQcGrammarConstruct[] _grammarConstructs;
private readonly ConcurrentDictionary<Type, IQcParser> _parserLookup = new ConcurrentDictionary<Type, IQcParser>();
private readonly HashSet<Type> _unparseableLookup = new HashSet<Type>();
private readonly Func<string, Type, object> _recursiveParser;
/// <summary>
/// Creates a Quantum Parser with a custom set of parsers.
/// </summary>
/// <param name="parsers">The IQcParsers to use in this Quantum Parser.</param>
/// <param name="grammarConstructs">The IQcGrammarConstructs to use in this Quantum Parser</param>
public QuantumParser(IEnumerable<IQcParser> parsers, IEnumerable<IQcGrammarConstruct> grammarConstructs)
{
_recursiveParser = Parse;
_parsers = parsers.OrderByDescending(x => x.Priority)
.ToArray();
_grammarConstructs = grammarConstructs.OrderBy(x => x.Precedence)
.ToArray();
}
/// <summary>
/// Creates a Quantum Parser with the default injected parsers.
/// </summary>
public QuantumParser() : this(new InjectionLoader<IQcParser>().GetInjectedInstances(), new InjectionLoader<IQcGrammarConstruct>().GetInjectedInstances())
{
}
public IQcParser GetParser(Type type)
{
if (_parserLookup.ContainsKey(type))
{
return _parserLookup[type];
}
else if (!_unparseableLookup.Contains(type))
{
foreach (IQcParser parser in _parsers)
{
try
{
if (parser.CanParse(type))
{
return _parserLookup[type] = parser;
}
}
catch (Exception e)
{
Debug.LogError($"{parser.GetType().GetDisplayName()}.CanParse is malformed and throws");
Debug.LogException(e);
}
}
_unparseableLookup.Add(type);
}
return null;
}
public bool CanParse(Type type)
{
return GetParser(type) != null;
}
private IQcGrammarConstruct GetMatchingGrammar(string value, Type type)
{
foreach (IQcGrammarConstruct grammar in _grammarConstructs)
{
try
{
if (grammar.Match(value, type))
{
return grammar;
}
}
catch (Exception e)
{
Debug.LogError($"{grammar.GetType().GetDisplayName()}.Match is malformed and throws");
Debug.LogException(e);
}
}
return null;
}
/// <summary>
/// Parses a serialized string of data.
/// </summary>
/// <typeparam name="T">The type of the value to parse.</typeparam>
/// <param name="value">The string to parse.</param>
/// <returns>The parsed value.</returns>
public T Parse<T>(string value)
{
return (T)Parse(value, typeof(T));
}
/// <summary>
/// Parses a serialized string of data.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <param name="type">The type of the value to parse.</param>
/// <returns>The parsed value.</returns>
public object Parse(string value, Type type)
{
value = value.ReduceScope('(', ')');
if (type.IsClass && value == "null")
{
return null;
}
IQcGrammarConstruct grammar = GetMatchingGrammar(value, type);
if (grammar != null)
{
try
{
return grammar.Parse(value, type, _recursiveParser);
}
catch (ParserException) { throw; }
catch (Exception e)
{
throw new Exception($"Parsing of {type.GetDisplayName()} via {grammar} failed:\n{e.Message}", e);
}
}
IQcParser parser = GetParser(type);
if (parser == null)
{
throw new ArgumentException($"Cannot parse object of type '{type.GetDisplayName()}'");
}
try
{
return parser.Parse(value, type, _recursiveParser);
}
catch (ParserException) { throw; }
catch (Exception e)
{
throw new Exception($"Parsing of {type.GetDisplayName()} via {parser} failed:\n{e.Message}", e);
}
}
#region Type Parser
private static readonly Dictionary<Type, string> _typeDisplayNames = new Dictionary<Type, string>
{
{ typeof(int), "int" }, { typeof(float), "float" }, { typeof(decimal), "decimal" },
{ typeof(double), "double" }, { typeof(string), "string" }, { typeof(bool), "bool" },
{ typeof(byte), "byte" }, { typeof(sbyte), "sbyte" }, { typeof(uint), "uint" },
{ typeof(short), "short" }, { typeof(ushort), "ushort" }, { typeof(long), "long" },
{ typeof(ulong), "ulong" }, { typeof(char), "char" }, { typeof(object), "object" }
};
private static readonly Dictionary<string, Type> _reverseTypeDisplayNames = _typeDisplayNames.Invert();
private static readonly Assembly[] _loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
private static readonly string[] _defaultNamespaces = new string[] { "System", "System.Collections", "System.Collections.Generic", "UnityEngine", "UnityEngine.UI", "QFSW.QC" };
private static readonly List<string> _namespaceTable = new List<string>(_defaultNamespaces);
private static readonly Regex _arrayTypeRegex = new Regex(@"^.*\[,*\]$");
private static readonly Regex _genericTypeRegex = new Regex(@"^.+<.*>$");
private static readonly Regex _tupleTypeRegex = new Regex(@"^\(.*\)$");
private static readonly Regex _nullableTypeRegex = new Regex(@"^.*\?$");
/// <summary>
/// Resets the namespace table to its initial state.
/// </summary>
[Command("reset-namespaces", "Resets the namespace table to its initial state")]
public static void ResetNamespaceTable()
{
_namespaceTable.Clear();
_namespaceTable.AddRange(_defaultNamespaces);
}
/// <summary>
/// Adds a namespace to the table so that it can be used to type resolution.
/// </summary>
[Command("use-namespace", "Adds a namespace to the table so that it can be used to type resolution")]
public static void AddNamespace(string namespaceName)
{
if (!_namespaceTable.Contains(namespaceName))
{
_namespaceTable.Add(namespaceName);
}
}
/// <summary>
/// Removes a namespace to the table so that it is no longer used to type resolution.
/// </summary>
[Command("remove-namespace", "Removes a namespace from the table")]
public static void RemoveNamespace(string namespaceName)
{
if (_namespaceTable.Contains(namespaceName))
{
_namespaceTable.Remove(namespaceName);
}
else
{
throw new ArgumentException($"No namespace named {namespaceName} was present in the table");
}
}
[Command("all-namespaces", "Displays all of the namespaces currently in use by the namespace table")]
private static string ShowNamespaces()
{
_namespaceTable.Sort();
if (_namespaceTable.Count == 0) { return "Namespace table is empty"; }
else { return string.Join("\n", _namespaceTable); }
}
/// <summary>
/// Returns a copy of the namespace table.
/// </summary>
public static IEnumerable<string> GetAllNamespaces() { return _namespaceTable; }
/// <summary>
/// Parses and infers the type specified by the string.
/// </summary>
/// <returns>The parsed type.</returns>
/// <param name="typeName">The type to parse.</param>
public static Type ParseType(string typeName)
{
typeName = typeName.Trim();
if (_reverseTypeDisplayNames.ContainsKey(typeName))
{
return _reverseTypeDisplayNames[typeName];
}
if (_tupleTypeRegex.IsMatch(typeName))
{
return ParseTupleType(typeName);
}
if (_arrayTypeRegex.IsMatch(typeName))
{
return ParseArrayType(typeName);
}
if (_genericTypeRegex.IsMatch(typeName))
{
return ParseGenericType(typeName);
}
if (_nullableTypeRegex.IsMatch(typeName))
{
return ParseNullableType(typeName);
}
if (typeName.Contains('`'))
{
string genericName = typeName.Split('`')[0];
if (_reverseTypeDisplayNames.ContainsKey(genericName))
{
return _reverseTypeDisplayNames[genericName];
}
}
return ParseTypeBaseCase(typeName);
}
private static Type ParseArrayType(string typeName)
{
int arrayPos = typeName.LastIndexOf('[');
int arrayRank = typeName.CountFromIndex(',', arrayPos) + 1;
Type elementType = ParseType(typeName.Substring(0, arrayPos));
return arrayRank > 1
? elementType.MakeArrayType(arrayRank)
: elementType.MakeArrayType();
}
private static Type ParseGenericType(string typeName)
{
string[] parts = typeName.Split(new[] { '<' }, 2);
string[] genericArgNames = $"<{parts[1]}".ReduceScope('<', '>').SplitScoped(',');
string incompleteGenericName = $"{parts[0]}`{Math.Max(1, genericArgNames.Length)}";
Type incompleteGenericType = ParseType(incompleteGenericName);
if (genericArgNames.All(string.IsNullOrWhiteSpace))
{
return incompleteGenericType;
}
Type[] genericArgs = genericArgNames.Select(ParseType).ToArray();
return incompleteGenericType.MakeGenericType(genericArgs);
}
private static Type ParseNullableType(string typeName)
{
string innerTypeName = typeName.Substring(0, typeName.Length - 1);
Type innerType = ParseType(innerTypeName);
return innerType.IsClass
? innerType
: typeof(Nullable<>).MakeGenericType(innerType);
}
private static Type ParseTupleType(string typeName)
{
string inner = typeName.Substring(1, typeName.Length - 2);
Type[] parts = inner
.SplitScoped(',')
.Select(ParseType)
.ToArray();
return CreateTupleType(parts);
}
private static readonly Type[] _valueTupleTypes =
{
typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>)
};
private static Type CreateTupleType(Type[] types)
{
const int maxFlatTupleSize = 8;
if (types.Length > maxFlatTupleSize - 1)
{
Type[] innerTypes = types.Skip(maxFlatTupleSize - 1).ToArray();
types = types
.Take(maxFlatTupleSize - 1)
.Concat(CreateTupleType(innerTypes).Yield())
.ToArray();
}
return _valueTupleTypes[types.Length - 1].MakeGenericType(types);
}
private static Type ParseTypeBaseCase(string typeName)
{
return GetTypeFromAssemblies(typeName, _loadedAssemblies, false, false)
?? GetTypeFromAssemblies(typeName, _namespaceTable, _loadedAssemblies, false, false)
?? GetTypeFromAssemblies(typeName, _loadedAssemblies, false, true)
?? GetTypeFromAssemblies(typeName, _namespaceTable, _loadedAssemblies, true, true);
}
private static Type GetTypeFromAssemblies(string typeName, IEnumerable<string> namespaces, IEnumerable<Assembly> assemblies, bool throwOnError, bool ignoreCase)
{
foreach (string namespaceName in namespaces)
{
Type type = GetTypeFromAssemblies($"{namespaceName}.{typeName}", assemblies, false, ignoreCase);
if (type != null) { return type; }
}
if (throwOnError)
{
throw new TypeLoadException($"No type of name '{typeName}' could be found in the specified assemblies and namespaces.");
}
return null;
}
private static Type GetTypeFromAssemblies(string typeName, IEnumerable<Assembly> assemblies, bool throwOnError, bool ignoreCase)
{
foreach (Assembly assembly in assemblies)
{
Type type = Type.GetType($"{typeName}, {assembly.FullName}", false, ignoreCase);
if (type != null) { return type; }
}
if (throwOnError)
{
throw new TypeLoadException($"No type of name '{typeName}' could be found in the specified assemblies.");
}
return null;
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f45dc8b7ae674cd99121d0933e891df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: