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