Skip to main content

portalis_transpiler/
indented_parser.rs

1//! Indentation-aware Python parser
2//!
3//! Handles Python's indentation-based block structure for:
4//! - if/elif/else blocks
5//! - for loops
6//! - while loops
7//! - function definitions
8
9use crate::python_ast::*;
10use crate::{Error, Result};
11
12#[derive(Debug, Clone)]
13struct Line {
14    content: String,
15    indent_level: usize,
16    line_number: usize,
17}
18
19pub struct IndentedPythonParser {
20    lines: Vec<Line>,
21    current_line: usize,
22    pending_decorators: Vec<PyExpr>,
23}
24
25impl IndentedPythonParser {
26    pub fn new(source: &str) -> Self {
27        let lines: Vec<Line> = source
28            .lines()
29            .enumerate()
30            .map(|(idx, s)| {
31                let indent_level = s.chars().take_while(|c| *c == ' ').count() / 4;
32                Line {
33                    content: s.trim().to_string(),
34                    indent_level,
35                    line_number: idx + 1,
36                }
37            })
38            .collect();
39
40        Self {
41            lines,
42            current_line: 0,
43            pending_decorators: Vec::new(),
44        }
45    }
46
47    pub fn parse(&mut self) -> Result<PyModule> {
48        let mut module = PyModule::new();
49
50        while self.current_line < self.lines.len() {
51            let line = &self.lines[self.current_line];
52
53            // Skip empty lines and comments
54            if line.content.is_empty() || line.content.starts_with('#') {
55                self.current_line += 1;
56                continue;
57            }
58
59            // Parse statement at current indentation level
60            if let Some(stmt) = self.parse_statement(0)? {
61                module.add_stmt(stmt);
62            }
63        }
64
65        Ok(module)
66    }
67
68    fn parse_statement(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
69        if self.current_line >= self.lines.len() {
70            return Ok(None);
71        }
72
73        let line = &self.lines[self.current_line].clone();
74
75        // Check indent level
76        if line.indent_level != expected_indent {
77            return Ok(None);
78        }
79
80        // If statement with block
81        if line.content.starts_with("if ") && line.content.ends_with(':') {
82            return self.parse_if_statement(expected_indent);
83        }
84
85        // While loop with block
86        if line.content.starts_with("while ") && line.content.ends_with(':') {
87            return self.parse_while_loop(expected_indent);
88        }
89
90        // For loop with block
91        if line.content.starts_with("for ") && line.content.ends_with(':') {
92            return self.parse_for_loop(expected_indent);
93        }
94
95        // Decorator (collects decorators for next function/class)
96        if line.content.starts_with('@') {
97            return self.parse_decorator(expected_indent);
98        }
99
100        // Class definition
101        if line.content.starts_with("class ") && line.content.ends_with(':') {
102            return self.parse_class_def(expected_indent);
103        }
104
105        // Async function definition
106        if line.content.starts_with("async def ") && line.content.ends_with(':') {
107            return self.parse_function_def(expected_indent);
108        }
109
110        // Function definition
111        if line.content.starts_with("def ") && line.content.ends_with(':') {
112            return self.parse_function_def(expected_indent);
113        }
114
115        // Import statement
116        if line.content.starts_with("import ") {
117            return self.parse_import_statement();
118        }
119
120        // From import statement
121        if line.content.starts_with("from ") && line.content.contains(" import ") {
122            return self.parse_from_import_statement();
123        }
124
125        // Try-except statement
126        if line.content == "try:" {
127            return self.parse_try_except(expected_indent);
128        }
129
130        // With statement (context manager)
131        if line.content.starts_with("with ") && line.content.ends_with(':') {
132            return self.parse_with_statement(expected_indent);
133        }
134
135        // Raise statement
136        if line.content.starts_with("raise") {
137            self.current_line += 1;
138            let exc = if line.content.len() > 5 {
139                Some(self.parse_expr(line.content[5..].trim())?)
140            } else {
141                None
142            };
143            return Ok(Some(PyStmt::Raise { exception: exc }));
144        }
145
146        // Return statement
147        if line.content.starts_with("return") {
148            self.current_line += 1;
149            let expr = if line.content.len() > 6 {
150                Some(self.parse_expr(line.content[6..].trim())?)
151            } else {
152                None
153            };
154            return Ok(Some(PyStmt::Return { value: expr }));
155        }
156
157        // Assert statement
158        if line.content.starts_with("assert ") {
159            self.current_line += 1;
160            let rest = line.content[7..].trim(); // Skip "assert "
161
162            // Check for optional message: assert condition, "message"
163            let (test_str, msg) = if let Some(comma_pos) = rest.find(',') {
164                let test_part = rest[..comma_pos].trim();
165                let msg_part = rest[comma_pos + 1..].trim();
166                let msg_expr = Some(self.parse_expr(msg_part)?);
167                (test_part, msg_expr)
168            } else {
169                (rest, None)
170            };
171
172            let test = self.parse_expr(test_str)?;
173            return Ok(Some(PyStmt::Assert { test, msg }));
174        }
175
176        // Simple statements (assignment, print, etc.)
177        let stmt = self.parse_simple_statement(&line.content)?;
178        self.current_line += 1;
179        Ok(stmt)
180    }
181
182    fn parse_if_statement(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
183        let line = &self.lines[self.current_line].clone();
184
185        // Handle both "if" and "elif"
186        let (condition_str, _is_elif) = if line.content.starts_with("elif ") {
187            (line.content[5..line.content.len() - 1].trim(), true)
188        } else {
189            (line.content[3..line.content.len() - 1].trim(), false)
190        };
191
192        let condition = self.parse_expr(condition_str)?;
193
194        self.current_line += 1;
195
196        // Parse if/elif body
197        let body = self.parse_block(expected_indent + 1)?;
198
199        // Check for else or elif (but not after elif)
200        let mut orelse = vec![];
201        if self.current_line < self.lines.len() {
202            let next_line = &self.lines[self.current_line];
203            if next_line.indent_level == expected_indent
204                && (next_line.content == "else:" || next_line.content.starts_with("elif "))
205            {
206                if next_line.content == "else:" {
207                    self.current_line += 1;
208                    orelse = self.parse_block(expected_indent + 1)?;
209                } else if next_line.content.starts_with("elif ") {
210                    // Treat elif as nested if in else block
211                    if let Some(elif_stmt) = self.parse_if_statement(expected_indent)? {
212                        orelse = vec![elif_stmt];
213                    }
214                }
215            }
216        }
217
218        Ok(Some(PyStmt::If {
219            test: condition,
220            body,
221            orelse,
222        }))
223    }
224
225    fn parse_while_loop(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
226        let line = &self.lines[self.current_line].clone();
227        let condition_str = line.content[6..line.content.len() - 1].trim();
228        let condition = self.parse_expr(condition_str)?;
229
230        self.current_line += 1;
231
232        let body = self.parse_block(expected_indent + 1)?;
233
234        // Check for optional else clause
235        let orelse = if self.current_line < self.lines.len() {
236            let line = &self.lines[self.current_line];
237            if line.indent_level == expected_indent && line.content == "else:" {
238                self.current_line += 1;
239                self.parse_block(expected_indent + 1)?
240            } else {
241                vec![]
242            }
243        } else {
244            vec![]
245        };
246
247        Ok(Some(PyStmt::While {
248            test: condition,
249            body,
250            orelse,
251        }))
252    }
253
254    fn parse_for_loop(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
255        let line = &self.lines[self.current_line].clone();
256        let for_content = &line.content[4..line.content.len() - 1].trim();
257
258        // Parse: for VAR in EXPR or for (VAR1, VAR2) in EXPR or for VAR1, VAR2 in EXPR
259        if let Some(in_pos) = for_content.find(" in ") {
260            let target_str = for_content[..in_pos].trim();
261
262            // Parse target - handle tuple unpacking
263            let target = if target_str.contains(',') {
264                // Tuple unpacking: "i, item" or "(i, item)"
265                let clean_str = if target_str.starts_with('(') && target_str.ends_with(')') {
266                    &target_str[1..target_str.len() - 1]
267                } else {
268                    target_str
269                };
270
271                let elements: Vec<PyExpr> = clean_str
272                    .split(',')
273                    .map(|s| PyExpr::Name(s.trim().to_string()))
274                    .collect();
275                PyExpr::Tuple(elements)
276            } else {
277                // Single target
278                PyExpr::Name(target_str.to_string())
279            };
280
281            let iter_str = for_content[in_pos + 4..].trim();
282            let iter = self.parse_expr(iter_str)?;
283
284            self.current_line += 1;
285
286            let body = self.parse_block(expected_indent + 1)?;
287
288            // Check for optional else clause
289            let orelse = if self.current_line < self.lines.len() {
290                let line = &self.lines[self.current_line];
291                if line.indent_level == expected_indent && line.content == "else:" {
292                    self.current_line += 1;
293                    self.parse_block(expected_indent + 1)?
294                } else {
295                    vec![]
296                }
297            } else {
298                vec![]
299            };
300
301            return Ok(Some(PyStmt::For {
302                target,
303                iter,
304                body,
305                orelse,
306            }));
307        }
308
309        Err(Error::CodeGeneration(
310            "Invalid for loop syntax".to_string(),
311        ))
312    }
313
314    fn parse_function_def(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
315        let line = &self.lines[self.current_line].clone();
316
317        // Check if async function
318        let is_async = line.content.starts_with("async def ");
319        let def_start = if is_async { 10 } else { 4 }; // "async def " = 10, "def " = 4
320        let def_content = &line.content[def_start..line.content.len() - 1].trim();
321
322        // Parse: def NAME(ARGS) -> RETURN_TYPE or def NAME(ARGS)
323        if let Some(paren_pos) = def_content.find('(') {
324            let name = def_content[..paren_pos].trim().to_string();
325            let close_paren = if let Some(pos) = def_content.find(')') {
326                pos
327            } else {
328                return Err(Error::CodeGeneration("Missing closing paren".to_string()));
329            };
330
331            let args_str = &def_content[paren_pos + 1..close_paren];
332            let params = self.parse_function_args(args_str)?;
333
334            // Check for return type annotation: -> TYPE
335            let return_type = if close_paren + 1 < def_content.len() {
336                let after_paren = &def_content[close_paren + 1..].trim();
337                if after_paren.starts_with("->") {
338                    Some(TypeAnnotation::Name(after_paren[2..].trim().to_string()))
339                } else {
340                    None
341                }
342            } else {
343                None
344            };
345
346            self.current_line += 1;
347
348            let body = self.parse_block(expected_indent + 1)?;
349
350            return Ok(Some(PyStmt::FunctionDef {
351                name,
352                params,
353                body,
354                return_type,
355                decorators: std::mem::take(&mut self.pending_decorators),
356                is_async,
357            }));
358        }
359
360        Err(Error::CodeGeneration(
361            "Invalid function definition".to_string(),
362        ))
363    }
364
365    fn parse_class_def(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
366        let line = &self.lines[self.current_line].clone();
367        let class_content = &line.content[6..line.content.len() - 1].trim();
368
369        // Parse: class NAME or class NAME(BASE)
370        let (name, bases) = if let Some(paren_pos) = class_content.find('(') {
371            // Has base classes
372            let name = class_content[..paren_pos].trim().to_string();
373            let close_paren = if let Some(pos) = class_content.find(')') {
374                pos
375            } else {
376                return Err(Error::CodeGeneration("Missing closing paren in class".to_string()));
377            };
378
379            let bases_str = &class_content[paren_pos + 1..close_paren];
380            let bases: Vec<PyExpr> = if bases_str.trim().is_empty() {
381                vec![]
382            } else {
383                bases_str.split(',').map(|s| PyExpr::Name(s.trim().to_string())).collect()
384            };
385
386            (name, bases)
387        } else {
388            // No base classes
389            (class_content.to_string(), vec![] as Vec<PyExpr>)
390        };
391
392        self.current_line += 1;
393
394        // Parse class body
395        let body = self.parse_block(expected_indent + 1)?;
396
397        Ok(Some(PyStmt::ClassDef {
398            name,
399            bases,
400            body,
401            decorators: std::mem::take(&mut self.pending_decorators),
402        }))
403    }
404
405    fn parse_decorator(&mut self, _expected_indent: usize) -> Result<Option<PyStmt>> {
406        let line = &self.lines[self.current_line].clone();
407
408        // Parse decorator: @decorator_name or @decorator_name(args)
409        let decorator_str = &line.content[1..].trim(); // Remove @ and trim
410
411        // Extract just the decorator name (before any parentheses)
412        let decorator_name = if let Some(paren_pos) = decorator_str.find('(') {
413            decorator_str[..paren_pos].trim().to_string()
414        } else {
415            decorator_str.to_string()
416        };
417
418        self.pending_decorators.push(PyExpr::Name(decorator_name));
419        self.current_line += 1;
420
421        // Return None to continue parsing (decorators don't generate statements themselves)
422        Ok(None)
423    }
424
425    fn parse_block(&mut self, expected_indent: usize) -> Result<Vec<PyStmt>> {
426        let mut stmts = vec![];
427
428        while self.current_line < self.lines.len() {
429            let line = &self.lines[self.current_line];
430
431            // Skip empty lines and comments
432            if line.content.is_empty() || line.content.starts_with('#') {
433                self.current_line += 1;
434                continue;
435            }
436
437            // Check if we're still in the block
438            if line.indent_level < expected_indent {
439                break;
440            }
441
442            if line.indent_level > expected_indent {
443                return Err(Error::CodeGeneration(format!(
444                    "Unexpected indentation at line {}",
445                    line.line_number
446                )));
447            }
448
449            if let Some(stmt) = self.parse_statement(expected_indent)? {
450                stmts.push(stmt);
451            } else {
452                break;
453            }
454        }
455
456        Ok(stmts)
457    }
458
459    fn parse_function_args(&self, args_str: &str) -> Result<Vec<FunctionParam>> {
460        if args_str.trim().is_empty() {
461            return Ok(vec![]);
462        }
463
464        let params: Vec<FunctionParam> = args_str
465            .split(',')
466            .map(|arg| {
467                let arg = arg.trim();
468                // Simple parsing: name or name: type
469                if let Some(colon_pos) = arg.find(':') {
470                    let name = arg[..colon_pos].trim().to_string();
471                    let type_annotation = Some(TypeAnnotation::Name(arg[colon_pos + 1..].trim().to_string()));
472                    FunctionParam {
473                        name,
474                        type_annotation,
475                        default_value: None,
476                    }
477                } else {
478                    FunctionParam {
479                        name: arg.to_string(),
480                        type_annotation: None,
481                        default_value: None,
482                    }
483                }
484            })
485            .collect();
486
487        Ok(params)
488    }
489
490    fn parse_simple_statement(&self, line: &str) -> Result<Option<PyStmt>> {
491        // Pass statement
492        if line == "pass" {
493            return Ok(Some(PyStmt::Pass));
494        }
495
496        // Break statement
497        if line == "break" {
498            return Ok(Some(PyStmt::Break));
499        }
500
501        // Continue statement
502        if line == "continue" {
503            return Ok(Some(PyStmt::Continue));
504        }
505
506        // Assignment (including chained assignment like x = y = z = 0)
507        // First, check if this looks like an assignment by checking for '='
508        // but exclude comparison operators
509        if line.contains('=') && !line.contains("==") && !line.contains("!=")
510            && !line.contains("<=") && !line.contains(">=") {
511
512            // Split by '=' but be careful about comparison operators
513            // For now, simple split - will handle edge cases later
514            let parts: Vec<&str> = line.split('=')
515                .map(|s| s.trim())
516                .filter(|s| !s.is_empty())
517                .collect();
518
519            if parts.len() >= 2 {
520                let last_part = parts[parts.len() - 1];
521
522                // Check if first part looks like augmented assignment (x+=, x-=, etc.)
523                if parts.len() == 2 {
524                    let target = parts[0];
525                    if let Some(op_char) = target.chars().last() {
526                        if matches!(op_char, '+' | '-' | '*' | '/' | '%') {
527                            let actual_target = target[..target.len() - 1].trim().to_string();
528                            let op = match op_char {
529                                '+' => BinOp::Add,
530                                '-' => BinOp::Sub,
531                                '*' => BinOp::Mult,
532                                '/' => BinOp::Div,
533                                '%' => BinOp::Mod,
534                                _ => unreachable!(),
535                            };
536                            let value = self.parse_expr(last_part)?;
537                            return Ok(Some(PyStmt::AugAssign {
538                                target: PyExpr::Name(actual_target),
539                                op,
540                                value,
541                            }));
542                        }
543                    }
544                }
545
546                // Regular assignment (take first target for chained assignments like a = b = 5)
547                let target_str = parts[parts.len() - 2].trim();
548                let value = self.parse_expr(last_part)?;
549
550                // Parse target as expression
551                let target = self.parse_expr(target_str)?;
552
553                // Return assignment
554                return Ok(Some(PyStmt::Assign {
555                    target,
556                    value,
557                }));
558            }
559        }
560
561        // Print function
562        if line.starts_with("print(") && line.ends_with(')') {
563            let args_str = &line[6..line.len() - 1];
564            let expr = self.parse_expr(args_str)?;
565            return Ok(Some(PyStmt::Expr(PyExpr::Call {
566                func: Box::new(PyExpr::Name("print".to_string())),
567                args: vec![expr],
568                kwargs: std::collections::HashMap::new(),
569            })));
570        }
571
572        // General function call as statement
573        if line.contains('(') && line.ends_with(')') {
574            // Try to parse as expression - if it's a Call, wrap it as Expr statement
575            if let Ok(expr) = self.parse_expr(line) {
576                if matches!(expr, PyExpr::Call { .. }) {
577                    return Ok(Some(PyStmt::Expr(expr)));
578                }
579            }
580        }
581
582        Ok(None)
583    }
584
585    /// Find position of operator outside of parentheses
586    fn find_op_outside_parens(&self, s: &str, op: &str) -> Option<usize> {
587        let mut depth = 0;
588        let mut i = 0;
589        let chars: Vec<char> = s.chars().collect();
590
591        while i < chars.len() {
592            match chars[i] {
593                '(' | '[' => depth += 1,
594                ')' | ']' => depth -= 1,
595                _ => {}
596            }
597
598            if depth == 0 && i + op.len() <= s.len() {
599                if &s[i..i + op.len()] == op {
600                    return Some(i);
601                }
602            }
603            i += 1;
604        }
605
606        None
607    }
608
609    fn parse_expr(&self, s: &str) -> Result<PyExpr> {
610        let s = s.trim();
611
612        // Await expression: await expr
613        if s.starts_with("await ") {
614            let inner = &s[6..].trim();
615            let value = self.parse_expr(inner)?;
616            return Ok(PyExpr::Await(Box::new(value)));
617        }
618
619        // Lambda expressions: lambda x: x + 1, lambda x, y: x + y
620        if s.starts_with("lambda ") {
621            return self.parse_lambda(s);
622        }
623
624        // Boolean literals
625        if s == "True" {
626            return Ok(PyExpr::Literal(PyLiteral::Bool(true)));
627        }
628        if s == "False" {
629            return Ok(PyExpr::Literal(PyLiteral::Bool(false)));
630        }
631
632        // None
633        if s == "None" {
634            return Ok(PyExpr::Literal(PyLiteral::None));
635        }
636
637        // range() function
638        if s.starts_with("range(") && s.ends_with(')') {
639            let args_str = &s[6..s.len() - 1];
640            let args: Vec<PyExpr> = args_str
641                .split(',')
642                .map(|arg| self.parse_expr(arg.trim()))
643                .collect::<Result<Vec<_>>>()?;
644
645            return Ok(PyExpr::Call {
646                func: Box::new(PyExpr::Name("range".to_string())),
647                args,
648                kwargs: std::collections::HashMap::new(),
649            });
650        }
651
652        // List literals or list comprehension
653        if s.starts_with('[') && s.ends_with(']') {
654            let content = &s[1..s.len() - 1].trim();
655            if content.is_empty() {
656                return Ok(PyExpr::List(vec![]));
657            }
658
659            // Check for list comprehension: [element for var in iterable]
660            if content.contains(" for ") {
661                return self.parse_list_comprehension(content);
662            }
663
664            // Regular list literal - use depth-aware comma splitting
665            let mut element_strings = Vec::new();
666            let mut current = String::new();
667            let mut depth = 0;
668
669            for ch in content.chars() {
670                match ch {
671                    '(' | '[' | '{' => {
672                        depth += 1;
673                        current.push(ch);
674                    }
675                    ')' | ']' | '}' => {
676                        depth -= 1;
677                        current.push(ch);
678                    }
679                    ',' if depth == 0 => {
680                        element_strings.push(current.trim().to_string());
681                        current.clear();
682                    }
683                    _ => current.push(ch),
684                }
685            }
686
687            if !current.trim().is_empty() {
688                element_strings.push(current.trim().to_string());
689            }
690
691            let elements: Result<Vec<_>> = element_strings
692                .iter()
693                .map(|e| self.parse_expr(e))
694                .collect();
695            return Ok(PyExpr::List(elements?));
696        }
697
698        // Dictionary literals
699        if s.starts_with('{') && s.ends_with('}') {
700            let content = &s[1..s.len() - 1].trim();
701            if content.is_empty() {
702                return Ok(PyExpr::Dict {
703                    keys: vec![],
704                    values: vec![],
705                });
706            }
707
708            // Parse key: value pairs - use depth-aware comma splitting
709            let mut keys = vec![];
710            let mut values = vec![];
711
712            // Split pairs by comma at depth 0
713            let mut pair_strings = Vec::new();
714            let mut current = String::new();
715            let mut depth = 0;
716
717            for ch in content.chars() {
718                match ch {
719                    '(' | '[' | '{' => {
720                        depth += 1;
721                        current.push(ch);
722                    }
723                    ')' | ']' | '}' => {
724                        depth -= 1;
725                        current.push(ch);
726                    }
727                    ',' if depth == 0 => {
728                        pair_strings.push(current.trim().to_string());
729                        current.clear();
730                    }
731                    _ => current.push(ch),
732                }
733            }
734
735            if !current.trim().is_empty() {
736                pair_strings.push(current.trim().to_string());
737            }
738
739            for pair in pair_strings {
740                if let Some(colon_pos) = pair.find(':') {
741                    let key_str = &pair[..colon_pos].trim();
742                    let value_str = &pair[colon_pos + 1..].trim();
743                    keys.push(self.parse_expr(key_str)?);
744                    values.push(self.parse_expr(value_str)?);
745                }
746            }
747
748            return Ok(PyExpr::Dict { keys, values });
749        }
750
751        // Tuple literals
752        if s.starts_with('(') && s.ends_with(')') {
753            let content = &s[1..s.len() - 1].trim();
754            if content.is_empty() {
755                return Ok(PyExpr::Tuple(vec![]));
756            }
757
758            // Use depth-aware comma splitting
759            let mut element_strings = Vec::new();
760            let mut current = String::new();
761            let mut depth = 0;
762
763            for ch in content.chars() {
764                match ch {
765                    '(' | '[' | '{' => {
766                        depth += 1;
767                        current.push(ch);
768                    }
769                    ')' | ']' | '}' => {
770                        depth -= 1;
771                        current.push(ch);
772                    }
773                    ',' if depth == 0 => {
774                        element_strings.push(current.trim().to_string());
775                        current.clear();
776                    }
777                    _ => current.push(ch),
778                }
779            }
780
781            if !current.trim().is_empty() {
782                element_strings.push(current.trim().to_string());
783            }
784
785            let elements: Result<Vec<_>> = element_strings
786                .iter()
787                .map(|e| self.parse_expr(e))
788                .collect();
789            return Ok(PyExpr::Tuple(elements?));
790        }
791
792        // String literals
793        if (s.starts_with('"') && s.ends_with('"'))
794            || (s.starts_with('\'') && s.ends_with('\''))
795        {
796            let content = &s[1..s.len() - 1];
797            return Ok(PyExpr::Literal(PyLiteral::String(content.to_string())));
798        }
799
800        // Float literals
801        if s.contains('.') && s.parse::<f64>().is_ok() {
802            return Ok(PyExpr::Literal(PyLiteral::Float(s.parse().unwrap())));
803        }
804
805        // Integer literals
806        if let Ok(value) = s.parse::<i64>() {
807            return Ok(PyExpr::Literal(PyLiteral::Int(value)));
808        }
809
810        // Logical operators
811        for (op_str, is_and) in &[(" and ", true), (" or ", false)] {
812            if let Some(pos) = self.find_op_outside_parens(s, op_str) {
813                let left = self.parse_expr(&s[..pos])?;
814                let right = self.parse_expr(&s[pos + op_str.len()..])?;
815                let op = if *is_and {
816                    BinOp::BitAnd
817                } else {
818                    BinOp::BitOr
819                };
820                return Ok(PyExpr::BinOp {
821                    left: Box::new(left),
822                    op,
823                    right: Box::new(right),
824                });
825            }
826        }
827
828        // Comparison operators
829        for (op_str, cmp_op) in &[
830            ("==", CmpOp::Eq),
831            ("!=", CmpOp::NotEq),
832            ("<=", CmpOp::LtE),
833            (">=", CmpOp::GtE),
834            ("<", CmpOp::Lt),
835            (">", CmpOp::Gt),
836        ] {
837            if let Some(pos) = self.find_op_outside_parens(s, op_str) {
838                let left = self.parse_expr(&s[..pos])?;
839                let right = self.parse_expr(&s[pos + op_str.len()..])?;
840                return Ok(PyExpr::Compare {
841                    left: Box::new(left),
842                    op: *cmp_op,
843                    right: Box::new(right),
844                });
845            }
846        }
847
848        // Unary not
849        if s.starts_with("not ") {
850            let operand = self.parse_expr(&s[4..])?;
851            return Ok(PyExpr::UnaryOp {
852                op: UnaryOp::Not,
853                operand: Box::new(operand),
854            });
855        }
856
857        // Arithmetic operators (in order of precedence)
858        for (op_str, op) in &[
859            (" + ", BinOp::Add),
860            (" - ", BinOp::Sub),
861            (" * ", BinOp::Mult),
862            (" / ", BinOp::Div),
863            (" // ", BinOp::FloorDiv),
864            (" % ", BinOp::Mod),
865            (" ** ", BinOp::Pow),
866        ] {
867            if let Some(pos) = self.find_op_outside_parens(s, op_str) {
868                let left = self.parse_expr(&s[..pos])?;
869                let right = self.parse_expr(&s[pos + op_str.len()..])?;
870                return Ok(PyExpr::BinOp {
871                    left: Box::new(left),
872                    op: *op,
873                    right: Box::new(right),
874                });
875            }
876        }
877
878        // List indexing or slicing
879        if let Some(bracket_pos) = s.find('[') {
880            if s.ends_with(']') {
881                let value_str = &s[..bracket_pos];
882                let index_str = &s[bracket_pos + 1..s.len() - 1];
883                let value = self.parse_expr(value_str)?;
884
885                // Check if it's a slice (contains ':')
886                if index_str.contains(':') {
887                    return self.parse_slice(value, index_str);
888                }
889
890                // Regular subscript
891                let index = self.parse_expr(index_str)?;
892                return Ok(PyExpr::Subscript {
893                    value: Box::new(value),
894                    index: Box::new(index),
895                });
896            }
897        }
898
899        // Function calls (general form: name(...))
900        if let Some(paren_pos) = s.find('(') {
901            if s.ends_with(')') {
902                let func_name = &s[..paren_pos];
903                // Make sure it's a valid identifier
904                if func_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
905                    let args_str = &s[paren_pos + 1..s.len() - 1];
906                    let args: Vec<PyExpr> = if args_str.trim().is_empty() {
907                        vec![]
908                    } else {
909                        // Split by comma at depth 0 only (depth-aware splitting)
910                        let mut arg_strings = Vec::new();
911                        let mut current = String::new();
912                        let mut depth = 0;
913
914                        for ch in args_str.chars() {
915                            match ch {
916                                '(' | '[' | '{' => {
917                                    depth += 1;
918                                    current.push(ch);
919                                }
920                                ')' | ']' | '}' => {
921                                    depth -= 1;
922                                    current.push(ch);
923                                }
924                                ',' if depth == 0 => {
925                                    arg_strings.push(current.trim().to_string());
926                                    current.clear();
927                                }
928                                _ => current.push(ch),
929                            }
930                        }
931
932                        if !current.trim().is_empty() {
933                            arg_strings.push(current.trim().to_string());
934                        }
935
936                        arg_strings
937                            .iter()
938                            .map(|arg| self.parse_expr(arg))
939                            .collect::<Result<Vec<_>>>()?
940                    };
941
942                    return Ok(PyExpr::Call {
943                        func: Box::new(PyExpr::Name(func_name.to_string())),
944                        args,
945                        kwargs: std::collections::HashMap::new(),
946                    });
947                }
948            }
949        }
950
951        // Unary minus (e.g., -x, -5)
952        if s.starts_with('-') && s.len() > 1 {
953            let operand_str = &s[1..].trim();
954            // Make sure it's not subtraction (has spaces around -)
955            if !operand_str.is_empty() {
956                let operand = self.parse_expr(operand_str)?;
957                return Ok(PyExpr::UnaryOp {
958                    op: UnaryOp::USub,
959                    operand: Box::new(operand),
960                });
961            }
962        }
963
964        // Method call or attribute access (obj.method(...) or obj.attr or obj.attr.method(...))
965        if let Some(dot_pos) = s.find('.') {
966            let value_str = &s[..dot_pos];
967            let attr_part = &s[dot_pos + 1..];
968
969            // Check if attr_part has a method call at the end
970            if let Some(paren_pos) = attr_part.rfind('(') {
971                if attr_part.ends_with(')') {
972                    // Find the method name (everything between last dot and paren, or from start if no dot)
973                    let last_dot_in_attr = attr_part[..paren_pos].rfind('.');
974                    let (nested_attrs, method_name) = if let Some(last_dot) = last_dot_in_attr {
975                        // Has nested attrs like path.exists in os.path.exists(...)
976                        (&attr_part[..last_dot], &attr_part[last_dot + 1..paren_pos])
977                    } else {
978                        // No nested attrs, like method in os.method(...)
979                        ("", &attr_part[..paren_pos])
980                    };
981
982                    if method_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
983                        let args_str = &attr_part[paren_pos + 1..attr_part.len() - 1];
984                        let args: Vec<PyExpr> = if args_str.trim().is_empty() {
985                            vec![]
986                        } else {
987                            // Use depth-aware comma splitting for method arguments
988                            let mut arg_strings = Vec::new();
989                            let mut current = String::new();
990                            let mut depth = 0;
991                            let mut in_string = false;
992                            let mut string_char = ' ';
993
994                            for ch in args_str.chars() {
995                                match ch {
996                                    '"' | '\'' if !in_string => {
997                                        in_string = true;
998                                        string_char = ch;
999                                        current.push(ch);
1000                                    }
1001                                    c if in_string && c == string_char => {
1002                                        in_string = false;
1003                                        current.push(ch);
1004                                    }
1005                                    '(' | '[' | '{' if !in_string => {
1006                                        depth += 1;
1007                                        current.push(ch);
1008                                    }
1009                                    ')' | ']' | '}' if !in_string => {
1010                                        depth -= 1;
1011                                        current.push(ch);
1012                                    }
1013                                    ',' if depth == 0 && !in_string => {
1014                                        arg_strings.push(current.trim().to_string());
1015                                        current.clear();
1016                                    }
1017                                    _ => current.push(ch),
1018                                }
1019                            }
1020
1021                            if !current.trim().is_empty() {
1022                                arg_strings.push(current.trim().to_string());
1023                            }
1024
1025                            arg_strings
1026                                .iter()
1027                                .map(|arg| self.parse_expr(arg))
1028                                .collect::<Result<Vec<_>>>()?
1029                        };
1030
1031                        // Build the nested attribute chain
1032                        let mut base = self.parse_expr(value_str)?;
1033
1034                        // If there are nested attributes, build them up
1035                        if !nested_attrs.is_empty() {
1036                            for attr in nested_attrs.split('.') {
1037                                base = PyExpr::Attribute {
1038                                    value: Box::new(base),
1039                                    attr: attr.to_string(),
1040                                };
1041                            }
1042                        }
1043
1044                        // Add the final method name
1045                        let method_attr = PyExpr::Attribute {
1046                            value: Box::new(base),
1047                            attr: method_name.to_string(),
1048                        };
1049
1050                        return Ok(PyExpr::Call {
1051                            func: Box::new(method_attr),
1052                            args,
1053                            kwargs: std::collections::HashMap::new(),
1054                        });
1055                    }
1056                }
1057            } else {
1058                // Simple attribute access (no method call) - can be nested like os.path
1059                let value = self.parse_expr(value_str)?;
1060
1061                // Handle nested attributes by splitting on dots
1062                let attrs: Vec<&str> = attr_part.split('.').collect();
1063                let mut result = value;
1064
1065                for attr in attrs {
1066                    if attr.chars().all(|c| c.is_alphanumeric() || c == '_') {
1067                        result = PyExpr::Attribute {
1068                            value: Box::new(result),
1069                            attr: attr.to_string(),
1070                        };
1071                    } else {
1072                        return Err(Error::CodeGeneration(format!(
1073                            "Invalid attribute name: {}",
1074                            attr
1075                        )));
1076                    }
1077                }
1078
1079                return Ok(result);
1080            }
1081        }
1082
1083        // Implicit tuple (comma-separated values without parentheses)
1084        // e.g., "i, item" or "1, 2, 3"
1085        // Only if there's a comma and we're not inside parentheses/brackets
1086        if s.contains(',') && !s.starts_with('(') {
1087            // Check for commas at depth 0 (not inside nested structures)
1088            let mut depth = 0;
1089            let mut has_top_level_comma = false;
1090
1091            for ch in s.chars() {
1092                match ch {
1093                    '(' | '[' | '{' => depth += 1,
1094                    ')' | ']' | '}' => depth -= 1,
1095                    ',' if depth == 0 => {
1096                        has_top_level_comma = true;
1097                        break;
1098                    }
1099                    _ => {}
1100                }
1101            }
1102
1103            if has_top_level_comma {
1104                // Split by comma at top level only
1105                let mut elements = Vec::new();
1106                let mut current = String::new();
1107                let mut depth = 0;
1108
1109                for ch in s.chars() {
1110                    match ch {
1111                        '(' | '[' | '{' => {
1112                            depth += 1;
1113                            current.push(ch);
1114                        }
1115                        ')' | ']' | '}' => {
1116                            depth -= 1;
1117                            current.push(ch);
1118                        }
1119                        ',' if depth == 0 => {
1120                            elements.push(current.trim().to_string());
1121                            current.clear();
1122                        }
1123                        _ => current.push(ch),
1124                    }
1125                }
1126
1127                // Don't forget the last element
1128                if !current.trim().is_empty() {
1129                    elements.push(current.trim().to_string());
1130                }
1131
1132                // Parse each element
1133                let parsed_elements: Result<Vec<_>> = elements
1134                    .iter()
1135                    .map(|e| self.parse_expr(e))
1136                    .collect();
1137
1138                return Ok(PyExpr::Tuple(parsed_elements?));
1139            }
1140        }
1141
1142        // Variable name
1143        if s.chars().all(|c| c.is_alphanumeric() || c == '_') {
1144            return Ok(PyExpr::Name(s.to_string()));
1145        }
1146
1147        Err(Error::CodeGeneration(format!(
1148            "Unable to parse expression: {}",
1149            s
1150        )))
1151    }
1152
1153    fn parse_list_comprehension(&self, content: &str) -> Result<PyExpr> {
1154        // Parse: element for var in iterable [if condition]
1155        // Example: x * 2 for x in range(10) if x > 5
1156
1157        // Find the first " for " (outside any parentheses)
1158        let for_pos = if let Some(pos) = content.find(" for ") {
1159            pos
1160        } else {
1161            return Err(Error::CodeGeneration("Invalid list comprehension: missing 'for'".to_string()));
1162        };
1163
1164        let element_str = content[..for_pos].trim();
1165        let rest = content[for_pos + 5..].trim(); // Skip " for "
1166
1167        // Find " in "
1168        let in_pos = if let Some(pos) = rest.find(" in ") {
1169            pos
1170        } else {
1171            return Err(Error::CodeGeneration("Invalid list comprehension: missing 'in'".to_string()));
1172        };
1173
1174        let target = rest[..in_pos].trim().to_string();
1175        let after_in = rest[in_pos + 4..].trim(); // Skip " in "
1176
1177        // Check for " if " condition
1178        let (iter_str, ifs) = if let Some(if_pos) = after_in.find(" if ") {
1179            let iter_part = after_in[..if_pos].trim();
1180            let condition_str = after_in[if_pos + 4..].trim();
1181            let condition = self.parse_expr(condition_str)?;
1182            (iter_part, vec![condition])
1183        } else {
1184            (after_in, vec![])
1185        };
1186
1187        let element = self.parse_expr(element_str)?;
1188        let iter = self.parse_expr(iter_str)?;
1189
1190        Ok(PyExpr::ListComp {
1191            element: Box::new(element),
1192            generators: vec![Comprehension {
1193                target: PyExpr::Name(target),
1194                iter,
1195                ifs,
1196            }],
1197        })
1198    }
1199
1200    fn parse_slice(&self, value: PyExpr, slice_str: &str) -> Result<PyExpr> {
1201        // Parse slice notation: [lower:upper:step]
1202        // Examples: [1:3], [:5], [2:], [::2], [1:10:2]
1203
1204        let parts: Vec<&str> = slice_str.split(':').collect();
1205
1206        let lower = if parts[0].trim().is_empty() {
1207            None
1208        } else {
1209            Some(Box::new(self.parse_expr(parts[0].trim())?))
1210        };
1211
1212        let upper = if parts.len() > 1 && !parts[1].trim().is_empty() {
1213            Some(Box::new(self.parse_expr(parts[1].trim())?))
1214        } else {
1215            None
1216        };
1217
1218        let step = if parts.len() > 2 && !parts[2].trim().is_empty() {
1219            Some(Box::new(self.parse_expr(parts[2].trim())?))
1220        } else {
1221            None
1222        };
1223
1224        Ok(PyExpr::Slice {
1225            value: Box::new(value),
1226            lower,
1227            upper,
1228            step,
1229        })
1230    }
1231
1232    fn parse_lambda(&self, s: &str) -> Result<PyExpr> {
1233        // Parse: lambda args: body
1234        // Examples: lambda x: x + 1, lambda x, y: x + y, lambda: 42
1235
1236        let rest = &s[7..].trim(); // Skip "lambda "
1237
1238        let colon_pos = if let Some(pos) = rest.find(':') {
1239            pos
1240        } else {
1241            return Err(Error::CodeGeneration("Invalid lambda: missing ':'".to_string()));
1242        };
1243
1244        let args_str = rest[..colon_pos].trim();
1245        let body_str = rest[colon_pos + 1..].trim();
1246
1247        // Parse arguments
1248        let args: Vec<String> = if args_str.is_empty() {
1249            vec![]
1250        } else {
1251            args_str.split(',').map(|a| a.trim().to_string()).collect()
1252        };
1253
1254        // Parse body
1255        let body = self.parse_expr(body_str)?;
1256
1257        Ok(PyExpr::Lambda {
1258            args,
1259            body: Box::new(body),
1260        })
1261    }
1262
1263    fn parse_try_except(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
1264        self.current_line += 1; // Skip "try:"
1265
1266        // Parse try body
1267        let body = self.parse_block(expected_indent + 1)?;
1268
1269        let mut handlers = vec![];
1270        let mut orelse = vec![];
1271        let mut finalbody = vec![];
1272
1273        // Parse except clauses and optional else/finally
1274        while self.current_line < self.lines.len() {
1275            let line = &self.lines[self.current_line];
1276            if line.indent_level != expected_indent {
1277                break;
1278            }
1279
1280            if line.content.starts_with("except") {
1281                self.current_line += 1;
1282
1283                // Parse: except ExceptionType as name:
1284                let except_str = line.content[6..line.content.len() - 1].trim();
1285                let (exc_type, name) = if except_str.is_empty() {
1286                    // bare except
1287                    (None, None)
1288                } else if let Some(as_pos) = except_str.find(" as ") {
1289                    let exc = Some(PyExpr::Name(except_str[..as_pos].trim().to_string()));
1290                    let var = Some(except_str[as_pos + 4..].trim().to_string());
1291                    (exc, var)
1292                } else {
1293                    (Some(PyExpr::Name(except_str.to_string())), None)
1294                };
1295
1296                let handler_body = self.parse_block(expected_indent + 1)?;
1297                handlers.push(ExceptHandler {
1298                    exception_type: exc_type,
1299                    name,
1300                    body: handler_body,
1301                });
1302            } else if line.content == "else:" {
1303                self.current_line += 1;
1304                orelse = self.parse_block(expected_indent + 1)?;
1305            } else if line.content == "finally:" {
1306                self.current_line += 1;
1307                finalbody = self.parse_block(expected_indent + 1)?;
1308                break; // finally is always last
1309            } else {
1310                break;
1311            }
1312        }
1313
1314        Ok(Some(PyStmt::Try {
1315            body,
1316            handlers,
1317            orelse,
1318            finalbody,
1319        }))
1320    }
1321
1322    fn parse_with_statement(&mut self, expected_indent: usize) -> Result<Option<PyStmt>> {
1323        let line = &self.lines[self.current_line].clone();
1324
1325        // Parse: with context_expr [as var]:
1326        let with_content = &line.content[5..line.content.len() - 1].trim();
1327
1328        // Parse with items (can have multiple: with open(f1) as f, open(f2) as g:)
1329        let mut items = Vec::new();
1330
1331        // Simple implementation: split by comma at depth 0
1332        let mut item_strings = Vec::new();
1333        let mut current = String::new();
1334        let mut depth = 0;
1335
1336        for ch in with_content.chars() {
1337            match ch {
1338                '(' | '[' | '{' => {
1339                    depth += 1;
1340                    current.push(ch);
1341                }
1342                ')' | ']' | '}' => {
1343                    depth -= 1;
1344                    current.push(ch);
1345                }
1346                ',' if depth == 0 => {
1347                    item_strings.push(current.trim().to_string());
1348                    current.clear();
1349                }
1350                _ => current.push(ch),
1351            }
1352        }
1353
1354        if !current.trim().is_empty() {
1355            item_strings.push(current.trim().to_string());
1356        }
1357
1358        // Parse each item
1359        for item_str in item_strings {
1360            let (context_expr_str, optional_vars) = if let Some(as_pos) = item_str.find(" as ") {
1361                let expr = item_str[..as_pos].trim();
1362                let var = Some(PyExpr::Name(item_str[as_pos + 4..].trim().to_string()));
1363                (expr, var)
1364            } else {
1365                (item_str.as_str(), None)
1366            };
1367
1368            let context_expr = self.parse_expr(context_expr_str)?;
1369            items.push(WithItem {
1370                context_expr,
1371                optional_vars,
1372            });
1373        }
1374
1375        self.current_line += 1;
1376
1377        // Parse body
1378        let body = self.parse_block(expected_indent + 1)?;
1379
1380        Ok(Some(PyStmt::With { items, body }))
1381    }
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386    use super::*;
1387
1388    #[test]
1389    fn test_if_statement() {
1390        let source = r#"
1391if x > 0:
1392    print("positive")
1393"#;
1394        let mut parser = IndentedPythonParser::new(source);
1395        let module = parser.parse().unwrap();
1396
1397        assert_eq!(module.statements.len(), 1);
1398        match &module.statements[0] {
1399            PyStmt::If { test, body, .. } => {
1400                assert_eq!(body.len(), 1);
1401            }
1402            _ => panic!("Expected if statement"),
1403        }
1404    }
1405
1406    #[test]
1407    fn test_if_else() {
1408        let source = r#"
1409if x > 0:
1410    y = 1
1411else:
1412    y = -1
1413"#;
1414        let mut parser = IndentedPythonParser::new(source);
1415        let module = parser.parse().unwrap();
1416
1417        assert_eq!(module.statements.len(), 1);
1418        match &module.statements[0] {
1419            PyStmt::If { body, orelse, .. } => {
1420                assert_eq!(body.len(), 1);
1421                assert_eq!(orelse.len(), 1);
1422            }
1423            _ => panic!("Expected if statement"),
1424        }
1425    }
1426
1427    #[test]
1428    fn test_for_loop() {
1429        let source = r#"
1430for i in range(10):
1431    print(i)
1432"#;
1433        let mut parser = IndentedPythonParser::new(source);
1434        let module = parser.parse().unwrap();
1435
1436        assert_eq!(module.statements.len(), 1);
1437        match &module.statements[0] {
1438            PyStmt::For { target, body, .. } => {
1439                assert_eq!(*target, PyExpr::Name("i".to_string()));
1440                assert_eq!(body.len(), 1);
1441            }
1442            _ => panic!("Expected for loop"),
1443        }
1444    }
1445
1446    #[test]
1447    fn test_while_loop() {
1448        let source = r#"
1449while x < 10:
1450    x = x + 1
1451"#;
1452        let mut parser = IndentedPythonParser::new(source);
1453        let module = parser.parse().unwrap();
1454
1455        assert_eq!(module.statements.len(), 1);
1456        match &module.statements[0] {
1457            PyStmt::While { test, body, .. } => {
1458                assert_eq!(body.len(), 1);
1459            }
1460            _ => panic!("Expected while loop"),
1461        }
1462    }
1463
1464    #[test]
1465    fn test_function_definition() {
1466        let source = r#"
1467def add(a, b):
1468    return a + b
1469"#;
1470        let mut parser = IndentedPythonParser::new(source);
1471        let module = parser.parse().unwrap();
1472
1473        assert_eq!(module.statements.len(), 1);
1474        match &module.statements[0] {
1475            PyStmt::FunctionDef { name, params, body, .. } => {
1476                assert_eq!(name, "add");
1477                assert_eq!(params.len(), 2);
1478                assert_eq!(body.len(), 1);
1479            }
1480            _ => panic!("Expected function definition"),
1481        }
1482    }
1483
1484    #[test]
1485    fn test_nested_blocks() {
1486        let source = r#"
1487if x > 0:
1488    if y > 0:
1489        print("both positive")
1490    else:
1491        print("x positive, y not")
1492"#;
1493        let mut parser = IndentedPythonParser::new(source);
1494        let module = parser.parse().unwrap();
1495
1496        assert_eq!(module.statements.len(), 1);
1497        match &module.statements[0] {
1498            PyStmt::If { body, .. } => {
1499                assert_eq!(body.len(), 1);
1500                // Inner if statement
1501                match &body[0] {
1502                    PyStmt::If { orelse, .. } => {
1503                        assert_eq!(orelse.len(), 1);
1504                    }
1505                    _ => panic!("Expected nested if"),
1506                }
1507            }
1508            _ => panic!("Expected if statement"),
1509        }
1510    }
1511
1512    #[test]
1513    fn test_import_statement() {
1514        let source = "import math";
1515        let mut parser = IndentedPythonParser::new(source);
1516        let module = parser.parse().unwrap();
1517
1518        assert_eq!(module.statements.len(), 1);
1519        match &module.statements[0] {
1520            PyStmt::Import { modules } => {
1521                assert_eq!(modules.len(), 1);
1522                assert_eq!(modules[0].0, "math");
1523            }
1524            _ => panic!("Expected import statement"),
1525        }
1526    }
1527
1528    #[test]
1529    fn test_from_import_statement() {
1530        let source = "from os import path";
1531        let mut parser = IndentedPythonParser::new(source);
1532        let module = parser.parse().unwrap();
1533
1534        assert_eq!(module.statements.len(), 1);
1535        match &module.statements[0] {
1536            PyStmt::ImportFrom { module, names, .. } => {
1537                assert_eq!(module, &Some("os".to_string()));
1538                assert_eq!(names.len(), 1);
1539                assert_eq!(names[0].0, "path");
1540            }
1541            _ => panic!("Expected from-import statement"),
1542        }
1543    }
1544}
1545
1546impl IndentedPythonParser {
1547    fn parse_import_statement(&mut self) -> Result<Option<PyStmt>> {
1548        let line = &self.lines[self.current_line].clone();
1549        self.current_line += 1;
1550
1551        // Parse "import module1 [as alias1], module2 [as alias2]"
1552        let import_str = line.content[7..].trim(); // Skip "import "
1553        let mut names = Vec::new();
1554        let mut aliases = Vec::new();
1555
1556        for part in import_str.split(',') {
1557            let part = part.trim();
1558            if part.contains(" as ") {
1559                let parts: Vec<&str> = part.split(" as ").collect();
1560                if parts.len() == 2 {
1561                    names.push(parts[0].trim().to_string());
1562                    aliases.push(Some(parts[1].trim().to_string()));
1563                }
1564            } else {
1565                names.push(part.to_string());
1566                aliases.push(None);
1567            }
1568        }
1569
1570        // Combine names and aliases into modules: Vec<(String, Option<String>)>
1571        let modules: Vec<(String, Option<String>)> = names.into_iter().zip(aliases.into_iter()).collect();
1572        Ok(Some(PyStmt::Import { modules }))
1573    }
1574
1575    fn parse_from_import_statement(&mut self) -> Result<Option<PyStmt>> {
1576        let line = &self.lines[self.current_line].clone();
1577        self.current_line += 1;
1578
1579        // Parse "from module import name1 [as alias1], name2 [as alias2]"
1580        let parts: Vec<&str> = line.content.split(" import ").collect();
1581        if parts.len() != 2 {
1582            #[cfg(target_arch = "wasm32")]
1583            return Err(Error::Parse(format!(
1584                "Invalid from-import statement: {}",
1585                line.content
1586            )));
1587
1588            #[cfg(not(target_arch = "wasm32"))]
1589            return Err(Error::CodeGeneration(format!(
1590                "Invalid from-import statement: {}",
1591                line.content
1592            )));
1593        }
1594
1595        let module = parts[0][5..].trim().to_string(); // Skip "from "
1596        let mut names = Vec::new();
1597        let mut aliases = Vec::new();
1598
1599        for part in parts[1].split(',') {
1600            let part = part.trim();
1601            if part.contains(" as ") {
1602                let alias_parts: Vec<&str> = part.split(" as ").collect();
1603                if alias_parts.len() == 2 {
1604                    names.push(alias_parts[0].trim().to_string());
1605                    aliases.push(Some(alias_parts[1].trim().to_string()));
1606                }
1607            } else {
1608                names.push(part.to_string());
1609                aliases.push(None);
1610            }
1611        }
1612
1613        // Combine names and aliases into names: Vec<(String, Option<String>)>
1614        let name_pairs: Vec<(String, Option<String>)> = names.into_iter().zip(aliases.into_iter()).collect();
1615        Ok(Some(PyStmt::ImportFrom {
1616            module: Some(module),
1617            names: name_pairs,
1618            level: 0  // Direct import, not relative
1619        }))
1620    }
1621}