oxur_comp/
lowering.rs

1//! Stage 3: Lower
2//!
3//! Converts Core Forms into Rust AST using the syn crate.
4
5use crate::Result;
6use oxur_lang::{CoreForm, NodeId};
7use std::collections::HashMap;
8use syn::{Block, FnArg};
9
10/// Lowerer converts Core Forms to Rust AST
11pub struct Lowerer {
12    source_map: oxur_smap::SourceMap,
13    #[allow(dead_code)] // Maintained for potential future use
14    node_map: HashMap<NodeId, syn::Expr>,
15}
16
17impl Lowerer {
18    pub fn new(source_map: oxur_smap::SourceMap) -> Self {
19        Self { source_map, node_map: HashMap::new() }
20    }
21
22    /// Lower Core Forms to Rust AST
23    ///
24    /// Returns the generated syn::File and the complete SourceMap with both
25    /// Surface → Core mappings (from expansion) and Core → Rust mappings (from lowering).
26    pub fn lower(&mut self, forms: Vec<CoreForm>) -> Result<(syn::File, oxur_smap::SourceMap)> {
27        let mut items = Vec::new();
28
29        for form in forms {
30            items.push(self.lower_top_level(form)?);
31        }
32
33        // Freeze the source map (no more modifications)
34        self.source_map.freeze();
35
36        Ok((syn::File { shebang: None, attrs: vec![], items }, self.source_map.clone()))
37    }
38
39    fn lower_top_level(&mut self, form: CoreForm) -> Result<syn::Item> {
40        match form {
41            CoreForm::DefineFunc { name, params, body, id } => {
42                self.lower_function(name, params, *body, id)
43            }
44            _ => Err(crate::Error::Lowering(
45                "Only function definitions are supported at top level".to_string(),
46            )),
47        }
48    }
49
50    fn lower_function(
51        &mut self,
52        name: String,
53        params: Vec<String>,
54        body: CoreForm,
55        id: NodeId,
56    ) -> Result<syn::Item> {
57        use quote::format_ident;
58        use syn::{ItemFn, ReturnType, Signature};
59
60        // Generate virtual Rust NodeId for this function
61        let rust_id = oxur_smap::new_node_id();
62        self.source_map.record_lowering(id, rust_id);
63
64        // Create function signature
65        let fn_name = format_ident!("{}", name);
66        let inputs = self.lower_params(params)?;
67
68        let sig = Signature {
69            constness: None,
70            asyncness: None,
71            unsafety: None,
72            abi: None,
73            fn_token: Default::default(),
74            ident: fn_name,
75            generics: Default::default(),
76            paren_token: Default::default(),
77            inputs,
78            variadic: None,
79            output: ReturnType::Default,
80        };
81
82        // Create function body
83        let block = self.lower_block(body)?;
84
85        Ok(syn::Item::Fn(ItemFn {
86            attrs: vec![],
87            vis: syn::Visibility::Inherited,
88            sig,
89            block: Box::new(block),
90        }))
91    }
92
93    fn lower_params(
94        &self,
95        _params: Vec<String>,
96    ) -> Result<syn::punctuated::Punctuated<FnArg, syn::Token![,]>> {
97        // For now, empty params (we'll handle typed params later)
98        Ok(syn::punctuated::Punctuated::new())
99    }
100
101    fn lower_block(&mut self, body: CoreForm) -> Result<Block> {
102        use syn::parse_quote;
103
104        // Convert the body CoreForm into a statement
105        let stmt = self.lower_to_stmt(body)?;
106
107        Ok(parse_quote! {
108            {
109                #stmt
110            }
111        })
112    }
113
114    fn lower_to_stmt(&mut self, form: CoreForm) -> Result<syn::Stmt> {
115        match form {
116            CoreForm::List { elements, id } => {
117                // Generate virtual Rust NodeId for this list
118                let rust_id = oxur_smap::new_node_id();
119                self.source_map.record_lowering(id, rust_id);
120
121                // Check if this is a macro call (like println!)
122                if !elements.is_empty() {
123                    if let CoreForm::Symbol { name, .. } = &elements[0] {
124                        if name.ends_with('!') {
125                            return self.lower_macro_call(name.clone(), elements[1..].to_vec());
126                        }
127                    }
128                }
129                Err(crate::Error::Lowering("Unsupported list form".to_string()))
130            }
131            _ => Err(crate::Error::Lowering(
132                "Only macro calls supported in function body for now".to_string(),
133            )),
134        }
135    }
136
137    fn lower_macro_call(&mut self, macro_name: String, args: Vec<CoreForm>) -> Result<syn::Stmt> {
138        use quote::format_ident;
139        use syn::{parse_quote, StmtMacro};
140
141        // Remove the '!' from macro name
142        let name = macro_name.trim_end_matches('!');
143        let macro_ident = format_ident!("{}", name);
144
145        // Convert arguments to token stream
146        let arg_tokens = self.lower_macro_args(args)?;
147
148        // Create the macro call
149        let mac: syn::Macro = parse_quote! {
150            #macro_ident!(#arg_tokens)
151        };
152
153        Ok(syn::Stmt::Macro(StmtMacro { attrs: vec![], mac, semi_token: Some(Default::default()) }))
154    }
155
156    fn lower_macro_args(&mut self, args: Vec<CoreForm>) -> Result<proc_macro2::TokenStream> {
157        use quote::quote;
158
159        if args.is_empty() {
160            return Ok(quote! {});
161        }
162
163        // For now, just handle a single string argument (for println!)
164        if args.len() == 1 {
165            if let CoreForm::String { value, id } = &args[0] {
166                // Generate virtual Rust NodeId for this string literal
167                let rust_id = oxur_smap::new_node_id();
168                self.source_map.record_lowering(*id, rust_id);
169
170                let string_lit = value.as_str();
171                return Ok(quote! { #string_lit });
172            }
173        }
174
175        Err(crate::Error::Lowering("Only single string arguments supported for macros".to_string()))
176    }
177}
178
179// Note: No Default implementation - Lowerer requires a SourceMap
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_lowerer_creation() {
187        let source_map = oxur_smap::SourceMap::new();
188        let lowerer = Lowerer::new(source_map);
189        assert_eq!(lowerer.node_map.len(), 0);
190    }
191
192    #[test]
193    fn test_lower_empty() {
194        let source_map = oxur_smap::SourceMap::new();
195        let mut lowerer = Lowerer::new(source_map);
196        let result = lowerer.lower(vec![]);
197        assert!(result.is_ok());
198        let (file, _source_map) = result.unwrap();
199        assert_eq!(file.items.len(), 0);
200    }
201
202    #[test]
203    fn test_lower_returns_syn_file() {
204        let source_map = oxur_smap::SourceMap::new();
205        let mut lowerer = Lowerer::new(source_map);
206        let result = lowerer.lower(vec![]);
207        assert!(result.is_ok());
208        let (file, _source_map) = result.unwrap();
209        assert!(file.shebang.is_none());
210        assert_eq!(file.attrs.len(), 0);
211    }
212
213    #[test]
214    fn test_lower_hello_world() {
215        use oxur_lang::{Expander, Parser};
216
217        // Parse and expand the Oxur code
218        let source = r#"(deffn main ()
219  (println! "Hello, world!"))"#;
220
221        let mut parser = Parser::new(source.to_string());
222        let surface_forms = parser.parse().unwrap();
223
224        let mut expander = Expander::new();
225        let core_forms = expander.expand(surface_forms).unwrap();
226        let source_map = expander.source_map().clone();
227
228        // Lower to Rust AST
229        let mut lowerer = Lowerer::new(source_map);
230        let result = lowerer.lower(core_forms);
231
232        assert!(result.is_ok());
233        let (file, _source_map) = result.unwrap();
234
235        // Should have one item (the main function)
236        assert_eq!(file.items.len(), 1);
237
238        // Check it's a function
239        if let syn::Item::Fn(item_fn) = &file.items[0] {
240            assert_eq!(item_fn.sig.ident.to_string(), "main");
241            assert_eq!(item_fn.sig.inputs.len(), 0); // No parameters
242
243            // Should have one statement in the body
244            assert_eq!(item_fn.block.stmts.len(), 1);
245
246            // Should be a macro call
247            if let syn::Stmt::Macro(stmt_mac) = &item_fn.block.stmts[0] {
248                // Check it's println!
249                let path = &stmt_mac.mac.path;
250                assert_eq!(quote::quote!(#path).to_string(), "println");
251            } else {
252                panic!("Expected macro statement");
253            }
254        } else {
255            panic!("Expected function item");
256        }
257    }
258
259    #[test]
260    fn test_source_map_function_mapping() {
261        use oxur_lang::{Expander, Parser};
262
263        let source = r#"(deffn main ()
264  (println! "Hello"))"#;
265        let mut parser = Parser::new(source.to_string());
266        let surface_forms = parser.parse().unwrap();
267
268        let mut expander = Expander::new();
269        let core_forms = expander.expand(surface_forms).unwrap();
270        let source_map = expander.source_map().clone();
271
272        // Lower with the source map
273        let mut lowerer = Lowerer::new(source_map);
274        let result = lowerer.lower(core_forms);
275        assert!(result.is_ok(), "Lowering failed: {:?}", result.err());
276
277        let (_file, source_map) = result.unwrap();
278        let stats = source_map.stats();
279
280        // Should have surface nodes from Stage 1.7
281        assert!(stats.surface_nodes > 0, "Should have surface node mappings");
282
283        // Should have lowering mappings from Stage 1.8
284        assert!(stats.lowerings > 0, "Should have lowering mappings");
285    }
286
287    #[test]
288    fn test_source_map_preserved_through_lowering() {
289        use oxur_lang::{Expander, Parser};
290
291        let source = r#"(deffn main ()
292  (println! "Hello, world!"))"#;
293
294        let mut parser = Parser::new(source.to_string());
295        let surface_forms = parser.parse().unwrap();
296
297        let mut expander = Expander::new();
298        let core_forms = expander.expand(surface_forms).unwrap();
299
300        // Get the Core NodeId for verification
301        let core_id = if let oxur_lang::CoreForm::DefineFunc { id, .. } = &core_forms[0] {
302            *id
303        } else {
304            panic!("Expected DefineFunc");
305        };
306
307        let source_map = expander.source_map().clone();
308        let surface_pos = source_map.get_surface_position(&core_id);
309        assert!(surface_pos.is_some(), "Core node should have surface position");
310
311        // Lower and verify lowering mapping added
312        let mut lowerer = Lowerer::new(source_map);
313        let result = lowerer.lower(core_forms);
314        assert!(result.is_ok());
315
316        let (_file, final_source_map) = result.unwrap();
317
318        // Should still have the surface position
319        let surface_pos_after = final_source_map.get_surface_position(&core_id);
320        assert!(surface_pos_after.is_some(), "Surface position should be preserved");
321
322        // Should have lowering mapping
323        let stats = final_source_map.stats();
324        assert!(stats.lowerings > 0, "Should have lowering mappings");
325    }
326
327    #[test]
328    fn test_source_map_frozen_after_lowering() {
329        use oxur_lang::{Expander, Parser};
330
331        let source = r#"(deffn main ()
332  (println! "Test"))"#;
333        let mut parser = Parser::new(source.to_string());
334        let surface_forms = parser.parse().unwrap();
335
336        let mut expander = Expander::new();
337        let core_forms = expander.expand(surface_forms).unwrap();
338        let source_map = expander.source_map().clone();
339
340        let mut lowerer = Lowerer::new(source_map);
341        let result = lowerer.lower(core_forms);
342        assert!(result.is_ok(), "Lowering failed: {:?}", result.err());
343
344        let (_file, source_map) = result.unwrap();
345        assert!(source_map.is_frozen(), "SourceMap should be frozen after lowering");
346    }
347}