refactor API and add support for more instructions
[power-instruction-analyzer.git] / src / python.rs
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // See Notices.txt for copyright information
3
4 #![cfg(feature = "python")]
5
6 use crate::{ConditionRegister, Instr, InstructionInput, InstructionResult, OverflowFlags};
7 use pyo3::{prelude::*, wrap_pyfunction, PyObjectProtocol};
8 use std::{borrow::Cow, cell::RefCell, fmt};
9
10 trait ToPythonRepr {
11 fn to_python_repr(&self) -> Cow<str> {
12 struct Helper<T>(RefCell<Option<T>>);
13
14 impl<T: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for Helper<T> {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 self.0.borrow_mut().take().unwrap()(f)
17 }
18 }
19
20 impl<T: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result> Helper<T> {
21 fn new(f: T) -> Self {
22 Helper(RefCell::new(Some(f)))
23 }
24 }
25 Cow::Owned(format!(
26 "{}",
27 Helper::new(|f: &mut fmt::Formatter<'_>| -> fmt::Result { self.write(f) })
28 ))
29 }
30 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.write_str(&self.to_python_repr())
32 }
33 }
34
35 fn write_list_body_to_python_repr<I: IntoIterator<Item = T>, T: ToPythonRepr>(
36 list: I,
37 f: &mut fmt::Formatter<'_>,
38 separator: &str,
39 ) -> fmt::Result {
40 let mut first = true;
41 for i in list {
42 if first {
43 first = false;
44 } else {
45 f.write_str(separator)?;
46 }
47 i.write(f)?;
48 }
49 Ok(())
50 }
51
52 struct NamedArgPythonRepr<'a> {
53 name: &'a str,
54 value: &'a (dyn ToPythonRepr + 'a),
55 }
56
57 impl ToPythonRepr for NamedArgPythonRepr<'_> {
58 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.write_str(self.name)?;
60 f.write_str("=")?;
61 self.value.write(f)
62 }
63 }
64
65 impl<T: ToPythonRepr> ToPythonRepr for &'_ T {
66 fn to_python_repr(&self) -> Cow<str> {
67 (**self).to_python_repr()
68 }
69 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 (**self).write(f)
71 }
72 }
73
74 impl ToPythonRepr for bool {
75 fn to_python_repr(&self) -> Cow<str> {
76 Cow::Borrowed(match self {
77 true => "True",
78 false => "False",
79 })
80 }
81 }
82
83 impl<T: ToPythonRepr> ToPythonRepr for Option<T> {
84 fn to_python_repr(&self) -> Cow<str> {
85 match self {
86 Some(v) => v.to_python_repr(),
87 None => Cow::Borrowed("None"),
88 }
89 }
90 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 Some(v) => v.write(f),
93 None => f.write_str("None"),
94 }
95 }
96 }
97
98 impl<T: ToPythonRepr> ToPythonRepr for Vec<T> {
99 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 f.write_str("[")?;
101 write_list_body_to_python_repr(self, f, ", ")?;
102 f.write_str("]")
103 }
104 }
105
106 macro_rules! impl_int_to_python_repr {
107 ($($int:ident,)*) => {
108 $(
109 impl ToPythonRepr for $int {
110 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}", self)
112 }
113 }
114 )*
115 };
116 }
117
118 impl_int_to_python_repr! {u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,}
119
120 macro_rules! wrap_type {
121 (
122 #[pymodule($m:expr)]
123 // use tt to work around PyO3 bug fixed in PyO3#832
124 #[pyclass $($pyclass_args:tt)?]
125 #[wrapped($value:ident: $wrapped:ident)]
126 #[args $new_args:tt]
127 $(#[$meta:meta])*
128 struct $wrapper:ident {
129 $(
130 #[set=$setter_name:ident]
131 $(#[$field_meta:meta])*
132 $field_name:ident:$field_type:ty,
133 )*
134 }
135 ) => {
136 #[pyclass $($pyclass_args)?]
137 $(#[$meta])*
138 #[derive(Clone)]
139 struct $wrapper {
140 $value: $wrapped,
141 }
142
143 impl<'source> FromPyObject<'source> for $wrapped {
144 fn extract(ob: &'source PyAny) -> PyResult<Self> {
145 Ok(ob.extract::<$wrapper>()?.$value)
146 }
147 }
148
149 impl IntoPy<PyObject> for $wrapped {
150 fn into_py(self, py: Python) -> PyObject {
151 $wrapper { $value: self }.into_py(py)
152 }
153 }
154
155 #[pymethods]
156 impl $wrapper {
157 #[new]
158 #[args $new_args]
159 fn new($($field_name:$field_type),*) -> Self {
160 Self {
161 $value: $wrapped {
162 $($field_name),*
163 }
164 }
165 }
166 $(
167 #[getter]
168 $(#[$field_meta:meta])*
169 fn $field_name(&self) -> $field_type {
170 self.$value.$field_name
171 }
172 #[setter]
173 fn $setter_name(&mut self, $field_name: $field_type) {
174 self.$value.$field_name = $field_name;
175 }
176 )*
177 }
178
179 impl ToPythonRepr for $wrapped {
180 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 f.write_str(concat!(stringify!($wrapped), "("))?;
182 write_list_body_to_python_repr(&[
183 $(
184 NamedArgPythonRepr {
185 name: stringify!($field_name),
186 value: &self.$field_name,
187 },
188 )*
189 ], f, ", ")?;
190 f.write_str(")")
191 }
192 }
193
194 #[pyproto]
195 impl PyObjectProtocol for $wrapper {
196 fn __str__(&self) -> String {
197 serde_json::to_string(&self.$value).unwrap()
198 }
199 fn __repr__(&self) -> String {
200 self.$value.to_python_repr().into_owned()
201 }
202 }
203
204 $m.add_class::<$wrapper>()?;
205 };
206 }
207
208 macro_rules! wrap_instr_fns {
209 (
210 #![pymodule($m:ident)]
211 $(
212 // use tt to work around PyO3 bug fixed in PyO3#832
213 $(#[pyfunction $pyfunction_args:tt])?
214 $(#[$meta:meta])*
215 fn $name:ident(inputs: $inputs:ty) -> $result:ty;
216 )*
217 ) => {
218 $(
219 {
220 #[pyfunction $($pyfunction_args)?]
221 #[text_signature = "(inputs)"]
222 $(#[$meta])*
223 fn $name(inputs: $inputs) -> $result {
224 $crate::instr_models::$name(inputs)
225 }
226
227 $m.add_wrapped(wrap_pyfunction!($name))?;
228 }
229 )*
230 };
231 }
232
233 #[pymodule]
234 fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> {
235 wrap_type! {
236 #[pymodule(m)]
237 #[pyclass(name = OverflowFlags)]
238 #[wrapped(value: OverflowFlags)]
239 #[args(so, ov, ov32)]
240 #[text_signature = "(so, ov, ov32)"]
241 struct PyOverflowFlags {
242 #[set = set_so]
243 so: bool,
244 #[set = set_ov]
245 ov: bool,
246 #[set = set_ov32]
247 ov32: bool,
248 }
249 }
250
251 wrap_type! {
252 #[pymodule(m)]
253 #[pyclass(name = ConditionRegister)]
254 #[wrapped(value: ConditionRegister)]
255 #[args(lt, gt, eq, so)]
256 #[text_signature = "(lt, gt, eq, so)"]
257 struct PyConditionRegister {
258 #[set = set_lt]
259 lt: bool,
260 #[set = set_gt]
261 gt: bool,
262 #[set = set_eq]
263 eq: bool,
264 #[set = set_so]
265 so: bool,
266 }
267 }
268
269 wrap_type! {
270 #[pymodule(m)]
271 #[pyclass(name = InstructionInput)]
272 #[wrapped(value: InstructionInput)]
273 #[args(ra, rb, rc)]
274 #[text_signature = "(ra, rb, rc)"]
275 struct PyInstructionInput {
276 #[set = set_ra]
277 ra: u64,
278 #[set = set_rb]
279 rb: u64,
280 #[set = set_rc]
281 rc: u64,
282 }
283 }
284
285 wrap_type! {
286 #[pymodule(m)]
287 #[pyclass(name = InstructionResult)]
288 #[wrapped(value: InstructionResult)]
289 #[args(
290 rt="None",
291 overflow="None",
292 cr0="None",
293 cr1="None",
294 cr2="None",
295 cr3="None",
296 cr4="None",
297 cr5="None",
298 cr6="None",
299 cr7="None"
300 )]
301 #[text_signature = "(\
302 rt=None, \
303 overflow=None, \
304 cr0=None, \
305 cr1=None, \
306 cr2=None, \
307 cr3=None, \
308 cr4=None, \
309 cr5=None, \
310 cr6=None, \
311 cr7=None)"
312 ]
313 struct PyInstructionResult {
314 #[set = set_rt]
315 rt: Option<u64>,
316 #[set = set_overflow]
317 overflow: Option<OverflowFlags>,
318 #[set = set_cr0]
319 cr0: Option<ConditionRegister>,
320 #[set = set_cr1]
321 cr1: Option<ConditionRegister>,
322 #[set = set_cr2]
323 cr2: Option<ConditionRegister>,
324 #[set = set_cr3]
325 cr3: Option<ConditionRegister>,
326 #[set = set_cr4]
327 cr4: Option<ConditionRegister>,
328 #[set = set_cr5]
329 cr5: Option<ConditionRegister>,
330 #[set = set_cr6]
331 cr6: Option<ConditionRegister>,
332 #[set = set_cr7]
333 cr7: Option<ConditionRegister>,
334 }
335 }
336
337 m.setattr(
338 "INSTRS",
339 Instr::VALUES
340 .iter()
341 .map(|&instr| instr.name())
342 .collect::<Vec<_>>(),
343 )?;
344
345 wrap_all_instr_fns!(m);
346 Ok(())
347 }