Init
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+13
@@ -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) { }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bac12a9ecc201a4d9988f4f23166952
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+13
@@ -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) { }
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7627ba6a0fc6d34182bb1aa178b4be5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a0906d67478d174c896a36b20dc10c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+56
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98ed033aecce1204bbfca244d563194b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+34
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e479a8c10bf9fa043959daa459ce97db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+147
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3a6802829bc65049b2c671ce148f547
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9296938ddd32064d999e8e7000789ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a87fe84f3c6c7ba4191321937395bd84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78358bd367d21fb44b571bf2d2dcd03f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18c4d964a275c32438dcb4abc6c1fb4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8528cd4143f809f459ca13f6415e8a03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8349fa6e81e457847afbe6291aada5fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fedde2bcbb582294d9c08ba7d622ab14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00dd2e1128bbc2b4db7fc3c2c1e840e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48f2484f770bd9744bac9ad2f4f92de4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0674e06c549290f46914c51adb79b5f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+59
@@ -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()}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4299ec1767412954f9b37800967fd471
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "QFSW.QC.Grammar",
|
||||
"references": [
|
||||
"QFSW.QC"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
+7
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c885a65409b41894ab2688fef15008b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+21
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 030edeaf5882fc4408d3ebb9368779a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "QFSW.QC.Parsers",
|
||||
"references": [
|
||||
"QFSW.QC"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5d04fa5e431fa844be99e9199658b16
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb12462fc0295f046957d1030894bea6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+33
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: feb6ca1c0ec89bf4e8bb8ae58737a1f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
Reference in New Issue
Block a user