python_ast/
macros.rs

1/// Macros for reducing code duplication in the python-ast library.
2
3/// Macro for generating FromPyObject implementations for operator enums.
4/// This reduces the boilerplate for extracting Python operator objects.
5#[macro_export]
6macro_rules! impl_from_py_object_for_op_enum {
7    ($enum_name:ident, $error_msg:literal) => {
8        impl<'a> FromPyObject<'a> for $enum_name {
9            fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
10                let err_msg = format!($error_msg, dump(ob, None)?);
11                Err(pyo3::exceptions::PyValueError::new_err(
12                    ob.error_message("<unknown>", err_msg),
13                ))
14            }
15        }
16    };
17}
18
19/// Macro for generating standard CodeGen trait implementations.
20/// This reduces boilerplate for the common pattern of CodeGen implementations.
21#[macro_export]
22macro_rules! impl_standard_codegen {
23    ($type_name:ident) => {
24        impl CodeGen for $type_name {
25            type Context = CodeGenContext;
26            type Options = PythonOptions;
27            type SymbolTable = SymbolTableScopes;
28
29            fn to_rust(
30                self,
31                ctx: Self::Context,
32                options: Self::Options,
33                symbols: Self::SymbolTable,
34            ) -> Result<proc_macro2::TokenStream, Box<dyn std::error::Error>> {
35                self.generate_rust_code(ctx, options, symbols)
36            }
37        }
38    };
39}
40
41/// Macro for implementing CodeGen with custom code generation logic.
42#[macro_export]
43macro_rules! impl_codegen_with_custom {
44    ($type_name:ident, $generate_fn:expr) => {
45        impl CodeGen for $type_name {
46            type Context = CodeGenContext;
47            type Options = PythonOptions;
48            type SymbolTable = SymbolTableScopes;
49
50            fn to_rust(
51                self,
52                ctx: Self::Context,
53                options: Self::Options,
54                symbols: Self::SymbolTable,
55            ) -> Result<proc_macro2::TokenStream, Box<dyn std::error::Error>> {
56                $generate_fn(self, ctx, options, symbols)
57            }
58        }
59    };
60}
61
62/// Macro for extracting PyAny attributes with consistent error handling.
63#[macro_export]
64macro_rules! extract_py_attr {
65    ($obj:expr, $attr:literal, $error_context:literal) => {
66        $obj.getattr($attr).expect(
67            $obj.error_message("<unknown>", concat!("error getting ", $error_context))
68                .as_str(),
69        )
70    };
71}
72
73/// Macro for extracting PyAny type names with error handling.
74#[macro_export]
75macro_rules! extract_py_type_name {
76    ($obj:expr, $context:literal) => {
77        $obj.get_type().name().expect(
78            $obj.error_message(
79                "<unknown>",
80                format!("extracting type name for {}", $context),
81            )
82            .as_str(),
83        )
84    };
85}
86
87/// Macro for generating binary operator FromPyObject implementations.
88/// This handles the common pattern of extracting left, right, and op from Python binary operators.
89#[macro_export]
90macro_rules! impl_binary_op_from_py {
91    ($struct_name:ident, $enum_name:ident, $op_variants:tt) => {
92        impl<'a> FromPyObject<'a> for $struct_name {
93            fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
94                log::debug!("ob: {}", dump(ob, None)?);
95                
96                let op = extract_py_attr!(ob, "op", "operator");
97                let op_type = extract_py_type_name!(op, "binary operator")?;
98                
99                let left = extract_py_attr!(ob, "left", "binary operand");
100                let right = extract_py_attr!(ob, "right", "binary operand");
101                
102                log::debug!("left: {}, right: {}", dump(&left, None)?, dump(&right, None)?);
103
104                let op_type_str: String = op_type.extract()?;
105                let op = match op_type_str.as_ref() {
106                    $op_variants,
107                    _ => {
108                        log::debug!("Found unknown {} {:?}", stringify!($enum_name), op);
109                        $enum_name::Unknown
110                    }
111                };
112
113                let left = left.extract().expect("getting binary operator operand");
114                let right = right.extract().expect("getting binary operator operand");
115
116                Ok($struct_name {
117                    op,
118                    left: Box::new(left),
119                    right: Box::new(right),
120                })
121            }
122        }
123    };
124}
125
126/// Macro for generating test functions for AST parsing.
127/// Reduces duplication in test code.
128#[macro_export]
129macro_rules! create_parse_test {
130    ($test_name:ident, $code:literal, $file_name:literal) => {
131        #[test]
132        fn $test_name() {
133            let options = PythonOptions::default();
134            let result = crate::parse($code, $file_name).unwrap();
135            log::info!("Python tree: {:?}", result);
136
137            let code = result.to_rust(
138                CodeGenContext::Module($file_name.replace(".py", "").to_string()),
139                options,
140                SymbolTableScopes::new(),
141            );
142            log::info!("Generated code: {:?}", code);
143        }
144    };
145}
146
147/// Macro for generating Node trait implementations with optional position fields.
148/// This macro automatically implements the Node trait for types that have position fields.
149#[macro_export]
150macro_rules! impl_node_with_positions {
151    ($type_name:ident { $($field:ident),* }) => {
152        impl $crate::Node for $type_name {
153            fn lineno(&self) -> Option<usize> {
154                $(
155                    if stringify!($field) == "lineno" {
156                        return self.$field;
157                    }
158                )*
159                None
160            }
161
162            fn col_offset(&self) -> Option<usize> {
163                $(
164                    if stringify!($field) == "col_offset" {
165                        return self.$field;
166                    }
167                )*
168                None
169            }
170
171            fn end_lineno(&self) -> Option<usize> {
172                $(
173                    if stringify!($field) == "end_lineno" {
174                        return self.$field;
175                    }
176                )*
177                None
178            }
179
180            fn end_col_offset(&self) -> Option<usize> {
181                $(
182                    if stringify!($field) == "end_col_offset" {
183                        return self.$field;
184                    }
185                )*
186                None
187            }
188        }
189    };
190    
191    // Variant for types without position fields
192    ($type_name:ident) => {
193        impl $crate::Node for $type_name {
194            // All methods return None (default implementation)
195        }
196    };
197}
198
199/// Macro for generating PyAny attribute extraction with error context.
200#[macro_export]
201macro_rules! extract_with_context {
202    ($obj:expr, $attr:literal) => {
203        $obj.getattr($attr).map_err(|e| {
204            pyo3::exceptions::PyAttributeError::new_err(format!(
205                "Failed to extract '{}': {}",
206                $attr, e
207            ))
208        })?
209    };
210}
211
212/// Generates repetitive match arms for operator conversions.
213#[macro_export]
214macro_rules! operator_match_arms {
215    ($($variant:ident => $string:literal),* $(,)?) => {
216        $(
217            $string => Self::$variant,
218        )*
219    };
220}
221
222/// Macro for generating symbol table tests with consistent patterns.
223#[macro_export]
224macro_rules! symbol_table_test {
225    ($test_name:ident, $setup:block, $assertion:block) => {
226        #[test]
227        fn $test_name() {
228            $setup
229            $assertion
230        }
231    };
232}