spydecy_python/
hir_converter.rs

1//! Python AST to HIR converter
2//!
3//! This module converts Python AST nodes into Spydecy's Python HIR.
4
5use crate::parser::PythonAST;
6use anyhow::{bail, Result};
7use spydecy_hir::{
8    metadata::Metadata,
9    python::{Literal, PythonHIR},
10    NodeId, Visibility,
11};
12
13/// Convert Python AST to HIR
14///
15/// # Errors
16///
17/// Returns an error if the AST cannot be converted to HIR
18pub fn convert_to_hir(ast: &PythonAST) -> Result<PythonHIR> {
19    let mut id_counter = 1;
20    convert_node(ast, &mut id_counter)
21}
22
23fn convert_node(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
24    match ast.node_type.as_str() {
25        "Module" => convert_module(ast, id_counter),
26        "FunctionDef" => convert_function_def(ast, id_counter),
27        "Return" => convert_return(ast, id_counter),
28        "Call" => convert_call(ast, id_counter),
29        "Name" => convert_name(ast, id_counter),
30        "Constant" => convert_constant(id_counter),
31        _ => bail!("Unsupported Python AST node type: {}", ast.node_type),
32    }
33}
34
35/// Convert Module node
36fn convert_module(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
37    let mut body = Vec::new();
38    for child in &ast.children {
39        body.push(convert_node(child, id_counter)?);
40    }
41    Ok(PythonHIR::Module {
42        name: "main".to_string(),
43        body,
44        meta: Metadata::new(),
45    })
46}
47
48/// Convert FunctionDef node
49fn convert_function_def(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
50    let name = ast
51        .attributes
52        .get("name")
53        .cloned()
54        .unwrap_or_else(|| "unknown".to_string());
55
56    let mut body = Vec::new();
57    for child in &ast.children {
58        body.push(convert_node(child, id_counter)?);
59    }
60
61    let id = next_id(id_counter);
62    Ok(PythonHIR::Function {
63        id,
64        name,
65        params: vec![],
66        return_type: None,
67        body,
68        decorators: vec![],
69        visibility: Visibility::Public,
70        meta: Metadata::new(),
71    })
72}
73
74/// Convert Return node
75fn convert_return(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
76    let value = if ast.children.is_empty() {
77        None
78    } else {
79        Some(Box::new(convert_node(&ast.children[0], id_counter)?))
80    };
81
82    let id = next_id(id_counter);
83    Ok(PythonHIR::Return {
84        id,
85        value,
86        meta: Metadata::new(),
87    })
88}
89
90/// Convert Call node
91fn convert_call(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
92    if ast.children.is_empty() {
93        bail!("Call node must have at least one child (the callee)");
94    }
95
96    let callee = Box::new(convert_node(&ast.children[0], id_counter)?);
97
98    let mut args = Vec::new();
99    for child in &ast.children[1..] {
100        args.push(convert_node(child, id_counter)?);
101    }
102
103    let id = next_id(id_counter);
104    Ok(PythonHIR::Call {
105        id,
106        callee,
107        args,
108        kwargs: vec![],
109        inferred_type: None,
110        meta: Metadata::new(),
111    })
112}
113
114/// Convert Name node
115#[allow(clippy::unnecessary_wraps)]
116fn convert_name(ast: &PythonAST, id_counter: &mut u64) -> Result<PythonHIR> {
117    let name = ast
118        .attributes
119        .get("id")
120        .cloned()
121        .unwrap_or_else(|| "unknown".to_string());
122
123    let id = next_id(id_counter);
124    Ok(PythonHIR::Variable {
125        id,
126        name,
127        inferred_type: None,
128        meta: Metadata::new(),
129    })
130}
131
132/// Convert Constant node
133#[allow(clippy::unnecessary_wraps)]
134fn convert_constant(id_counter: &mut u64) -> Result<PythonHIR> {
135    let id = next_id(id_counter);
136    Ok(PythonHIR::Literal {
137        id,
138        value: Literal::None, // Placeholder
139        meta: Metadata::new(),
140    })
141}
142
143fn next_id(counter: &mut u64) -> NodeId {
144    let id = NodeId::new(*counter);
145    *counter += 1;
146    id
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_convert_simple_function() {
155        let mut ast = PythonAST::new("Module".to_string());
156        let mut func = PythonAST::new("FunctionDef".to_string());
157        func.attributes
158            .insert("name".to_string(), "my_len".to_string());
159        ast.children.push(func);
160
161        let hir = convert_to_hir(&ast).unwrap();
162
163        if let PythonHIR::Module { body, .. } = hir {
164            assert_eq!(body.len(), 1);
165        } else {
166            panic!("Expected Module");
167        }
168    }
169
170    #[test]
171    fn test_convert_function_with_return() {
172        let mut module = PythonAST::new("Module".to_string());
173        let mut func = PythonAST::new("FunctionDef".to_string());
174        func.attributes
175            .insert("name".to_string(), "test".to_string());
176
177        let ret = PythonAST::new("Return".to_string());
178        func.children.push(ret);
179        module.children.push(func);
180
181        let hir = convert_to_hir(&module).unwrap();
182        assert!(matches!(hir, PythonHIR::Module { .. }));
183    }
184}