1 //
2 // Copyright (c) 2010-2025 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 System.IO;
11 using System.Linq;
12 using System.Text;
13 using Antmicro.Migrant;
14 using Antmicro.Migrant.Hooks;
15 using AntShell.Commands;
16 using IronPython.Runtime;
17 using IronPython.Runtime.Operations;
18 using Microsoft.Scripting.Hosting;
19 using Antmicro.Renode.Core;
20 using Antmicro.Renode.Exceptions;
21 using Antmicro.Renode.UserInterface.Tokenizer;
22 using Antmicro.Renode.Utilities;
23 
24 namespace Antmicro.Renode.UserInterface
25 {
26     public class MonitorPythonEngine : PythonEngine, IDisposable
27     {
28         private readonly string[] Imports =
29         {
30             "clr.AddReference('Infrastructure')",
31         };
32 
MonitorPythonEngine(Monitor monitor)33         public MonitorPythonEngine(Monitor monitor)
34         {
35             this.monitor = monitor;
36             var rootPath = Misc.GetRootDirectory();
37 
38             var imports = Engine.CreateScriptSourceFromString(Aggregate(Imports));
39             imports.Execute(Scope);
40 
41             var monitorPath = Path.Combine(rootPath, MonitorPyPath);
42             if(File.Exists(monitorPath))
43             {
44                 var script = Engine.CreateScriptSourceFromFile(monitorPath); // standard lib
45                 script.Compile().Execute(Scope);
46                 Logging.Logger.Log(Logging.LogLevel.Info, "Loaded monitor commands from: {0}", monitorPath);
47             }
48 
49             Scope.SetVariable("self", monitor);
50             Scope.SetVariable("monitor", monitor);
51         }
52 
Dispose()53         public void Dispose()
54         {
55             if(streamToEventConverter != null && streamToEventConverterForError != null)
56             {
57                 streamToEventConverter.IgnoreWrites = true;
58                 streamToEventConverterForError.IgnoreWrites = true;
59             }
60         }
61 
62         [PreSerialization]
BeforeSerialization()63         protected void BeforeSerialization()
64         {
65             throw new NotSupportedException("MonitorPythonEngine should not be serialized!");
66         }
67 
ExecuteBuiltinCommand(Token[] command, ICommandInteraction writer)68         public bool ExecuteBuiltinCommand(Token[] command, ICommandInteraction writer)
69         {
70             var command_name = ((LiteralToken)command[0]).Value;
71             if(!Scope.ContainsVariable("mc_" + command_name))
72             {
73                 return false;
74             }
75 
76             object comm = Scope.GetVariable("mc_" + command_name); // get a method
77             var arguments = command.Skip(1);
78             var argumentsLength = command.Length - 1;
79 
80             var firstEqualIndex = arguments.IndexOf(t => t is EqualityToken);
81             var parametersLength = firstEqualIndex == -1 ? argumentsLength : firstEqualIndex - 1;
82 
83             if(arguments.Any() && arguments.First() is EqualityToken firstArgument)
84             {
85                 throw new RecoverableException($"Invalid argument {firstArgument} at position 1");
86             }
87 
88             var parameters = arguments.Take(parametersLength).Select(GetTokenValue).ToArray();
89             var keywordArguments = arguments.Skip(parametersLength).Split(3).Select((kwarg, idx) =>
90             {
91                 if(kwarg.Length != 3 || !(kwarg[0] is LiteralToken) || !(kwarg[1] is EqualityToken))
92                 {
93                     var argument = string.Join<object>("", kwarg);
94                     throw new RecoverableException($"Invalid keyword argument {argument} at position {parametersLength + 1 + idx}");
95                 }
96 
97                 var key = kwarg[0].GetObjectValue();
98                 var value = GetTokenValue(kwarg[2]);
99                 return new KeyValuePair<object, object>(key, value);
100             }).ToDictionary(kv => kv.Key, kv => kv.Value);
101 
102             ConfigureOutput(writer);
103 
104             try
105             {
106                 var result = PythonCalls.CallWithKeywordArgs(DefaultContext.Default, comm, parameters, keywordArguments);
107                 if(result != null && (!(result is bool) || !(bool)result))
108                 {
109                     writer.WriteError(String.Format("Command {0} failed, returning \"{1}\".", command_name, result));
110                 }
111             }
112             catch(Exception e)
113             {
114                 throw new RecoverableException($"Command '{command_name} {String.Join(" ", parameters)}' failed", e);
115             }
116             return true;
117         }
118 
TryExecutePythonScript(ReadFilePath fileName, ICommandInteraction writer)119         public bool TryExecutePythonScript(ReadFilePath fileName, ICommandInteraction writer)
120         {
121             var script = Engine.CreateScriptSourceFromFile(fileName);
122             ExecutePythonScriptInner(script, writer);
123             return true;
124         }
125 
ExecutePythonCommand(string command, ICommandInteraction writer)126         public object ExecutePythonCommand(string command, ICommandInteraction writer)
127         {
128             try
129             {
130                 var script = Engine.CreateScriptSourceFromString(command);
131                 return ExecutePythonScriptInner(script, writer);
132             }
133             catch(Microsoft.Scripting.SyntaxErrorException e)
134             {
135                 throw new RecoverableException(String.Format("Line : {0}\n{1}", e.Line, e.Message));
136             }
137         }
138 
GetTokenValue(Token token)139         private object GetTokenValue(Token token)
140         {
141             var value = token.GetObjectValue();
142             if(token is LiteralToken)
143             {
144                 if(EmulationManager.Instance.CurrentEmulation.TryGetEmulationElementByName(value as string, monitor.Machine, out var emulationElement))
145                 {
146                     return emulationElement;
147                 }
148                 throw new RecoverableException($"No such emulation element: {value}");
149             }
150             return value;
151         }
152 
ExecutePythonScriptInner(ScriptSource script, ICommandInteraction writer)153         private object ExecutePythonScriptInner(ScriptSource script, ICommandInteraction writer)
154         {
155             ConfigureOutput(writer);
156             try
157             {
158                 return script.Execute(Scope);
159             }
160             catch(Exception e)
161             {
162                 throw new RecoverableException(e);
163             }
164         }
165 
GetPythonCommands(string prefix = R, bool trimPrefix = true)166         public string[] GetPythonCommands(string prefix = "mc_", bool trimPrefix = true)
167         {
168             return Scope.GetVariableNames().Where(x => x.StartsWith(prefix ?? string.Empty, StringComparison.Ordinal)).Select(x => x.Substring(trimPrefix ? prefix.Length : 0)).ToArray();
169         }
170 
ConfigureOutput(ICommandInteraction writer)171         private void ConfigureOutput(ICommandInteraction writer)
172         {
173             streamToEventConverter = new StreamToEventConverter();
174             streamToEventConverterForError = new StreamToEventConverter();
175             var utf8WithoutBom = new UTF8Encoding(false);
176 
177             var inputStream = writer.GetRawInputStream();
178             if(inputStream != null)
179             {
180                 Engine.Runtime.IO.SetInput(inputStream, utf8WithoutBom);
181             }
182             Engine.Runtime.IO.SetOutput(streamToEventConverter, utf8WithoutBom);
183             Engine.Runtime.IO.SetErrorOutput(streamToEventConverterForError, utf8WithoutBom);
184             streamToEventConverter.BytesWritten += bytes => writer.Write(utf8WithoutBom.GetString(bytes).Replace("\n", "\r\n"));
185             streamToEventConverterForError.BytesWritten += bytes => writer.WriteError(utf8WithoutBom.GetString(bytes).Replace("\n", "\r\n"));
186         }
187 
188         private StreamToEventConverter streamToEventConverter;
189         private StreamToEventConverter streamToEventConverterForError;
190         private readonly Monitor monitor;
191         private const string MonitorPyPath = "scripts/monitor.py";
192     }
193 }
194