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