spydecy_python/
hir_converter.rs1use crate::parser::PythonAST;
6use anyhow::{bail, Result};
7use spydecy_hir::{
8 metadata::Metadata,
9 python::{Literal, PythonHIR},
10 NodeId, Visibility,
11};
12
13pub 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
35fn 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
48fn 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
74fn 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
90fn 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#[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#[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, 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}