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 !is_known_function(name)
321 && ![
322 "log",
323 "diff",
324 "factorial",
325 "binomial",
326 "C",
327 "atan2",
328 "rising_factorial",
329 "falling_factorial",
330 "beta",
331 "min",
332 "max",
333 ]
334 .contains(&name.as_str())
335 {
336 return Err(syn::Error::new(
337 *span,
338 format!(
339 "unknown function '{}' in expr!(). Supported: {}, log, diff, factorial, binomial, C, atan2, rising_factorial, falling_factorial, beta, min, max",
340 name,
341 KNOWN_FUNCTIONS.join(", ")
342 ),
343 ));
344 }
345 if args.len() != 1 {
346 return Err(syn::Error::new(
347 *span,
348 format!("{}() takes exactly 1 argument in expr!()", name),
349 ));
350 }
351 let arg_code = generate_expr_as_ex(ctx, &args[0])?;
352 let method = match name.as_str() {
353 "sin" => quote! { sin },
354 "cos" => quote! { cos },
355 "tan" => quote! { tan },
356 "asin" => quote! { asin },
357 "acos" => quote! { acos },
358 "atan" => quote! { atan },
359 "sinh" => quote! { sinh },
360 "cosh" => quote! { cosh },
361 "tanh" => quote! { tanh },
362 "asinh" => quote! { asinh },
363 "acosh" => quote! { acosh },
364 "atanh" => quote! { atanh },
365 "exp" => quote! { exp },
366 "ln" => quote! { ln },
367 "sqrt" => quote! { sqrt },
368 "cbrt" => quote! { cbrt },
369 "abs" => quote! { abs },
370 "sign" => quote! { sign },
371 "floor" => quote! { floor },
372 "ceiling" => quote! { ceiling },
373 "sec" => quote! { sec },
375 "csc" => quote! { csc },
376 "cot" => quote! { cot },
377 "acot" => quote! { acot },
378 "asec" => quote! { asec },
379 "acsc" => quote! { acsc },
380 "coth" => quote! { coth },
381 "sech" => quote! { sech },
382 "csch" => quote! { csch },
383 "acoth" => quote! { acoth },
384 "asech" => quote! { asech },
385 "acsch" => quote! { acsch },
386 "sinc" => quote! { sinc },
387 "arg" => quote! { arg },
389 "conjugate" => quote! { conjugate },
390 "fibonacci" => quote! { fibonacci },
392 "lucas" => quote! { lucas },
393 "catalan_number" => quote! { catalan_number },
394 "bell" => quote! { bell },
395 "euler_number" => quote! { euler_number },
396 "harmonic" => quote! { harmonic },
397 "subfactorial" => quote! { subfactorial },
398 "factorial2" => quote! { factorial2 },
399 "bernoulli_number" => quote! { bernoulli_number },
400 "heaviside" => quote! { heaviside },
402 "dirac_delta" => quote! { dirac_delta },
403 "lambertw" => quote! { lambertw },
404 "gamma" => quote! { gamma },
406 "log_gamma" => quote! { log_gamma },
407 "digamma" => quote! { digamma },
408 "erf" => quote! { erf },
409 "erfc" => quote! { erfc },
410 _ => unreachable!(),
411 };
412 Ok(quote! { (#arg_code).#method() })
413 }
414 }
415}
416
417#[proc_macro]
465pub fn dim(input: TokenStream) -> TokenStream {
466 let input = syn::parse_macro_input!(input as DimMacroInput);
467 let output_type = &input.output_type;
468 match generate_dim_expr(&input.ctx, &input.expr) {
469 Ok(expr_tokens) => quote! {
470 <#output_type as ::symplex::units::qty::FromDimExpr<_>>::from_dim_expr(#expr_tokens)
471 }
472 .into(),
473 Err(e) => e.to_compile_error().into(),
474 }
475}
476
477fn generate_dim_expr(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
483 match expr {
484 MathExpr::Int(n, _span) => Ok(quote! {
485 ::symplex::units::Dimensionless::from_ex(#ctx.int(#n)).as_qty()
486 }),
487
488 MathExpr::Ident(id) => {
489 let name = id.to_string();
490 match name.as_str() {
491 "pi" | "Pi" | "PI" => Ok(quote! {
492 ::symplex::units::Dimensionless::from_ex(#ctx.pi()).as_qty()
493 }),
494 "E" => Ok(quote! {
495 ::symplex::units::Dimensionless::from_ex(#ctx.e()).as_qty()
496 }),
497 _ => Ok(quote! { (#id).clone().as_qty() }),
498 }
499 }
500
501 MathExpr::Neg(inner) => {
502 let inner_code = generate_dim_expr(ctx, inner)?;
503 Ok(quote! { (-(#inner_code)) })
504 }
505
506 MathExpr::LogicalNot(_) => Err(syn::Error::new(
507 Span::call_site(),
508 "logical NOT (!) is not supported in dim!()",
509 )),
510
511 MathExpr::BinOp { op, lhs, rhs } => match op {
512 BinOp::Add => {
513 let l = generate_dim_expr(ctx, lhs)?;
514 let r = generate_dim_expr(ctx, rhs)?;
515 Ok(quote! { ((#l) + (#r)) })
516 }
517 BinOp::Sub => {
518 let l = generate_dim_expr(ctx, lhs)?;
519 let r = generate_dim_expr(ctx, rhs)?;
520 Ok(quote! { ((#l) - (#r)) })
521 }
522 BinOp::Mul => {
523 let l = generate_dim_expr(ctx, lhs)?;
524 let r = generate_dim_expr(ctx, rhs)?;
525 Ok(quote! { ((#l) * (#r)) })
526 }
527 BinOp::Div => {
528 if let (Some(p), Some(q)) = (lhs.as_int(), rhs.as_int()) {
530 if q == 0 {
531 return Err(syn::Error::new(
532 Span::call_site(),
533 "division by zero in dim!()",
534 ));
535 }
536 return Ok(quote! {
537 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#p, #q)).as_qty()
538 });
539 }
540 if let MathExpr::Neg(inner_lhs) = lhs.as_ref()
542 && let (Some(p), Some(q)) = (inner_lhs.as_int(), rhs.as_int())
543 {
544 if q == 0 {
545 return Err(syn::Error::new(
546 Span::call_site(),
547 "division by zero in dim!()",
548 ));
549 }
550 let neg_p = -p;
551 return Ok(quote! {
552 ::symplex::units::Dimensionless::from_ex(#ctx.rational(#neg_p, #q)).as_qty()
553 });
554 }
555 let l = generate_dim_expr(ctx, lhs)?;
556 let r = generate_dim_expr(ctx, rhs)?;
557 Ok(quote! { ((#l) / (#r)) })
558 }
559 BinOp::Pow => {
560 if let Some(n) = rhs.as_int() {
563 return generate_dim_pow(ctx, lhs, n);
564 }
565 if let MathExpr::Neg(inner_rhs) = rhs.as_ref()
567 && let Some(n) = inner_rhs.as_int()
568 {
569 let pow_code = generate_dim_pow(ctx, lhs, n)?;
570 return Ok(quote! {
571 (::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() / (#pow_code))
572 });
573 }
574 let b = generate_dim_expr(ctx, lhs)?;
577 let e = generate_dim_expr(ctx, rhs)?;
578 Ok(quote! {
579 ::symplex::units::Dimensionless::from_ex(
580 (#b).into_inner().pow(&#e.into_inner())
581 ).as_qty()
582 })
583 }
584 _ => Err(syn::Error::new(
585 Span::call_site(),
586 format!("operator {:?} is not supported in dim!()", op),
587 )),
588 },
589
590 MathExpr::Func { name, span, args } => {
591 let func_str = name.as_str();
594 if args.len() == 1 {
595 let arg = generate_dim_expr(ctx, &args[0])?;
596 let method = match func_str {
597 "sin" => quote! { sin },
598 "cos" => quote! { cos },
599 "tan" => quote! { tan },
600 "asin" => quote! { asin },
601 "acos" => quote! { acos },
602 "atan" => quote! { atan },
603 "sinh" => quote! { sinh },
604 "cosh" => quote! { cosh },
605 "tanh" => quote! { tanh },
606 "exp" => quote! { exp },
607 "ln" => quote! { ln },
608 "sqrt" => quote! { sqrt },
609 "abs" => quote! { abs },
610 _ => {
611 return Err(syn::Error::new(
612 *span,
613 format!("dim!: unsupported function '{}'", func_str),
614 ));
615 }
616 };
617 Ok(quote! {
618 ::symplex::units::Dimensionless::from_ex(
619 (#arg).into_inner().#method()
620 ).as_qty()
621 })
622 } else {
623 Err(syn::Error::new(
624 *span,
625 format!(
626 "dim!: function '{}' with {} args is not supported",
627 func_str,
628 args.len()
629 ),
630 ))
631 }
632 }
633 }
634}
635
636fn generate_dim_pow(ctx: &Ident, base: &MathExpr, n: i64) -> syn::Result<TokenStream2> {
642 if n == 0 {
643 return Ok(quote! { ::symplex::units::Dimensionless::from_ex(#ctx.int(1)).as_qty() });
644 }
645 if n == 1 {
646 return generate_dim_expr(ctx, base);
647 }
648 if (2..=8).contains(&n) {
649 let mut factors = Vec::new();
653 for _ in 0..n {
654 factors.push(generate_dim_expr(ctx, base)?);
655 }
656 let mut result = factors.remove(0);
657 for factor in factors {
658 result = quote! { ((#result) * (#factor)) };
659 }
660 return Ok(result);
661 }
662 let base_code = generate_dim_expr(ctx, base)?;
665 Ok(quote! {
666 ::symplex::units::Dimensionless::from_ex(
667 (#base_code).into_inner().powi(#n)
668 ).as_qty()
669 })
670}
671
672#[proc_macro]
715pub fn rule(input: TokenStream) -> TokenStream {
716 let input = syn::parse_macro_input!(input as RuleMacroInput);
717 match generate_rule(&input) {
718 Ok(tokens) => tokens.into(),
719 Err(e) => e.to_compile_error().into(),
720 }
721}
722
723fn generate_rule(input: &RuleMacroInput) -> syn::Result<TokenStream2> {
728 let arena = &input.arena;
729 let name = &input.name;
730
731 let lhs_wilds = input.lhs.collect_wilds();
733
734 for w in input.rhs.collect_wilds() {
736 if !lhs_wilds.iter().any(|existing| existing == &w) {
737 return Err(syn::Error::new_spanned(
738 &input.name,
739 format!(
740 "wild '{}' appears in RHS but not in LHS — it will never be bound by matching",
741 w
742 ),
743 ));
744 }
745 }
746
747 let all_wilds = lhs_wilds;
748
749 let mut codegen = RuleCodeGen {
750 arena: arena.clone(),
751 temp_counter: 0,
752 bindings: Vec::new(),
753 wild_expr_idents: Vec::new(),
754 wild_wid_idents: Vec::new(),
755 wild_names: Vec::new(),
756 };
757
758 for wild in &all_wilds {
760 let wild_name = wild.to_string();
761 let expr_ident = format_ident!("__wild_expr_{}", wild_name);
762 let wid_ident = format_ident!("__wild_wid_{}", wild_name);
763 codegen.bindings.push(quote! {
764 let (#expr_ident, #wid_ident) = #arena.wild();
765 });
766 codegen.wild_expr_idents.push(expr_ident);
767 codegen.wild_wid_idents.push(wid_ident);
768 codegen.wild_names.push(wild.clone());
769 }
770
771 let lhs_temp = codegen.generate_arena_expr(&input.lhs)?;
773
774 let rhs_temp = codegen.generate_arena_expr(&input.rhs)?;
776
777 let wild_inserts: Vec<TokenStream2> = codegen
779 .wild_names
780 .iter()
781 .zip(
782 codegen
783 .wild_expr_idents
784 .iter()
785 .zip(codegen.wild_wid_idents.iter()),
786 )
787 .map(|(_, (expr_id, wid_id))| {
788 quote! { __wilds.insert(#expr_id, #wid_id); }
789 })
790 .collect();
791
792 let bindings = &codegen.bindings;
793
794 let condition_code = if let Some(cond) = &input.condition {
795 quote! { Some(#cond) }
796 } else {
797 quote! { None }
798 };
799
800 Ok(quote! {
801 {
802 #(#bindings)*
803
804 let mut __wilds = ::symplex::__macro_support::FxHashMap::default();
805 #(#wild_inserts)*
806
807 ::symplex::__macro_support::Rule {
808 name: #name,
809 pattern: ::symplex::__macro_support::Pattern {
810 root: #lhs_temp,
811 wilds: __wilds,
812 },
813 template: #rhs_temp,
814 condition: #condition_code,
815 }
816 }
817 })
818}
819
820struct RuleCodeGen {
823 arena: Ident,
824 temp_counter: usize,
825 bindings: Vec<TokenStream2>,
826 wild_expr_idents: Vec<Ident>,
827 wild_wid_idents: Vec<Ident>,
828 wild_names: Vec<Ident>,
829}
830
831impl RuleCodeGen {
832 fn fresh_temp(&mut self) -> Ident {
834 let id = format_ident!("__t{}", self.temp_counter);
835 self.temp_counter += 1;
836 id
837 }
838
839 fn generate_arena_expr(&mut self, expr: &MathExpr) -> syn::Result<Ident> {
842 let arena = self.arena.clone();
843
844 match expr {
845 MathExpr::Int(n, _) => {
846 let temp = self.fresh_temp();
847 match *n {
848 0 => {
849 self.bindings.push(quote! { let #temp = #arena.zero(); });
850 }
851 1 => {
852 self.bindings.push(quote! { let #temp = #arena.one(); });
853 }
854 -1 => {
855 self.bindings.push(quote! { let #temp = #arena.neg_one(); });
856 }
857 _ => {
858 self.bindings.push(quote! { let #temp = #arena.int(#n); });
859 }
860 }
861 Ok(temp)
862 }
863
864 MathExpr::Ident(id) => {
865 let name = id.to_string();
866
867 if name.ends_with('_') {
869 for (i, wn) in self.wild_names.iter().enumerate() {
870 if wn == id {
871 return Ok(self.wild_expr_idents[i].clone());
872 }
873 }
874 return Err(syn::Error::new(id.span(), format!("unknown wild '{name}'")));
875 }
876
877 if is_known_constant(&name) {
879 let temp = self.fresh_temp();
880 let access = match name.as_str() {
881 "pi" => quote! { #arena.pi() },
882 "E" => quote! { #arena.e_const() },
883 "I" => quote! { #arena.i_unit() },
884 "oo" => quote! { #arena.infinity() },
885 "nan" => quote! { #arena.nan() },
886 "zoo" => quote! { #arena.complex_infinity() },
887 _ => unreachable!(),
888 };
889 self.bindings.push(quote! { let #temp = #access; });
890 return Ok(temp);
891 }
892
893 Err(syn::Error::new(
895 id.span(),
896 format!(
897 "unknown identifier '{name}' in rule!(). \
898 Use '{name}_' for a wild, or a known constant (pi, E, I, oo, nan, zoo), \
899 or an integer literal."
900 ),
901 ))
902 }
903
904 MathExpr::Neg(inner) => {
905 let inner_temp = self.generate_arena_expr(inner)?;
906 let temp = self.fresh_temp();
907 self.bindings
908 .push(quote! { let #temp = #arena.neg(#inner_temp); });
909 Ok(temp)
910 }
911
912 MathExpr::LogicalNot(inner) => {
913 let inner_temp = self.generate_arena_expr(inner)?;
914 let temp = self.fresh_temp();
915 self.bindings
916 .push(quote! { let #temp = #arena.not(#inner_temp); });
917 Ok(temp)
918 }
919
920 MathExpr::BinOp { op, lhs, rhs } => {
921 let lhs_temp = self.generate_arena_expr(lhs)?;
922 let rhs_temp = self.generate_arena_expr(rhs)?;
923 let temp = self.fresh_temp();
924
925 let call = match op {
926 BinOp::Add => quote! { #arena.add(&[#lhs_temp, #rhs_temp]) },
927 BinOp::Sub => quote! { #arena.sub(#lhs_temp, #rhs_temp) },
928 BinOp::Mul => quote! { #arena.mul(&[#lhs_temp, #rhs_temp]) },
929 BinOp::Div => quote! { #arena.div(#lhs_temp, #rhs_temp) },
930 BinOp::Pow => quote! { #arena.pow(#lhs_temp, #rhs_temp) },
931 BinOp::Gt => quote! { #arena.gt(#lhs_temp, #rhs_temp) },
932 BinOp::Lt => quote! { #arena.gt(#rhs_temp, #lhs_temp) },
933 BinOp::Ge => quote! { #arena.ge(#lhs_temp, #rhs_temp) },
934 BinOp::Le => quote! { #arena.ge(#rhs_temp, #lhs_temp) },
935 BinOp::EqEq => quote! { #arena.eq_(#lhs_temp, #rhs_temp) },
936 BinOp::Ne => quote! { #arena.ne_(#lhs_temp, #rhs_temp) },
937 BinOp::AndAnd => quote! { #arena.and(&[#lhs_temp, #rhs_temp]) },
938 BinOp::OrOr => quote! { #arena.or(&[#lhs_temp, #rhs_temp]) },
939 };
940
941 self.bindings.push(quote! { let #temp = #call; });
942 Ok(temp)
943 }
944
945 MathExpr::Func { name, span, args } => {
946 if !is_known_function(name) {
947 return Err(syn::Error::new(
948 *span,
949 format!(
950 "unknown function '{}' in rule!(). Supported: {}",
951 name,
952 KNOWN_FUNCTIONS.join(", ")
953 ),
954 ));
955 }
956
957 if name == "beta" && args.len() == 2 {
959 let a_temp = self.generate_arena_expr(&args[0])?;
960 let b_temp = self.generate_arena_expr(&args[1])?;
961 let temp = self.fresh_temp();
962 self.bindings
963 .push(quote! { let #temp = #arena.beta(#a_temp, #b_temp); });
964 return Ok(temp);
965 }
966 if name == "atan2" && args.len() == 2 {
967 let y_temp = self.generate_arena_expr(&args[0])?;
968 let x_temp = self.generate_arena_expr(&args[1])?;
969 let temp = self.fresh_temp();
970 self.bindings
971 .push(quote! { let #temp = #arena.atan2(#y_temp, #x_temp); });
972 return Ok(temp);
973 }
974
975 if args.len() != 1 {
976 return Err(syn::Error::new(
977 *span,
978 format!("{}() takes exactly 1 argument in rule!()", name),
979 ));
980 }
981
982 let arg_temp = self.generate_arena_expr(&args[0])?;
983 let temp = self.fresh_temp();
984
985 let call = match name.as_str() {
986 "sin" => quote! { #arena.sin(#arg_temp) },
988 "cos" => quote! { #arena.cos(#arg_temp) },
989 "tan" => quote! { #arena.tan(#arg_temp) },
990 "asin" => quote! { #arena.asin(#arg_temp) },
991 "acos" => quote! { #arena.acos(#arg_temp) },
992 "atan" => quote! { #arena.atan(#arg_temp) },
993 "sinh" => quote! { #arena.sinh(#arg_temp) },
994 "cosh" => quote! { #arena.cosh(#arg_temp) },
995 "tanh" => quote! { #arena.tanh(#arg_temp) },
996 "asinh" => quote! { #arena.asinh(#arg_temp) },
997 "acosh" => quote! { #arena.acosh(#arg_temp) },
998 "atanh" => quote! { #arena.atanh(#arg_temp) },
999 "exp" => quote! { #arena.exp(#arg_temp) },
1001 "ln" => quote! { #arena.ln(#arg_temp) },
1002 "sqrt" => quote! { #arena.sqrt(#arg_temp) },
1003 "cbrt" => quote! { #arena.cbrt(#arg_temp) },
1004 "abs" => quote! { #arena.abs(#arg_temp) },
1005 "sign" => quote! { #arena.sign(#arg_temp) },
1006 "floor" => quote! { #arena.floor(#arg_temp) },
1008 "ceiling" => quote! { #arena.ceiling(#arg_temp) },
1009 "gamma" => quote! { #arena.gamma(#arg_temp) },
1011 "log_gamma" => quote! { #arena.log_gamma(#arg_temp) },
1012 "digamma" => quote! { #arena.digamma(#arg_temp) },
1013 "erf" => quote! { #arena.erf(#arg_temp) },
1014 "erfc" => quote! { #arena.erfc(#arg_temp) },
1015 "heaviside" => quote! { #arena.heaviside(#arg_temp) },
1017 "dirac_delta" => quote! { #arena.dirac_delta(#arg_temp) },
1018 "lambertw" => quote! { #arena.lambertw(#arg_temp) },
1019 "fibonacci" => quote! { #arena.fibonacci(#arg_temp) },
1021 "lucas" => quote! { #arena.lucas(#arg_temp) },
1022 "catalan_number" => quote! { #arena.catalan_number(#arg_temp) },
1023 "bell" => quote! { #arena.bell(#arg_temp) },
1024 "euler_number" => quote! { #arena.euler_number(#arg_temp) },
1025 "harmonic" => quote! { #arena.harmonic(#arg_temp) },
1026 "subfactorial" => quote! { #arena.subfactorial(#arg_temp) },
1027 "factorial2" => quote! { #arena.factorial2(#arg_temp) },
1028 "bernoulli_number" => quote! { #arena.bernoulli_number(#arg_temp) },
1029 other => {
1030 return Err(syn::Error::new(
1031 *span,
1032 format!(
1033 "function '{}' is recognised but not yet supported in rule!()",
1034 other
1035 ),
1036 ));
1037 }
1038 };
1039
1040 self.bindings.push(quote! { let #temp = #call; });
1041 Ok(temp)
1042 }
1043 }
1044 }
1045}
1046
1047#[proc_macro]
1063pub fn matrix(input: TokenStream) -> TokenStream {
1064 let input = syn::parse_macro_input!(input as MatrixMacroInput);
1065 match generate_matrix(&input.ctx, &input) {
1066 Ok(tokens) => tokens.into(),
1067 Err(e) => e.to_compile_error().into(),
1068 }
1069}
1070
1071fn generate_matrix(ctx: &Ident, input: &MatrixMacroInput) -> syn::Result<TokenStream2> {
1072 let mut row_codes = Vec::new();
1073 for row in &input.rows {
1074 let mut cell_codes = Vec::new();
1075 for cell in row {
1076 let cell_expr = generate_expr_as_ex(ctx, cell)?;
1077 cell_codes.push(quote! { #cell_expr });
1078 }
1079 row_codes.push(quote! { vec![#(#cell_codes),*] });
1080 }
1081 Ok(quote! {
1082 ::symplex::matrix::Matrix::new(vec![#(#row_codes),*])
1083 .expect("matrix! macro: invalid literal data")
1084 })
1085}
1086
1087#[proc_macro]
1103pub fn eq(input: TokenStream) -> TokenStream {
1104 let input = syn::parse_macro_input!(input as EqMacroInput);
1105 match generate_eq(&input.ctx, &input) {
1106 Ok(tokens) => tokens.into(),
1107 Err(e) => e.to_compile_error().into(),
1108 }
1109}
1110
1111fn generate_eq(ctx: &Ident, input: &EqMacroInput) -> syn::Result<TokenStream2> {
1112 let lhs_code = generate_expr_as_ex(ctx, &input.lhs)?;
1113 let rhs_code = generate_expr_as_ex(ctx, &input.rhs)?;
1114 Ok(quote! {
1115 ::symplex::eq::Equation::new(#lhs_code, #rhs_code)
1116 })
1117}
1118
1119fn generate_expr_as_ex(ctx: &Ident, expr: &MathExpr) -> syn::Result<TokenStream2> {
1126 match expr {
1127 MathExpr::Int(n, _) => Ok(quote! { #ctx.int(#n) }),
1128 MathExpr::Neg(inner) => {
1129 if let Some(n) = inner.as_int() {
1130 let neg_n = -n;
1131 Ok(quote! { #ctx.int(#neg_n) })
1132 } else {
1133 let code = generate_expr(ctx, expr)?;
1134 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1135 }
1136 }
1137 MathExpr::Func { .. } => generate_expr(ctx, expr),
1138 _ => {
1139 let code = generate_expr(ctx, expr)?;
1140 Ok(quote! { { let __v: ::symplex::expr::Ex = (#code).clone(); __v } })
1141 }
1142 }
1143}