1use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum WrapperError {
15 #[error("Invalid code structure: {0}")]
16 InvalidCode(String),
17
18 #[error("Variable name conflict: {0}")]
19 NameConflict(String),
20}
21
22pub type Result<T> = std::result::Result<T, WrapperError>;
23
24#[derive(Debug, Clone)]
44pub struct RustAstWrapper {
45 debug: bool,
47}
48
49impl RustAstWrapper {
50 pub fn new() -> Self {
52 Self { debug: false }
53 }
54
55 pub fn with_debug(mut self, debug: bool) -> Self {
57 self.debug = debug;
58 self
59 }
60
61 pub fn wrap(
76 &self,
77 cache_key: impl AsRef<str>,
78 user_code: impl AsRef<str>,
79 source_map: Option<&oxur_smap::SourceMap>,
80 ) -> Result<String> {
81 let cache_key = cache_key.as_ref();
82 let user_code = user_code.as_ref();
83
84 if !is_valid_identifier(cache_key) {
86 return Err(WrapperError::InvalidCode(format!(
87 "Cache key must be valid identifier: {}",
88 cache_key
89 )));
90 }
91
92 let mut user_stmts = self.parse_user_code(user_code)?;
94
95 let needs_result_capture = if let Some(last_stmt) = user_stmts.last() {
97 matches!(last_stmt, syn::Stmt::Expr(_, None))
98 } else {
99 false
100 };
101
102 if needs_result_capture {
104 if let Some(syn::Stmt::Expr(expr, None)) = user_stmts.pop() {
105 let result_capture = quote::quote! {
108 {
109 let __result_value = #expr;
110 let __result_string = format!("{:?}", __result_value);
111 unsafe {
113 OXUR_RESULT_BUFFER = Some(__result_string);
114 }
115 }
116 };
117
118 let result_stmt: syn::Stmt = syn::parse2(result_capture).map_err(|e| {
120 WrapperError::InvalidCode(format!("Failed to parse result capture: {}", e))
121 })?;
122
123 user_stmts.push(result_stmt);
124 }
125 }
126
127 let fn_name =
129 syn::Ident::new(&format!("oxur_eval_{}", cache_key), proc_macro2::Span::call_site());
130
131 let wrapped_fn = if needs_result_capture {
133 quote::quote! {
134 static mut OXUR_RESULT_BUFFER: Option<String> = None;
136
137 #[no_mangle]
138 pub extern "C" fn #fn_name() {
139 #(#user_stmts)*
140 }
141
142 #[no_mangle]
144 pub extern "C" fn oxur_get_result() -> *const u8 {
145 unsafe {
146 if let Some(ref s) = OXUR_RESULT_BUFFER {
147 s.as_ptr()
148 } else {
149 std::ptr::null()
150 }
151 }
152 }
153
154 #[no_mangle]
155 pub extern "C" fn oxur_get_result_len() -> usize {
156 unsafe {
157 OXUR_RESULT_BUFFER.as_ref().map(|s| s.len()).unwrap_or(0)
158 }
159 }
160 }
161 } else {
162 quote::quote! {
163 #[no_mangle]
164 pub extern "C" fn #fn_name() {
165 #(#user_stmts)*
166 }
167 }
168 };
169
170 let file: syn::File = syn::parse2(wrapped_fn).map_err(|e| {
172 WrapperError::InvalidCode(format!("Failed to parse generated code: {}", e))
173 })?;
174
175 let formatted = prettyplease::unparse(&file);
177
178 let mut output = String::new();
180 output.push_str("// Generated by Oxur REPL wrapper\n");
181 output.push_str("// Do not edit manually\n\n");
182
183 if self.debug {
184 output.push_str("// DEBUG MODE ENABLED\n\n");
185 }
186
187 output.push_str(&formatted);
188
189 let final_output = if let Some(smap) = source_map {
191 self.insert_source_map_comments(&output, user_stmts.len(), smap)
192 } else {
193 output
194 };
195
196 Ok(final_output)
197 }
198
199 fn parse_user_code(&self, code: &str) -> Result<Vec<syn::Stmt>> {
203 if let Ok(file) = syn::parse_str::<syn::File>(code) {
205 let mut stmts = Vec::new();
207 for item in file.items {
208 stmts.push(syn::Stmt::Item(item));
210 }
211 return Ok(stmts);
212 }
213
214 let wrapped_code = format!("{{ {} }}", code);
216 if let Ok(block) = syn::parse_str::<syn::Block>(&wrapped_code) {
217 return Ok(block.stmts);
218 }
219
220 if let Ok(expr) = syn::parse_str::<syn::Expr>(code) {
222 return Ok(vec![syn::Stmt::Expr(expr, None)]);
224 }
225
226 Err(WrapperError::InvalidCode(format!("Could not parse user code as valid Rust: {}", code)))
228 }
229
230 pub fn wrap_with_store(
262 &self,
263 cache_key: impl AsRef<str>,
264 user_code: impl AsRef<str>,
265 variables: &[(String, String)],
266 _source_map: Option<&oxur_smap::SourceMap>,
267 ) -> Result<String> {
268 let cache_key = cache_key.as_ref();
269 let user_code = user_code.as_ref();
270
271 if !is_valid_identifier(cache_key) {
273 return Err(WrapperError::InvalidCode(format!(
274 "Cache key must be valid identifier: {}",
275 cache_key
276 )));
277 }
278
279 let user_stmts = self.parse_user_code(user_code)?;
281
282 let new_vars = self.extract_variables(user_code);
284
285 let var_loads = self.generate_var_loads(variables)?;
287
288 let mut all_vars = variables.to_vec();
290 for (name, ty) in &new_vars {
291 if !all_vars.iter().any(|(n, _)| n == name) {
293 all_vars.push((name.clone(), ty.clone()));
294 }
295 }
296 let var_stores = self.generate_var_stores(&all_vars)?;
297
298 let fn_name =
300 syn::Ident::new(&format!("oxur_eval_{}", cache_key), proc_macro2::Span::call_site());
301
302 let wrapped_fn = quote::quote! {
305 #[no_mangle]
306 pub extern "C" fn #fn_name() {
307 #(#var_loads)*
308
309 #(#user_stmts)*
310
311 #(#var_stores)*
312 }
313 };
314
315 let file: syn::File = syn::parse2(wrapped_fn).map_err(|e| {
317 WrapperError::InvalidCode(format!("Failed to parse generated code: {}", e))
318 })?;
319 let formatted = prettyplease::unparse(&file);
320
321 let mut output = String::new();
323 output.push_str("// Generated by Oxur REPL wrapper\n");
324 output.push_str("// Do not edit manually\n\n");
325
326 if self.debug {
327 output.push_str("// DEBUG MODE ENABLED\n\n");
328 }
329
330 output.push_str(&formatted);
331
332 let final_output = if let Some(source_map) = _source_map {
334 self.insert_source_map_comments(&output, user_stmts.len(), source_map)
335 } else {
336 output
337 };
338
339 Ok(final_output)
340 }
341
342 fn generate_var_loads(&self, variables: &[(String, String)]) -> Result<Vec<syn::Stmt>> {
346 let mut stmts = Vec::new();
347
348 for (var_name, var_type) in variables {
349 let ty: syn::Type = syn::parse_str(var_type).map_err(|e| {
351 WrapperError::InvalidCode(format!("Invalid type '{}': {}", var_type, e))
352 })?;
353
354 let var_ident = syn::Ident::new(var_name, proc_macro2::Span::call_site());
355
356 let stmt = quote::quote! {
358 let #var_ident: #ty = oxur_repl::subprocess::with_store(|store| {
359 store.get::<#ty>(#var_name)
360 .cloned()
361 .unwrap_or_default()
362 });
363 };
364
365 let parsed_stmt: syn::Stmt = syn::parse2(stmt).map_err(|e| {
366 WrapperError::InvalidCode(format!("Failed to parse load statement: {}", e))
367 })?;
368 stmts.push(parsed_stmt);
369 }
370
371 Ok(stmts)
372 }
373
374 fn generate_var_stores(&self, variables: &[(String, String)]) -> Result<Vec<syn::Stmt>> {
378 let mut stmts = Vec::new();
379
380 for (var_name, _var_type) in variables {
381 let var_ident = syn::Ident::new(var_name, proc_macro2::Span::call_site());
382
383 let stmt = quote::quote! {
385 oxur_repl::subprocess::with_store(|store| {
386 store.set(#var_name.to_string(), #var_ident);
387 });
388 };
389
390 let parsed_stmt: syn::Stmt = syn::parse2(stmt).map_err(|e| {
391 WrapperError::InvalidCode(format!("Failed to parse store statement: {}", e))
392 })?;
393 stmts.push(parsed_stmt);
394 }
395
396 Ok(stmts)
397 }
398
399 pub fn extract_variables(&self, user_code: impl AsRef<str>) -> Vec<(String, String)> {
419 use crate::type_inference::TypeInference;
420
421 let inference = TypeInference::new();
422 let type_infos = inference.infer_from_code(user_code);
423
424 type_infos
426 .into_iter()
427 .map(|(name, type_info)| (name, type_info.type_name.clone()))
428 .collect()
429 }
430
431 fn insert_source_map_comments(
464 &self,
465 generated_code: &str,
466 num_user_stmts: usize,
467 source_map: &oxur_smap::SourceMap,
468 ) -> String {
469 let stats = source_map.stats();
470 let mut lines: Vec<String> = generated_code.lines().map(|s| s.to_string()).collect();
471
472 let mut in_function_body = false;
474 let mut inserted_count = 0;
475
476 let mut insert_indices = Vec::new();
482
483 for (idx, line) in lines.iter().enumerate() {
484 if line.contains("pub extern \"C\" fn") {
486 in_function_body = true;
487 continue;
488 }
489
490 if !in_function_body {
491 continue;
492 }
493
494 let is_var_load = line.contains("with_store(|store| {") && line.contains("store.get");
498 let is_var_store = line.contains("with_store(|store| {") && line.contains("store.set");
499
500 if !is_var_load
502 && !is_var_store
503 && !line.trim().is_empty()
504 && !line.trim().starts_with('}')
505 && !line.trim().starts_with("//")
506 && inserted_count < num_user_stmts
507 {
508 if line.trim().starts_with("let ")
511 || (!line.contains("with_store") && line.trim().ends_with(';'))
512 {
513 insert_indices.push(idx);
514 inserted_count += 1;
515 }
516 }
517 }
518
519 for (comment_idx, &line_idx) in insert_indices.iter().enumerate().rev() {
521 let offset = num_user_stmts.saturating_sub(comment_idx);
522 let node_id = stats.surface_nodes.saturating_sub(offset) as u32;
523 let comment = format!(
524 "{}/* oxur_node={} */",
525 " ".repeat(lines[line_idx].len() - lines[line_idx].trim_start().len()), node_id
527 );
528 lines.insert(line_idx, comment);
529 }
530
531 lines.join("\n")
532 }
533}
534
535impl Default for RustAstWrapper {
536 fn default() -> Self {
537 Self::new()
538 }
539}
540
541fn is_valid_identifier(s: &str) -> bool {
543 if s.is_empty() {
544 return false;
545 }
546
547 let mut chars = s.chars();
548
549 if let Some(first) = chars.next() {
551 if !first.is_alphabetic() && first != '_' {
552 return false;
553 }
554 }
555
556 chars.all(|c| c.is_alphanumeric() || c == '_')
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_wrapper_creation() {
566 let wrapper = RustAstWrapper::new();
567 assert!(!wrapper.debug);
568 }
569
570 #[test]
571 fn test_wrapper_with_debug() {
572 let wrapper = RustAstWrapper::new().with_debug(true);
573 assert!(wrapper.debug);
574 }
575
576 #[test]
577 fn test_wrap_simple_code() {
578 let wrapper = RustAstWrapper::new();
579
580 let user_code = "let x = 42;";
581 let wrapped = wrapper.wrap("test", user_code, None).expect("Wrapping failed");
582
583 assert!(wrapped.contains("#[no_mangle]"));
584 assert!(wrapped.contains("pub extern \"C\" fn oxur_eval_test()"));
585 assert!(wrapped.contains("let x = 42;"));
586 }
587
588 #[test]
589 fn test_wrap_multiline_code() {
590 let wrapper = RustAstWrapper::new();
591
592 let user_code = "let x = 42;\nlet y = 100;\nprintln!(\"sum = {}\", x + y);";
593 let wrapped = wrapper.wrap("multiline", user_code, None).expect("Wrapping failed");
594
595 assert!(wrapped.contains("let x = 42;"));
596 assert!(wrapped.contains("let y = 100;"));
597 assert!(wrapped.contains("println!"));
598 }
599
600 #[test]
601 fn test_wrap_with_debug() {
602 let wrapper = RustAstWrapper::new().with_debug(true);
603
604 let user_code = "let x = 1;";
605 let wrapped = wrapper.wrap("debug_test", user_code, None).expect("Wrapping failed");
606
607 assert!(wrapped.contains("DEBUG MODE ENABLED"));
608 }
609
610 #[test]
611 fn test_wrap_invalid_cache_key() {
612 let wrapper = RustAstWrapper::new();
613
614 let user_code = "let x = 1;";
615
616 assert!(wrapper.wrap("123invalid", user_code, None).is_err());
618 assert!(wrapper.wrap("invalid-key", user_code, None).is_err());
619 assert!(wrapper.wrap("", user_code, None).is_err());
620 }
621
622 #[test]
623 fn test_wrap_valid_cache_keys() {
624 let wrapper = RustAstWrapper::new();
625
626 let user_code = "let x = 1;";
627
628 assert!(wrapper.wrap("valid", user_code, None).is_ok());
630 assert!(wrapper.wrap("valid_key", user_code, None).is_ok());
631 assert!(wrapper.wrap("_valid", user_code, None).is_ok());
632 assert!(wrapper.wrap("valid123", user_code, None).is_ok());
633 }
634
635 #[test]
636 fn test_is_valid_identifier() {
637 assert!(is_valid_identifier("valid"));
638 assert!(is_valid_identifier("_valid"));
639 assert!(is_valid_identifier("valid123"));
640 assert!(is_valid_identifier("Valid_Identifier_123"));
641
642 assert!(!is_valid_identifier(""));
643 assert!(!is_valid_identifier("123invalid"));
644 assert!(!is_valid_identifier("invalid-key"));
645 assert!(!is_valid_identifier("invalid.key"));
646 assert!(!is_valid_identifier("invalid key"));
647 }
648
649 #[test]
650 fn test_extract_variables() {
651 let wrapper = RustAstWrapper::new();
652
653 let user_code = "let x: i32 = 42;\nlet y: i32 = 100;";
654 let vars = wrapper.extract_variables(user_code);
655
656 assert!(vars.len() >= 2);
658 assert!(vars.iter().any(|(name, ty)| name == "x" && ty.contains("i32")));
659 assert!(vars.iter().any(|(name, ty)| name == "y" && ty.contains("i32")));
660 }
661
662 #[test]
663 fn test_extract_variables_type_inference() {
664 let wrapper = RustAstWrapper::new();
665
666 let user_code = "let x = 42;";
668 let vars = wrapper.extract_variables(user_code);
669
670 assert!(vars.len() >= 1);
672 let has_x = vars.iter().any(|(name, _ty)| name == "x");
674 assert!(has_x, "Should extract variable 'x'");
675 }
676
677 #[test]
678 fn test_extract_variables_shadowing() {
679 let wrapper = RustAstWrapper::new();
680
681 let user_code = r#"
683 let x = 42;
684 let x = "hello";
685 "#;
686 let vars = wrapper.extract_variables(user_code);
687
688 assert!(vars.len() >= 2);
691 let x_count = vars.iter().filter(|(name, _)| name == "x").count();
692 assert!(x_count >= 1, "Should extract at least one 'x' binding");
693 }
694
695 #[test]
696 fn test_wrap_with_store_simple() {
697 let wrapper = RustAstWrapper::new();
698
699 let user_code = "let z = x + y;";
700 let variables =
701 vec![("x".to_string(), "i32".to_string()), ("y".to_string(), "i32".to_string())];
702
703 let wrapped = wrapper
704 .wrap_with_store("store_test", user_code, &variables, None)
705 .expect("Wrapping failed");
706
707 assert!(wrapped.contains("#[no_mangle]"));
709 assert!(wrapped.contains("pub extern \"C\" fn oxur_eval_store_test()"));
710
711 assert!(wrapped.contains("with_store"));
713 assert!(wrapped.contains("\"x\"") || wrapped.contains("let x"));
714 assert!(wrapped.contains("\"y\"") || wrapped.contains("let y"));
715
716 assert!(wrapped.contains("let z = x + y"));
718
719 assert!(wrapped.contains("store.set"));
721 }
722
723 #[test]
724 fn test_wrap_with_store_no_existing_vars() {
725 let wrapper = RustAstWrapper::new();
726
727 let user_code = "let x = 42;";
728 let variables = vec![]; let wrapped = wrapper
731 .wrap_with_store("no_vars", user_code, &variables, None)
732 .expect("Wrapping failed");
733
734 assert!(wrapped.contains("#[no_mangle]"));
736 assert!(wrapped.contains("oxur_eval_no_vars"));
737
738 assert!(wrapped.contains("let x = 42"));
740
741 assert!(wrapped.contains("store.set") || wrapped.contains("with_store"));
743 }
744
745 #[test]
746 fn test_wrap_with_store_multiple_vars() {
747 let wrapper = RustAstWrapper::new();
748
749 let user_code = "let sum = a + b + c;";
750 let variables = vec![
751 ("a".to_string(), "i32".to_string()),
752 ("b".to_string(), "i32".to_string()),
753 ("c".to_string(), "i32".to_string()),
754 ];
755
756 let wrapped = wrapper
757 .wrap_with_store("multi_vars", user_code, &variables, None)
758 .expect("Wrapping failed");
759
760 assert!(wrapped.contains("\"a\"") || wrapped.contains("let a"));
762 assert!(wrapped.contains("\"b\"") || wrapped.contains("let b"));
763 assert!(wrapped.contains("\"c\"") || wrapped.contains("let c"));
764
765 assert!(wrapped.contains("store.set"));
767 }
768
769 #[test]
770 fn test_default() {
771 let wrapper1 = RustAstWrapper::default();
772 let wrapper2 = RustAstWrapper::new();
773
774 assert_eq!(wrapper1.debug, wrapper2.debug);
775 }
776
777 #[test]
780 fn test_generated_code_parses_as_valid_rust() {
781 let wrapper = RustAstWrapper::new();
782
783 let user_code = "let result = x * 2 + y;";
784 let variables =
785 vec![("x".to_string(), "i32".to_string()), ("y".to_string(), "i32".to_string())];
786
787 let wrapped =
788 wrapper.wrap_with_store("test", user_code, &variables, None).expect("Wrapping failed");
789
790 let parsed = syn::parse_file(&wrapped);
792 assert!(parsed.is_ok(), "Generated code should parse as valid Rust: {}", wrapped);
793
794 if let Ok(file) = parsed {
795 assert!(!file.items.is_empty());
797 }
798 }
799
800 #[test]
801 fn test_round_trip_wrap_and_parse() {
802 let wrapper = RustAstWrapper::new();
803
804 let user_code = r#"
805 let a = 10;
806 let b = 20;
807 let sum = a + b;
808 "#;
809
810 let wrapped1 = wrapper.wrap("trip1", user_code, None).expect("Wrap failed");
812
813 let parsed = syn::parse_file(&wrapped1);
815 assert!(parsed.is_ok());
816
817 let vars = vec![("x".to_string(), "i32".to_string())];
819 let wrapped2 = wrapper
820 .wrap_with_store("trip2", user_code, &vars, None)
821 .expect("Wrap with store failed");
822
823 let parsed2 = syn::parse_file(&wrapped2);
824 assert!(parsed2.is_ok());
825 }
826
827 #[test]
828 fn test_end_to_end_variable_flow() {
829 let wrapper = RustAstWrapper::new();
830
831 let code1 = "let x: i32 = 42; let y: i32 = 100;";
835 let wrapped1 = wrapper.wrap_with_store("eval1", code1, &[], None).expect("Eval 1 failed");
836
837 assert!(wrapped1.contains("store.set"));
839 assert!(syn::parse_file(&wrapped1).is_ok());
840
841 let vars_after_eval1 = wrapper.extract_variables(code1);
843 assert!(vars_after_eval1.len() >= 2);
844
845 let code2 = "let sum = x + y;";
847 let wrapped2 = wrapper
848 .wrap_with_store("eval2", code2, &vars_after_eval1, None)
849 .expect("Eval 2 failed");
850
851 assert!(wrapped2.contains("with_store"));
853 assert!(wrapped2.contains("\"x\"") || wrapped2.contains("let x"));
854 assert!(wrapped2.contains("\"y\"") || wrapped2.contains("let y"));
855 assert!(syn::parse_file(&wrapped2).is_ok());
856
857 let mut all_vars = vars_after_eval1.clone();
859 let new_vars = wrapper.extract_variables(code2);
860 for (name, ty) in new_vars {
861 if !all_vars.iter().any(|(n, _)| n == &name) {
862 all_vars.push((name, ty));
863 }
864 }
865
866 assert!(all_vars.len() >= 3);
868
869 let code3 = "let product = sum * x;";
871 let wrapped3 =
872 wrapper.wrap_with_store("eval3", code3, &all_vars, None).expect("Eval 3 failed");
873
874 assert!(wrapped3.contains("with_store"));
875 assert!(syn::parse_file(&wrapped3).is_ok());
876 }
877
878 #[test]
879 fn test_complex_type_handling() {
880 let wrapper = RustAstWrapper::new();
881
882 let code = r#"
884 let s: String = String::from("hello");
885 let v: Vec<i32> = vec![1, 2, 3];
886 let opt: Option<i32> = Some(42);
887 "#;
888
889 let vars = vec![
890 ("existing_str".to_string(), "String".to_string()),
891 ("existing_vec".to_string(), "Vec<i32>".to_string()),
892 ];
893
894 let wrapped =
895 wrapper.wrap_with_store("complex", code, &vars, None).expect("Complex types failed");
896
897 assert!(wrapped.contains("with_store"));
899 assert!(syn::parse_file(&wrapped).is_ok());
900 }
901
902 #[test]
905 fn test_wrap_expression() {
906 let wrapper = RustAstWrapper::new();
907
908 let user_code = "42";
910 let wrapped = wrapper.wrap("expr_test", user_code, None).expect("Wrapping failed");
911
912 assert!(wrapped.contains("#[no_mangle]"));
913 assert!(wrapped.contains("pub extern \"C\" fn oxur_eval_expr_test()"));
914 assert!(wrapped.contains("42"));
915 }
916
917 #[test]
918 fn test_wrap_multiple_statements() {
919 let wrapper = RustAstWrapper::new();
920
921 let user_code = r#"
922 let x = 10;
923 let y = 20;
924 let z = x + y;
925 "#;
926 let wrapped = wrapper.wrap("multi", user_code, None).expect("Wrapping failed");
927
928 assert!(wrapped.contains("let x = 10;"));
929 assert!(wrapped.contains("let y = 20;"));
930 assert!(wrapped.contains("let z = x + y;"));
931 }
932
933 #[test]
934 fn test_wrap_function_definition_as_expression() {
935 let wrapper = RustAstWrapper::new();
936
937 let user_code = r#"
939 fn add(a: i32, b: i32) -> i32 {
940 a + b
941 }
942 "#;
943 let wrapped = wrapper.wrap("func_def", user_code, None).expect("Wrapping failed");
944
945 assert!(wrapped.contains("oxur_eval_func_def"));
947 assert!(wrapped.contains("a + b"));
948 }
949
950 #[test]
951 fn test_wrap_invalid_syntax() {
952 let wrapper = RustAstWrapper::new();
953
954 let user_code = "let x = ;";
956 let result = wrapper.wrap("invalid", user_code, None);
957
958 assert!(result.is_err());
959 match result {
960 Err(WrapperError::InvalidCode(msg)) => {
961 assert!(msg.contains("Could not parse"));
962 }
963 _ => panic!("Expected InvalidCode error"),
964 }
965 }
966
967 #[test]
968 fn test_parse_user_code_as_file() {
969 let wrapper = RustAstWrapper::new();
970
971 let code = "fn main() { println!(\"hello\"); }";
972 let stmts = wrapper.parse_user_code(code).expect("Parse failed");
973
974 assert!(!stmts.is_empty());
976 }
977
978 #[test]
979 fn test_parse_user_code_as_statements() {
980 let wrapper = RustAstWrapper::new();
981
982 let code = "let x = 42; let y = 100;";
983 let stmts = wrapper.parse_user_code(code).expect("Parse failed");
984
985 assert_eq!(stmts.len(), 2);
986 }
987
988 #[test]
989 fn test_parse_user_code_as_expression() {
990 let wrapper = RustAstWrapper::new();
991
992 let code = "42 + 58";
993 let stmts = wrapper.parse_user_code(code).expect("Parse failed");
994
995 assert_eq!(stmts.len(), 1);
996 }
997
998 #[test]
1001 fn test_wrap_with_store_and_source_map() {
1002 use oxur_smap::{new_node_id, SourceMap, SourcePos};
1003
1004 let wrapper = RustAstWrapper::new();
1005 let mut source_map = SourceMap::new();
1006
1007 let node1 = new_node_id();
1010 source_map.record_surface_node(node1, SourcePos::repl(1, 1, 10));
1011 let node2 = new_node_id();
1012 source_map.record_surface_node(node2, SourcePos::repl(2, 1, 15));
1013
1014 let user_code = "let result = x + y;";
1015 let variables =
1016 vec![("x".to_string(), "i32".to_string()), ("y".to_string(), "i32".to_string())];
1017
1018 let wrapped = wrapper
1020 .wrap_with_store("smap_test", user_code, &variables, Some(&source_map))
1021 .expect("Wrapping with source map failed");
1022
1023 assert!(wrapped.contains("/* oxur_node="), "Should have source map comments: {}", wrapped);
1025
1026 assert!(wrapped.contains("oxur_eval_smap_test"));
1028 assert!(wrapped.contains("with_store"));
1029
1030 let parsed = syn::parse_file(&wrapped);
1032 assert!(parsed.is_ok(), "Code with source map comments should parse: {}", wrapped);
1033 }
1034
1035 #[test]
1036 fn test_wrap_with_store_without_source_map() {
1037 let wrapper = RustAstWrapper::new();
1038
1039 let user_code = "let z = 42;";
1040 let variables = vec![];
1041
1042 let wrapped = wrapper
1044 .wrap_with_store("no_smap", user_code, &variables, None)
1045 .expect("Wrapping without source map failed");
1046
1047 assert!(
1049 !wrapped.contains("/* oxur_node="),
1050 "Should not have source map comments when source_map is None"
1051 );
1052
1053 assert!(wrapped.contains("oxur_eval_no_smap"));
1055 let parsed = syn::parse_file(&wrapped);
1056 assert!(parsed.is_ok());
1057 }
1058
1059 #[test]
1060 fn test_source_map_comments_format() {
1061 use oxur_smap::{new_node_id, SourceMap, SourcePos};
1062
1063 let wrapper = RustAstWrapper::new();
1064 let mut source_map = SourceMap::new();
1065
1066 for i in 0..5 {
1068 let node = new_node_id();
1069 source_map.record_surface_node(node, SourcePos::repl(i + 1, 1, 10));
1070 }
1071
1072 let user_code = r#"
1073 let a = 1;
1074 let b = 2;
1075 let c = a + b;
1076 "#;
1077
1078 let wrapped = wrapper
1079 .wrap_with_store("format_test", user_code, &[], Some(&source_map))
1080 .expect("Wrapping failed");
1081
1082 assert!(wrapped.contains("/* oxur_node="));
1085
1086 if let Some(idx) = wrapped.find("/* oxur_node=") {
1088 let after_comment = &wrapped[idx..];
1089 assert!(
1091 after_comment.contains("let") || after_comment.contains("fn"),
1092 "Comments should be followed by code"
1093 );
1094 }
1095 }
1096
1097 #[test]
1098 fn test_insert_source_map_comments_preserves_semantics() {
1099 use oxur_smap::{new_node_id, SourceMap, SourcePos};
1100
1101 let wrapper = RustAstWrapper::new();
1102 let mut source_map = SourceMap::new();
1103
1104 let node = new_node_id();
1105 source_map.record_surface_node(node, SourcePos::repl(1, 1, 20));
1106
1107 let user_code = "let x: i32 = 42;";
1108
1109 let wrapped = wrapper
1111 .wrap_with_store("semantics_test", user_code, &[], Some(&source_map))
1112 .expect("Wrapping with source map failed");
1113
1114 assert!(
1116 wrapped.contains("x") && wrapped.contains("42"),
1117 "Annotated code should preserve semantics"
1118 );
1119
1120 assert!(wrapped.contains("/* oxur_node="), "Should have source map comments");
1122
1123 let parsed = syn::parse_file(&wrapped);
1125 assert!(parsed.is_ok(), "Code with comments should be valid Rust: {}", wrapped);
1126 }
1127}