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() {
325 "expint" => Some(("expint", 1)),
326 "lowergamma" => Some(("lowergamma", 1)),
327 "uppergamma" => Some(("uppergamma", 1)),
328 "polylog" => Some(("polylog", 1)),
329 "elliptic_f" => Some(("elliptic_f", 1)),
330 "elliptic_pi" => Some(("elliptic_pi", 1)),
331 "gegenbauer" => Some(("gegenbauer", 2)),
332 "assoc_legendre" => Some(("assoc_legendre", 2)),
333 "assoc_laguerre" => Some(("assoc_laguerre", 2)),
334 "jacobi" => Some(("jacobi", 3)),
335 "betainc" => Some(("betainc", 3)),
336 "betainc_regularized" => Some(("betainc_regularized", 3)),
337 _ => None,
338 } {
339 if args.len() != nparams + 1 {
340 return Err(syn::Error::new(
341 *span,
342 format!(
343 "{name}() takes exactly {} arguments in expr!()",
344 nparams + 1
345 ),
346 ));
347 }
348 let x = generate_expr_as_ex(ctx, &args[nparams])?;
349 let params = args[..nparams]
350 .iter()
351 .map(|a| generate_expr_as_ex(ctx, a))
352 .collect::<syn::Result<Vec<_>>>()?;
353 let method = format_ident!("{method}");
354 return Ok(quote! { (#x).#method(#(&(#params)),*) });
355 }
356
357 if !is_known_function(name)
358 && ![
359 "log",
360 "diff",
361 "factorial",
362 "binomial",
363 "C",
364 "atan2",
365 "rising_factorial",
366 "falling_factorial",
367 "beta",
368 "min",
369 "max",
370 "expint",
371 "lowergamma",
372 "uppergamma",
373 "polylog",
374 "elliptic_f",
375 "elliptic_pi",
376 "gegenbauer",
377 "assoc_legendre",
378 "assoc_laguerre",
379 "jacobi",
380 "betainc",
381 "betainc_regularized",
382 ]
383 .contains(&name.as_str())
384 {
385 return Err(syn::Error::new(
386 *span,
387 format!(
388 "unknown function '{}' in expr!(). Supported: {}, log, diff, factorial, binomial, C, atan2, rising_factorial, falling_factorial, beta, min, max",
389 name,
390 KNOWN_FUNCTIONS.join(", ")
391 ),
392 ));
393 }
394 if args.len() != 1 {
395 return Err(syn::Error::new(
396 *span,
397 format!("{}() takes exactly 1 argument in expr!()", name),
398 ));
399 }
400 let arg_code = generate_expr_as_ex(ctx, &args[0])?;
401 let method = match name.as_str() {
402 "sin" => quote! { sin },
403 "cos" => quote! { cos },
404 "tan" => quote! { tan },
405 "asin" => quote! { asin },
406 "acos" => quote! { acos },
407 "atan" => quote! { atan },
408 "sinh" => quote! { sinh },
409 "cosh" => quote! { cosh },
410 "tanh" => quote! { tanh },
411 "asinh" => quote! { asinh },
412 "acosh" => quote! { acosh },
413 "atanh" => quote! { atanh },
414 "exp" => quote! { exp },
415 "ln" => quote! { ln },
416 "sqrt" => quote! { sqrt },
417 "cbrt" => quote! { cbrt },
418 "abs" => quote! { abs },
419 "sign" => quote! { sign },
420 "floor" => quote! { floor },
421 "ceiling" => quote! { ceiling },
422 "sec" => quote! { sec },
424 "csc" => quote! { csc },
425 "cot" => quote! { cot },
426 "acot" => quote! { acot },
427 "asec" => quote! { asec },
428 "acsc" => quote! { acsc },
429 "coth" => quote! { coth },
430 "sech" => quote! { sech },
431 "csch" => quote! { csch },
432 "acoth" => quote! { acoth },
433 "asech" => quote! { asech },
434 "acsch" => quote! { acsch },
435 "sinc" => quote! { sinc },
436 "arg" => quote! { arg },
438 "conjugate" => quote! { conjugate },
439 "fibonacci" => quote! { fibonacci },
441 "lucas" => quote! { lucas },
442 "catalan_number" => quote! { catalan_number },
443 "bell" => quote! { bell },
444 "euler_number" => quote! { euler_number },
445 "harmonic" => quote! { harmonic },
446 "subfactorial" => quote! { subfactorial },
447 "factorial2" => quote! { factorial2 },
448 "bernoulli_number" => quote! { bernoulli_number },
449 "heaviside" => quote! { heaviside },
451 "dirac_delta" => quote! { dirac_delta },
452 "lambertw" => quote! { lambertw },
453 "gamma" => quote! { gamma },
455 "log_gamma" => quote! { log_gamma },
456 "digamma" => quote! { digamma },
457 "erf" => quote! { erf },
458 "erfc" => quote! { erfc },
459 "erfi" | "erfinv" | "erfcinv" | "e1" | "shi" | "chi" | "fresnels" | "fresnelc"
462 | "dirichlet_eta" | "airyai" | "airybi" | "airyaiprime" | "airybiprime"
463 | "elliptic_k" | "elliptic_e" => {
464 let m = format_ident!("{}", name.as_str());
465 quote! { #m }
466 }
467 other => {
468 return Err(syn::Error::new(
469 *span,
470 format!("function '{other}' is not supported in expr!()"),
471 ));
472 }
473 };
474 Ok(quote! { (#arg_code).#method() })
475 }
476 }
477}
478
479#[proc_macro]
527pub fn dim(input: TokenStream) -> TokenStream {
528 let input = syn::parse_macro_input!(input as DimMacroInput);
529 let output_type = &input.output_type;
530 match generate_dim_expr(&input.ctx, &input.expr) {
531 Ok(expr_tokens) => quote! {
532 <#output_type as ::symplex::units::qty::FromDimExpr<_>>::from_dim_expr(#expr_tokens)
533 }
534 .into(),
535 Err(e) => e.to_compile_error().into(),
536 }
537}
538
539fn generate_dim_expr(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
545 match expr {
546 MathExpr::Int(n, _span) => Ok(quote! {
547 ::symplex::units::Dimensionless::from_ex(#ctx.int(#n)).as_qty()
548 }),
549
550 MathExpr::Ident(id) => {
551 let name = id.to_string();
552 match name.as_str() {
553 "pi" | "Pi" | "PI" => Ok(quote! {
554 ::symplex::units::Dimensionless::from_ex(#ctx.pi()).as_qty()
555 }),
556 "E" => Ok(quote! {
557 ::symplex::units::Dimensionless::from_ex(#ctx.e()).as_qty()
558 }),
559 _ => Ok(quote! { (#id).clone().as_qty() }),
560 }
561 }
562
563 MathExpr::Neg(inner) => {
564 let inner_code = generate_dim_expr(ctx, inner)?;
565 Ok(quote! { (-(#inner_code)) })
566 }
567
568 MathExpr::LogicalNot(_) => Err(syn::Error::new(
569 Span::call_site(),
570 "logical NOT (!) is not supported in dim!()",
571 )),
572
573 MathExpr::BinOp { op, lhs, rhs } => match op {
574 BinOp::Add => {
575 let l = generate_dim_expr(ctx, lhs)?;
576 let r = generate_dim_expr(ctx, rhs)?;
577 Ok(quote! { ((#l) + (#r)) })
578 }
579 BinOp::Sub => {
580 let l = generate_dim_expr(ctx, lhs)?;
581 let r = generate_dim_expr(ctx, rhs)?;
582 Ok(quote! { ((#l) - (#r)) })
583 }
584 BinOp::Mul => {
585 let l = generate_dim_expr(ctx, lhs)?;
586 let r = generate_dim_expr(ctx, rhs)?;
587 Ok(quote! { ((#l) * (#r)) })
588 }
589 BinOp::Div => {
590 if let (Some(p), Some(q)) = (lhs.as_int(), rhs.as_int()) {
592 if q == 0 {
593 return Err(syn::Error::new(
594 Span::call_site(),
595 "division by zero in dim!()",
596 ));
597 }
598 return Ok(quote! {
599 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#p, #q)).as_qty()
600 });
601 }
602 if let MathExpr::Neg(inner_lhs) = lhs.as_ref()
604 && let (Some(p), Some(q)) = (inner_lhs.as_int(), rhs.as_int())
605 {
606 if q == 0 {
607 return Err(syn::Error::new(
608 Span::call_site(),
609 "division by zero in dim!()",
610 ));
611 }
612 let neg_p = -p;
613 return Ok(quote! {
614 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#neg_p, #q)).as_qty()
615 });
616 }
617 let l = generate_dim_expr(ctx, lhs)?;
618 let r = generate_dim_expr(ctx, rhs)?;
619 Ok(quote! { ((#l) / (#r)) })
620 }
621 BinOp::Pow => {
622 if let Some(n) = rhs.as_int() {
625 return generate_dim_pow(ctx, lhs, n);
626 }
627 if let MathExpr::Neg(inner_rhs) = rhs.as_ref()
629 && let Some(n) = inner_rhs.as_int()
630 {
631 let pow_code = generate_dim_pow(ctx, lhs, n)?;
632 return Ok(quote! {
633 (::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() / (#pow_code))
634 });
635 }
636 let b = generate_dim_expr(ctx, lhs)?;
639 let e = generate_dim_expr(ctx, rhs)?;
640 Ok(quote! {
641 ::symplex::units::Dimensionless::from_ex(
642 (#b).into_inner().pow(&#e.into_inner())
643 ).as_qty()
644 })
645 }
646 _ => Err(syn::Error::new(
647 Span::call_site(),
648 format!("operator {:?} is not supported in dim!()", op),
649 )),
650 },
651
652 MathExpr::Func { name, span, args } => {
653 let func_str = name.as_str();
656 if args.len() == 1 {
657 let arg = generate_dim_expr(ctx, &args[0])?;
658 let method = match func_str {
659 "sin" => quote! { sin },
660 "cos" => quote! { cos },
661 "tan" => quote! { tan },
662 "asin" => quote! { asin },
663 "acos" => quote! { acos },
664 "atan" => quote! { atan },
665 "sinh" => quote! { sinh },
666 "cosh" => quote! { cosh },
667 "tanh" => quote! { tanh },
668 "exp" => quote! { exp },
669 "ln" => quote! { ln },
670 "sqrt" => quote! { sqrt },
671 "abs" => quote! { abs },
672 _ => {
673 return Err(syn::Error::new(
674 *span,
675 format!("dim!: unsupported function '{}'", func_str),
676 ));
677 }
678 };
679 Ok(quote! {
680 ::symplex::units::Dimensionless::from_ex(
681 (#arg).into_inner().#method()
682 ).as_qty()
683 })
684 } else {
685 Err(syn::Error::new(
686 *span,
687 format!(
688 "dim!: function '{}' with {} args is not supported",
689 func_str,
690 args.len()
691 ),
692 ))
693 }
694 }
695 }
696}
697
698fn generate_dim_pow(ctx: &Ident, base: &MathExpr, n: i64) -> syn::Result<TokenStream2> {
704 if n == 0 {
705 return Ok(quote! { ::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() });
706 }
707 if n == 1 {
708 return generate_dim_expr(ctx, base);
709 }
710 if (2..=8).contains(&n) {
711 let mut factors = Vec::new();
715 for _ in 0..n {
716 factors.push(generate_dim_expr(ctx, base)?);
717 }
718 let mut result = factors.remove(0);
719 for factor in factors {
720 result = quote! { ((#result) * (#factor)) };
721 }
722 return Ok(result);
723 }
724 let base_code = generate_dim_expr(ctx, base)?;
727 Ok(quote! {
728 ::symplex::units::Dimensionless::from_ex(
729 (#base_code).into_inner().powi(#n)
730 ).as_qty()
731 })
732}
733
734#[proc_macro]
777pub fn rule(input: TokenStream) -> TokenStream {
778 let input = syn::parse_macro_input!(input as RuleMacroInput);
779 match generate_rule(&input) {
780 Ok(tokens) => tokens.into(),
781 Err(e) => e.to_compile_error().into(),
782 }
783}
784
785fn generate_rule(input: &RuleMacroInput) -> syn::Result<TokenStream2> {
790 let arena = &input.arena;
791 let name = &input.name;
792
793 let lhs_wilds = input.lhs.collect_wilds();
795
796 for w in input.rhs.collect_wilds() {
798 if !lhs_wilds.iter().any(|existing| existing == &w) {
799 return Err(syn::Error::new_spanned(
800 &input.name,
801 format!(
802 "wild '{}' appears in RHS but not in LHS — it will never be bound by matching",
803 w
804 ),
805 ));
806 }
807 }
808
809 let all_wilds = lhs_wilds;
810
811 let mut codegen = RuleCodeGen {
812 arena: arena.clone(),
813 temp_counter: 0,
814 bindings: Vec::new(),
815 wild_expr_idents: Vec::new(),
816 wild_wid_idents: Vec::new(),
817 wild_names: Vec::new(),
818 };
819
820 for wild in &all_wilds {
822 let wild_name = wild.to_string();
823 let expr_ident = format_ident!("__wild_expr_{}", wild_name);
824 let wid_ident = format_ident!("__wild_wid_{}", wild_name);
825 codegen.bindings.push(quote! {
826 let (#expr_ident, #wid_ident) = #arena.wild();
827 });
828 codegen.wild_expr_idents.push(expr_ident);
829 codegen.wild_wid_idents.push(wid_ident);
830 codegen.wild_names.push(wild.clone());
831 }
832
833 let lhs_temp = codegen.generate_arena_expr(&input.lhs)?;
835
836 let rhs_temp = codegen.generate_arena_expr(&input.rhs)?;
838
839 let wild_inserts: Vec<TokenStream2> = codegen
841 .wild_names
842 .iter()
843 .zip(
844 codegen
845 .wild_expr_idents
846 .iter()
847 .zip(codegen.wild_wid_idents.iter()),
848 )
849 .map(|(_, (expr_id, wid_id))| {
850 quote! { __wilds.insert(#expr_id, #wid_id); }
851 })
852 .collect();
853
854 let bindings = &codegen.bindings;
855
856 let condition_code = if let Some(cond) = &input.condition {
857 quote! { Some(#cond) }
858 } else {
859 quote! { None }
860 };
861
862 Ok(quote! {
863 {
864 #(#bindings)*
865
866 let mut __wilds = ::symplex::__macro_support::FxHashMap::default();
867 #(#wild_inserts)*
868
869 ::symplex::__macro_support::Rule {
870 name: #name,
871 pattern: ::symplex::__macro_support::Pattern {
872 root: #lhs_temp,
873 wilds: __wilds,
874 },
875 template: #rhs_temp,
876 condition: #condition_code,
877 }
878 }
879 })
880}
881
882struct RuleCodeGen {
885 arena: Ident,
886 temp_counter: usize,
887 bindings: Vec<TokenStream2>,
888 wild_expr_idents: Vec<Ident>,
889 wild_wid_idents: Vec<Ident>,
890 wild_names: Vec<Ident>,
891}
892
893impl RuleCodeGen {
894 fn fresh_temp(&mut self) -> Ident {
896 let id = format_ident!("__t{}", self.temp_counter);
897 self.temp_counter += 1;
898 id
899 }
900
901 fn generate_arena_expr(&mut self, expr: &MathExpr) -> syn::Result<Ident> {
904 let arena = self.arena.clone();
905
906 match expr {
907 MathExpr::Int(n, _) => {
908 let temp = self.fresh_temp();
909 match *n {
910 0 => {
911 self.bindings.push(quote! { let #temp = #arena.zero(); });
912 }
913 1 => {
914 self.bindings.push(quote! { let #temp = #arena.one(); });
915 }
916 -1 => {
917 self.bindings.push(quote! { let #temp = #arena.neg_one(); });
918 }
919 _ => {
920 self.bindings.push(quote! { let #temp = #arena.int(#n); });
921 }
922 }
923 Ok(temp)
924 }
925
926 MathExpr::Ident(id) => {
927 let name = id.to_string();
928
929 if name.ends_with('_') {
931 for (i, wn) in self.wild_names.iter().enumerate() {
932 if wn == id {
933 return Ok(self.wild_expr_idents[i].clone());
934 }
935 }
936 return Err(syn::Error::new(id.span(), format!("unknown wild '{name}'")));
937 }
938
939 if is_known_constant(&name) {
941 let temp = self.fresh_temp();
942 let access = match name.as_str() {
943 "pi" => quote! { #arena.pi() },
944 "E" => quote! { #arena.e_const() },
945 "I" => quote! { #arena.i_unit() },
946 "oo" => quote! { #arena.infinity() },
947 "nan" => quote! { #arena.nan() },
948 "zoo" => quote! { #arena.complex_infinity() },
949 _ => unreachable!(),
950 };
951 self.bindings.push(quote! { let #temp = #access; });
952 return Ok(temp);
953 }
954
955 Err(syn::Error::new(
957 id.span(),
958 format!(
959 "unknown identifier '{name}' in rule!(). \
960 Use '{name}_' for a wild, or a known constant (pi, E, I, oo, nan, zoo), \
961 or an integer literal."
962 ),
963 ))
964 }
965
966 MathExpr::Neg(inner) => {
967 let inner_temp = self.generate_arena_expr(inner)?;
968 let temp = self.fresh_temp();
969 self.bindings
970 .push(quote! { let #temp = #arena.neg(#inner_temp); });
971 Ok(temp)
972 }
973
974 MathExpr::LogicalNot(inner) => {
975 let inner_temp = self.generate_arena_expr(inner)?;
976 let temp = self.fresh_temp();
977 self.bindings
978 .push(quote! { let #temp = #arena.not(#inner_temp); });
979 Ok(temp)
980 }
981
982 MathExpr::BinOp { op, lhs, rhs } => {
983 let lhs_temp = self.generate_arena_expr(lhs)?;
984 let rhs_temp = self.generate_arena_expr(rhs)?;
985 let temp = self.fresh_temp();
986
987 let call = match op {
988 BinOp::Add => quote! { #arena.add(&[#lhs_temp, #rhs_temp]) },
989 BinOp::Sub => quote! { #arena.sub(#lhs_temp, #rhs_temp) },
990 BinOp::Mul => quote! { #arena.mul(&[#lhs_temp, #rhs_temp]) },
991 BinOp::Div => quote! { #arena.div(#lhs_temp, #rhs_temp) },
992 BinOp::Pow => quote! { #arena.pow(#lhs_temp, #rhs_temp) },
993 BinOp::Gt => quote! { #arena.gt(#lhs_temp, #rhs_temp) },
994 BinOp::Lt => quote! { #arena.gt(#rhs_temp, #lhs_temp) },
995 BinOp::Ge => quote! { #arena.ge(#lhs_temp, #rhs_temp) },
996 BinOp::Le => quote! { #arena.ge(#rhs_temp, #lhs_temp) },
997 BinOp::EqEq => quote! { #arena.eq_(#lhs_temp, #rhs_temp) },
998 BinOp::Ne => quote! { #arena.ne_(#lhs_temp, #rhs_temp) },
999 BinOp::AndAnd => quote! { #arena.and(&[#lhs_temp, #rhs_temp]) },
1000 BinOp::OrOr => quote! { #arena.or(&[#lhs_temp, #rhs_temp]) },
1001 };
1002
1003 self.bindings.push(quote! { let #temp = #call; });
1004 Ok(temp)
1005 }
1006
1007 MathExpr::Func { name, span, args } => {
1008 if !is_known_function(name) {
1009 return Err(syn::Error::new(
1010 *span,
1011 format!(
1012 "unknown function '{}' in rule!(). Supported: {}",
1013 name,
1014 KNOWN_FUNCTIONS.join(", ")
1015 ),
1016 ));
1017 }
1018
1019 if name == "beta" && args.len() == 2 {
1021 let a_temp = self.generate_arena_expr(&args[0])?;
1022 let b_temp = self.generate_arena_expr(&args[1])?;
1023 let temp = self.fresh_temp();
1024 self.bindings
1025 .push(quote! { let #temp = #arena.beta(#a_temp, #b_temp); });
1026 return Ok(temp);
1027 }
1028 if name == "atan2" && args.len() == 2 {
1029 let y_temp = self.generate_arena_expr(&args[0])?;
1030 let x_temp = self.generate_arena_expr(&args[1])?;
1031 let temp = self.fresh_temp();
1032 self.bindings
1033 .push(quote! { let #temp = #arena.atan2(#y_temp, #x_temp); });
1034 return Ok(temp);
1035 }
1036
1037 if args.len() != 1 {
1038 return Err(syn::Error::new(
1039 *span,
1040 format!("{}() takes exactly 1 argument in rule!()", name),
1041 ));
1042 }
1043
1044 let arg_temp = self.generate_arena_expr(&args[0])?;
1045 let temp = self.fresh_temp();
1046
1047 let call = match name.as_str() {
1048 "sin" => quote! { #arena.sin(#arg_temp) },
1050 "cos" => quote! { #arena.cos(#arg_temp) },
1051 "tan" => quote! { #arena.tan(#arg_temp) },
1052 "asin" => quote! { #arena.asin(#arg_temp) },
1053 "acos" => quote! { #arena.acos(#arg_temp) },
1054 "atan" => quote! { #arena.atan(#arg_temp) },
1055 "sinh" => quote! { #arena.sinh(#arg_temp) },
1056 "cosh" => quote! { #arena.cosh(#arg_temp) },
1057 "tanh" => quote! { #arena.tanh(#arg_temp) },
1058 "asinh" => quote! { #arena.asinh(#arg_temp) },
1059 "acosh" => quote! { #arena.acosh(#arg_temp) },
1060 "atanh" => quote! { #arena.atanh(#arg_temp) },
1061 "exp" => quote! { #arena.exp(#arg_temp) },
1063 "ln" => quote! { #arena.ln(#arg_temp) },
1064 "sqrt" => quote! { #arena.sqrt(#arg_temp) },
1065 "cbrt" => quote! { #arena.cbrt(#arg_temp) },
1066 "abs" => quote! { #arena.abs(#arg_temp) },
1067 "sign" => quote! { #arena.sign(#arg_temp) },
1068 "floor" => quote! { #arena.floor(#arg_temp) },
1070 "ceiling" => quote! { #arena.ceiling(#arg_temp) },
1071 "gamma" => quote! { #arena.gamma(#arg_temp) },
1073 "log_gamma" => quote! { #arena.log_gamma(#arg_temp) },
1074 "digamma" => quote! { #arena.digamma(#arg_temp) },
1075 "erf" => quote! { #arena.erf(#arg_temp) },
1076 "erfc" => quote! { #arena.erfc(#arg_temp) },
1077 "heaviside" => quote! { #arena.heaviside(#arg_temp) },
1079 "dirac_delta" => quote! { #arena.dirac_delta(#arg_temp) },
1080 "lambertw" => quote! { #arena.lambertw(#arg_temp) },
1081 "fibonacci" => quote! { #arena.fibonacci(#arg_temp) },
1083 "lucas" => quote! { #arena.lucas(#arg_temp) },
1084 "catalan_number" => quote! { #arena.catalan_number(#arg_temp) },
1085 "bell" => quote! { #arena.bell(#arg_temp) },
1086 "euler_number" => quote! { #arena.euler_number(#arg_temp) },
1087 "harmonic" => quote! { #arena.harmonic(#arg_temp) },
1088 "subfactorial" => quote! { #arena.subfactorial(#arg_temp) },
1089 "factorial2" => quote! { #arena.factorial2(#arg_temp) },
1090 "bernoulli_number" => quote! { #arena.bernoulli_number(#arg_temp) },
1091 other => {
1092 return Err(syn::Error::new(
1093 *span,
1094 format!(
1095 "function '{}' is recognised but not yet supported in rule!()",
1096 other
1097 ),
1098 ));
1099 }
1100 };
1101
1102 self.bindings.push(quote! { let #temp = #call; });
1103 Ok(temp)
1104 }
1105 }
1106 }
1107}
1108
1109#[proc_macro]
1126pub fn matrix(input: TokenStream) -> TokenStream {
1127 let input = syn::parse_macro_input!(input as MatrixMacroInput);
1128 match generate_matrix(&input.ctx, &input) {
1129 Ok(tokens) => tokens.into(),
1130 Err(e) => e.to_compile_error().into(),
1131 }
1132}
1133
1134fn generate_matrix(ctx: &Ident, input: &MatrixMacroInput) -> syn::Result<TokenStream2> {
1135 let mut row_codes = Vec::new();
1136 for row in &input.rows {
1137 let mut cell_codes = Vec::new();
1138 for cell in row {
1139 let cell_expr = generate_expr_as_ex(ctx, cell)?;
1140 cell_codes.push(quote! { #cell_expr });
1141 }
1142 row_codes.push(quote! { vec![#(#cell_codes),*] });
1143 }
1144 let nrows = input.rows.len();
1147 let ncols = input.rows[0].len();
1148 Ok(quote! {
1149 {
1150 let __rows: ::std::vec::Vec<::std::vec::Vec<::symplex::expr::Ex>> = vec![#(#row_codes),*];
1151 ::symplex::matrix::Matrix::from_fn(#nrows, #ncols, |__i, __j| __rows[__i][__j].clone())
1152 }
1153 })
1154}
1155
1156#[proc_macro]
1173pub fn eq(input: TokenStream) -> TokenStream {
1174 let input = syn::parse_macro_input!(input as EqMacroInput);
1175 match generate_eq(&input.ctx, &input) {
1176 Ok(tokens) => tokens.into(),
1177 Err(e) => e.to_compile_error().into(),
1178 }
1179}
1180
1181fn generate_eq(ctx: &Ident, input: &EqMacroInput) -> syn::Result<TokenStream2> {
1182 let lhs_code = generate_expr_as_ex(ctx, &input.lhs)?;
1183 let rhs_code = generate_expr_as_ex(ctx, &input.rhs)?;
1184 Ok(quote! {
1185 ::symplex::eq::Equation::new(#lhs_code, #rhs_code)
1186 })
1187}
1188
1189fn generate_expr_as_ex(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
1196 match expr {
1197 MathExpr::Int(n, _) => Ok(quote! { #ctx.int(#n) }),
1198 MathExpr::Neg(inner) => {
1199 if let Some(n) = inner.as_int() {
1200 let neg_n = -n;
1201 Ok(quote! { #ctx.int(#neg_n) })
1202 } else {
1203 let code = generate_expr(ctx, expr)?;
1204 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1205 }
1206 }
1207 MathExpr::Func { .. } => generate_expr(ctx, expr),
1208 _ => {
1209 let code = generate_expr(ctx, expr)?;
1210 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1211 }
1212 }
1213}