python_ast/ast/tree/
f_string.rs

1use proc_macro2::TokenStream;
2use pyo3::{Bound, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods};
3use quote::quote;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    CodeGen, CodeGenContext, ExprType, Node, PythonOptions, SymbolTableScopes,
8    extract_list,
9};
10
11/// Joined string (f-string, e.g., f"Hello {name}")
12#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
13pub struct JoinedStr {
14    /// The values that make up the f-string (mix of strings and expressions)
15    pub values: Vec<ExprType>,
16    /// Position information
17    pub lineno: Option<usize>,
18    pub col_offset: Option<usize>,
19    pub end_lineno: Option<usize>,
20    pub end_col_offset: Option<usize>,
21}
22
23/// Formatted value within an f-string (e.g., the {name} part)
24#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
25pub struct FormattedValue {
26    /// The expression to be formatted
27    pub value: Box<ExprType>,
28    /// Conversion flag (None, 's', 'r', 'a') - represented as optional integer
29    pub conversion: Option<i32>,
30    /// Format specifier (optional)
31    pub format_spec: Option<Box<ExprType>>,
32    /// Position information
33    pub lineno: Option<usize>,
34    pub col_offset: Option<usize>,
35    pub end_lineno: Option<usize>,
36    pub end_col_offset: Option<usize>,
37}
38
39impl<'a> FromPyObject<'a> for JoinedStr {
40    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
41        // Extract values
42        let values: Vec<ExprType> = extract_list(ob, "values", "joined string values")?;
43        
44        Ok(JoinedStr {
45            values,
46            lineno: ob.lineno(),
47            col_offset: ob.col_offset(),
48            end_lineno: ob.end_lineno(),
49            end_col_offset: ob.end_col_offset(),
50        })
51    }
52}
53
54impl<'a> FromPyObject<'a> for FormattedValue {
55    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
56        // Extract value
57        let value: ExprType = ob.getattr("value")?.extract()?;
58        
59        // Extract conversion (optional)
60        let conversion: Option<i32> = if let Ok(conv_attr) = ob.getattr("conversion") {
61            let conv_val: i32 = conv_attr.extract()?;
62            if conv_val == -1 {
63                None // -1 means no conversion
64            } else {
65                Some(conv_val)
66            }
67        } else {
68            None
69        };
70        
71        // Extract format_spec (optional)
72        let format_spec: Option<Box<ExprType>> = if let Ok(spec_attr) = ob.getattr("format_spec") {
73            if spec_attr.is_none() {
74                None
75            } else {
76                Some(Box::new(spec_attr.extract()?))
77            }
78        } else {
79            None
80        };
81        
82        Ok(FormattedValue {
83            value: Box::new(value),
84            conversion,
85            format_spec,
86            lineno: ob.lineno(),
87            col_offset: ob.col_offset(),
88            end_lineno: ob.end_lineno(),
89            end_col_offset: ob.end_col_offset(),
90        })
91    }
92}
93
94impl Node for JoinedStr {
95    fn lineno(&self) -> Option<usize> { self.lineno }
96    fn col_offset(&self) -> Option<usize> { self.col_offset }
97    fn end_lineno(&self) -> Option<usize> { self.end_lineno }
98    fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
99}
100
101impl Node for FormattedValue {
102    fn lineno(&self) -> Option<usize> { self.lineno }
103    fn col_offset(&self) -> Option<usize> { self.col_offset }
104    fn end_lineno(&self) -> Option<usize> { self.end_lineno }
105    fn end_col_offset(&self) -> Option<usize> { self.end_col_offset }
106}
107
108impl CodeGen for JoinedStr {
109    type Context = CodeGenContext;
110    type Options = PythonOptions;
111    type SymbolTable = SymbolTableScopes;
112
113    fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
114        self.values.into_iter().fold(symbols, |acc, val| val.find_symbols(acc))
115    }
116
117    fn to_rust(
118        self,
119        ctx: Self::Context,
120        options: Self::Options,
121        symbols: Self::SymbolTable,
122    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
123        // Generate each part of the f-string
124        let part_tokens: Result<Vec<TokenStream>, Box<dyn std::error::Error>> = self.values.into_iter()
125            .map(|val| val.to_rust(ctx.clone(), options.clone(), symbols.clone()))
126            .collect();
127        let part_tokens = part_tokens?;
128
129        // For now, generate a simple format! macro call
130        // This is a simplified translation - real f-strings are more complex
131        if part_tokens.is_empty() {
132            Ok(quote! { String::new() })
133        } else {
134            Ok(quote! {
135                format!("{}", #(#part_tokens)+*)
136            })
137        }
138    }
139}
140
141impl CodeGen for FormattedValue {
142    type Context = CodeGenContext;
143    type Options = PythonOptions;
144    type SymbolTable = SymbolTableScopes;
145
146    fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
147        let symbols = (*self.value).find_symbols(symbols);
148        if let Some(format_spec) = self.format_spec {
149            (*format_spec).find_symbols(symbols)
150        } else {
151            symbols
152        }
153    }
154
155    fn to_rust(
156        self,
157        ctx: Self::Context,
158        options: Self::Options,
159        symbols: Self::SymbolTable,
160    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
161        let value_tokens = (*self.value).to_rust(ctx.clone(), options.clone(), symbols.clone())?;
162        
163        if let Some(format_spec) = self.format_spec {
164            let _spec_tokens = (*format_spec).to_rust(ctx, options, symbols)?;
165            // For now, generate a simple format with the format specifier
166            // TODO: Properly handle format specifications
167            Ok(quote! {
168                format!("{}", #value_tokens)
169            })
170        } else {
171            // Simple case - just format with default formatting
172            Ok(quote! {
173                format!("{}", #value_tokens)
174            })
175        }
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    // Tests would go here - currently commented out as they need full AST infrastructure
182    // create_parse_test!(test_simple_fstring, "f'Hello {name}'", "test.py");
183}