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 Microsoft.Scripting; 9 using Microsoft.Scripting.Hosting; 10 using IronPython.Hosting; 11 using IronPython.Modules; 12 using Antmicro.Migrant.Hooks; 13 using Antmicro.Renode.Exceptions; 14 using System.Collections.Generic; 15 using System.Linq; 16 using IronPython.Runtime; 17 using System; 18 using Antmicro.Migrant; 19 20 namespace Antmicro.Renode.Core 21 { 22 public abstract class PythonEngine 23 { 24 #region Python Engine 25 26 private static readonly ScriptEngine _Engine = Python.CreateEngine(); 27 protected ScriptEngine Engine { get { return PythonEngine._Engine; } } 28 29 #endregion 30 31 [Transient] 32 protected ScriptScope Scope; 33 34 private readonly string[] Imports = 35 { 36 "import clr", 37 "clr.AddReference('Infrastructure')", 38 "clr.AddReference('Renode')", 39 #if NET 40 "clr.AddReference('System.Console')", // It was moved to separate assembly on .NET Core. 41 #else 42 "clr.AddReference('IronPython.StdLib')", // It is referenced by default on NET Core, but not on mono. 43 #endif 44 "import Antmicro.Renode", 45 "import System", 46 "import time", 47 "import sys", 48 "import Antmicro.Renode.Logging.Logger", 49 "clr.ImportExtensions(Antmicro.Renode.Logging.Logger)", 50 "clr.ImportExtensions(Antmicro.Renode.Peripherals.IPeripheralExtensions)", 51 "import Antmicro.Renode.Peripherals.CPU.ICPUWithRegistersExtensions", 52 "clr.ImportExtensions(Antmicro.Renode.Peripherals.CPU.ICPUWithRegistersExtensions)", 53 "import Antmicro.Renode.Core.Extensions.FileLoaderExtensions", 54 "clr.ImportExtensions(Antmicro.Renode.Core.Extensions.FileLoaderExtensions)", 55 "import Antmicro.Renode.Logging.LogLevel as LogLevel", 56 "clr.ImportExtensions(Antmicro.Renode.Peripherals.Bus.BusControllerExtensions)", 57 }; 58 59 protected virtual string[] ReservedVariables 60 { 61 get 62 { 63 return new [] 64 { 65 "__doc__", 66 "__builtins__", 67 "Antmicro", 68 "System", 69 "cpu", 70 "clr", 71 "sysbus", 72 "time", 73 "__file__", 74 "__name__", 75 "sys", 76 "LogLevel", 77 "emulationManager", 78 "pythonEngine", 79 }; 80 } 81 } 82 PythonEngine()83 protected PythonEngine() 84 { 85 InnerInit(); 86 } 87 InnerInit()88 private void InnerInit() 89 { 90 Scope = Engine.CreateScope(); 91 Scope.SetVariable("emulationManager", EmulationManager.Instance); 92 Scope.SetVariable("pythonEngine", Engine); 93 // `Monitor` is located in a different project, so we cannot reference the class directly 94 var monitor = ObjectCreator.Instance.GetSurrogate(MonitorTypeName); 95 if(monitor != null) 96 { 97 Scope.SetVariable("monitor", monitor); 98 } 99 PythonTime.localtime(); 100 101 var imports = Engine.CreateScriptSourceFromString(Aggregate(Imports)); 102 imports.Execute(Scope); 103 } 104 Init()105 protected virtual void Init() 106 { 107 InnerInit(); 108 } 109 110 #region Serialization 111 112 [PreSerialization] BeforeSerialization()113 private void BeforeSerialization() 114 { 115 var variablesToSerialize = Scope.GetVariableNames().Except(ReservedVariables); 116 variables = new Dictionary<string, object>(); 117 foreach(var variable in variablesToSerialize) 118 { 119 var value = Scope.GetVariable<object>(variable); 120 if(value.GetType() == typeof(PythonModule)) 121 { 122 continue; 123 } 124 if(value.GetType().FullName == MonitorTypeName) 125 { 126 continue; 127 } 128 variables[variable] = value; 129 } 130 } 131 132 [PostSerialization] AfterSerialization()133 private void AfterSerialization() 134 { 135 variables = null; 136 } 137 138 [PostDeserialization] AfterDeserialization()139 private void AfterDeserialization() 140 { 141 Init(); 142 foreach(var variable in variables) 143 { 144 Scope.SetVariable(variable.Key, variable.Value); 145 } 146 variables = null; 147 } 148 149 private Dictionary<string, object> variables; 150 151 #endregion 152 153 #region Helper methods 154 Aggregate(string[] array)155 protected static string Aggregate(string[] array) 156 { 157 return array.Aggregate((prev, curr) => string.Format("{0}{1}{2}", prev, Environment.NewLine, curr)); 158 } 159 Compile(ScriptSource source)160 protected CompiledCode Compile(ScriptSource source) 161 { 162 return Compile(source, error => throw new RecoverableException(error)); 163 } 164 Compile(ScriptSource source, Action<string> errorCallback)165 protected CompiledCode Compile(ScriptSource source, Action<string> errorCallback) 166 { 167 return source.Compile(new ErrorHandler(errorCallback)); 168 } 169 Execute(CompiledCode code)170 protected void Execute(CompiledCode code) 171 { 172 Execute(code, error => throw new RecoverableException($"Python runtime error: {error}")); 173 } 174 Execute(CompiledCode code, Action<string> errorCallback)175 protected void Execute(CompiledCode code, Action<string> errorCallback) 176 { 177 try 178 { 179 // code can be null when compiled SourceScript had syntax errors 180 code?.Execute(Scope); 181 } 182 catch(Exception e) 183 { 184 if(e is UnboundNameException || e is MissingMemberException || e is ArithmeticException) 185 { 186 errorCallback?.Invoke(e.Message); 187 } 188 else 189 { 190 throw; 191 } 192 } 193 } 194 195 #endregion 196 197 public class ErrorHandler : ErrorListener 198 { ErrorHandler(Action<string> errorCallback)199 public ErrorHandler(Action<string> errorCallback) 200 { 201 this.errorCallback = errorCallback; 202 } 203 ErrorReported(ScriptSource source, string message, SourceSpan span, int errorCode, Severity severity)204 public override void ErrorReported(ScriptSource source, string message, SourceSpan span, int errorCode, Severity severity) 205 { 206 errorCallback?.Invoke($"[{severity}] {message} (Line {span.Start.Line}, Column {span.Start.Column})"); 207 } 208 209 private readonly Action<string> errorCallback; 210 } 211 212 private const string MonitorTypeName = "Antmicro.Renode.UserInterface.Monitor"; 213 } 214 } 215 216