// // Copyright (c) 2010-2023 Antmicro // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Antmicro.Renode.Utilities; namespace Antmicro.Renode.RobotFramework { internal class Keyword { public Keyword(KeywordManager manager, MethodInfo info) { this.manager = manager; methodInfo = info; } public bool TryMatchArguments(object[] arguments, out object[] parsedArguments) { var parameters = methodInfo.GetParameters(); if(parameters.Length == 1 && parameters[0].ParameterType == typeof(string[]) && arguments.All(a => a is string)) { parsedArguments = new object[] { arguments.Select(a => (string)a).ToArray() }; return true; } return TryParseArguments(parameters, arguments, out parsedArguments); } public object Execute(object[] arguments) { var obj = manager.GetOrCreateObject(methodInfo.DeclaringType); return methodInfo.Invoke(obj, arguments); } public int NumberOfArguments { get { return methodInfo.GetParameters().Length; } } public Replay ReplayMode { get { var attr = methodInfo.GetCustomAttributes().Single(); return attr.ReplayMode; } } private object ChangeType(object input, Type type) { var underlyingType = Nullable.GetUnderlyingType(type); if(underlyingType != null && input != null) { type = underlyingType; } return Convert.ChangeType(input, type); } private bool TryParseArguments(ParameterInfo[] parameters, object[] arguments, out object[] parsedArguments) { parsedArguments = null; if(arguments.Length > parameters.Length) { return false; } var args = new ArgumentDescriptor[parameters.Length]; var positionalArgumentIndex = 0; var namedArgumentDetected = false; var pattern = new Regex(@"^([a-zA-Z0-9_]+)=(.+)"); foreach(var argumentObj in arguments) { int position; if(!(argumentObj is string)) { // Non-string arguments can only be positional position = positionalArgumentIndex++; args[position].IsParsed = true; // Allow type conversions of non-string arguments to allow calling methods that // take a float with a Python float which becomes a double on the C# side args[position].Value = ChangeType(argumentObj, parameters[position].ParameterType); continue; } var argument = (string)argumentObj; object result; string valueToParse; // check if it's a named argument var m = pattern.Match(argument); if(m.Success) { namedArgumentDetected = true; var name = m.Groups[1].Value; var param = parameters.SingleOrDefault(x => x.Name == name); if(param == null) { return false; } if(args[param.Position].IsParsed) { throw new ArgumentException("Named argument `{0}' specified multiple times", name); } position = param.Position; valueToParse = m.Groups[2].Value; } else { if(namedArgumentDetected) { // this is a serious error throw new ArgumentException("Named arguments must appear after the positional arguments"); } position = positionalArgumentIndex++; valueToParse = argument; } if(!SmartParser.Instance.TryParse(valueToParse, parameters[position].ParameterType, out result)) { return false; } args[position].IsParsed = true; args[position].Value = result; } for(var i = 0; i < args.Length; i++) { if(args[i].IsParsed) { continue; } if(!parameters[i].HasDefaultValue) { return false; } args[i].IsParsed = true; args[i].Value = parameters[i].DefaultValue; } parsedArguments = args.Select(x => x.Value).ToArray(); return true; } private readonly MethodInfo methodInfo; private readonly KeywordManager manager; private struct ArgumentDescriptor { public bool IsParsed; public object Value; } } }