1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Globalization; 10 using System.Linq; 11 using System.Linq.Expressions; 12 using System.Reflection; 13 14 namespace Antmicro.Renode.Utilities 15 { 16 public class SmartParser 17 { 18 public static SmartParser Instance = new SmartParser(); 19 TryParse(string input, Type outputType, out object result)20 public bool TryParse(string input, Type outputType, out object result) 21 { 22 if(outputType == typeof(string)) 23 { 24 result = input; 25 return true; 26 } 27 var underlyingType = Nullable.GetUnderlyingType(outputType); 28 if(underlyingType != null && TryParse(input, underlyingType, out var res)) 29 { 30 result = res; 31 return true; 32 } 33 if(outputType.IsEnum) 34 { 35 if(Enum.GetNames(outputType).Contains(input, StringComparer.Ordinal)) 36 { 37 result = Enum.Parse(outputType, input, false); 38 return true; 39 } 40 if(Int32.TryParse(input, out var number)) 41 { 42 if(outputType.GetCustomAttributes<AllowAnyNumericalValue>().Any()) 43 { 44 result = Enum.Parse(outputType, input, false); 45 return true; 46 } 47 // We cannot use Enum.IsDefined here, because it requires us to provide the number cast to the enum's underlying type, which we do not know. 48 // Therefore, we use the loop below. 49 foreach(var enumValue in Enum.GetValues(outputType)) 50 { 51 if(Convert.ToInt32(enumValue) == number) 52 { 53 result = Enum.Parse(outputType, input, false); 54 return true; 55 } 56 } 57 } 58 result = null; 59 return false; 60 } 61 62 Delegate parser; 63 if(input.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) 64 { 65 input = input.Substring(2); 66 parser = GetFromCacheOrAdd( 67 hexCache, 68 outputType, 69 () => 70 { 71 TryGetParseMethodDelegate(outputType, new[] { typeof(string), typeof(NumberStyles), typeof(CultureInfo) }, new object[] { NumberStyles.HexNumber, CultureInfo.InvariantCulture }, out Delegate del); 72 return del; 73 } 74 ); 75 } 76 else if(input.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) 77 { 78 input = input.Substring(2); 79 parser = GetFromCacheOrAdd( 80 binaryCache, 81 outputType, 82 () => 83 { 84 TryGetConvertMethodDelegate(outputType, 2, out Delegate del); 85 return del; 86 } 87 ); 88 } 89 else if(GetDefault(outputType) == null && (input == "null" || input == "")) 90 { 91 result = null; 92 return true; 93 } 94 else 95 { 96 parser = GetFromCacheOrAdd( 97 cache, 98 outputType, 99 () => 100 { 101 if(!TryGetParseMethodDelegate(outputType, new[] { typeof(string), typeof(CultureInfo) }, new object[] { CultureInfo.InvariantCulture }, out Delegate del) 102 && !TryGetParseMethodDelegate(outputType, new[] { typeof(string) }, new object[0], out del)) 103 { 104 del = null; 105 } 106 return del; 107 } 108 ); 109 } 110 try 111 { 112 result = parser.DynamicInvoke(input); 113 return true; 114 } 115 catch(TargetInvocationException) 116 { 117 result = null; 118 return false; 119 } 120 } 121 GetDefault(Type type)122 private static object GetDefault(Type type) 123 { 124 if(type.IsValueType) 125 { 126 // this will handle the nullable case 127 return Activator.CreateInstance(type); 128 } 129 return null; 130 } 131 TryGetConvertMethodDelegate(Type type, int fromBase, out Delegate result)132 private static bool TryGetConvertMethodDelegate(Type type, int fromBase, out Delegate result) 133 { 134 var parameters = new [] { typeof(string), typeof(int) }; 135 var method = typeof(Convert).GetMethod($"To{type.Name}", parameters); 136 137 if(method == null) 138 { 139 result = null; 140 return false; 141 } 142 143 var delegateType = Expression.GetDelegateType(parameters.Concat(new[] { method.ReturnType }).ToArray()); 144 var methodDelegate = method.CreateDelegate(delegateType); 145 result = (Func<string, object>)(i => methodDelegate.DynamicInvoke(new object[] { i, fromBase })); 146 147 return true; 148 } 149 TryGetParseMethodDelegate(Type type, Type[] parameters, object[] additionalParameters, out Delegate result)150 private static bool TryGetParseMethodDelegate(Type type, Type[] parameters, object[] additionalParameters, out Delegate result) 151 { 152 var method = type.GetMethod("Parse", parameters); 153 if(method == null) 154 { 155 result = null; 156 return false; 157 } 158 159 var delegateType = Expression.GetDelegateType(parameters.Concat(new[] { method.ReturnType }).ToArray()); 160 var methodDelegate = method.CreateDelegate(delegateType); 161 result = additionalParameters.Length > 0 ? (Func<string, object>)(i => methodDelegate.DynamicInvoke(new object[] { i }.Concat(additionalParameters).ToArray())) : (Func<string, object>)(i => methodDelegate.DynamicInvoke(i)); 162 163 return true; 164 } 165 SmartParser()166 private SmartParser() 167 { 168 cache = new Dictionary<Type, Delegate>(); 169 hexCache = new Dictionary<Type, Delegate>(); 170 binaryCache = new Dictionary<Type, Delegate>(); 171 sync = new object(); 172 } 173 GetFromCacheOrAdd(Dictionary<Type, Delegate> cacheDict, Type outputType, Func<Delegate> function)174 private Delegate GetFromCacheOrAdd(Dictionary<Type, Delegate> cacheDict, Type outputType, Func<Delegate> function) 175 { 176 lock(sync) 177 { 178 if(!cacheDict.TryGetValue(outputType, out Delegate parser)) 179 { 180 parser = function(); 181 if(parser == null) 182 { 183 throw new ArgumentException(string.Format("Type \"{0}\" does not have a \"Parse\" method with the requested parameters", outputType.Name)); 184 } 185 cacheDict.Add(outputType, parser); 186 } 187 return parser; 188 } 189 } 190 191 private readonly Dictionary<Type, Delegate> cache; 192 private readonly Dictionary<Type, Delegate> hexCache; 193 private readonly Dictionary<Type, Delegate> binaryCache; 194 private readonly object sync; 195 } 196 } 197 198