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 { /// /// Handles parsing values to use as console inputs. /// public class QuantumParser { private readonly IQcParser[] _parsers; private readonly IQcGrammarConstruct[] _grammarConstructs; private readonly ConcurrentDictionary _parserLookup = new ConcurrentDictionary(); private readonly HashSet _unparseableLookup = new HashSet(); private readonly Func _recursiveParser; /// /// Creates a Parser Serializer with a custom set of parsers. /// /// The IQcParsers to use in this Quantum Parser. /// The IQcGrammarConstructs to use in this Quantum Parser public QuantumParser(IEnumerable parsers, IEnumerable grammarConstructs) { _recursiveParser = Parse; _parsers = parsers.OrderByDescending(x => x.Priority) .ToArray(); _grammarConstructs = grammarConstructs.OrderBy(x => x.Precedence) .ToArray(); } /// /// Creates a Quantum Parser with the default injected parsers. /// public QuantumParser() : this(new InjectionLoader().GetInjectedInstances(), new InjectionLoader().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; } /// /// Parses a serialized string of data. /// /// The type of the value to parse. /// The string to parse. /// The parsed value. public T Parse(string value) { return (T)Parse(value, typeof(T)); } /// /// Parses a serialized string of data. /// /// The string to parse. /// The type of the value to parse. /// The parsed value. 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 _typeDisplayNames = new Dictionary { { 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 _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 _namespaceTable = new List(_defaultNamespaces); private static readonly Regex _arrayTypeRegex = new Regex(@"^.*\[,*\]$"); private static readonly Regex _genericTypeRegex = new Regex(@"^.+<.*>$"); private static readonly Regex _tupleTypeRegex = new Regex(@"^\(.*\)$"); /// /// Resets the namespace table to its initial state. /// [Command("reset-namespaces", "Resets the namespace table to its initial state")] public static void ResetNamespaceTable() { _namespaceTable.Clear(); _namespaceTable.AddRange(_defaultNamespaces); } /// /// Adds a namespace to the table so that it can be used to type resolution. /// [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); } } /// /// Removes a namespace to the table so that it is no longer used to type resolution. /// [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); } } /// /// Returns a copy of the namespace table. /// public static IEnumerable GetAllNamespaces() { return _namespaceTable; } /// /// Parses and infers the type specified by the string. /// /// The parsed type. /// The type to parse. 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 (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 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 namespaces, IEnumerable 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 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 } }