venus_core/compile/
source_processor.rs1use proc_macro2::TokenStream;
7use quote::ToTokens;
8use syn::{parse_file, Attribute, File, Item};
9
10pub struct NotebookSourceProcessor;
22
23impl NotebookSourceProcessor {
24 pub fn process_for_production(source: &str) -> Result<String, syn::Error> {
32 let file = parse_file(source)?;
33 let processed = Self::process_file(file);
34 Ok(Self::tokens_to_string(processed))
35 }
36
37 fn process_file(file: File) -> File {
39 let items = file
40 .items
41 .into_iter()
42 .filter_map(Self::process_item)
43 .collect();
44
45 File {
46 shebang: file.shebang,
47 attrs: Self::filter_module_docs(file.attrs),
48 items,
49 }
50 }
51
52 fn filter_module_docs(attrs: Vec<Attribute>) -> Vec<Attribute> {
54 attrs
55 .into_iter()
56 .filter(|attr| {
57 !attr.path().is_ident("doc")
59 || matches!(attr.style, syn::AttrStyle::Outer)
61 })
62 .collect()
63 }
64
65 fn process_item(item: Item) -> Option<Item> {
67 match item {
68 Item::Fn(mut func) => {
69 if func.sig.ident == "main" {
71 return None;
72 }
73
74 func.attrs.retain(|attr| !Self::is_venus_cell_attr(attr));
76
77 Some(Item::Fn(func))
78 }
79 other => Some(other),
81 }
82 }
83
84 fn is_venus_cell_attr(attr: &Attribute) -> bool {
86 let path = attr.path();
87
88 if path.segments.len() == 2 {
90 let first = &path.segments[0];
91 let second = &path.segments[1];
92 return first.ident == "venus" && second.ident == "cell";
93 }
94
95 false
96 }
97
98 fn tokens_to_string(file: File) -> String {
100 let tokens: TokenStream = file.into_token_stream();
101 tokens.to_string()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_strips_module_docs() {
111 let source = r#"
112//! Module documentation
113//! More docs
114
115use something;
116
117fn foo() -> i32 { 42 }
118"#;
119 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
120
121 assert!(!result.contains("//!"));
122 assert!(!result.contains("Module documentation"));
123 assert!(result.contains("use something"));
124 assert!(result.contains("fn foo"));
125 }
126
127 #[test]
128 fn test_strips_venus_cell_attribute() {
129 let source = r#"
130#[venus::cell]
131pub fn my_cell() -> i32 { 42 }
132
133#[derive(Debug)]
134struct MyStruct { x: i32 }
135"#;
136 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
137
138 assert!(!result.contains("venus :: cell") && !result.contains("venus::cell"));
140 assert!(result.contains("pub fn my_cell"));
142 assert!(result.contains("derive") && result.contains("Debug"));
144 assert!(result.contains("struct MyStruct"));
145 }
146
147 #[test]
148 fn test_removes_main_function() {
149 let source = r#"
150fn helper() -> i32 { 1 }
151
152fn main() {
153 println!("Hello");
154 let x = {
155 let y = 2;
156 y + 1
157 };
158}
159
160fn another() -> i32 { 2 }
161"#;
162 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
163
164 assert!(result.contains("fn helper"));
165 assert!(result.contains("fn another"));
166 assert!(!result.contains("fn main"));
167 assert!(!result.contains("println"));
168 }
169
170 #[test]
171 fn test_handles_braces_in_strings() {
172 let source = r#"
173fn foo() -> String {
174 "this has { braces } inside".to_string()
175}
176
177fn main() {
178 println!("main function");
179}
180"#;
181 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
182
183 assert!(result.contains("fn foo"));
185 assert!(result.contains("braces"));
186 assert!(!result.contains("fn main"));
188 }
189
190 #[test]
191 fn test_handles_braces_in_comments() {
192 let source = r#"
193// This comment has { braces }
194fn foo() -> i32 {
195 /* Multi-line comment with { braces } */
196 42
197}
198
199fn main() { }
200"#;
201 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
202
203 assert!(result.contains("fn foo"));
204 assert!(!result.contains("fn main"));
205 }
206
207 #[test]
208 fn test_preserves_regular_doc_comments() {
209 let source = r#"
210//! Module doc (should be removed)
211
212/// Function doc (should be kept)
213fn foo() -> i32 { 42 }
214"#;
215 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
216
217 assert!(!result.contains("Module doc"));
218 assert!(result.contains("foo"));
221 }
222
223 #[test]
224 fn test_handles_nested_functions() {
225 let source = r#"
226fn outer() -> i32 {
227 fn inner() -> i32 { 1 }
228 inner() + 1
229}
230
231fn main() {
232 outer();
233}
234"#;
235 let result = NotebookSourceProcessor::process_for_production(source).unwrap();
236
237 assert!(result.contains("fn outer"));
238 assert!(result.contains("fn inner"));
239 assert!(!result.contains("fn main"));
240 }
241}