Skip to main content

portalis_transpiler/
simple_parser.rs

1//! Simple Python parser for Low complexity features
2//!
3//! This is a simplified parser that handles the first 10 Low complexity features:
4//! 1. Integer literals
5//! 2. Float literals
6//! 3. String literals
7//! 4. Boolean literals
8//! 5. Simple assignment
9//! 6. Multiple assignment
10//! 7. Augmented assignment
11//! 8. Print function
12//! 9. Comments
13//! 10. Function docstrings
14
15use crate::python_ast::*;
16use crate::{Error, Result};
17
18pub struct SimplePythonParser {
19    lines: Vec<String>,
20    current_line: usize,
21}
22
23impl SimplePythonParser {
24    pub fn new(source: &str) -> Self {
25        let lines: Vec<String> = source.lines().map(|s| s.to_string()).collect();
26        Self {
27            lines,
28            current_line: 0,
29        }
30    }
31
32    pub fn parse(&mut self) -> Result<PyModule> {
33        let mut module = PyModule::new();
34
35        while self.current_line < self.lines.len() {
36            let line = self.lines[self.current_line].trim();
37
38            // Skip empty lines and comments
39            if line.is_empty() || line.starts_with('#') {
40                self.current_line += 1;
41                continue;
42            }
43
44            // Parse statement
45            if let Some(stmt) = self.parse_statement(line)? {
46                module.add_stmt(stmt);
47            }
48
49            self.current_line += 1;
50        }
51
52        Ok(module)
53    }
54
55    fn parse_statement(&self, line: &str) -> Result<Option<PyStmt>> {
56        // If statement: if condition:
57        if line.starts_with("if ") && line.ends_with(':') {
58            let condition_str = &line[3..line.len() - 1].trim();
59            let condition = self.parse_expr(condition_str)?;
60
61            return Ok(Some(PyStmt::If {
62                test: condition,
63                body: vec![], // Will be filled by multi-line parser
64                orelse: vec![],
65            }));
66        }
67
68        // Pass statement
69        if line == "pass" {
70            return Ok(Some(PyStmt::Pass));
71        }
72
73        // Break statement
74        if line == "break" {
75            return Ok(Some(PyStmt::Break));
76        }
77
78        // Continue statement
79        if line == "continue" {
80            return Ok(Some(PyStmt::Continue));
81        }
82
83        // Simple assignment: x = 42
84        if let Some(pos) = line.find('=') {
85            // Check it's not ==, !=, <=, >=
86            if pos > 0 && line.len() > pos + 1 {
87                let before = line.chars().nth(pos.saturating_sub(1));
88                let after = line.chars().nth(pos + 1);
89
90                if !matches!(before, Some('=') | Some('!') | Some('<') | Some('>'))
91                    && !matches!(after, Some('='))
92                {
93                    let target = line[..pos].trim().to_string();
94                    let value_str = line[pos + 1..].trim();
95
96                    // Check for augmented assignment
97                    if let Some(op_char) = target.chars().last() {
98                        if matches!(op_char, '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^') {
99                            let actual_target = target[..target.len() - 1].trim().to_string();
100                            let op = match op_char {
101                                '+' => BinOp::Add,
102                                '-' => BinOp::Sub,
103                                '*' => BinOp::Mult,
104                                '/' => BinOp::Div,
105                                '%' => BinOp::Mod,
106                                '&' => BinOp::BitAnd,
107                                '|' => BinOp::BitOr,
108                                '^' => BinOp::BitXor,
109                                _ => unreachable!(),
110                            };
111                            let value = self.parse_expr(value_str)?;
112
113                            return Ok(Some(PyStmt::AugAssign {
114                                target: PyExpr::Name(actual_target),
115                                op,
116                                value,
117                            }));
118                        }
119                    }
120
121                    // Simple assignment
122                    let value = self.parse_expr(value_str)?;
123                    return Ok(Some(PyStmt::Assign {
124                        target: PyExpr::Name(target),
125                        value,
126                    }));
127                }
128            }
129        }
130
131        // Print function: print(...)
132        if line.starts_with("print(") && line.ends_with(')') {
133            let args_str = &line[6..line.len() - 1];
134            let expr = self.parse_expr(args_str)?;
135
136            return Ok(Some(PyStmt::Expr(PyExpr::Call {
137                func: Box::new(PyExpr::Name("print".to_string())),
138                args: vec![expr],
139                kwargs: std::collections::HashMap::new(),
140            })));
141        }
142
143        Ok(None)
144    }
145
146    fn parse_expr(&self, s: &str) -> Result<PyExpr> {
147        let s = s.trim();
148
149        // Boolean literals
150        if s == "True" {
151            return Ok(PyExpr::Literal(PyLiteral::Bool(true)));
152        }
153        if s == "False" {
154            return Ok(PyExpr::Literal(PyLiteral::Bool(false)));
155        }
156
157        // None literal
158        if s == "None" {
159            return Ok(PyExpr::Literal(PyLiteral::None));
160        }
161
162        // List literals: [1, 2, 3]
163        if s.starts_with('[') && s.ends_with(']') {
164            let content = &s[1..s.len() - 1].trim();
165            if content.is_empty() {
166                return Ok(PyExpr::List(vec![]));
167            }
168            let elements: Result<Vec<_>> = content
169                .split(',')
170                .map(|e| self.parse_expr(e.trim()))
171                .collect();
172            return Ok(PyExpr::List(elements?));
173        }
174
175        // Tuple literals: (1, 2, 3)
176        if s.starts_with('(') && s.ends_with(')') {
177            let content = &s[1..s.len() - 1].trim();
178            if content.is_empty() {
179                return Ok(PyExpr::Tuple(vec![]));
180            }
181            let elements: Result<Vec<_>> = content
182                .split(',')
183                .map(|e| self.parse_expr(e.trim()))
184                .collect();
185            return Ok(PyExpr::Tuple(elements?));
186        }
187
188        // String literals
189        if (s.starts_with('"') && s.ends_with('"'))
190            || (s.starts_with('\'') && s.ends_with('\''))
191        {
192            let content = &s[1..s.len() - 1];
193            return Ok(PyExpr::Literal(PyLiteral::String(content.to_string())));
194        }
195
196        // Float literals (must check before int - has decimal point)
197        if s.contains('.') && s.parse::<f64>().is_ok() {
198            let value = s.parse::<f64>().unwrap();
199            return Ok(PyExpr::Literal(PyLiteral::Float(value)));
200        }
201
202        // Integer literals
203        if let Ok(value) = s.parse::<i64>() {
204            return Ok(PyExpr::Literal(PyLiteral::Int(value)));
205        }
206
207        // Logical operators (must check before comparison)
208        for (op_str, python_op) in &[
209            (" and ", "&&"),
210            (" or ", "||"),
211        ] {
212            if let Some(pos) = s.find(op_str) {
213                let left = self.parse_expr(&s[..pos])?;
214                let right = self.parse_expr(&s[pos + op_str.len()..])?;
215                // Store as BinOp for now, will be handled specially
216                let op = if *python_op == "&&" {
217                    BinOp::BitAnd // Reuse for logical and
218                } else {
219                    BinOp::BitOr // Reuse for logical or
220                };
221                return Ok(PyExpr::BinOp {
222                    left: Box::new(left),
223                    op,
224                    right: Box::new(right),
225                });
226            }
227        }
228
229        // Comparison operators
230        for (op_str, cmp_op) in &[
231            ("==", CmpOp::Eq),
232            ("!=", CmpOp::NotEq),
233            ("<=", CmpOp::LtE),
234            (">=", CmpOp::GtE),
235            ("<", CmpOp::Lt),
236            (">", CmpOp::Gt),
237        ] {
238            if let Some(pos) = s.find(op_str) {
239                let left = self.parse_expr(&s[..pos])?;
240                let right = self.parse_expr(&s[pos + op_str.len()..])?;
241                return Ok(PyExpr::Compare {
242                    left: Box::new(left),
243                    op: *cmp_op,
244                    right: Box::new(right),
245                });
246            }
247        }
248
249        // Unary not
250        if s.starts_with("not ") {
251            let operand = self.parse_expr(&s[4..])?;
252            return Ok(PyExpr::UnaryOp {
253                op: UnaryOp::Not,
254                operand: Box::new(operand),
255            });
256        }
257
258        // Binary arithmetic operations
259        for (op_str, op) in &[
260            (" + ", BinOp::Add),
261            (" - ", BinOp::Sub),
262            (" * ", BinOp::Mult),
263            (" / ", BinOp::Div),
264            (" % ", BinOp::Mod),
265        ] {
266            if let Some(pos) = s.find(op_str) {
267                let left = self.parse_expr(&s[..pos])?;
268                let right = self.parse_expr(&s[pos + op_str.len()..])?;
269                return Ok(PyExpr::BinOp {
270                    left: Box::new(left),
271                    op: *op,
272                    right: Box::new(right),
273                });
274            }
275        }
276
277        // List indexing: list[index]
278        if let Some(bracket_pos) = s.find('[') {
279            if s.ends_with(']') {
280                let value_str = &s[..bracket_pos];
281                let index_str = &s[bracket_pos + 1..s.len() - 1];
282                let value = self.parse_expr(value_str)?;
283                let index = self.parse_expr(index_str)?;
284                return Ok(PyExpr::Subscript {
285                    value: Box::new(value),
286                    index: Box::new(index),
287                });
288            }
289        }
290
291        // Variable name
292        if s.chars().all(|c| c.is_alphanumeric() || c == '_') {
293            return Ok(PyExpr::Name(s.to_string()));
294        }
295
296        Err(Error::CodeGeneration(format!(
297            "Unable to parse expression: {}",
298            s
299        )))
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_parse_integer_literal() {
309        let source = "x = 42";
310        let mut parser = SimplePythonParser::new(source);
311        let module = parser.parse().unwrap();
312
313        assert_eq!(module.statements.len(), 1);
314        match &module.statements[0] {
315            PyStmt::Assign { target, value } => {
316                assert_eq!(*target, PyExpr::Name("x".to_string()));
317                assert_eq!(*value, PyExpr::Literal(PyLiteral::Int(42)));
318            }
319            _ => panic!("Expected assignment"),
320        }
321    }
322
323    #[test]
324    fn test_parse_float_literal() {
325        let source = "pi = 3.14";
326        let mut parser = SimplePythonParser::new(source);
327        let module = parser.parse().unwrap();
328
329        assert_eq!(module.statements.len(), 1);
330        match &module.statements[0] {
331            PyStmt::Assign { value, .. } => {
332                assert_eq!(*value, PyExpr::Literal(PyLiteral::Float(3.14)));
333            }
334            _ => panic!("Expected assignment"),
335        }
336    }
337
338    #[test]
339    fn test_parse_string_literal() {
340        let source = r#"msg = "hello""#;
341        let mut parser = SimplePythonParser::new(source);
342        let module = parser.parse().unwrap();
343
344        assert_eq!(module.statements.len(), 1);
345        match &module.statements[0] {
346            PyStmt::Assign { value, .. } => {
347                assert_eq!(
348                    *value,
349                    PyExpr::Literal(PyLiteral::String("hello".to_string()))
350                );
351            }
352            _ => panic!("Expected assignment"),
353        }
354    }
355
356    #[test]
357    fn test_parse_boolean_literal() {
358        let source = "flag = True";
359        let mut parser = SimplePythonParser::new(source);
360        let module = parser.parse().unwrap();
361
362        assert_eq!(module.statements.len(), 1);
363        match &module.statements[0] {
364            PyStmt::Assign { value, .. } => {
365                assert_eq!(*value, PyExpr::Literal(PyLiteral::Bool(true)));
366            }
367            _ => panic!("Expected assignment"),
368        }
369    }
370
371    #[test]
372    fn test_parse_augmented_assignment() {
373        let source = "x += 5";
374        let mut parser = SimplePythonParser::new(source);
375        let module = parser.parse().unwrap();
376
377        assert_eq!(module.statements.len(), 1);
378        match &module.statements[0] {
379            PyStmt::AugAssign { target, op, value } => {
380                assert_eq!(*target, PyExpr::Name("x".to_string()));
381                assert_eq!(*op, BinOp::Add);
382                assert_eq!(*value, PyExpr::Literal(PyLiteral::Int(5)));
383            }
384            _ => panic!("Expected augmented assignment"),
385        }
386    }
387
388    #[test]
389    fn test_parse_print() {
390        let source = r#"print("hello")"#;
391        let mut parser = SimplePythonParser::new(source);
392        let module = parser.parse().unwrap();
393
394        assert_eq!(module.statements.len(), 1);
395        match &module.statements[0] {
396            PyStmt::Expr(PyExpr::Call { func, args, .. }) => {
397                assert_eq!(**func, PyExpr::Name("print".to_string()));
398                assert_eq!(args.len(), 1);
399            }
400            _ => panic!("Expected print call"),
401        }
402    }
403
404    #[test]
405    fn test_skip_comments() {
406        let source = "# This is a comment\nx = 42\n# Another comment";
407        let mut parser = SimplePythonParser::new(source);
408        let module = parser.parse().unwrap();
409
410        assert_eq!(module.statements.len(), 1); // Only the assignment, comments skipped
411    }
412
413    #[test]
414    fn test_parse_binary_operation() {
415        let source = "result = 2 + 3";
416        let mut parser = SimplePythonParser::new(source);
417        let module = parser.parse().unwrap();
418
419        assert_eq!(module.statements.len(), 1);
420        match &module.statements[0] {
421            PyStmt::Assign { value, .. } => match value {
422                PyExpr::BinOp { left, op, right } => {
423                    assert_eq!(**left, PyExpr::Literal(PyLiteral::Int(2)));
424                    assert_eq!(*op, BinOp::Add);
425                    assert_eq!(**right, PyExpr::Literal(PyLiteral::Int(3)));
426                }
427                _ => panic!("Expected binary operation"),
428            },
429            _ => panic!("Expected assignment"),
430        }
431    }
432}