1use crate::{dump, error_to_pyerr, parsing_error, Module, Name, Result as CrateResult, SourceLocation, *};
2
3use pyo3::prelude::*;
4use std::ffi::CString;
5
6use std::path::MAIN_SEPARATOR;
7
8fn parse_to_py(
10 input: impl AsRef<str>,
11 filename: impl AsRef<str>,
12 py: Python<'_>,
13) -> PyResult<Py<PyAny>> {
14 let pymodule_code = include_str!("__init__.py");
15
16 let code_cstr = CString::new(pymodule_code)?;
18 let pymodule = PyModule::from_code(py, &code_cstr, c"__init__.py", c"parser")?;
19 let t = pymodule.getattr("parse")?;
20 assert!(t.is_callable());
21 let args = (input.as_ref(), filename.as_ref());
22
23 let py_tree = t.call1(args)?;
24 tracing::debug!("py_tree: {}", dump(&py_tree, Some(4))?);
25
26 Ok(py_tree.into())
27}
28
29pub fn parse_enhanced(input: impl AsRef<str>, filename: impl AsRef<str>) -> CrateResult<Module> {
50 let filename = filename.as_ref();
51 let input_str = input.as_ref();
52 let location = SourceLocation::new(filename);
53
54 let mut module: Module = Python::attach(|py| {
57 let py_tree = parse_to_py(input_str, filename, py)
58 .map_err(|py_err| {
59 let help_msg = if format!("{}", py_err).contains("IndentationError") {
65 "Fix indentation issues. Python requires consistent indentation (use either spaces or tabs, not both)."
66 } else if format!("{}", py_err).contains("SyntaxError") {
67 "Check your Python syntax. Common issues include missing colons, incorrect indentation, or unclosed brackets."
68 } else {
69 "Ensure the input contains valid Python code. Check for syntax errors or unsupported constructs."
70 };
71
72 parsing_error(
76 location.clone(),
77 format!("Python parsing failed: {}", py_err),
78 help_msg,
79 )
80 .with_field_debug("py_err", &py_err)
81 })?;
82
83 py_tree.extract(py)
84 .map_err(|py_err: pyo3::PyErr| {
85 let cause = py_err
89 .value(py)
90 .str()
91 .map(|s| s.to_string())
92 .unwrap_or_else(|_| py_err.to_string());
93 parsing_error(
94 location.clone(),
95 cause,
96 "This Python construct is not yet supported by rython. Rewrite it using supported constructs, or file an issue."
97 )
98 .with_field_debug("py_err", &py_err)
99 })
100 })?;
101
102 module.filename = Some(filename.into());
103
104 if let Some(name_str) = filename.replace(MAIN_SEPARATOR, "__").strip_suffix(".py") {
105 module.name = Some(Name::try_from(name_str).map_err(|_| {
106 parsing_error(
107 location,
108 "Invalid module name derived from filename",
109 "Use a valid Python identifier for the filename (without special characters except underscores).",
110 )
111 .with_field("name", name_str)
112 })?);
113 }
114
115 tracing::debug!("module: {:#?}", module);
116 for item in module.__dir__() {
117 tracing::debug!("module.__dir__: {:#?}", item.as_ref());
118 }
119 Ok(module)
120}
121
122pub fn parse(input: impl AsRef<str>, filename: impl AsRef<str>) -> PyResult<Module> {
142 parse_enhanced(input, filename).map_err(error_to_pyerr)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_parse_simple_expression() {
152 let code = "1 + 2";
153 let result = parse(code, "test.py");
154 assert!(result.is_ok());
155
156 let module = result.unwrap();
157 assert!(module.filename.is_some());
158 assert_eq!(module.filename.as_ref().unwrap(), "test.py");
159 assert!(!module.raw.body.is_empty());
160 }
161
162 #[test]
163 fn test_parse_function_definition() {
164 let code = r#"
165def hello_world():
166 return "Hello, World!"
167"#;
168 let result = parse(code, "function_test.py");
169 assert!(result.is_ok());
170
171 let module = result.unwrap();
172 assert_eq!(module.raw.body.len(), 1);
173 }
174
175 #[test]
176 fn test_parse_class_definition() {
177 let code = r#"
178class TestClass:
179 def __init__(self):
180 self.value = 42
181
182 def get_value(self):
183 return self.value
184"#;
185 let result = parse(code, "class_test.py");
186 assert!(result.is_ok());
187
188 let module = result.unwrap();
189 assert_eq!(module.raw.body.len(), 1);
190 }
191
192 #[test]
193 fn test_parse_import_statements() {
194 let code = r#"
195import os
196import sys
197from collections import defaultdict
198from typing import List, Dict
199"#;
200 let result = parse(code, "import_test.py");
201 assert!(result.is_ok());
202
203 let module = result.unwrap();
204 assert_eq!(module.raw.body.len(), 4);
205 }
206
207 #[test]
208 fn test_parse_complex_expressions() {
209 let code = r#"
210result = [x**2 for x in range(10) if x % 2 == 0]
211data = {"key": value for key, value in items.items()}
212condition = (a > b) and (c < d) or (e == f)
213"#;
214 let result = parse(code, "expressions_test.py");
215 assert!(result.is_ok());
216
217 let module = result.unwrap();
218 assert_eq!(module.raw.body.len(), 3);
219 }
220
221 #[test]
222 fn test_parse_control_flow() {
223 let code = r#"
224if condition:
225 for i in range(10):
226 if i % 2 == 0:
227 continue
228 else:
229 break
230else:
231 while True:
232 try:
233 do_something()
234 except Exception as e:
235 handle_error(e)
236 finally:
237 cleanup()
238"#;
239 let result = parse(code, "control_flow_test.py");
240 assert!(result.is_ok());
241
242 let module = result.unwrap();
243 assert_eq!(module.raw.body.len(), 1);
244 }
245
246 #[test]
247 fn test_parse_async_code() {
248 let code = r#"
249async def async_function():
250 async with async_context():
251 result = await some_async_operation()
252 async for item in async_iterator:
253 yield item
254"#;
255 let result = parse(code, "async_test.py");
256 assert!(result.is_ok());
257
258 let module = result.unwrap();
259 assert_eq!(module.raw.body.len(), 1);
260 }
261
262 #[test]
263 fn test_parse_decorators() {
264 let code = r#"
265@decorator
266@another_decorator(arg1, arg2)
267def decorated_function():
268 pass
269
270@property
271def getter(self):
272 return self._value
273"#;
274 let result = parse(code, "decorators_test.py");
275 assert!(result.is_ok());
276
277 let module = result.unwrap();
278 assert_eq!(module.raw.body.len(), 2);
279 }
280
281 #[test]
282 fn test_parse_invalid_syntax() {
283 let code = "def invalid_function("; let result = parse(code, "invalid.py");
285 assert!(result.is_err());
286 }
287
288 #[test]
289 fn test_parse_empty_file() {
290 let code = "";
291 let result = parse(code, "empty.py");
292 assert!(result.is_ok());
293
294 let module = result.unwrap();
295 assert!(module.raw.body.is_empty());
296 }
297
298 #[test]
299 fn test_parse_comments_and_docstrings() {
300 let code = r#"
301"""Module docstring"""
302# This is a comment
303def function_with_docstring():
304 """Function docstring"""
305 pass # Another comment
306"#;
307 let result = parse(code, "comments_test.py");
308 assert!(result.is_ok());
309
310 let module = result.unwrap();
311 assert_eq!(module.raw.body.len(), 2); }
313
314 #[test]
315 fn test_module_name_generation() {
316 let result = parse("x = 1", "some_file.py");
317 assert!(result.is_ok());
318
319 let module = result.unwrap();
320 assert!(module.name.is_some());
321 assert_eq!(module.name.unwrap().id, "some_file");
322 }
323
324 #[test]
325 fn test_module_name_with_path_separators() {
326 let code = "x = 1";
327 let filename = format!("path{}to{}module.py", std::path::MAIN_SEPARATOR, std::path::MAIN_SEPARATOR);
328 let result = parse(code, &filename);
329 assert!(result.is_ok());
330
331 let module = result.unwrap();
332 assert!(module.name.is_some());
333 assert_eq!(module.name.unwrap().id, "path__to__module");
334 }
335}