1use proc_macro2::TokenStream;
18use quote::quote;
19
20#[derive(Debug, Clone)]
22pub enum NumpyCall {
23 Array { elements: Vec<TokenStream> },
25 Dot { a: TokenStream, b: TokenStream },
27 Sum { arr: TokenStream },
29 Mean { arr: TokenStream },
31 Sqrt { arr: TokenStream },
33 Abs { arr: TokenStream },
35 Min { arr: TokenStream },
37 Max { arr: TokenStream },
39 Exp { arr: TokenStream },
41 Log { arr: TokenStream },
43 Sin { arr: TokenStream },
45 Cos { arr: TokenStream },
47 Clip {
49 arr: TokenStream,
50 min: TokenStream,
51 max: TokenStream,
52 },
53 ArgMax { arr: TokenStream },
55 ArgMin { arr: TokenStream },
57 Std { arr: TokenStream },
59 Var { arr: TokenStream },
61 Zeros { size: TokenStream },
63 Ones { size: TokenStream },
65 Norm { arr: TokenStream },
67}
68
69pub fn generate_trueno_code(call: &NumpyCall) -> TokenStream {
75 match call {
76 NumpyCall::Array { elements } => {
77 quote! {
78 Vector::from_slice(&[#(#elements as f32),*])
79 }
80 }
81 NumpyCall::Dot { a, b } => {
82 quote! {
83 #a.dot(&#b).expect("numpy operation failed")
84 }
85 }
86 NumpyCall::Sum { arr } => {
87 quote! {
88 #arr.sum().expect("numpy operation failed")
89 }
90 }
91 NumpyCall::Mean { arr } => {
92 quote! {
93 #arr.mean().expect("numpy operation failed")
94 }
95 }
96 NumpyCall::Sqrt { arr } => {
97 quote! {
98 #arr.sqrt().expect("numpy operation failed")
99 }
100 }
101 NumpyCall::Abs { arr } => {
102 quote! {
103 #arr.abs().expect("numpy operation failed")
104 }
105 }
106 NumpyCall::Min { arr } => {
107 quote! {
108 #arr.min().expect("numpy operation failed")
109 }
110 }
111 NumpyCall::Max { arr } => {
112 quote! {
113 #arr.max().expect("numpy operation failed")
114 }
115 }
116 NumpyCall::Exp { arr } => {
117 quote! {
118 #arr.exp().expect("numpy operation failed")
119 }
120 }
121 NumpyCall::Log { arr } => {
122 quote! {
123 #arr.ln().expect("numpy operation failed")
124 }
125 }
126 NumpyCall::Sin { arr } => {
127 quote! {
128 #arr.sin().expect("numpy operation failed")
129 }
130 }
131 NumpyCall::Cos { arr } => {
132 quote! {
133 #arr.cos().expect("numpy operation failed")
134 }
135 }
136 NumpyCall::Clip { arr, min, max } => {
137 quote! {
138 #arr.clamp(#min, #max).expect("numpy operation failed")
139 }
140 }
141 NumpyCall::ArgMax { arr } => {
142 quote! {
143 #arr.argmax().expect("numpy operation failed")
144 }
145 }
146 NumpyCall::ArgMin { arr } => {
147 quote! {
148 #arr.argmin().expect("numpy operation failed")
149 }
150 }
151 NumpyCall::Std { arr } => {
152 quote! {
154 #arr.stddev().expect("numpy operation failed")
155 }
156 }
157 NumpyCall::Var { arr } => {
158 quote! {
159 #arr.variance().expect("numpy operation failed")
160 }
161 }
162 NumpyCall::Zeros { size } => {
163 quote! {
164 Vector::zeros(#size)
165 }
166 }
167 NumpyCall::Ones { size } => {
168 quote! {
169 Vector::ones(#size)
170 }
171 }
172 NumpyCall::Norm { arr } => {
173 quote! {
177 (#arr).norm_l2().expect("numpy operation failed")
178 }
179 }
180 }
181}
182
183pub fn is_numpy_module(module: &str) -> bool {
185 module == "numpy" || module == "np"
186}
187
188pub fn parse_numpy_function(func_name: &str) -> Option<&'static str> {
192 match func_name {
193 "array" => Some("Array"),
194 "dot" => Some("Dot"),
195 "sum" => Some("Sum"),
196 "mean" => Some("Mean"),
197 "sqrt" => Some("Sqrt"),
198 "abs" => Some("Abs"),
199 "min" | "amin" => Some("Min"),
200 "max" | "amax" => Some("Max"),
201 "exp" => Some("Exp"),
202 "log" => Some("Log"),
203 "sin" => Some("Sin"),
204 "cos" => Some("Cos"),
205 "clip" => Some("Clip"),
206 "argmax" => Some("ArgMax"),
207 "argmin" => Some("ArgMin"),
208 "std" => Some("Std"),
209 "var" => Some("Var"),
210 "zeros" => Some("Zeros"),
211 "ones" => Some("Ones"),
212 "norm" => Some("Norm"),
213 _ => None,
214 }
215}
216
217pub fn trueno_use_statement() -> TokenStream {
219 quote! {
220 use trueno::Vector;
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_generate_array() {
230 let elements = vec![quote!(1.0), quote!(2.0), quote!(3.0)];
231 let call = NumpyCall::Array { elements };
232 let code = generate_trueno_code(&call);
233 let code_str = code.to_string();
234
235 assert!(
236 code_str.contains("Vector :: from_slice"),
237 "Should generate Vector::from_slice: {}",
238 code_str
239 );
240 assert!(
241 code_str.contains("1.0") && code_str.contains("2.0") && code_str.contains("3.0"),
242 "Should contain elements: {}",
243 code_str
244 );
245 }
246
247 #[test]
248 fn test_generate_dot() {
249 let call = NumpyCall::Dot {
250 a: quote!(a),
251 b: quote!(b),
252 };
253 let code = generate_trueno_code(&call);
254 let code_str = code.to_string();
255
256 assert!(
257 code_str.contains("dot"),
258 "Should generate dot call: {}",
259 code_str
260 );
261 assert!(
262 code_str.contains("unwrap") || code_str.contains("expect"),
263 "Should handle result: {}",
264 code_str
265 );
266 }
267
268 #[test]
269 fn test_generate_sum() {
270 let call = NumpyCall::Sum { arr: quote!(arr) };
271 let code = generate_trueno_code(&call);
272 let code_str = code.to_string();
273
274 assert!(
275 code_str.contains("sum"),
276 "Should generate sum call: {}",
277 code_str
278 );
279 }
280
281 #[test]
282 fn test_generate_mean() {
283 let call = NumpyCall::Mean { arr: quote!(arr) };
284 let code = generate_trueno_code(&call);
285 let code_str = code.to_string();
286
287 assert!(
288 code_str.contains("mean"),
289 "Should generate mean call: {}",
290 code_str
291 );
292 }
293
294 #[test]
295 fn test_generate_sqrt() {
296 let call = NumpyCall::Sqrt { arr: quote!(arr) };
297 let code = generate_trueno_code(&call);
298 let code_str = code.to_string();
299
300 assert!(
301 code_str.contains("sqrt"),
302 "Should generate sqrt call: {}",
303 code_str
304 );
305 }
306
307 #[test]
308 fn test_generate_zeros() {
309 let call = NumpyCall::Zeros { size: quote!(10) };
310 let code = generate_trueno_code(&call);
311 let code_str = code.to_string();
312
313 assert!(
314 code_str.contains("zeros"),
315 "Should generate zeros call: {}",
316 code_str
317 );
318 }
319
320 #[test]
321 fn test_generate_ones() {
322 let call = NumpyCall::Ones { size: quote!(10) };
323 let code = generate_trueno_code(&call);
324 let code_str = code.to_string();
325
326 assert!(
327 code_str.contains("ones"),
328 "Should generate ones call: {}",
329 code_str
330 );
331 }
332
333 #[test]
334 fn test_generate_clip() {
335 let call = NumpyCall::Clip {
336 arr: quote!(arr),
337 min: quote!(0.0),
338 max: quote!(1.0),
339 };
340 let code = generate_trueno_code(&call);
341 let code_str = code.to_string();
342
343 assert!(
344 code_str.contains("clamp"),
345 "Should generate clamp call: {}",
346 code_str
347 );
348 }
349
350 #[test]
351 fn test_is_numpy_module() {
352 assert!(is_numpy_module("numpy"));
353 assert!(is_numpy_module("np"));
354 assert!(!is_numpy_module("math"));
355 assert!(!is_numpy_module("random"));
356 }
357
358 #[test]
359 fn test_parse_numpy_function() {
360 assert_eq!(parse_numpy_function("array"), Some("Array"));
361 assert_eq!(parse_numpy_function("dot"), Some("Dot"));
362 assert_eq!(parse_numpy_function("sum"), Some("Sum"));
363 assert_eq!(parse_numpy_function("mean"), Some("Mean"));
364 assert_eq!(parse_numpy_function("sqrt"), Some("Sqrt"));
365 assert_eq!(parse_numpy_function("min"), Some("Min"));
366 assert_eq!(parse_numpy_function("amin"), Some("Min"));
367 assert_eq!(parse_numpy_function("max"), Some("Max"));
368 assert_eq!(parse_numpy_function("amax"), Some("Max"));
369 assert_eq!(parse_numpy_function("unknown"), None);
370 }
371
372 #[test]
373 fn test_trueno_use_statement() {
374 let stmt = trueno_use_statement();
375 let stmt_str = stmt.to_string();
376
377 assert!(
378 stmt_str.contains("trueno"),
379 "Should use trueno: {}",
380 stmt_str
381 );
382 assert!(
383 stmt_str.contains("Vector"),
384 "Should import Vector: {}",
385 stmt_str
386 );
387 }
388
389 #[test]
390 fn test_generate_norm() {
391 let call = NumpyCall::Norm { arr: quote!(v) };
392 let code = generate_trueno_code(&call);
393 let code_str = code.to_string();
394
395 assert!(
396 code_str.contains("norm"),
397 "Should generate norm call: {}",
398 code_str
399 );
400 }
401
402 #[test]
403 fn test_generate_argmax() {
404 let call = NumpyCall::ArgMax { arr: quote!(arr) };
405 let code = generate_trueno_code(&call);
406 let code_str = code.to_string();
407
408 assert!(
409 code_str.contains("argmax"),
410 "Should generate argmax call: {}",
411 code_str
412 );
413 }
414
415 #[test]
416 fn test_generate_std() {
417 let call = NumpyCall::Std { arr: quote!(arr) };
418 let code = generate_trueno_code(&call);
419 let code_str = code.to_string();
420
421 assert!(
423 code_str.contains("stddev"),
424 "Should generate stddev call (trueno API): {}",
425 code_str
426 );
427 }
428
429 #[test]
430 fn test_generate_var() {
431 let call = NumpyCall::Var { arr: quote!(arr) };
432 let code = generate_trueno_code(&call);
433 let code_str = code.to_string();
434
435 assert!(
436 code_str.contains("variance"),
437 "Should generate variance call: {}",
438 code_str
439 );
440 }
441
442 #[test]
443 fn test_generate_abs() {
444 let call = NumpyCall::Abs { arr: quote!(arr) };
445 let code = generate_trueno_code(&call);
446 let code_str = code.to_string();
447 assert!(code_str.contains("abs"));
448 assert!(code_str.contains("expect"));
449 }
450
451 #[test]
452 fn test_generate_min() {
453 let call = NumpyCall::Min { arr: quote!(arr) };
454 let code = generate_trueno_code(&call);
455 assert!(code.to_string().contains("min"));
456 }
457
458 #[test]
459 fn test_generate_max() {
460 let call = NumpyCall::Max { arr: quote!(arr) };
461 let code = generate_trueno_code(&call);
462 assert!(code.to_string().contains("max"));
463 }
464
465 #[test]
466 fn test_generate_exp() {
467 let call = NumpyCall::Exp { arr: quote!(arr) };
468 let code = generate_trueno_code(&call);
469 assert!(code.to_string().contains("exp"));
470 }
471
472 #[test]
473 fn test_generate_log() {
474 let call = NumpyCall::Log { arr: quote!(arr) };
475 let code = generate_trueno_code(&call);
476 assert!(code.to_string().contains("ln"));
477 }
478
479 #[test]
480 fn test_generate_sin() {
481 let call = NumpyCall::Sin { arr: quote!(arr) };
482 let code = generate_trueno_code(&call);
483 assert!(code.to_string().contains("sin"));
484 }
485
486 #[test]
487 fn test_generate_cos() {
488 let call = NumpyCall::Cos { arr: quote!(arr) };
489 let code = generate_trueno_code(&call);
490 assert!(code.to_string().contains("cos"));
491 }
492
493 #[test]
494 fn test_generate_argmin() {
495 let call = NumpyCall::ArgMin { arr: quote!(arr) };
496 let code = generate_trueno_code(&call);
497 assert!(code.to_string().contains("argmin"));
498 }
499
500 #[test]
501 fn test_parse_numpy_function_exhaustive() {
502 assert_eq!(parse_numpy_function("abs"), Some("Abs"));
503 assert_eq!(parse_numpy_function("exp"), Some("Exp"));
504 assert_eq!(parse_numpy_function("log"), Some("Log"));
505 assert_eq!(parse_numpy_function("sin"), Some("Sin"));
506 assert_eq!(parse_numpy_function("cos"), Some("Cos"));
507 assert_eq!(parse_numpy_function("clip"), Some("Clip"));
508 assert_eq!(parse_numpy_function("argmax"), Some("ArgMax"));
509 assert_eq!(parse_numpy_function("argmin"), Some("ArgMin"));
510 assert_eq!(parse_numpy_function("std"), Some("Std"));
511 assert_eq!(parse_numpy_function("var"), Some("Var"));
512 assert_eq!(parse_numpy_function("zeros"), Some("Zeros"));
513 assert_eq!(parse_numpy_function("ones"), Some("Ones"));
514 assert_eq!(parse_numpy_function("norm"), Some("Norm"));
515 }
516
517 #[test]
518 fn test_is_numpy_module_edge_cases() {
519 assert!(!is_numpy_module(""));
520 assert!(!is_numpy_module("Numpy"));
521 assert!(!is_numpy_module("NP"));
522 assert!(!is_numpy_module("scipy"));
523 }
524
525 #[test]
526 fn test_generate_norm_uses_norm_l2() {
527 let call = NumpyCall::Norm {
528 arr: quote!(a + b),
529 };
530 let code = generate_trueno_code(&call);
531 assert!(code.to_string().contains("norm_l2"));
532 }
533
534 #[test]
535 fn test_generate_array_empty() {
536 let call = NumpyCall::Array { elements: vec![] };
537 let code = generate_trueno_code(&call);
538 assert!(code.to_string().contains("Vector :: from_slice"));
539 }
540
541 #[test]
542 fn test_generate_dot_with_expressions() {
543 let call = NumpyCall::Dot {
544 a: quote!(vec1),
545 b: quote!(vec2),
546 };
547 let code = generate_trueno_code(&call);
548 let s = code.to_string();
549 assert!(s.contains("vec1"));
550 assert!(s.contains("vec2"));
551 }
552
553 #[test]
554 fn test_numpy_call_debug() {
555 let call = NumpyCall::Sum { arr: quote!(x) };
556 let debug = format!("{:?}", call);
557 assert!(debug.contains("Sum"));
558 }
559
560 #[test]
561 fn test_numpy_call_clone() {
562 let call = NumpyCall::Zeros { size: quote!(10) };
563 let cloned = call.clone();
564 let code1 = generate_trueno_code(&call).to_string();
565 let code2 = generate_trueno_code(&cloned).to_string();
566 assert_eq!(code1, code2);
567 }
568}