1mod parse;
12
13use parse::{
14 BinOp, DimMacroInput, EqMacroInput, ExprMacroInput, MathExpr, MatrixMacroInput, RuleMacroInput,
15};
16use parse::{KNOWN_FUNCTIONS, is_known_constant, is_known_function};
17
18use proc_macro::TokenStream;
19use proc_macro2::Span;
20use proc_macro2::TokenStream as TokenStream2;
21use quote::{format_ident, quote};
22use syn::Ident;
23
24#[proc_macro]
78pub fn expr(input: TokenStream) -> TokenStream {
79 let input = syn::parse_macro_input!(input as ExprMacroInput);
80 let result = if input.expr.is_numeric_only() {
83 generate_expr_as_ex(&input.ctx, &input.expr)
84 } else {
85 generate_expr(&input.ctx, &input.expr)
86 };
87 match result {
88 Ok(tokens) => tokens.into(),
89 Err(e) => e.to_compile_error().into(),
90 }
91}
92
93fn generate_expr(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
103 match expr {
104 MathExpr::Int(n, _span) => Ok(quote! { #n }),
105
106 MathExpr::Ident(id) => {
107 let name = id.to_string();
108 match name.as_str() {
109 "pi" | "Pi" | "PI" => Ok(quote! { #ctx.pi() }),
110 "E" => Ok(quote! { #ctx.e() }),
111 "I" => Ok(quote! { #ctx.i_unit() }),
112 "oo" | "inf" => Ok(quote! { #ctx.infinity() }),
113 _ => Ok(quote! { (&#id) }),
114 }
115 }
116
117 MathExpr::Neg(inner) => {
118 let inner_code = generate_expr(ctx, inner)?;
119 Ok(quote! { (-(#inner_code)) })
120 }
121
122 MathExpr::LogicalNot(inner) => {
123 let inner_code = generate_expr(ctx, inner)?;
124 Ok(quote! { (#inner_code).not() })
125 }
126
127 MathExpr::BinOp { op, lhs, rhs } => {
128 let numeric_binop = lhs.is_numeric_only()
132 && rhs.is_numeric_only()
133 && matches!(
134 op,
135 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Pow
136 );
137 let gen_lhs = |ctx: &Ident, lhs: &MathExpr| -> syn::Result<TokenStream2> {
138 if numeric_binop {
139 generate_expr_as_ex(ctx, lhs)
140 } else {
141 generate_expr(ctx, lhs)
142 }
143 };
144 match op {
145 BinOp::Pow => {
146 let lhs_code = gen_lhs(ctx, lhs)?;
147 if let Some(n) = rhs.as_int() {
150 Ok(quote! { (#lhs_code).powi(#n) })
151 } else if let MathExpr::Neg(inner) = rhs.as_ref() {
152 if let Some(n) = inner.as_int() {
153 let neg_n = -n;
154 Ok(quote! { (#lhs_code).powi(#neg_n) })
155 } else {
156 let rhs_code = generate_expr(ctx, rhs)?;
157 Ok(quote! { (#lhs_code).pow(&(#rhs_code)) })
158 }
159 } else {
160 let rhs_code = generate_expr(ctx, rhs)?;
161 Ok(quote! { (#lhs_code).pow(&(#rhs_code)) })
162 }
163 }
164 BinOp::Div => {
165 if let (Some(p), Some(q)) = (lhs.as_int(), rhs.as_int()) {
166 if q == 0 {
167 return Err(syn::Error::new(
168 Span::call_site(),
169 "division by zero in expr!()",
170 ));
171 }
172 return Ok(quote! { #ctx.rational(#p, #q) });
173 }
174 if let MathExpr::Neg(inner_lhs) = lhs.as_ref()
179 && let (Some(p), Some(q)) = (inner_lhs.as_int(), rhs.as_int())
180 {
181 if q == 0 {
182 return Err(syn::Error::new(
183 Span::call_site(),
184 "division by zero in expr!()",
185 ));
186 }
187 let neg_p = -p;
188 return Ok(quote! { #ctx.rational(#neg_p, #q) });
189 }
190 let lhs_code = gen_lhs(ctx, lhs)?;
191 let rhs_code = generate_expr(ctx, rhs)?;
192 Ok(quote! { ((#lhs_code) / (#rhs_code)) })
193 }
194 BinOp::Gt => {
195 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
196 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
197 Ok(quote! { (#lhs_code).gt(&(#rhs_code)) })
198 }
199 BinOp::Lt => {
200 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
201 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
202 Ok(quote! { (#lhs_code).lt(&(#rhs_code)) })
203 }
204 BinOp::Ge => {
205 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
206 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
207 Ok(quote! { (#lhs_code).ge(&(#rhs_code)) })
208 }
209 BinOp::Le => {
210 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
211 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
212 Ok(quote! { (#lhs_code).le(&(#rhs_code)) })
213 }
214 BinOp::EqEq => {
215 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
216 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
217 Ok(quote! { (#lhs_code).eq_expr(&(#rhs_code)) })
218 }
219 BinOp::Ne => {
220 let lhs_code = generate_expr_as_ex(ctx, lhs)?;
221 let rhs_code = generate_expr_as_ex(ctx, rhs)?;
222 Ok(quote! { (#lhs_code).ne_expr(&(#rhs_code)) })
223 }
224 BinOp::AndAnd => {
225 let lhs_code = generate_expr(ctx, lhs)?;
226 let rhs_code = generate_expr(ctx, rhs)?;
227 Ok(quote! { (#lhs_code).and(&(#rhs_code)) })
228 }
229 BinOp::OrOr => {
230 let lhs_code = generate_expr(ctx, lhs)?;
231 let rhs_code = generate_expr(ctx, rhs)?;
232 Ok(quote! { (#lhs_code).or(&(#rhs_code)) })
233 }
234 _ => {
235 let lhs_code = gen_lhs(ctx, lhs)?;
236 let rhs_code = generate_expr(ctx, rhs)?;
237 let op_token = match op {
238 BinOp::Add => quote! { + },
239 BinOp::Sub => quote! { - },
240 BinOp::Mul => quote! { * },
241 _ => unreachable!(),
242 };
243 Ok(quote! { ((#lhs_code) #op_token (#rhs_code)) })
244 }
245 }
246 }
247
248 MathExpr::Func { name, span, args } => {
249 if name == "log" && args.len() == 2 {
251 let arg_code = generate_expr(ctx, &args[0])?;
252 let base_code = generate_expr_as_ex(ctx, &args[1])?;
255 return Ok(quote! { (#arg_code).log(&(#base_code)) });
256 }
257
258 if name == "diff" && args.len() == 2 {
260 let f_code = generate_expr(ctx, &args[0])?;
261 let var_code = generate_expr(ctx, &args[1])?;
262 return Ok(quote! { (#f_code).formal_diff(&(#var_code)) });
263 }
264
265 if name == "factorial" && args.len() == 1 {
267 let arg_code = generate_expr_as_ex(ctx, &args[0])?;
268 return Ok(quote! { (#arg_code).factorial() });
269 }
270
271 if (name == "binomial" || name == "C") && args.len() == 2 {
273 let n_code = generate_expr_as_ex(ctx, &args[0])?;
274 let k_code = generate_expr_as_ex(ctx, &args[1])?;
275 return Ok(quote! { (#n_code).binomial(&(#k_code)) });
276 }
277
278 if name == "atan2" && args.len() == 2 {
280 let y_code = generate_expr_as_ex(ctx, &args[0])?;
281 let x_code = generate_expr_as_ex(ctx, &args[1])?;
282 return Ok(quote! { (#y_code).atan2(&(#x_code)) });
283 }
284
285 if name == "rising_factorial" && args.len() == 2 {
287 let x_code = generate_expr_as_ex(ctx, &args[0])?;
288 let n_code = generate_expr_as_ex(ctx, &args[1])?;
289 return Ok(quote! { (#x_code).rising_factorial(&(#n_code)) });
290 }
291
292 if name == "falling_factorial" && args.len() == 2 {
294 let x_code = generate_expr_as_ex(ctx, &args[0])?;
295 let n_code = generate_expr_as_ex(ctx, &args[1])?;
296 return Ok(quote! { (#x_code).falling_factorial(&(#n_code)) });
297 }
298
299 if name == "beta" && args.len() == 2 {
301 let a_code = generate_expr_as_ex(ctx, &args[0])?;
302 let b_code = generate_expr_as_ex(ctx, &args[1])?;
303 return Ok(quote! { (#a_code).beta(&(#b_code)) });
304 }
305
306 if name == "min" && args.len() == 2 {
308 let a = generate_expr_as_ex(ctx, &args[0])?;
309 let b = generate_expr_as_ex(ctx, &args[1])?;
310 return Ok(quote! { (#a).min_with(&(#b)) });
311 }
312
313 if name == "max" && args.len() == 2 {
315 let a = generate_expr_as_ex(ctx, &args[0])?;
316 let b = generate_expr_as_ex(ctx, &args[1])?;
317 return Ok(quote! { (#a).max_with(&(#b)) });
318 }
319
320 if let Some((method, nparams)) = match name.as_str() {
324 "expint" => Some(("expint", 1)),
325 "lowergamma" => Some(("lowergamma", 1)),
326 "uppergamma" => Some(("uppergamma", 1)),
327 "polylog" => Some(("polylog", 1)),
328 "elliptic_f" => Some(("elliptic_f", 1)),
329 "elliptic_pi" => Some(("elliptic_pi", 1)),
330 "gegenbauer" => Some(("gegenbauer", 2)),
331 "assoc_legendre" => Some(("assoc_legendre", 2)),
332 "assoc_laguerre" => Some(("assoc_laguerre", 2)),
333 "jacobi" => Some(("jacobi", 3)),
334 _ => None,
335 } {
336 if args.len() != nparams + 1 {
337 return Err(syn::Error::new(
338 *span,
339 format!(
340 "{name}() takes exactly {} arguments in expr!()",
341 nparams + 1
342 ),
343 ));
344 }
345 let x = generate_expr_as_ex(ctx, &args[nparams])?;
346 let params = args[..nparams]
347 .iter()
348 .map(|a| generate_expr_as_ex(ctx, a))
349 .collect::<syn::Result<Vec<_>>>()?;
350 let method = format_ident!("{method}");
351 return Ok(quote! { (#x).#method(#(&(#params)),*) });
352 }
353
354 if !is_known_function(name)
355 && ![
356 "log",
357 "diff",
358 "factorial",
359 "binomial",
360 "C",
361 "atan2",
362 "rising_factorial",
363 "falling_factorial",
364 "beta",
365 "min",
366 "max",
367 "expint",
368 "lowergamma",
369 "uppergamma",
370 "polylog",
371 "elliptic_f",
372 "elliptic_pi",
373 "gegenbauer",
374 "assoc_legendre",
375 "assoc_laguerre",
376 "jacobi",
377 ]
378 .contains(&name.as_str())
379 {
380 return Err(syn::Error::new(
381 *span,
382 format!(
383 "unknown function '{}' in expr!(). Supported: {}, log, diff, factorial, binomial, C, atan2, rising_factorial, falling_factorial, beta, min, max",
384 name,
385 KNOWN_FUNCTIONS.join(", ")
386 ),
387 ));
388 }
389 if args.len() != 1 {
390 return Err(syn::Error::new(
391 *span,
392 format!("{}() takes exactly 1 argument in expr!()", name),
393 ));
394 }
395 let arg_code = generate_expr_as_ex(ctx, &args[0])?;
396 let method = match name.as_str() {
397 "sin" => quote! { sin },
398 "cos" => quote! { cos },
399 "tan" => quote! { tan },
400 "asin" => quote! { asin },
401 "acos" => quote! { acos },
402 "atan" => quote! { atan },
403 "sinh" => quote! { sinh },
404 "cosh" => quote! { cosh },
405 "tanh" => quote! { tanh },
406 "asinh" => quote! { asinh },
407 "acosh" => quote! { acosh },
408 "atanh" => quote! { atanh },
409 "exp" => quote! { exp },
410 "ln" => quote! { ln },
411 "sqrt" => quote! { sqrt },
412 "cbrt" => quote! { cbrt },
413 "abs" => quote! { abs },
414 "sign" => quote! { sign },
415 "floor" => quote! { floor },
416 "ceiling" => quote! { ceiling },
417 "sec" => quote! { sec },
419 "csc" => quote! { csc },
420 "cot" => quote! { cot },
421 "acot" => quote! { acot },
422 "asec" => quote! { asec },
423 "acsc" => quote! { acsc },
424 "coth" => quote! { coth },
425 "sech" => quote! { sech },
426 "csch" => quote! { csch },
427 "acoth" => quote! { acoth },
428 "asech" => quote! { asech },
429 "acsch" => quote! { acsch },
430 "sinc" => quote! { sinc },
431 "arg" => quote! { arg },
433 "conjugate" => quote! { conjugate },
434 "fibonacci" => quote! { fibonacci },
436 "lucas" => quote! { lucas },
437 "catalan_number" => quote! { catalan_number },
438 "bell" => quote! { bell },
439 "euler_number" => quote! { euler_number },
440 "harmonic" => quote! { harmonic },
441 "subfactorial" => quote! { subfactorial },
442 "factorial2" => quote! { factorial2 },
443 "bernoulli_number" => quote! { bernoulli_number },
444 "heaviside" => quote! { heaviside },
446 "dirac_delta" => quote! { dirac_delta },
447 "lambertw" => quote! { lambertw },
448 "gamma" => quote! { gamma },
450 "log_gamma" => quote! { log_gamma },
451 "digamma" => quote! { digamma },
452 "erf" => quote! { erf },
453 "erfc" => quote! { erfc },
454 "erfi" | "erfinv" | "erfcinv" | "e1" | "shi" | "chi" | "fresnels" | "fresnelc"
457 | "dirichlet_eta" | "airyai" | "airybi" | "airyaiprime" | "airybiprime"
458 | "elliptic_k" | "elliptic_e" => {
459 let m = format_ident!("{}", name.as_str());
460 quote! { #m }
461 }
462 other => {
463 return Err(syn::Error::new(
464 *span,
465 format!("function '{other}' is not supported in expr!()"),
466 ));
467 }
468 };
469 Ok(quote! { (#arg_code).#method() })
470 }
471 }
472}
473
474#[proc_macro]
522pub fn dim(input: TokenStream) -> TokenStream {
523 let input = syn::parse_macro_input!(input as DimMacroInput);
524 let output_type = &input.output_type;
525 match generate_dim_expr(&input.ctx, &input.expr) {
526 Ok(expr_tokens) => quote! {
527 <#output_type as ::symplex::units::qty::FromDimExpr<_>>::from_dim_expr(#expr_tokens)
528 }
529 .into(),
530 Err(e) => e.to_compile_error().into(),
531 }
532}
533
534fn generate_dim_expr(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
540 match expr {
541 MathExpr::Int(n, _span) => Ok(quote! {
542 ::symplex::units::Dimensionless::from_ex(#ctx.int(#n)).as_qty()
543 }),
544
545 MathExpr::Ident(id) => {
546 let name = id.to_string();
547 match name.as_str() {
548 "pi" | "Pi" | "PI" => Ok(quote! {
549 ::symplex::units::Dimensionless::from_ex(#ctx.pi()).as_qty()
550 }),
551 "E" => Ok(quote! {
552 ::symplex::units::Dimensionless::from_ex(#ctx.e()).as_qty()
553 }),
554 _ => Ok(quote! { (#id).clone().as_qty() }),
555 }
556 }
557
558 MathExpr::Neg(inner) => {
559 let inner_code = generate_dim_expr(ctx, inner)?;
560 Ok(quote! { (-(#inner_code)) })
561 }
562
563 MathExpr::LogicalNot(_) => Err(syn::Error::new(
564 Span::call_site(),
565 "logical NOT (!) is not supported in dim!()",
566 )),
567
568 MathExpr::BinOp { op, lhs, rhs } => match op {
569 BinOp::Add => {
570 let l = generate_dim_expr(ctx, lhs)?;
571 let r = generate_dim_expr(ctx, rhs)?;
572 Ok(quote! { ((#l) + (#r)) })
573 }
574 BinOp::Sub => {
575 let l = generate_dim_expr(ctx, lhs)?;
576 let r = generate_dim_expr(ctx, rhs)?;
577 Ok(quote! { ((#l) - (#r)) })
578 }
579 BinOp::Mul => {
580 let l = generate_dim_expr(ctx, lhs)?;
581 let r = generate_dim_expr(ctx, rhs)?;
582 Ok(quote! { ((#l) * (#r)) })
583 }
584 BinOp::Div => {
585 if let (Some(p), Some(q)) = (lhs.as_int(), rhs.as_int()) {
587 if q == 0 {
588 return Err(syn::Error::new(
589 Span::call_site(),
590 "division by zero in dim!()",
591 ));
592 }
593 return Ok(quote! {
594 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#p, #q)).as_qty()
595 });
596 }
597 if let MathExpr::Neg(inner_lhs) = lhs.as_ref()
599 && let (Some(p), Some(q)) = (inner_lhs.as_int(), rhs.as_int())
600 {
601 if q == 0 {
602 return Err(syn::Error::new(
603 Span::call_site(),
604 "division by zero in dim!()",
605 ));
606 }
607 let neg_p = -p;
608 return Ok(quote! {
609 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#neg_p, #q)).as_qty()
610 });
611 }
612 let l = generate_dim_expr(ctx, lhs)?;
613 let r = generate_dim_expr(ctx, rhs)?;
614 Ok(quote! { ((#l) / (#r)) })
615 }
616 BinOp::Pow => {
617 if let Some(n) = rhs.as_int() {
620 return generate_dim_pow(ctx, lhs, n);
621 }
622 if let MathExpr::Neg(inner_rhs) = rhs.as_ref()
624 && let Some(n) = inner_rhs.as_int()
625 {
626 let pow_code = generate_dim_pow(ctx, lhs, n)?;
627 return Ok(quote! {
628 (::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() / (#pow_code))
629 });
630 }
631 let b = generate_dim_expr(ctx, lhs)?;
634 let e = generate_dim_expr(ctx, rhs)?;
635 Ok(quote! {
636 ::symplex::units::Dimensionless::from_ex(
637 (#b).into_inner().pow(&#e.into_inner())
638 ).as_qty()
639 })
640 }
641 _ => Err(syn::Error::new(
642 Span::call_site(),
643 format!("operator {:?} is not supported in dim!()", op),
644 )),
645 },
646
647 MathExpr::Func { name, span, args } => {
648 let func_str = name.as_str();
651 if args.len() == 1 {
652 let arg = generate_dim_expr(ctx, &args[0])?;
653 let method = match func_str {
654 "sin" => quote! { sin },
655 "cos" => quote! { cos },
656 "tan" => quote! { tan },
657 "asin" => quote! { asin },
658 "acos" => quote! { acos },
659 "atan" => quote! { atan },
660 "sinh" => quote! { sinh },
661 "cosh" => quote! { cosh },
662 "tanh" => quote! { tanh },
663 "exp" => quote! { exp },
664 "ln" => quote! { ln },
665 "sqrt" => quote! { sqrt },
666 "abs" => quote! { abs },
667 _ => {
668 return Err(syn::Error::new(
669 *span,
670 format!("dim!: unsupported function '{}'", func_str),
671 ));
672 }
673 };
674 Ok(quote! {
675 ::symplex::units::Dimensionless::from_ex(
676 (#arg).into_inner().#method()
677 ).as_qty()
678 })
679 } else {
680 Err(syn::Error::new(
681 *span,
682 format!(
683 "dim!: function '{}' with {} args is not supported",
684 func_str,
685 args.len()
686 ),
687 ))
688 }
689 }
690 }
691}
692
693fn generate_dim_pow(ctx: &Ident, base: &MathExpr, n: i64) -> syn::Result<TokenStream2> {
699 if n == 0 {
700 return Ok(quote! { ::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() });
701 }
702 if n == 1 {
703 return generate_dim_expr(ctx, base);
704 }
705 if (2..=8).contains(&n) {
706 let mut factors = Vec::new();
710 for _ in 0..n {
711 factors.push(generate_dim_expr(ctx, base)?);
712 }
713 let mut result = factors.remove(0);
714 for factor in factors {
715 result = quote! { ((#result) * (#factor)) };
716 }
717 return Ok(result);
718 }
719 let base_code = generate_dim_expr(ctx, base)?;
722 Ok(quote! {
723 ::symplex::units::Dimensionless::from_ex(
724 (#base_code).into_inner().powi(#n)
725 ).as_qty()
726 })
727}
728
729#[proc_macro]
772pub fn rule(input: TokenStream) -> TokenStream {
773 let input = syn::parse_macro_input!(input as RuleMacroInput);
774 match generate_rule(&input) {
775 Ok(tokens) => tokens.into(),
776 Err(e) => e.to_compile_error().into(),
777 }
778}
779
780fn generate_rule(input: &RuleMacroInput) -> syn::Result<TokenStream2> {
785 let arena = &input.arena;
786 let name = &input.name;
787
788 let lhs_wilds = input.lhs.collect_wilds();
790
791 for w in input.rhs.collect_wilds() {
793 if !lhs_wilds.iter().any(|existing| existing == &w) {
794 return Err(syn::Error::new_spanned(
795 &input.name,
796 format!(
797 "wild '{}' appears in RHS but not in LHS — it will never be bound by matching",
798 w
799 ),
800 ));
801 }
802 }
803
804 let all_wilds = lhs_wilds;
805
806 let mut codegen = RuleCodeGen {
807 arena: arena.clone(),
808 temp_counter: 0,
809 bindings: Vec::new(),
810 wild_expr_idents: Vec::new(),
811 wild_wid_idents: Vec::new(),
812 wild_names: Vec::new(),
813 };
814
815 for wild in &all_wilds {
817 let wild_name = wild.to_string();
818 let expr_ident = format_ident!("__wild_expr_{}", wild_name);
819 let wid_ident = format_ident!("__wild_wid_{}", wild_name);
820 codegen.bindings.push(quote! {
821 let (#expr_ident, #wid_ident) = #arena.wild();
822 });
823 codegen.wild_expr_idents.push(expr_ident);
824 codegen.wild_wid_idents.push(wid_ident);
825 codegen.wild_names.push(wild.clone());
826 }
827
828 let lhs_temp = codegen.generate_arena_expr(&input.lhs)?;
830
831 let rhs_temp = codegen.generate_arena_expr(&input.rhs)?;
833
834 let wild_inserts: Vec<TokenStream2> = codegen
836 .wild_names
837 .iter()
838 .zip(
839 codegen
840 .wild_expr_idents
841 .iter()
842 .zip(codegen.wild_wid_idents.iter()),
843 )
844 .map(|(_, (expr_id, wid_id))| {
845 quote! { __wilds.insert(#expr_id, #wid_id); }
846 })
847 .collect();
848
849 let bindings = &codegen.bindings;
850
851 let condition_code = if let Some(cond) = &input.condition {
852 quote! { Some(#cond) }
853 } else {
854 quote! { None }
855 };
856
857 Ok(quote! {
858 {
859 #(#bindings)*
860
861 let mut __wilds = ::symplex::__macro_support::FxHashMap::default();
862 #(#wild_inserts)*
863
864 ::symplex::__macro_support::Rule {
865 name: #name,
866 pattern: ::symplex::__macro_support::Pattern {
867 root: #lhs_temp,
868 wilds: __wilds,
869 },
870 template: #rhs_temp,
871 condition: #condition_code,
872 }
873 }
874 })
875}
876
877struct RuleCodeGen {
880 arena: Ident,
881 temp_counter: usize,
882 bindings: Vec<TokenStream2>,
883 wild_expr_idents: Vec<Ident>,
884 wild_wid_idents: Vec<Ident>,
885 wild_names: Vec<Ident>,
886}
887
888impl RuleCodeGen {
889 fn fresh_temp(&mut self) -> Ident {
891 let id = format_ident!("__t{}", self.temp_counter);
892 self.temp_counter += 1;
893 id
894 }
895
896 fn generate_arena_expr(&mut self, expr: &MathExpr) -> syn::Result<Ident> {
899 let arena = self.arena.clone();
900
901 match expr {
902 MathExpr::Int(n, _) => {
903 let temp = self.fresh_temp();
904 match *n {
905 0 => {
906 self.bindings.push(quote! { let #temp = #arena.zero(); });
907 }
908 1 => {
909 self.bindings.push(quote! { let #temp = #arena.one(); });
910 }
911 -1 => {
912 self.bindings.push(quote! { let #temp = #arena.neg_one(); });
913 }
914 _ => {
915 self.bindings.push(quote! { let #temp = #arena.int(#n); });
916 }
917 }
918 Ok(temp)
919 }
920
921 MathExpr::Ident(id) => {
922 let name = id.to_string();
923
924 if name.ends_with('_') {
926 for (i, wn) in self.wild_names.iter().enumerate() {
927 if wn == id {
928 return Ok(self.wild_expr_idents[i].clone());
929 }
930 }
931 return Err(syn::Error::new(id.span(), format!("unknown wild '{name}'")));
932 }
933
934 if is_known_constant(&name) {
936 let temp = self.fresh_temp();
937 let access = match name.as_str() {
938 "pi" => quote! { #arena.pi() },
939 "E" => quote! { #arena.e_const() },
940 "I" => quote! { #arena.i_unit() },
941 "oo" => quote! { #arena.infinity() },
942 "nan" => quote! { #arena.nan() },
943 "zoo" => quote! { #arena.complex_infinity() },
944 _ => unreachable!(),
945 };
946 self.bindings.push(quote! { let #temp = #access; });
947 return Ok(temp);
948 }
949
950 Err(syn::Error::new(
952 id.span(),
953 format!(
954 "unknown identifier '{name}' in rule!(). \
955 Use '{name}_' for a wild, or a known constant (pi, E, I, oo, nan, zoo), \
956 or an integer literal."
957 ),
958 ))
959 }
960
961 MathExpr::Neg(inner) => {
962 let inner_temp = self.generate_arena_expr(inner)?;
963 let temp = self.fresh_temp();
964 self.bindings
965 .push(quote! { let #temp = #arena.neg(#inner_temp); });
966 Ok(temp)
967 }
968
969 MathExpr::LogicalNot(inner) => {
970 let inner_temp = self.generate_arena_expr(inner)?;
971 let temp = self.fresh_temp();
972 self.bindings
973 .push(quote! { let #temp = #arena.not(#inner_temp); });
974 Ok(temp)
975 }
976
977 MathExpr::BinOp { op, lhs, rhs } => {
978 let lhs_temp = self.generate_arena_expr(lhs)?;
979 let rhs_temp = self.generate_arena_expr(rhs)?;
980 let temp = self.fresh_temp();
981
982 let call = match op {
983 BinOp::Add => quote! { #arena.add(&[#lhs_temp, #rhs_temp]) },
984 BinOp::Sub => quote! { #arena.sub(#lhs_temp, #rhs_temp) },
985 BinOp::Mul => quote! { #arena.mul(&[#lhs_temp, #rhs_temp]) },
986 BinOp::Div => quote! { #arena.div(#lhs_temp, #rhs_temp) },
987 BinOp::Pow => quote! { #arena.pow(#lhs_temp, #rhs_temp) },
988 BinOp::Gt => quote! { #arena.gt(#lhs_temp, #rhs_temp) },
989 BinOp::Lt => quote! { #arena.gt(#rhs_temp, #lhs_temp) },
990 BinOp::Ge => quote! { #arena.ge(#lhs_temp, #rhs_temp) },
991 BinOp::Le => quote! { #arena.ge(#rhs_temp, #lhs_temp) },
992 BinOp::EqEq => quote! { #arena.eq_(#lhs_temp, #rhs_temp) },
993 BinOp::Ne => quote! { #arena.ne_(#lhs_temp, #rhs_temp) },
994 BinOp::AndAnd => quote! { #arena.and(&[#lhs_temp, #rhs_temp]) },
995 BinOp::OrOr => quote! { #arena.or(&[#lhs_temp, #rhs_temp]) },
996 };
997
998 self.bindings.push(quote! { let #temp = #call; });
999 Ok(temp)
1000 }
1001
1002 MathExpr::Func { name, span, args } => {
1003 if !is_known_function(name) {
1004 return Err(syn::Error::new(
1005 *span,
1006 format!(
1007 "unknown function '{}' in rule!(). Supported: {}",
1008 name,
1009 KNOWN_FUNCTIONS.join(", ")
1010 ),
1011 ));
1012 }
1013
1014 if name == "beta" && args.len() == 2 {
1016 let a_temp = self.generate_arena_expr(&args[0])?;
1017 let b_temp = self.generate_arena_expr(&args[1])?;
1018 let temp = self.fresh_temp();
1019 self.bindings
1020 .push(quote! { let #temp = #arena.beta(#a_temp, #b_temp); });
1021 return Ok(temp);
1022 }
1023 if name == "atan2" && args.len() == 2 {
1024 let y_temp = self.generate_arena_expr(&args[0])?;
1025 let x_temp = self.generate_arena_expr(&args[1])?;
1026 let temp = self.fresh_temp();
1027 self.bindings
1028 .push(quote! { let #temp = #arena.atan2(#y_temp, #x_temp); });
1029 return Ok(temp);
1030 }
1031
1032 if args.len() != 1 {
1033 return Err(syn::Error::new(
1034 *span,
1035 format!("{}() takes exactly 1 argument in rule!()", name),
1036 ));
1037 }
1038
1039 let arg_temp = self.generate_arena_expr(&args[0])?;
1040 let temp = self.fresh_temp();
1041
1042 let call = match name.as_str() {
1043 "sin" => quote! { #arena.sin(#arg_temp) },
1045 "cos" => quote! { #arena.cos(#arg_temp) },
1046 "tan" => quote! { #arena.tan(#arg_temp) },
1047 "asin" => quote! { #arena.asin(#arg_temp) },
1048 "acos" => quote! { #arena.acos(#arg_temp) },
1049 "atan" => quote! { #arena.atan(#arg_temp) },
1050 "sinh" => quote! { #arena.sinh(#arg_temp) },
1051 "cosh" => quote! { #arena.cosh(#arg_temp) },
1052 "tanh" => quote! { #arena.tanh(#arg_temp) },
1053 "asinh" => quote! { #arena.asinh(#arg_temp) },
1054 "acosh" => quote! { #arena.acosh(#arg_temp) },
1055 "atanh" => quote! { #arena.atanh(#arg_temp) },
1056 "exp" => quote! { #arena.exp(#arg_temp) },
1058 "ln" => quote! { #arena.ln(#arg_temp) },
1059 "sqrt" => quote! { #arena.sqrt(#arg_temp) },
1060 "cbrt" => quote! { #arena.cbrt(#arg_temp) },
1061 "abs" => quote! { #arena.abs(#arg_temp) },
1062 "sign" => quote! { #arena.sign(#arg_temp) },
1063 "floor" => quote! { #arena.floor(#arg_temp) },
1065 "ceiling" => quote! { #arena.ceiling(#arg_temp) },
1066 "gamma" => quote! { #arena.gamma(#arg_temp) },
1068 "log_gamma" => quote! { #arena.log_gamma(#arg_temp) },
1069 "digamma" => quote! { #arena.digamma(#arg_temp) },
1070 "erf" => quote! { #arena.erf(#arg_temp) },
1071 "erfc" => quote! { #arena.erfc(#arg_temp) },
1072 "heaviside" => quote! { #arena.heaviside(#arg_temp) },
1074 "dirac_delta" => quote! { #arena.dirac_delta(#arg_temp) },
1075 "lambertw" => quote! { #arena.lambertw(#arg_temp) },
1076 "fibonacci" => quote! { #arena.fibonacci(#arg_temp) },
1078 "lucas" => quote! { #arena.lucas(#arg_temp) },
1079 "catalan_number" => quote! { #arena.catalan_number(#arg_temp) },
1080 "bell" => quote! { #arena.bell(#arg_temp) },
1081 "euler_number" => quote! { #arena.euler_number(#arg_temp) },
1082 "harmonic" => quote! { #arena.harmonic(#arg_temp) },
1083 "subfactorial" => quote! { #arena.subfactorial(#arg_temp) },
1084 "factorial2" => quote! { #arena.factorial2(#arg_temp) },
1085 "bernoulli_number" => quote! { #arena.bernoulli_number(#arg_temp) },
1086 other => {
1087 return Err(syn::Error::new(
1088 *span,
1089 format!(
1090 "function '{}' is recognised but not yet supported in rule!()",
1091 other
1092 ),
1093 ));
1094 }
1095 };
1096
1097 self.bindings.push(quote! { let #temp = #call; });
1098 Ok(temp)
1099 }
1100 }
1101 }
1102}
1103
1104#[proc_macro]
1121pub fn matrix(input: TokenStream) -> TokenStream {
1122 let input = syn::parse_macro_input!(input as MatrixMacroInput);
1123 match generate_matrix(&input.ctx, &input) {
1124 Ok(tokens) => tokens.into(),
1125 Err(e) => e.to_compile_error().into(),
1126 }
1127}
1128
1129fn generate_matrix(ctx: &Ident, input: &MatrixMacroInput) -> syn::Result<TokenStream2> {
1130 let mut row_codes = Vec::new();
1131 for row in &input.rows {
1132 let mut cell_codes = Vec::new();
1133 for cell in row {
1134 let cell_expr = generate_expr_as_ex(ctx, cell)?;
1135 cell_codes.push(quote! { #cell_expr });
1136 }
1137 row_codes.push(quote! { vec![#(#cell_codes),*] });
1138 }
1139 let nrows = input.rows.len();
1142 let ncols = input.rows[0].len();
1143 Ok(quote! {
1144 {
1145 let __rows: ::std::vec::Vec<::std::vec::Vec<::symplex::expr::Ex>> = vec![#(#row_codes),*];
1146 ::symplex::matrix::Matrix::from_fn(#nrows, #ncols, |__i, __j| __rows[__i][__j].clone())
1147 }
1148 })
1149}
1150
1151#[proc_macro]
1168pub fn eq(input: TokenStream) -> TokenStream {
1169 let input = syn::parse_macro_input!(input as EqMacroInput);
1170 match generate_eq(&input.ctx, &input) {
1171 Ok(tokens) => tokens.into(),
1172 Err(e) => e.to_compile_error().into(),
1173 }
1174}
1175
1176fn generate_eq(ctx: &Ident, input: &EqMacroInput) -> syn::Result<TokenStream2> {
1177 let lhs_code = generate_expr_as_ex(ctx, &input.lhs)?;
1178 let rhs_code = generate_expr_as_ex(ctx, &input.rhs)?;
1179 Ok(quote! {
1180 ::symplex::eq::Equation::new(#lhs_code, #rhs_code)
1181 })
1182}
1183
1184fn generate_expr_as_ex(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
1191 match expr {
1192 MathExpr::Int(n, _) => Ok(quote! { #ctx.int(#n) }),
1193 MathExpr::Neg(inner) => {
1194 if let Some(n) = inner.as_int() {
1195 let neg_n = -n;
1196 Ok(quote! { #ctx.int(#neg_n) })
1197 } else {
1198 let code = generate_expr(ctx, expr)?;
1199 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1200 }
1201 }
1202 MathExpr::Func { .. } => generate_expr(ctx, expr),
1203 _ => {
1204 let code = generate_expr(ctx, expr)?;
1205 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1206 }
1207 }
1208}