1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using System.Reflection; 11 using System.Text.RegularExpressions; 12 using Antmicro.Renode.Utilities; 13 14 namespace Antmicro.Renode.RobotFramework 15 { 16 internal class Keyword 17 { Keyword(KeywordManager manager, MethodInfo info)18 public Keyword(KeywordManager manager, MethodInfo info) 19 { 20 this.manager = manager; 21 methodInfo = info; 22 } 23 TryMatchArguments(object[] arguments, out object[] parsedArguments)24 public bool TryMatchArguments(object[] arguments, out object[] parsedArguments) 25 { 26 var parameters = methodInfo.GetParameters(); 27 28 if(parameters.Length == 1 && parameters[0].ParameterType == typeof(string[]) 29 && arguments.All(a => a is string)) 30 { 31 parsedArguments = new object[] { arguments.Select(a => (string)a).ToArray() }; 32 return true; 33 } 34 35 return TryParseArguments(parameters, arguments, out parsedArguments); 36 } 37 Execute(object[] arguments)38 public object Execute(object[] arguments) 39 { 40 var obj = manager.GetOrCreateObject(methodInfo.DeclaringType); 41 return methodInfo.Invoke(obj, arguments); 42 } 43 44 public int NumberOfArguments 45 { 46 get 47 { 48 return methodInfo.GetParameters().Length; 49 } 50 } 51 52 public Replay ReplayMode 53 { 54 get 55 { 56 var attr = methodInfo.GetCustomAttributes<RobotFrameworkKeywordAttribute>().Single(); 57 return attr.ReplayMode; 58 } 59 } 60 ChangeType(object input, Type type)61 private object ChangeType(object input, Type type) 62 { 63 var underlyingType = Nullable.GetUnderlyingType(type); 64 if(underlyingType != null && input != null) 65 { 66 type = underlyingType; 67 } 68 return Convert.ChangeType(input, type); 69 } 70 TryParseArguments(ParameterInfo[] parameters, object[] arguments, out object[] parsedArguments)71 private bool TryParseArguments(ParameterInfo[] parameters, object[] arguments, out object[] parsedArguments) 72 { 73 parsedArguments = null; 74 if(arguments.Length > parameters.Length) 75 { 76 return false; 77 } 78 79 var args = new ArgumentDescriptor[parameters.Length]; 80 81 var positionalArgumentIndex = 0; 82 var namedArgumentDetected = false; 83 var pattern = new Regex(@"^([a-zA-Z0-9_]+)=(.+)"); 84 foreach(var argumentObj in arguments) 85 { 86 int position; 87 88 if(!(argumentObj is string)) 89 { 90 // Non-string arguments can only be positional 91 position = positionalArgumentIndex++; 92 args[position].IsParsed = true; 93 // Allow type conversions of non-string arguments to allow calling methods that 94 // take a float with a Python float which becomes a double on the C# side 95 args[position].Value = ChangeType(argumentObj, parameters[position].ParameterType); 96 continue; 97 } 98 99 var argument = (string)argumentObj; 100 object result; 101 string valueToParse; 102 // check if it's a named argument 103 var m = pattern.Match(argument); 104 if(m.Success) 105 { 106 namedArgumentDetected = true; 107 var name = m.Groups[1].Value; 108 var param = parameters.SingleOrDefault(x => x.Name == name); 109 110 if(param == null) 111 { 112 return false; 113 } 114 115 if(args[param.Position].IsParsed) 116 { 117 throw new ArgumentException("Named argument `{0}' specified multiple times", name); 118 } 119 120 position = param.Position; 121 valueToParse = m.Groups[2].Value; 122 } 123 else 124 { 125 if(namedArgumentDetected) 126 { 127 // this is a serious error 128 throw new ArgumentException("Named arguments must appear after the positional arguments"); 129 } 130 131 position = positionalArgumentIndex++; 132 valueToParse = argument; 133 } 134 135 if(!SmartParser.Instance.TryParse(valueToParse, parameters[position].ParameterType, out result)) 136 { 137 return false; 138 } 139 140 args[position].IsParsed = true; 141 args[position].Value = result; 142 } 143 144 for(var i = 0; i < args.Length; i++) 145 { 146 if(args[i].IsParsed) 147 { 148 continue; 149 } 150 151 if(!parameters[i].HasDefaultValue) 152 { 153 return false; 154 } 155 156 args[i].IsParsed = true; 157 args[i].Value = parameters[i].DefaultValue; 158 } 159 160 parsedArguments = args.Select(x => x.Value).ToArray(); 161 return true; 162 } 163 164 private readonly MethodInfo methodInfo; 165 private readonly KeywordManager manager; 166 167 private struct ArgumentDescriptor 168 { 169 public bool IsParsed; 170 public object Value; 171 } 172 } 173 } 174 175