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