python_ast/ast/tree/
bool_ops.rs

1use proc_macro2::TokenStream;
2use pyo3::{Bound, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods, types::PyTypeMethods};
3use quote::quote;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    dump, CodeGen, CodeGenContext, Error, ExprType, Node, PythonOptions, SymbolTableScopes,
8};
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
11pub enum BoolOps {
12    And,
13    Or,
14    Unknown,
15}
16
17impl<'a> FromPyObject<'a> for BoolOps {
18    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
19        let err_msg = format!("Unimplemented unary op {}", dump(ob, None)?);
20        Err(pyo3::exceptions::PyValueError::new_err(
21            ob.error_message("<unknown>", err_msg),
22        ))
23    }
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
27pub struct BoolOp {
28    op: BoolOps,
29    left: Box<ExprType>,
30    right: Box<ExprType>,
31}
32
33impl<'a> FromPyObject<'a> for BoolOp {
34    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
35        log::debug!("ob: {}", dump(ob, None)?);
36        let op = ob.getattr("op").expect(
37            ob.error_message("<unknown>", "error getting unary operator")
38                .as_str(),
39        );
40
41        let op_type = op.get_type().name().expect(
42            ob.error_message(
43                "<unknown>",
44                format!("extracting type name {:?} for binary operator", op),
45            )
46            .as_str(),
47        );
48
49        let values = ob.getattr("values").expect(
50            ob.error_message("<unknown>", "error getting binary operand")
51                .as_str(),
52        );
53
54        println!("BoolOps values: {}", dump(&values, None)?);
55
56        let value: Vec<ExprType> = values.extract().expect("getting values from BoolOp");
57        let left = value[0].clone();
58        let right = value[1].clone();
59
60        let op_type_str: String = op_type.extract()?;
61        let op = match op_type_str.as_str() {
62            "And" => BoolOps::And,
63            "Or" => BoolOps::Or,
64
65            _ => {
66                log::debug!("Found unknown BoolOp {:?}", op);
67                BoolOps::Unknown
68            }
69        };
70
71        log::debug!(
72            "left: {:?}, right: {:?}, op: {:?}/{:?}",
73            left,
74            right,
75            op_type,
76            op
77        );
78
79        return Ok(BoolOp {
80            op: op,
81            left: Box::new(left),
82            right: Box::new(right),
83        });
84    }
85}
86
87impl<'a> CodeGen for BoolOp {
88    type Context = CodeGenContext;
89    type Options = PythonOptions;
90    type SymbolTable = SymbolTableScopes;
91
92    fn to_rust(
93        self,
94        ctx: Self::Context,
95        options: Self::Options,
96        symbols: Self::SymbolTable,
97    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
98        let left = self
99            .left
100            .clone()
101            .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
102        let right = self
103            .right
104            .clone()
105            .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
106        match self.op {
107            BoolOps::Or => Ok(quote!((#left) || (#right))),
108            BoolOps::And => Ok(quote!((#left) && (#right))),
109
110            _ => Err(Error::BoolOpNotYetImplemented(self).into()),
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_and() {
121        let options = PythonOptions::default();
122        let result = crate::parse("1 and 2", "test_case.py").unwrap();
123        log::info!("Python tree: {:?}", result);
124        //log::info!("{}", result.to_rust().unwrap());
125
126        let code = result
127            .to_rust(
128                CodeGenContext::Module("test_case".to_string()),
129                options,
130                SymbolTableScopes::new(),
131            )
132            .unwrap();
133        log::info!("module: {:?}", code);
134    }
135
136    #[test]
137    fn test_or() {
138        let options = PythonOptions::default();
139        let result = crate::parse("1 or 2", "test_case.py").unwrap();
140        log::info!("Python tree: {:?}", result);
141        //log::info!("{}", result);
142
143        let code = result
144            .to_rust(
145                CodeGenContext::Module("test_case".to_string()),
146                options,
147                SymbolTableScopes::new(),
148            )
149            .unwrap();
150        log::info!("module: {:?}", code);
151    }
152}