1use 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 if line.content.is_empty() || line.content.starts_with('#') {
55 self.current_line += 1;
56 continue;
57 }
58
59 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 if line.indent_level != expected_indent {
77 return Ok(None);
78 }
79
80 if line.content.starts_with("if ") && line.content.ends_with(':') {
82 return self.parse_if_statement(expected_indent);
83 }
84
85 if line.content.starts_with("while ") && line.content.ends_with(':') {
87 return self.parse_while_loop(expected_indent);
88 }
89
90 if line.content.starts_with("for ") && line.content.ends_with(':') {
92 return self.parse_for_loop(expected_indent);
93 }
94
95 if line.content.starts_with('@') {
97 return self.parse_decorator(expected_indent);
98 }
99
100 if line.content.starts_with("class ") && line.content.ends_with(':') {
102 return self.parse_class_def(expected_indent);
103 }
104
105 if line.content.starts_with("async def ") && line.content.ends_with(':') {
107 return self.parse_function_def(expected_indent);
108 }
109
110 if line.content.starts_with("def ") && line.content.ends_with(':') {
112 return self.parse_function_def(expected_indent);
113 }
114
115 if line.content.starts_with("import ") {
117 return self.parse_import_statement();
118 }
119
120 if line.content.starts_with("from ") && line.content.contains(" import ") {
122 return self.parse_from_import_statement();
123 }
124
125 if line.content == "try:" {
127 return self.parse_try_except(expected_indent);
128 }
129
130 if line.content.starts_with("with ") && line.content.ends_with(':') {
132 return self.parse_with_statement(expected_indent);
133 }
134
135 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 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 if line.content.starts_with("assert ") {
159 self.current_line += 1;
160 let rest = line.content[7..].trim(); 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 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 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 let body = self.parse_block(expected_indent + 1)?;
198
199 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 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 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 if let Some(in_pos) = for_content.find(" in ") {
260 let target_str = for_content[..in_pos].trim();
261
262 let target = if target_str.contains(',') {
264 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 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 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 let is_async = line.content.starts_with("async def ");
319 let def_start = if is_async { 10 } else { 4 }; let def_content = &line.content[def_start..line.content.len() - 1].trim();
321
322 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 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 let (name, bases) = if let Some(paren_pos) = class_content.find('(') {
371 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 (class_content.to_string(), vec![] as Vec<PyExpr>)
390 };
391
392 self.current_line += 1;
393
394 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 let decorator_str = &line.content[1..].trim(); 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 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 if line.content.is_empty() || line.content.starts_with('#') {
433 self.current_line += 1;
434 continue;
435 }
436
437 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 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 if line == "pass" {
493 return Ok(Some(PyStmt::Pass));
494 }
495
496 if line == "break" {
498 return Ok(Some(PyStmt::Break));
499 }
500
501 if line == "continue" {
503 return Ok(Some(PyStmt::Continue));
504 }
505
506 if line.contains('=') && !line.contains("==") && !line.contains("!=")
510 && !line.contains("<=") && !line.contains(">=") {
511
512 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 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 let target_str = parts[parts.len() - 2].trim();
548 let value = self.parse_expr(last_part)?;
549
550 let target = self.parse_expr(target_str)?;
552
553 return Ok(Some(PyStmt::Assign {
555 target,
556 value,
557 }));
558 }
559 }
560
561 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 if line.contains('(') && line.ends_with(')') {
574 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 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 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 if s.starts_with("lambda ") {
621 return self.parse_lambda(s);
622 }
623
624 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 if s == "None" {
634 return Ok(PyExpr::Literal(PyLiteral::None));
635 }
636
637 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 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 if content.contains(" for ") {
661 return self.parse_list_comprehension(content);
662 }
663
664 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 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 let mut keys = vec![];
710 let mut values = vec![];
711
712 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 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 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 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 if s.contains('.') && s.parse::<f64>().is_ok() {
802 return Ok(PyExpr::Literal(PyLiteral::Float(s.parse().unwrap())));
803 }
804
805 if let Ok(value) = s.parse::<i64>() {
807 return Ok(PyExpr::Literal(PyLiteral::Int(value)));
808 }
809
810 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 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 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 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 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 if index_str.contains(':') {
887 return self.parse_slice(value, index_str);
888 }
889
890 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 if let Some(paren_pos) = s.find('(') {
901 if s.ends_with(')') {
902 let func_name = &s[..paren_pos];
903 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 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 if s.starts_with('-') && s.len() > 1 {
953 let operand_str = &s[1..].trim();
954 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 if let Some(dot_pos) = s.find('.') {
966 let value_str = &s[..dot_pos];
967 let attr_part = &s[dot_pos + 1..];
968
969 if let Some(paren_pos) = attr_part.rfind('(') {
971 if attr_part.ends_with(')') {
972 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 (&attr_part[..last_dot], &attr_part[last_dot + 1..paren_pos])
977 } else {
978 ("", &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 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 let mut base = self.parse_expr(value_str)?;
1033
1034 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 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 let value = self.parse_expr(value_str)?;
1060
1061 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 if s.contains(',') && !s.starts_with('(') {
1087 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 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 if !current.trim().is_empty() {
1129 elements.push(current.trim().to_string());
1130 }
1131
1132 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 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 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(); 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(); 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 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 let rest = &s[7..].trim(); 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 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 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; 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 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 let except_str = line.content[6..line.content.len() - 1].trim();
1285 let (exc_type, name) = if except_str.is_empty() {
1286 (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; } 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 let with_content = &line.content[5..line.content.len() - 1].trim();
1327
1328 let mut items = Vec::new();
1330
1331 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 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 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 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 let import_str = line.content[7..].trim(); 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 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 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(); 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 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 }))
1620 }
1621}