1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Collections.Generic;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Exceptions;
12 using Antmicro.Renode.Peripherals;
13 using Antmicro.Renode.Peripherals.CPU;
14 using Antmicro.Renode.Utilities;
15 using Antmicro.Renode.Utilities.Collections;
16 using System.Linq;
17 using System.Reflection;
18 using System.Collections;
19 using Microsoft.CSharp.RuntimeBinder;
20 using AntShell.Commands;
21 using Dynamitey;
22 using Antmicro.Renode.UserInterface.Tokenizer;
23 using Antmicro.Renode.UserInterface.Commands;
24 using Antmicro.Renode.UserInterface.Exceptions;
25 using System.Runtime.InteropServices;
26 using System.Globalization;
27 using System.Text;
28 
29 namespace Antmicro.Renode.UserInterface
30 {
31     public partial class Monitor
32     {
33         public event Action Quitted;
34 
35         private readonly List<string> usings = new List<string>() { "sysbus." };
36 
37         public enum NumberModes
38         {
39             Hexadecimal,
40             Decimal,
41             Both
42         }
43 
44         public NumberModes CurrentNumberFormat{ get; set; }
45 
SanitizePathSeparator(string baseString)46         public static string SanitizePathSeparator(string baseString)
47         {
48             var sanitizedFile = baseString.Replace("\\", "/");
49             if(sanitizedFile.Contains("/ "))
50             {
51                 sanitizedFile = sanitizedFile.Replace("/ ", "\\ ");
52             }
53             return sanitizedFile;
54         }
55 
GetPossibleEnumValues(Type type)56         private static string GetPossibleEnumValues(Type type)
57         {
58             if(!type.IsEnum)
59             {
60                 throw new ArgumentException("Type is not Enum!", nameof(type));
61             }
62 
63             var builder = new StringBuilder();
64             builder.AppendLine("Possible values are:");
65             foreach(var name in Enum.GetNames(type))
66             {
67                 builder.AppendLine($"\t{name}");
68             }
69             builder.AppendLine();
70 
71             if(type == typeof(ExecutionMode))
72             {
73                 var emulation = EmulationManager.Instance.CurrentEmulation;
74                 var isBlocking = emulation.SingleStepBlocking;
75                 var blockingString = isBlocking ? "blocking" : "non-blocking";
76                 builder.AppendLine($"{nameof(ExecutionMode.SingleStep)} is {blockingString}. It can be changed with:");
77                 builder.AppendLine($"\t{EmulationToken} {nameof(emulation.SingleStepBlocking)} {!isBlocking}");
78             }
79             return builder.ToString();
80         }
81 
RunCommand(ICommandInteraction writer, Command command, IList<Token> parameters)82         private bool RunCommand(ICommandInteraction writer, Command command, IList<Token> parameters)
83         {
84             var commandType = command.GetType();
85             var runnables = commandType.GetMethods().Where(x => x.GetCustomAttributes(typeof(RunnableAttribute), true).Any());
86             ICommandInteraction candidateWriter = null;
87             MethodInfo foundCandidate = null;
88             bool lastIsAccurateMatch = false;
89             IEnumerable<object> preparedParameters = null;
90 
91             foreach(var candidate in runnables)
92             {
93                 bool isAccurateMatch = false;
94                 var candidateParameters = candidate.GetParameters();
95                 var writers = candidateParameters.Where(x => typeof(ICommandInteraction).IsAssignableFrom(x.ParameterType)).ToList();
96 
97                 var lastIsArray = candidateParameters.Length > 0
98                                   && typeof(IEnumerable<Token>).IsAssignableFrom(candidateParameters[candidateParameters.Length - 1].ParameterType);
99 
100                 if(writers.Count > 1
101                 //all but last (and optional writer) should be tokens
102                    || candidateParameters.Skip(writers.Count).Take(candidateParameters.Length - writers.Count - 1).Any(x => !typeof(Token).IsAssignableFrom(x.ParameterType))
103                 //last one should be Token or IEnumerable<Token>
104                    || (candidateParameters.Length > writers.Count
105                    && !typeof(Token).IsAssignableFrom(candidateParameters[candidateParameters.Length - 1].ParameterType)
106                    && !lastIsArray))
107                 {
108                     throw new RecoverableException(String.Format("Method {0} of command {1} has invalid signature, will not process further. You should file a bug report.",
109                         candidate.Name, command.Name));
110                 }
111                 IList<Token> parametersWithoutLastArray = null;
112                 IList<ParameterInfo> candidateParametersWithoutArrayAndWriters = null;
113                 if(lastIsArray)
114                 {
115                     candidateParametersWithoutArrayAndWriters = candidateParameters.Skip(writers.Count).Take(candidateParameters.Length - writers.Count - 1).ToList();
116                     if(parameters.Count < candidateParameters.Length - writers.Count) //without writer
117                     {
118                         continue;
119                     }
120                     parametersWithoutLastArray = parameters.Take(candidateParametersWithoutArrayAndWriters.Count()).ToList();
121                 }
122                 else
123                 {
124                     candidateParametersWithoutArrayAndWriters = candidateParameters.Skip(writers.Count).ToList();
125                     if(parameters.Count != candidateParameters.Length - writers.Count) //without writer
126                     {
127                         continue;
128                     }
129                     parametersWithoutLastArray = parameters;
130                 }
131                 //Check for types
132                 if(parametersWithoutLastArray.Zip(
133                        candidateParametersWithoutArrayAndWriters,
134                        (x, y) => new {FromUser = x.GetType(), FromMethod = y.ParameterType})
135                     .Any(x => !x.FromMethod.IsAssignableFrom(x.FromUser)))
136                 {
137                     continue;
138                 }
139 
140                 bool constraintsOk = true;
141                 //Check for constraints
142                 for(var i = 0; i < parametersWithoutLastArray.Count; ++i)
143                 {
144                     var attribute = candidateParametersWithoutArrayAndWriters[i].GetCustomAttributes(typeof(ValuesAttribute), true);
145                     if(attribute.Any())
146                     {
147                         if(!((ValuesAttribute)attribute[0]).Values.Contains(parametersWithoutLastArray[i].GetObjectValue()))
148                         {
149                             constraintsOk = false;
150                             break;
151                         }
152                     }
153                 }
154                 if(lastIsArray)
155                 {
156                     var arrayParameters = parameters.Skip(parametersWithoutLastArray.Count()).ToArray();
157                     var elementType = candidateParameters.Last().ParameterType.GetElementType();
158                     if(!arrayParameters.All(x => elementType.IsAssignableFrom(x.GetType())))
159                     {
160                         constraintsOk = false;
161                     }
162                     else
163                     {
164                         var array = Array.CreateInstance(elementType, arrayParameters.Length);
165                         for(var i = 0; i < arrayParameters.Length; ++i)
166                         {
167                             array.SetValue(arrayParameters[i], i);
168                         }
169                         preparedParameters = parametersWithoutLastArray.Concat(new object[] { array });
170                     }
171                 }
172                 else
173                 {
174                     preparedParameters = parameters;
175                 }
176 
177                 if(!constraintsOk)
178                 {
179                     continue;
180                 }
181 
182                 if(!parametersWithoutLastArray.Zip(
183                        candidateParametersWithoutArrayAndWriters,
184                        (x, y) => new {FromUser = x.GetType(), FromMethod = y.ParameterType})
185                     .Any(x => x.FromMethod != x.FromUser))
186                 {
187                     isAccurateMatch = true;
188                 }
189                 if(foundCandidate != null && (lastIsAccurateMatch == isAccurateMatch)) // if one is not better than the other
190                 {
191                     throw new RecoverableException(String.Format("Ambiguous choice between methods {0} and {1} of command {2}. You should file a bug report.",
192                         foundCandidate.Name, candidate.Name, command.Name));
193                 }
194                 if(lastIsAccurateMatch) // previous was better
195                 {
196                     continue;
197                 }
198                 foundCandidate = candidate;
199                 lastIsAccurateMatch = isAccurateMatch;
200 
201                 if(writers.Count == 1)
202                 {
203                     candidateWriter = writer;
204                 }
205             }
206 
207             if(foundCandidate != null)
208             {
209                 var parametersWithWriter = candidateWriter == null ? preparedParameters : new object[] { candidateWriter }.Concat(preparedParameters);
210                 try
211                 {
212                     if(foundCandidate.ReturnType == typeof(bool))
213                     {
214                         return (bool)foundCandidate.Invoke(command, parametersWithWriter.ToArray());
215                     }
216                     else
217                     {
218                         foundCandidate.Invoke(command, parametersWithWriter.ToArray());
219                     }
220                 }
221                 catch(TargetInvocationException e)
222                 {
223                     // rethrow only the inner exception but with a nice stack trace
224                     System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
225                 }
226 
227                 return true;
228             }
229             if(parameters.Any(x => x is VariableToken))
230             {
231                 return RunCommand(writer, command, ExpandVariables(parameters));
232             }
233             writer.WriteError(String.Format("Bad parameters for command {0} {1}", command.Name, string.Join(" ", parameters.Select(x => x.OriginalValue))));
234             command.PrintHelp(writer);
235             writer.WriteLine();
236 
237             return false;
238         }
239 
240         //TODO: unused, but maybe should be used.
PrintPython(IEnumerable<Token> p, ICommandInteraction writer)241         private void PrintPython(IEnumerable<Token> p, ICommandInteraction writer)
242         {
243             if(!p.Any())
244             {
245                 writer.WriteLine("\nPython commands:");
246                 writer.WriteLine("===========================");
247                 foreach(var command in pythonRunner.GetPythonCommands())
248                 {
249                     writer.WriteLine(command);
250                 }
251                 writer.WriteLine();
252             }
253         }
254 
255         #region Parsing
256 
257         private const string DefaultNamespace = "Antmicro.Renode.Peripherals.";
258 
GetObjectSuggestions(object node)259         private IEnumerable<String> GetObjectSuggestions(object node)
260         {
261             if(node != null)
262             {
263                 return GetMonitorInfo(node.GetType()).AllNames;
264             }
265             return new List<String>();
266         }
267 
GetDevice(string name)268         private object GetDevice(string name)
269         {
270             var staticBound = FromStaticMapping(name);
271             var iface = GetExternalInterfaceOrNull(name);
272             if(currentMachine != null || staticBound != null || iface != null)
273             {
274                 var boundObject = staticBound ?? FromMapping(name) ?? iface;
275                 if(boundObject != null)
276                 {
277                     return boundObject;
278                 }
279 
280                 IPeripheral device;
281                 string longestMatch;
282                 if(TryFindPeripheralByName(name, out device, out longestMatch))
283                 {
284                     return device;
285                 }
286             }
287             return null;
288         }
289 
GetResultFormat(object result, int num, int? width = null)290         private string GetResultFormat(object result, int num, int? width = null)
291         {
292             string format;
293             if(result is int || result is long || result is uint || result is ushort || result is byte)
294             {
295                 format = "0x{" + num + ":X";
296             }
297             else
298             {
299                 format = "{" + num;
300             }
301             if(width.HasValue)
302             {
303                 format = format + ",-" + width.Value;
304             }
305             format = format + "}";
306             return format;
307         }
308 
GetNumberFormat(NumberModes mode, int width)309         private string GetNumberFormat(NumberModes mode, int width)
310         {
311             return NumberFormats[mode].Replace("X", "X" + width);
312         }
313 
314         private Dictionary<NumberModes, string> NumberFormats = new Dictionary<NumberModes, string> {
315             { NumberModes.Both, "0x{0:X} ({0})" },
316             { NumberModes.Decimal, "{0}" },
317             { NumberModes.Hexadecimal, "0x{0:X}" },
318         };
319 
PrintActionResult(object result, ICommandInteraction writer, bool withNewLine = true)320         private void PrintActionResult(object result, ICommandInteraction writer, bool withNewLine = true)
321         {
322             var endl = "";
323             if(withNewLine)
324             {
325                 endl = "\r\n"; //Cannot be Environment.NewLine, we need \r explicitly.
326             }
327             var enumerable = result as IEnumerable;
328             if(result is int || result is long || result is uint || result is ushort || result is byte || result is ulong || result is short)
329             {
330                 writer.Write(string.Format(CultureInfo.InvariantCulture, GetNumberFormat(CurrentNumberFormat, 2 * Marshal.SizeOf(result.GetType())) + endl, result));
331             }
332             else if(result is string[,])
333             {
334                 var table = result as string[,];
335                 PrettyPrint2DArray(table, writer);
336             }
337             else if(result is IDictionary)
338             {
339                 dynamic dict = result;
340                 var length = 0;
341                 foreach(var entry in dict)
342                 {
343                     var value = entry.Key.ToString();
344                     length = length > value.Length ? length : value.Length;
345                 }
346                 foreach(var entry in dict)
347                 {
348                     var format = GetResultFormat(entry.Key, 0, length) + " : " + GetResultFormat(entry.Value, 1);
349                     string entryResult = string.Format(CultureInfo.InvariantCulture, format, entry.Key, entry.Value); //DO NOT INLINE WITH WriteLine. May result with CS1973, but may even fail in runtime.
350                     writer.WriteLine(entryResult);
351                 }
352                 return;
353             }
354             else if(enumerable != null && !(result is string))
355             {
356                 var i = 0;
357                 writer.Write("[\r\n");
358                 foreach(var item in enumerable)
359                 {
360                     ++i;
361                     PrintActionResult(item, writer, false);
362                     writer.Write(", ");
363                     if(i % 10 == 0)
364                     {
365                         writer.Write("\r\n");
366                     }
367                 }
368                 writer.Write("\r\n]" + endl);
369             }
370             else if(result is RawImageData image)
371             {
372                 writer.WriteRaw(InlineImage.Encode(image.ToPng()));
373             }
374             else
375             {
376                 writer.Write(string.Format(CultureInfo.InvariantCulture, "{0}" + endl, result));
377             }
378         }
379 
PrettyPrint2DArray(string[,] table, ICommandInteraction writer)380         private static void PrettyPrint2DArray(string[,] table, ICommandInteraction writer)
381         {
382             var columnLengths = new int[table.GetLength(1)];
383             for(var i = 0; i < columnLengths.Length; i++)
384             {
385                 for(var j = 0; j < table.GetLength(0); j++)
386                 {
387                     columnLengths[i] = Math.Max(table[j, i].Length, columnLengths[i]);
388                 }
389             }
390             var lineLength = columnLengths.Sum() + columnLengths.Length + 1;
391             writer.WriteLine("".PadRight(lineLength, '-'));
392             for(var i = 0; i < table.GetLength(0); i++)
393             {
394                 if(i == 1)
395                 {
396                     writer.WriteLine("".PadRight(lineLength, '-'));
397                 }
398                 writer.Write('|');
399                 for(var j = 0; j < table.GetLength(1); j++)
400                 {
401                     writer.Write(table[i, j].PadRight(columnLengths[j]));
402                     writer.Write('|');
403                 }
404                 writer.WriteLine();
405             }
406             writer.WriteLine("".PadRight(lineLength, '-'));
407         }
408 
ProcessDeviceAction(Type deviceType, string name, IEnumerable<Token> p, ICommandInteraction writer)409         private void ProcessDeviceAction(Type deviceType, string name, IEnumerable<Token> p, ICommandInteraction writer)
410         {
411             var devInfo = GetMonitorInfo(deviceType);
412             if(!p.Any())
413             {
414                 if(devInfo != null)
415                 {
416                     PrintMonitorInfo(name, devInfo, writer);
417                 }
418             }
419             else
420             {
421                 object result;
422                 try
423                 {
424                     var device = IdentifyDevice(name);
425                     result = ExecuteDeviceAction(name, device, p);
426                 }
427                 catch(ParametersMismatchException e)
428                 {
429                     var nodeInfo = GetMonitorInfo(e.Type);
430                     if(nodeInfo != null)
431                     {
432                         PrintMonitorInfo(e.Name, nodeInfo, writer, e.Command);
433                     }
434                     throw;
435                 }
436                 if(result != null)
437                 {
438                     PrintActionResult(result, writer);
439 
440                     if(result.GetType().IsEnum)
441                     {
442                         writer.WriteLine();
443                         writer.WriteLine(GetPossibleEnumValues(result.GetType()));
444                     }
445                 }
446             }
447         }
448 
ProcessDeviceActionByName(string name, IEnumerable<Token> p, ICommandInteraction writer)449         private void ProcessDeviceActionByName(string name, IEnumerable<Token> p, ICommandInteraction writer)
450         {
451             var staticBound = FromStaticMapping(name);
452             var iface = GetExternalInterfaceOrNull(name);
453             if(currentMachine != null || staticBound != null || iface != null)
454             { //special cases
455                 var boundElement = staticBound ?? FromMapping(name);
456                 if(boundElement != null)
457                 {
458                     ProcessDeviceAction(boundElement.GetType(), name, p, writer);
459                     return;
460                 }
461                 if(iface != null)
462                 {
463                     ProcessDeviceAction(iface.GetType(), name, p, writer);
464                     return;
465                 }
466 
467                 Type device;
468                 string longestMatch;
469                 string actualName;
470                 if(TryFindPeripheralTypeByName(name, out device, out longestMatch, out actualName))
471                 {
472                     ProcessDeviceAction(device, actualName, p, writer);
473                 }
474                 else
475                 {
476                     if(longestMatch.Length > 0)
477                     {
478                         throw new RecoverableException(String.Format("Could not find device {0}, the longest match is {1}.", name, longestMatch));
479                     }
480                     throw new RecoverableException(String.Format("Could not find device {0}.", name));
481                 }
482             }
483         }
484 
TryFindPeripheralTypeByName(string name, out Type type, out string longestMatch, out string actualName)485         private bool TryFindPeripheralTypeByName(string name, out Type type, out string longestMatch, out string actualName)
486         {
487             IPeripheral peripheral;
488             type = null;
489             longestMatch = string.Empty;
490             actualName = name;
491             string longestMatching;
492             string currentMatch;
493             string longestPrefix = string.Empty;
494             var ret = currentMachine.TryGetByName(name, out peripheral, out longestMatching);
495             longestMatch = longestMatching;
496 
497             if(!ret)
498             {
499                 foreach(var prefix in usings)
500                 {
501                     ret = currentMachine.TryGetByName(prefix + name, out peripheral, out currentMatch);
502                     if(longestMatching.Split('.').Length < currentMatch.Split('.').Length - prefix.Split('.').Length)
503                     {
504                         longestMatching = currentMatch;
505                         longestPrefix = prefix;
506                     }
507                     if(ret)
508                     {
509                         actualName = prefix + name;
510                         break;
511                     }
512                 }
513             }
514             longestMatch = longestPrefix + longestMatching;
515             if(ret)
516             {
517                 type = peripheral.GetType();
518             }
519             return ret;
520         }
521 
TryFindPeripheralByName(string name, out IPeripheral peripheral, out string longestMatch)522         private bool TryFindPeripheralByName(string name, out IPeripheral peripheral, out string longestMatch)
523         {
524             longestMatch = string.Empty;
525 
526             if(currentMachine == null)
527             {
528                 peripheral = null;
529                 return false;
530             }
531 
532             string longestMatching;
533             string currentMatch;
534             string longestPrefix = string.Empty;
535             var ret = currentMachine.TryGetByName(name, out peripheral, out longestMatching);
536             longestMatch = longestMatching;
537 
538             if(!ret)
539             {
540                 foreach(var prefix in usings)
541                 {
542                     ret = currentMachine.TryGetByName(prefix + name, out peripheral, out currentMatch);
543                     if(longestMatching.Split('.').Length < currentMatch.Split('.').Length - prefix.Split('.').Length)
544                     {
545                         longestMatching = currentMatch;
546                         longestPrefix = prefix;
547                     }
548                     if(ret)
549                     {
550                         break;
551                     }
552                 }
553             }
554             longestMatch = longestPrefix + longestMatching;
555             return ret;
556         }
557 
TypePrettyName(Type type)558         private static string TypePrettyName(Type type)
559         {
560             var genericArguments = type.GetGenericArguments();
561             if(genericArguments.Length == 0)
562             {
563                 return type.Name;
564             }
565             if(type.GetGenericTypeDefinition() == typeof(Nullable<>) && genericArguments.Length == 1)
566             {
567                 return genericArguments.Select(x => TypePrettyName(x) + "?").First();
568             }
569             var typeDefeninition = type.Name;
570             var unmangledName = typeDefeninition.Substring(0, typeDefeninition.IndexOf("`", StringComparison.Ordinal));
571             return unmangledName + "<" + String.Join(",", genericArguments.Select(TypePrettyName)) + ">";
572         }
573 
PrintMonitorInfo(string name, MonitorInfo info, ICommandInteraction writer, string lookup = null)574         private void PrintMonitorInfo(string name, MonitorInfo info, ICommandInteraction writer, string lookup = null)
575         {
576             if(info == null)
577             {
578                 return;
579             }
580             if(info.Methods != null && info.Methods.Any(x => lookup == null || x.Name == lookup))
581             {
582                 writer.WriteLine("\nThe following methods are available:");
583 
584                 foreach(var method in info.Methods.Where(x=> lookup == null || x.Name==lookup))
585                 {
586                     writer.Write(" - ");
587                     writer.Write(TypePrettyName(method.ReturnType), ConsoleColor.Green);
588                     writer.Write($" {method.Name} (");
589 
590                     IEnumerable<ParameterInfo> parameters;
591 
592                     if(method.IsExtension())
593                     {
594                         parameters = method.GetParameters().Skip(1);
595                     }
596                     else
597                     {
598                         parameters = method.GetParameters();
599                     }
600                     parameters = parameters.Where(x => !Attribute.IsDefined(x, typeof(AutoParameterAttribute)));
601 
602                     var lastParameter = parameters.LastOrDefault();
603                     foreach(var param in parameters.Where(x=> !x.IsRetval))
604                     {
605                         if(param.IsOut)
606                         {
607                             writer.Write("out ", ConsoleColor.Yellow);
608                         }
609                         if(param.IsDefined(typeof(ParamArrayAttribute)))
610                         {
611                             writer.Write("params ", ConsoleColor.Yellow);
612                         }
613                         writer.Write(TypePrettyName(param.ParameterType), ConsoleColor.Green);
614                         writer.Write($" {param.Name}");
615 
616                         if(param.IsOptional)
617                         {
618                             writer.Write(" = ");
619                             if(param.DefaultValue == null)
620                             {
621                                 writer.Write("null", ConsoleColor.DarkRed);
622                             }
623                             else
624                             {
625                                 if(param.ParameterType.Name == "String")
626                                 {
627                                     writer.Write("\"", ConsoleColor.DarkRed);
628                                 }
629                                 writer.Write(param.DefaultValue.ToString(), ConsoleColor.DarkRed);
630                                 if(param.ParameterType.Name == "String")
631                                 {
632                                     writer.Write("\"", ConsoleColor.DarkRed);
633                                 }
634                             }
635                         }
636                         if(lastParameter != param)
637                         {
638                             writer.Write(", ");
639                         }
640                     }
641                     writer.WriteLine(")");
642                 }
643                 writer.WriteLine(string.Format("\n\rUsage:\n\r {0} MethodName param1 param2 ...\n\r", name));
644             }
645 
646             if(info.Properties != null && info.Properties.Any(x => lookup == null || x.Name == lookup))
647             {
648                 writer.WriteLine("\nThe following properties are available:");
649 
650                 foreach(var property in info.Properties.Where(x=> lookup==null || x.Name==lookup))
651                 {
652                     writer.Write(" - ");
653                     writer.Write(TypePrettyName(property.PropertyType), ConsoleColor.Green);
654                     writer.WriteLine($" {property.Name}");
655                     writer.Write("     available for ");
656                     if(property.IsCurrentlyGettable(CurrentBindingFlags))
657                     {
658                         writer.Write("'get'", ConsoleColor.Yellow);
659                     }
660                     if(property.IsCurrentlyGettable(CurrentBindingFlags) && property.IsCurrentlySettable(CurrentBindingFlags))
661                     {
662                         writer.Write(" and ");
663                     }
664                     if(property.IsCurrentlySettable(CurrentBindingFlags))
665                     {
666                         writer.Write("'set'", ConsoleColor.Yellow);
667                     }
668                     writer.WriteLine();
669                 }
670                 writer.Write("\n\rUsage:\n\r - ");
671                 writer.Write("get", ConsoleColor.Yellow);
672                 writer.Write($": {name} PropertyName\n\r - ");
673                 writer.Write("set", ConsoleColor.Yellow);
674                 writer.WriteLine($": {name} PropertyName Value\n\r");
675             }
676 
677             if(info.Indexers != null && info.Indexers.Any(x => lookup == null || x.Name == lookup))
678             {
679                 writer.WriteLine("\nThe following indexers are available:");
680                 foreach(var indexer in info.Indexers.Where(x=> lookup==null || x.Name==lookup))
681                 {
682                     writer.Write(" - ");
683                     writer.Write(TypePrettyName(indexer.PropertyType), ConsoleColor.Green);
684                     writer.Write($" {indexer.Name}[");
685                     var parameters = indexer.GetIndexParameters();
686                     var lastParameter = parameters.LastOrDefault();
687                     foreach(var param in parameters)
688                     {
689                         writer.Write(TypePrettyName(param.ParameterType), ConsoleColor.Green);
690                         writer.Write($" {param.Name}");
691                         if(param.IsOptional)
692                         {
693                             writer.Write(" = ");
694                             if(param.DefaultValue == null)
695                             {
696                                 writer.Write("null", ConsoleColor.DarkRed);
697                             }
698                             else
699                             {
700                                 if(param.ParameterType.Name == "String")
701                                 {
702                                     writer.Write("\"", ConsoleColor.DarkRed);
703                                 }
704                                 writer.Write(param.DefaultValue.ToString(), ConsoleColor.DarkRed);
705                                 if(param.ParameterType.Name == "String")
706                                 {
707                                     writer.Write("\"", ConsoleColor.DarkRed);
708                                 }
709                             }
710                         }
711                         if(lastParameter != param)
712                         {
713                             writer.Write(", ");
714                         }
715                     }
716                     writer.Write("]     available for ");
717                     if(indexer.IsCurrentlyGettable(CurrentBindingFlags))
718                     {
719                         writer.Write("'get'", ConsoleColor.Yellow);
720                     }
721                     if(indexer.IsCurrentlyGettable(CurrentBindingFlags) && indexer.IsCurrentlySettable(CurrentBindingFlags))
722                     {
723                         writer.Write(" and ");
724                     }
725                     if(indexer.IsCurrentlySettable(CurrentBindingFlags))
726                     {
727                         writer.Write("'set'", ConsoleColor.Yellow);
728                     }
729                     writer.WriteLine();
730                 }
731                 writer.Write("\n\rUsage:\n\r - ");
732                 writer.Write("get", ConsoleColor.Yellow);
733                 writer.Write($": {name} IndexerName [param1 param2 ...]\n\r - ");
734                 writer.Write("set", ConsoleColor.Yellow);
735                 writer.WriteLine($": {name} IndexerName [param1 param2 ...] Value\n\r   IndexerName is optional if every indexer has the same name.");
736             }
737 
738             if(info.Fields != null && info.Fields.Any(x => lookup == null || x.Name == lookup))
739             {
740                 writer.WriteLine("\nThe following fields are available:");
741 
742                 foreach(var field in info.Fields.Where(x=> lookup==null || x.Name==lookup))
743                 {
744                     writer.Write(" - ");
745                     writer.Write(TypePrettyName(field.FieldType), ConsoleColor.Green);
746                     writer.Write($" {field.Name}");
747                     if(field.IsLiteral || field.IsInitOnly)
748                     {
749                         writer.Write(" (read only)");
750                     }
751                     writer.WriteLine("");
752                 }
753                 writer.Write("\n\rUsage:\n\r - ");
754                 writer.Write("get", ConsoleColor.Yellow);
755                 writer.Write($": {name} fieldName\n\r - ");
756                 writer.Write("set", ConsoleColor.Yellow);
757                 writer.WriteLine($": {name} fieldName Value\n\r");
758             }
759         }
760 
761         private class MachineWithWasPaused
762         {
763             public Machine Machine { get; set; }
764 
765             public bool WasPaused { get; set; }
766         }
767 
GetMonitorInfo(Type device)768         public MonitorInfo GetMonitorInfo(Type device)
769         {
770             var info = new MonitorInfo();
771             var methodsAndExtensions = new List<MethodInfo>();
772 
773             var methods = cache.Get(device, GetAvailableMethods);
774             if(methods.Any())
775             {
776                 methodsAndExtensions.AddRange(methods);
777             }
778 
779             var properties = cache.Get(device, GetAvailableProperties);
780             if(properties.Any())
781             {
782                 info.Properties = properties.OrderBy(x => x.Name);
783             }
784 
785             var indexers = cache.Get(device, GetAvailableIndexers);
786             if(indexers.Any())
787             {
788                 info.Indexers = indexers.OrderBy(x => x.Name);
789             }
790 
791             var fields = cache.Get(device, GetAvailableFields);
792             if(fields.Any())
793             {
794                 info.Fields = fields.OrderBy(x => x.Name);
795             }
796 
797             var extensions = cache.Get(device, GetAvailableExtensions);
798 
799             if(extensions.Any())
800             {
801                 methodsAndExtensions.AddRange(extensions);
802             }
803             if(methodsAndExtensions.Any())
804             {
805                 info.Methods = methodsAndExtensions.OrderBy(x => x.Name);
806             }
807             return info;
808         }
809 
810         #endregion
811 
812         #region Device invocations
813 
ConvertValueOrThrowRecoverable(object value, Type type)814         public object ConvertValueOrThrowRecoverable(object value, Type type)
815         {
816             try
817             {
818                 var convertedValue = ConvertValue(value, type);
819                 return convertedValue;
820             }
821             catch(Exception e)
822             {
823                 if(e is FormatException || e is RuntimeBinderException || e is OverflowException || e is InvalidCastException)
824                 {
825                     throw new RecoverableException(e);
826                 }
827                 throw;
828             }
829         }
830 
ConvertValue(object value, Type type)831         private object ConvertValue(object value, Type type)
832         {
833             var underlyingType = Nullable.GetUnderlyingType(type);
834             if((!type.IsValueType || underlyingType != null) && value == null)
835             {
836                 return null;
837             }
838             if(type.IsInstanceOfType(value))
839             {
840                 return Dynamic.InvokeConvert(value, type, true);
841             }
842             if(value is string)
843             {
844                 IPeripheral peripheral;
845                 string longestMatch;
846                 if(TryFindPeripheralByName((string)value, out peripheral, out longestMatch))
847                 {
848                     if(type.IsInstanceOfType(peripheral))
849                     {
850                         return peripheral;
851                     }
852                 }
853 
854                 if(currentMachine != null)
855                 {
856                     IPeripheralsGroup group;
857                     if(currentMachine.PeripheralsGroups.TryGetByName((string)value, out group))
858                     {
859                         if(type.IsInstanceOfType(group))
860                         {
861                             return group;
862                         }
863                     }
864                 }
865 
866                 IHostMachineElement @interface;
867                 if(Emulation.ExternalsManager.TryGetByName((string)value, out @interface))
868                 {
869                     if(type.IsInstanceOfType(@interface))
870                     {
871                         return @interface;
872                     }
873                 }
874 
875                 IExternal external;
876                 if(Emulation.ExternalsManager.TryGetByName((string)value, out external))
877                 {
878                     if(type.IsInstanceOfType(external))
879                     {
880                         return external;
881                     }
882                 }
883             }//intentionally no else (may be iconvertible or enum)
884             if(type.IsEnum)
885             {
886                 if(value is string valueString)
887                 {
888                     if(Enum.IsDefined(type, value))
889                     {
890                         return Enum.Parse(type, valueString);
891                     }
892                 }
893                 else
894                 {
895                     var enumValue = Enum.ToObject(type, value);
896                     if(Enum.IsDefined(type, enumValue) || type.IsDefined(typeof(FlagsAttribute), false))
897                     {
898                         return enumValue;
899                     }
900                 }
901                 throw new FormatException(String.Format(
902                     "Enum value {0} is not defined for {1}!\n\n{2}",
903                     value, type.Name, GetPossibleEnumValues(type)
904                 ));
905             }
906             if(underlyingType != null)
907             {
908                 return ConvertValue(value, underlyingType);
909             }
910             return Dynamic.InvokeConvert(value, type, true);
911         }
912 
IdentifyDevice(string name)913         private object IdentifyDevice(string name)
914         {
915             var device = FromStaticMapping(name);
916             var iface = GetExternalInterfaceOrNull(name);
917             device = device ?? FromMapping(name) ?? iface ?? (object)currentMachine[name];
918             return device;
919         }
920 
InvokeGet(object device, MemberInfo info)921         private object InvokeGet(object device, MemberInfo info)
922         {
923             var context = CreateInvocationContext(device, info);
924             if(context != null)
925             {
926                 return Dynamic.InvokeGet(context, info.Name);
927             }
928             else
929             {
930                 var propInfo = info as PropertyInfo;
931                 var fieldInfo = info as FieldInfo;
932                 if(fieldInfo != null)
933                 {
934                     return fieldInfo.GetValue(null);
935                 }
936                 if(propInfo != null)
937                 {
938                     return propInfo.GetValue(!propInfo.IsStatic() ? device : null, null);
939                 }
940                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeGet", info.Name));
941             }
942         }
943 
InvokeSet(object device, MemberInfo info, object parameter)944         private void InvokeSet(object device, MemberInfo info, object parameter)
945         {
946             var context = CreateInvocationContext(device, info);
947             if(context != null)
948             {
949                 Dynamic.InvokeSet(context, info.Name, parameter);
950             }
951             else
952             {
953                 var propInfo = info as PropertyInfo;
954                 var fieldInfo = info as FieldInfo;
955                 if(fieldInfo != null)
956                 {
957                     fieldInfo.SetValue(null, parameter);
958                     return;
959                 }
960                 if(propInfo != null)
961                 {
962                     propInfo.SetValue(!propInfo.IsStatic() ? device : null, parameter, null);
963                     return;
964                 }
965                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeSet", info.Name));
966             }
967         }
968 
InvokeExtensionMethod(object device, MethodInfo method, List<object> parameters)969         private object InvokeExtensionMethod(object device, MethodInfo method, List<object> parameters)
970         {
971             var context = InvokeContext.CreateStatic(method.ReflectedType);
972             if(context != null)
973             {
974                 return InvokeWithContext(context, method, (new [] { device }.Concat(parameters)).ToArray());
975             }
976             else
977             {
978                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeExtensionMethod", method.Name));
979             }
980         }
981 
InvokeMethod(object device, MethodInfo method, List<object> parameters)982         private object InvokeMethod(object device, MethodInfo method, List<object> parameters)
983         {
984             var context = CreateInvocationContext(device, method);
985             if(context != null)
986             {
987                 return InvokeWithContext(context, method, parameters.ToArray());
988             }
989             else
990             {
991                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeMethod", method.Name));
992             }
993         }
994 
InvokeSetIndex(object device, PropertyInfo property, List<object> parameters)995         private void InvokeSetIndex(object device, PropertyInfo property, List<object> parameters)
996         {
997             var context = CreateInvocationContext(device, property);
998             if(context != null)
999             {
1000                 Dynamic.InvokeSetIndex(context, parameters.ToArray());
1001             }
1002             else
1003             {
1004                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeSetIndex", property.Name));
1005             }
1006         }
1007 
InvokeGetIndex(object device, PropertyInfo property, List<object> parameters)1008         private object InvokeGetIndex(object device, PropertyInfo property, List<object> parameters)
1009         {
1010             var context = CreateInvocationContext(device, property);
1011             if(context != null)
1012             {
1013                 return Dynamic.InvokeGetIndex(context, parameters.ToArray());
1014             }
1015             else
1016             {
1017                 throw new NotImplementedException(String.Format("Unsupported field {0} in InvokeGetIndex", property.Name));
1018             }
1019         }
1020 
InvokeWithContext(InvokeContext context, MethodInfo method, object[] parameters)1021         private object InvokeWithContext(InvokeContext context, MethodInfo method, object[] parameters)
1022         {
1023             if(method.ReturnType == typeof(void))
1024             {
1025                 Dynamic.InvokeMemberAction(context, method.Name, parameters);
1026                 return null;
1027             }
1028             else
1029             {
1030                 return Dynamic.InvokeMember(context, method.Name, parameters);
1031             }
1032         }
1033 
1034         /// <summary>
1035         /// Creates the invocation context.
1036         /// </summary>
1037         /// <returns>The invokation context or null, if can't be handled by Dynamitey.</returns>
1038         /// <param name="device">Target device.</param>
1039         /// <param name="info">Field, property or method info.</param>
CreateInvocationContext(object device, MemberInfo info)1040         private static InvokeContext CreateInvocationContext(object device, MemberInfo info)
1041         {
1042             if(info.IsStatic())
1043             {
1044                 if(info is FieldInfo || info is PropertyInfo)
1045                 {
1046                     //FieldInfo not supported in Dynamitey
1047                     return null;
1048                 }
1049                 return InvokeContext.CreateStatic(device.GetType());
1050             }
1051             var propertyInfo = info as PropertyInfo;
1052             if(propertyInfo != null)
1053             {
1054                 //private properties not supported in Dynamitey
1055                 if((propertyInfo.CanRead && propertyInfo.GetGetMethod(true).IsPrivate)
1056                    || (propertyInfo.CanWrite && propertyInfo.GetSetMethod(true).IsPrivate))
1057                 {
1058                     return null;
1059                 }
1060             }
1061             return InvokeContext.CreateContext(device, info.ReflectedType);
1062         }
1063 
GetExternalInterfaceOrNull(string name)1064         private IEmulationElement GetExternalInterfaceOrNull(string name)
1065         {
1066             IEmulationElement external;
1067             Emulation.ExternalsManager.TryGetByName(name, out external);
1068             return external;
1069         }
1070 
1071         private readonly HashSet<Tuple<Type, Type>> acceptableTokensTypes = new HashSet<Tuple<Type, Type>>() {
1072             { new Tuple<Type, Type>(typeof(string), typeof(StringToken)) },
1073             { new Tuple<Type, Type>(typeof(string), typeof(PathToken)) },
1074             { new Tuple<Type, Type>(typeof(int), typeof(DecimalIntegerToken)) },
1075             { new Tuple<Type, Type>(typeof(bool), typeof(BooleanToken)) },
1076             { new Tuple<Type, Type>(typeof(long), typeof(DecimalIntegerToken)) },
1077             { new Tuple<Type, Type>(typeof(short), typeof(DecimalIntegerToken)) },
1078         };
1079 
TryParseTokenForParamType(Token token, Type type, out object result)1080         private bool TryParseTokenForParamType(Token token, Type type, out object result)
1081         {
1082             var tokenTypes = acceptableTokensTypes.Where(x => x.Item1 == type);
1083             //If this result type is limited to specific token types, and this is not one of them, fail
1084             if(tokenTypes.Any() && !tokenTypes.Any(tt => tt.Item2.IsInstanceOfType(token)))
1085             {
1086                 result = null;
1087                 return false;
1088             }
1089             result = ConvertValue(token.GetObjectValue(), type);
1090             return true;
1091         }
1092 
TryPrepareParameters(IList<Token> values, IList<ParameterInfo> parameters, out List<object> result)1093         private bool TryPrepareParameters(IList<Token> values, IList<ParameterInfo> parameters, out List<object> result)
1094         {
1095             result = new List<object>();
1096             //this might be expanded - try all parameters with the attribute, try to fill from factory based on it's type
1097             if(parameters.Count > 0 && typeof(IMachine).IsAssignableFrom(parameters[0].ParameterType)
1098                 && Attribute.IsDefined(parameters[0], typeof(AutoParameterAttribute)))
1099             {
1100                 result.Add(currentMachine);
1101                 parameters = parameters.Skip(1).ToList();
1102             }
1103 
1104             //The last parameter can be a param array
1105             Type paramArrayElementType = null;
1106             var lastParam = parameters.LastOrDefault();
1107             if(lastParam?.IsDefined(typeof(ParamArrayAttribute)) ?? false)
1108             {
1109                 parameters = parameters.Take(parameters.Count - 1).ToList();
1110                 paramArrayElementType = lastParam.ParameterType.GetElementType();
1111             }
1112 
1113             var indexedValues = new Dictionary<int, Token>();
1114             var allowPositional = true;
1115             for(int i = 0, currentPos = 0; i < values.Count; ++i, ++currentPos)
1116             {
1117                 //Parse named arguments
1118                 if(i < values.Count - 2
1119                     && values[i] is LiteralToken lit
1120                     && values[i + 1] is EqualityToken)
1121                 {
1122                     var parameterIndex = parameters.IndexOf(p => p.Name == lit.Value);
1123                     //Fail on nonexistent or duplicate names
1124                     if(parameterIndex == -1 || indexedValues.ContainsKey(parameterIndex))
1125                     {
1126                         return false;
1127                     }
1128                     //Disallow further positional arguments only if the name doesn't match the position
1129                     //For example, for f(a=0, b=0) `f a=4 9` is allowed, like in C#
1130                     allowPositional &= parameterIndex == currentPos;
1131                     indexedValues[parameterIndex] = values[i + 2];
1132                     i += 2; //Skip the name and = sign
1133                 }
1134                 else
1135                 {
1136                     //If we have filled all positional slots then allow further positional arguments
1137                     //no matter what. This is used for a params T[] after named parameters
1138                     if(!allowPositional && currentPos < parameters.Count)
1139                     {
1140                         return false;
1141                     }
1142                     indexedValues[currentPos] = values[i];
1143                 }
1144             }
1145 
1146             //Too many arguments and no trailing params T[]
1147             var valueCount = indexedValues.Count;
1148             if(valueCount > parameters.Count && paramArrayElementType == null)
1149             {
1150                 return false;
1151             }
1152 
1153             //Grab all arguments that we can treat as positional off the front
1154             values = new List<Token>(valueCount);
1155             for(int i = 0; i < valueCount; ++i)
1156             {
1157                 if(!indexedValues.TryGetValue(i, out var value))
1158                 {
1159                     break;
1160                 }
1161                 indexedValues.Remove(i);
1162                 values.Add(value);
1163             }
1164 
1165             try
1166             {
1167                 int i;
1168                 //Convert all given positional parameters
1169                 for(i = 0; i < values.Count; ++i)
1170                 {
1171                     var paramType = parameters.ElementAtOrDefault(i)?.ParameterType ?? paramArrayElementType;
1172                     if(!TryParseTokenForParamType(values[i], paramType, out var parsed))
1173                     {
1174                         return false;
1175                     }
1176                     result.Add(parsed);
1177                 }
1178                 //If not enough parameters, check for default values and named parameters
1179                 if(i < parameters.Count)
1180                 {
1181                     for(; i < parameters.Count; ++i)
1182                     {
1183                         //See if it was passed as a named parameter
1184                         if(indexedValues.TryGetValue(i, out var value))
1185                         {
1186                             //This can technically be a params T[], but it's not worth handling since
1187                             //it would only be possible to pass one value
1188                             if(!TryParseTokenForParamType(value, parameters[i].ParameterType, out var parsed))
1189                             {
1190                                 return false;
1191                             }
1192                             result.Add(parsed);
1193                         }
1194                         else if(parameters[i].IsOptional)
1195                         {
1196                             result.Add(parameters[i].DefaultValue);
1197                         }
1198                         else
1199                         {
1200                             return false; //non-optional parameter encountered
1201                         }
1202                     }
1203                 }
1204             }
1205             catch(Exception e)
1206             {
1207                 if(e is FormatException || e is RuntimeBinderException || e is OverflowException || e is InvalidCastException)
1208                 {
1209                     return false;
1210                 }
1211                 throw;
1212             }
1213             return true;
1214         }
1215 
FindFieldOrProperty(object node, string name)1216         public object FindFieldOrProperty(object node, string name)
1217         {
1218             var type = node.GetType();
1219             var fields = cache.Get(type, GetAvailableFields);
1220             var properties = cache.Get(type, GetAvailableProperties);
1221             var foundField = fields.FirstOrDefault(x => x.Name == name);
1222             var foundProp = properties.FirstOrDefault(x => x.Name == name);
1223 
1224             if(foundProp?.GetMethod != null)
1225             {
1226                 return InvokeGet(node, foundProp);
1227             }
1228             if(foundField != null)
1229             {
1230                 return InvokeGet(node, foundField);
1231             }
1232 
1233             return null;
1234         }
1235 
ExecuteDeviceAction(string name, object device, IEnumerable<Token> p)1236         public object ExecuteDeviceAction(string name, object device, IEnumerable<Token> p)
1237         {
1238             string commandValue;
1239             var type = device.GetType();
1240             var command = p.FirstOrDefault();
1241             if(command is LiteralToken || command is LeftBraceToken)
1242             {
1243                 commandValue = command.GetObjectValue() as string;
1244             }
1245             else
1246             {
1247                 throw new RecoverableException("Bad syntax");
1248             }
1249 
1250             var methods = cache.Get(type, GetAvailableMethods);
1251             var fields = cache.Get(type, GetAvailableFields);
1252             var properties = cache.Get(type, GetAvailableProperties);
1253             var indexers = cache.Get(type, GetAvailableIndexers).ToList();
1254             var extensions = cache.Get(type, GetAvailableExtensions);
1255 
1256             var foundMethods = methods.Where(x => x.Name == commandValue).ToList();
1257             var foundField = fields.FirstOrDefault(x => x.Name == commandValue);
1258             var foundProp = properties.FirstOrDefault(x => x.Name == commandValue);
1259             var foundExts = extensions.Where(x => x.Name == commandValue).ToList();
1260             var foundIndexers = command is LeftBraceToken && indexers.Any() && indexers.All(x => x.Name == indexers[0].Name) //can use default indexer
1261                                 ? indexers.ToList() : indexers.Where(x => x.Name == commandValue).ToList();
1262 
1263             var parameterArray = p.Skip(command is LeftBraceToken ? 0 : 1).ToArray(); //Don't skip left brace, proper code to do that is below.
1264 
1265             var setValue = parameterArray.FirstOrDefault();
1266 
1267             if(foundMethods.Any())
1268             {
1269                 foreach(var foundMethod in foundMethods.OrderBy(x=>x.GetParameters().Count())
1270                         .ThenBy(y=>y.GetParameters().Count(z=>z.ParameterType==typeof(String))))
1271                 {
1272                     var methodParameters = foundMethod.GetParameters();
1273 
1274                     List<object> parameters;
1275                     if(TryPrepareParameters(parameterArray, methodParameters, out parameters))
1276                     {
1277                         return InvokeMethod(device, foundMethod, parameters);
1278                     }
1279 
1280                 }
1281                 if(!foundExts.Any())
1282                 {
1283                     throw new ParametersMismatchException(type, commandValue, name);
1284                 }
1285             }
1286             if(foundExts.Any())
1287             { //intentionaly no 'else' - extensions may override methods as well
1288                 foreach(var foundExt in foundExts.OrderBy(x=>x.GetParameters().Count())
1289                         .ThenBy(y=>y.GetParameters().Count(z=>z.ParameterType==typeof(String))))
1290                 {
1291                     var extensionParameters = foundExt.GetParameters().Skip(1).ToList();
1292                     List<object> parameters;
1293                     if(TryPrepareParameters(parameterArray, extensionParameters, out parameters))
1294                     {
1295                         return InvokeExtensionMethod(device, foundExt, parameters);
1296                     }
1297                 }
1298                 throw new ParametersMismatchException(type, commandValue, name);
1299 
1300             }
1301             else if(foundField != null)
1302             {
1303                 //if setValue is a LiteralToken then it must contain the next command to process in recursive call
1304                 if(CanTypeBeChained(foundField.FieldType) && setValue != null && setValue is LiteralToken)
1305                 {
1306                     var currentObject = InvokeGet(device, foundField);
1307                     var objectFullName = $"{name} {commandValue}";
1308                     return RecursiveExecuteDeviceAction(objectFullName, currentObject, p, 1);
1309                 }
1310                 else if(setValue != null && !foundField.IsLiteral && !foundField.IsInitOnly)
1311                 {
1312                     object value;
1313                     try
1314                     {
1315                         value = ConvertValue(setValue.GetObjectValue(), foundField.FieldType);
1316                     }
1317                     catch(Exception e)
1318                     {
1319                         if(e is FormatException || e is RuntimeBinderException)
1320                         {
1321                             throw new RecoverableException(e);
1322                         }
1323                         throw;
1324                     }
1325                     InvokeSet(device, foundField, value);
1326                     return null;
1327                 }
1328                 else
1329                 {
1330                     return InvokeGet(device, foundField);
1331                 }
1332             }
1333             else if(foundProp != null)
1334             {
1335                 //if setValue is a LiteralToken then it must contain the next command to process in recursive call
1336                 if(CanTypeBeChained(foundProp.PropertyType) && setValue != null && setValue is LiteralToken)
1337                 {
1338                     var currentObject = InvokeGet(device, foundProp);
1339                     var objectFullName = $"{name} {commandValue}";
1340                     return RecursiveExecuteDeviceAction(objectFullName, currentObject, p, 1);
1341                 }
1342                 else if(setValue != null && foundProp.IsCurrentlySettable(CurrentBindingFlags))
1343                 {
1344                     object value;
1345                     try
1346                     {
1347                         value = ConvertValue(setValue.GetObjectValue(), foundProp.PropertyType);
1348                     }
1349                     catch(Exception e)
1350                     {
1351                         if(e is FormatException || e is RuntimeBinderException)
1352                         {
1353                             throw new RecoverableException(e);
1354                         }
1355                         throw;
1356                     }
1357                     InvokeSet(device, foundProp, value);
1358                     return null;
1359                 }
1360                 else if(foundProp.IsCurrentlyGettable(CurrentBindingFlags))
1361                 {
1362                     return InvokeGet(device, foundProp);
1363                 }
1364                 else
1365                 {
1366                     throw new RecoverableException(String.Format(
1367                         "Could not execute this action on property {0}",
1368                         foundProp.Name
1369                     )
1370                     );
1371                 }
1372             }
1373             else if(foundIndexers.Any())
1374             {
1375                 setValue = null;
1376                 if(parameterArray.Length < 3 || !(parameterArray[0] is LeftBraceToken))
1377                 {
1378                     throw new ParametersMismatchException(type, commandValue, name);
1379                 }
1380                 var index = parameterArray.IndexOf(x => x is RightBraceToken);
1381                 if(index == -1)
1382                 {
1383                     throw new ParametersMismatchException(type, commandValue, name);
1384                 }
1385                 if(index == parameterArray.Length - 2)
1386                 {
1387                     setValue = parameterArray[parameterArray.Length - 1];
1388                 }
1389                 else if(index != parameterArray.Length - 1)
1390                 {
1391                     throw new ParametersMismatchException(type, commandValue, name);
1392                 }
1393                 var getParameters = parameterArray.Skip(1).Take(index - 1).ToArray();
1394                 foreach(var foundIndexer in foundIndexers.OrderBy(x=>x.GetIndexParameters ().Count())
1395                          .ThenByDescending(y=>y.GetIndexParameters().Count(z=>z.ParameterType==typeof(String))))
1396                 {
1397                     List<object> parameters;
1398                     var indexerParameters = foundIndexer.GetIndexParameters();
1399 
1400                     object value;
1401                     if(TryPrepareParameters(getParameters, indexerParameters, out parameters))
1402                     {
1403                         try
1404                         {
1405                             if(setValue != null && foundIndexer.IsCurrentlySettable(CurrentBindingFlags))
1406                             {
1407                                 value = ConvertValue(setValue.GetObjectValue(), foundIndexer.PropertyType);
1408 
1409 
1410                                 InvokeSetIndex(device, foundIndexer, parameters.Concat(new[] { value }).ToList());
1411                                 return null;
1412                             }
1413                             else
1414                             {
1415                                 return InvokeGetIndex(device, foundIndexer, parameters);
1416                             }
1417                         }
1418                         catch(Exception e)
1419                         {
1420                             if(e is FormatException || e is RuntimeBinderException || e is KeyNotFoundException)
1421                             {
1422                                 throw new RecoverableException(e);
1423                             }
1424                             throw;
1425                         }
1426                     }
1427                 }
1428                 throw new ParametersMismatchException(type, commandValue, name);
1429             }
1430             if(command is LiteralToken)
1431             {
1432                 throw new RecoverableException(String.Format("{1} does not provide a field, method or property {0}.", command.GetObjectValue(), name));
1433             }
1434             else
1435             {
1436                 throw new RecoverableException(String.Format("{0} does not provide a default-named indexer.", name));
1437             }
1438         }
1439 
CanTypeBeChained(Type type)1440         private bool CanTypeBeChained(Type type)
1441         {
1442             return !type.IsEnum && !type.IsValueType && type != typeof(string);
1443         }
1444 
RecursiveExecuteDeviceAction(string name, object currentObject, IEnumerable<Token> p, int tokensToSkip)1445         private object RecursiveExecuteDeviceAction(string name, object currentObject, IEnumerable<Token> p, int tokensToSkip)
1446         {
1447             if(currentObject == null)
1448             {
1449                 return null;
1450             }
1451             return ExecuteDeviceAction(name, currentObject, p.Skip(tokensToSkip));
1452         }
1453 
GetAvailableIndexers(Type objectType)1454         IEnumerable<PropertyInfo> GetAvailableIndexers(Type objectType)
1455         {
1456             var properties = new List<PropertyInfo>();
1457             var type = objectType;
1458             while(type != null && type != typeof(object))
1459             {
1460                 properties.AddRange(type.GetProperties(CurrentBindingFlags)
1461                                     .Where(x => x.IsCallableIndexer())
1462                 );
1463                 type = type.BaseType;
1464             }
1465             return properties.DistinctBy(x=> x.ToString()); //Look @ GetAvailableMethods for explanation.
1466         }
1467 
GetAvailableProperties(Type objectType)1468         IEnumerable<PropertyInfo> GetAvailableProperties(Type objectType)
1469         {
1470             var properties = new List<PropertyInfo>();
1471             var type = objectType;
1472             while(type != null && type != typeof(object))
1473             {
1474                 properties.AddRange(type.GetProperties(CurrentBindingFlags)
1475                                     .Where(x => x.IsCallable())
1476                 );
1477                 type = type.BaseType;
1478             }
1479             return properties.DistinctBy(x=> x.ToString()); //Look @ GetAvailableMethods for explanation.
1480         }
1481 
GetAvailableFields(Type objectType)1482         private IEnumerable<FieldInfo> GetAvailableFields(Type objectType)
1483         {
1484             var fields = new List<FieldInfo>();
1485             var type = objectType;
1486             while(type != null && type != typeof(object))
1487             {
1488                 fields.AddRange(type.GetFields(CurrentBindingFlags)
1489                                 .Where(x => x.IsCallable())
1490                 );
1491                 type = type.BaseType;
1492             }
1493             return fields.DistinctBy(x=> x.ToString()); //Look @ GetAvailableMethods for explanation.
1494         }
1495 
GetAvailableMethods(Type objectType)1496         private IEnumerable<MethodInfo> GetAvailableMethods(Type objectType)
1497         {
1498             var methods = new List<MethodInfo>();
1499             var type = objectType;
1500             while(type != null && type != typeof(object))
1501             {
1502                 methods.AddRange(type.GetMethods(CurrentBindingFlags)
1503                                  .Where(x => !(x.IsSpecialName
1504                 && (x.Name.StartsWith("get_", StringComparison.Ordinal) || x.Name.StartsWith("set_", StringComparison.Ordinal)
1505                 || x.Name.StartsWith("add_", StringComparison.Ordinal) || x.Name.StartsWith("remove_", StringComparison.Ordinal)))
1506                 && !x.IsAbstract
1507                 && !x.IsConstructor
1508                 && !x.IsGenericMethod
1509                 && x.IsCallable()
1510                 )
1511                 );
1512                 type = type.BaseType;
1513             }
1514             return methods.DistinctBy(x=> x.ToString()); //This acutally gives us a full, easily comparable signature. Brilliant solution to avoid duplicates from overloaded methods.
1515         }
1516 
1517         private IEnumerable<MethodInfo> GetAvailableExtensions(Type type) => TypeManager.Instance.GetExtensionMethods(type).Where(y => y.IsExtensionCallable()).OrderBy(y => y.Name);
1518 
ClearCache()1519         public void ClearCache()
1520         {
1521             cache.ClearCache();
1522         }
1523 
1524         public BindingFlags CurrentBindingFlags { get; set; }
1525         private readonly SimpleCache cache = new SimpleCache();
1526 
1527         #endregion
1528     }
1529 }
1530 
1531