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