1#![deny(
41 clippy::unwrap_used,
42 clippy::expect_used,
43 clippy::panic,
44 missing_docs,
45 clippy::missing_errors_doc
46)]
47
48use proc_macro::TokenStream;
49use quote::quote;
50use syn::ext::IdentExt;
51use syn::parse::{Parse, ParseStream};
52use syn::{parse_macro_input, Ident, Result, Token};
53
54struct ShapeArgs {
57 name: Ident,
58 left: Ident,
59 right: Ident,
60}
61
62impl Parse for ShapeArgs {
63 fn parse(input: ParseStream) -> Result<Self> {
64 let name: Ident = input.parse()?;
65 input.parse::<Token![,]>()?;
66 let left: Ident = input.parse()?;
67 input.parse::<Token![,]>()?;
68 let right: Ident = input.parse()?;
69 let _ = input.parse::<Token![,]>();
71 Ok(Self { name, left, right })
72 }
73}
74
75#[proc_macro]
109pub fn product_shape(input: TokenStream) -> TokenStream {
110 let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
111 let iri = format!(
112 "urn:uor:product:{}:{}",
113 lexically_earlier(&left, &right),
114 lexically_later(&left, &right)
115 );
116 let (l, r) = canonical_operand_pair(&left, &right);
122 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
123 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
124
125 let expansion = quote! {
126 pub struct #name;
128
129 const #raw_const:
130 [::uor_foundation::pipeline::ConstraintRef;
131 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
132 = ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>();
133
134 const #len_const: usize =
135 ::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>();
136
137 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
138 const IRI: &'static str = #iri;
139 const SITE_BUDGET: usize =
140 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
141 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
142 const SITE_COUNT: usize =
143 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
144 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
145 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
146 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
147 match buf.split_at_checked(#len_const) {
148 Some((head, _tail)) => head,
149 None => &[],
150 }
151 };
152 const CYCLE_SIZE: u64 = ::uor_foundation::pipeline::cycle_size_product(
154 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
155 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
156 );
157 }
158
159 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
168 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
169 fn as_binding_value<const INLINE_BYTES: usize>(
170 &self,
171 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
172 ::uor_foundation::pipeline::TermValue::empty()
173 }
174 }
175 impl ::uor_foundation::enforcement::GroundedShape for #name {}
176
177 impl ::uor_foundation::pipeline::PartitionProductFields for #name {
184 const FIELDS: &'static [(u32, u32)] = &[
185 (0u32, <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
186 (<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32,
187 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
188 ];
189 const FIELD_NAMES: &'static [&'static str] = &["", ""];
190 }
191
192 impl ::uor_foundation::pipeline::PartitionProductFactor<0> for #name {
196 type Factor = #l;
197 }
198 impl ::uor_foundation::pipeline::PartitionProductFactor<1> for #name {
199 type Factor = #r;
200 }
201
202 impl #name {
203 #[allow(clippy::too_many_arguments)]
216 pub fn mint_product_witness(
217 witt_bits: u16,
218 left_fingerprint: ::uor_foundation::ContentFingerprint,
219 right_fingerprint: ::uor_foundation::ContentFingerprint,
220 left_euler: i32,
221 right_euler: i32,
222 left_entropy_nats_bits: u64,
223 right_entropy_nats_bits: u64,
224 combined_euler: i32,
225 combined_entropy_nats_bits: u64,
226 combined_fingerprint: ::uor_foundation::ContentFingerprint,
227 ) -> ::core::result::Result<
228 ::uor_foundation::PartitionProductWitness,
229 ::uor_foundation::enforcement::GenericImpossibilityWitness,
230 > {
231 use ::uor_foundation::pipeline::ConstrainedTypeShape;
232 let inputs = ::uor_foundation::PartitionProductMintInputs {
233 witt_bits,
234 left_fingerprint,
235 right_fingerprint,
236 left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
237 right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
238 left_total_site_count: <#l as ConstrainedTypeShape>::SITE_COUNT as u16,
239 right_total_site_count: <#r as ConstrainedTypeShape>::SITE_COUNT as u16,
240 left_euler,
241 right_euler,
242 left_entropy_nats_bits,
243 right_entropy_nats_bits,
244 combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
245 combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
246 combined_euler,
247 combined_entropy_nats_bits,
248 combined_fingerprint,
249 };
250 <::uor_foundation::PartitionProductWitness
251 as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
252 }
253 }
254 };
255
256 expansion.into()
257}
258
259#[proc_macro]
303pub fn coproduct_shape(input: TokenStream) -> TokenStream {
304 let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
305 let iri = format!(
306 "urn:uor:coproduct:{}:{}",
307 lexically_earlier(&left, &right),
308 lexically_later(&left, &right)
309 );
310 let (l, r) = canonical_operand_pair(&left, &right);
311
312 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
325 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
326 let tag_coeffs_l = format_ident_suffix(&name, "__TAG_COEFFS_L");
327 let tag_coeffs_r = format_ident_suffix(&name, "__TAG_COEFFS_R");
328 let tag_coeff_count = format_ident_suffix(&name, "__TAG_COEFF_COUNT");
329
330 let expansion = quote! {
331 pub struct #name;
333
334 const #tag_coeffs_l: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = {
348 let mut out = [0i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS];
349 let tag_site = {
350 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
351 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
352 if a > b { a } else { b }
353 };
354 if tag_site < ::uor_foundation::pipeline::AFFINE_MAX_COEFFS {
355 out[tag_site] = 1;
356 }
357 out
358 };
359 const #tag_coeffs_r: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = #tag_coeffs_l;
360 const #tag_coeff_count: u32 = {
361 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
362 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
363 let tag_site = if a > b { a } else { b };
364 (tag_site as u32).saturating_add(1)
365 };
366
367 const #raw_const:
380 [::uor_foundation::pipeline::ConstraintRef;
381 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2]
382 = {
383 let mut out =
384 [::uor_foundation::pipeline::ConstraintRef::Site { position: u32::MAX };
385 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2];
386 let left_arr = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
387 let right_arr = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
388 let mut i = 0;
389 while i < left_arr.len() {
390 out[i] = left_arr[i];
391 i += 1;
392 }
393 out[i] = ::uor_foundation::pipeline::ConstraintRef::Affine {
395 coefficients: #tag_coeffs_l,
396 coefficient_count: #tag_coeff_count,
397 bias: 0,
398 };
399 let left_boundary = i + 1;
400 let mut j = 0;
401 while j < right_arr.len() {
402 out[left_boundary + j] = right_arr[j];
403 j += 1;
404 }
405 out[left_boundary + right_arr.len()] = ::uor_foundation::pipeline::ConstraintRef::Affine {
407 coefficients: #tag_coeffs_r,
408 coefficient_count: #tag_coeff_count,
409 bias: -1,
410 };
411 out
412 };
413 const #len_const: usize =
414 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
415 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
416 + 2;
417
418 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
419 const IRI: &'static str = #iri;
420 const SITE_BUDGET: usize = {
421 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
422 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
423 if a > b { a } else { b }
424 };
425 const SITE_COUNT: usize = {
426 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
427 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
428 (if a > b { a } else { b }) + 1
429 };
430 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
431 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
432 match buf.split_at_checked(#len_const) {
433 Some((head, _tail)) => head,
434 None => &[],
435 }
436 };
437 const CYCLE_SIZE: u64 = ::uor_foundation::pipeline::cycle_size_coproduct(
440 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
441 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
442 );
443 }
444
445 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
451 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
452 fn as_binding_value<const INLINE_BYTES: usize>(
453 &self,
454 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
455 ::uor_foundation::pipeline::TermValue::empty()
456 }
457 }
458 impl ::uor_foundation::enforcement::GroundedShape for #name {}
459
460 impl #name {
461 #[allow(clippy::too_many_arguments)]
472 pub fn mint_coproduct_witness(
473 witt_bits: u16,
474 left_fingerprint: ::uor_foundation::ContentFingerprint,
475 right_fingerprint: ::uor_foundation::ContentFingerprint,
476 left_euler: i32,
477 right_euler: i32,
478 left_entropy_nats_bits: u64,
479 right_entropy_nats_bits: u64,
480 left_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
481 right_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
482 combined_euler: i32,
483 combined_entropy_nats_bits: u64,
484 combined_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
485 combined_fingerprint: ::uor_foundation::ContentFingerprint,
486 ) -> ::core::result::Result<
487 ::uor_foundation::PartitionCoproductWitness,
488 ::uor_foundation::enforcement::GenericImpossibilityWitness,
489 > {
490 use ::uor_foundation::pipeline::ConstrainedTypeShape;
491 let left_total_site_count = <#l as ConstrainedTypeShape>::SITE_COUNT as u16;
492 let right_total_site_count = <#r as ConstrainedTypeShape>::SITE_COUNT as u16;
493 let tag_site = if left_total_site_count > right_total_site_count {
494 left_total_site_count
495 } else {
496 right_total_site_count
497 };
498 let left_constraint_count =
499 <#l as ConstrainedTypeShape>::CONSTRAINTS.len() + 1;
500 let inputs = ::uor_foundation::PartitionCoproductMintInputs {
501 witt_bits,
502 left_fingerprint,
503 right_fingerprint,
504 left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
505 right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
506 left_total_site_count,
507 right_total_site_count,
508 left_euler,
509 right_euler,
510 left_entropy_nats_bits,
511 right_entropy_nats_bits,
512 left_betti,
513 right_betti,
514 combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
515 combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
516 combined_euler,
517 combined_entropy_nats_bits,
518 combined_betti,
519 combined_fingerprint,
520 combined_constraints: <#name as ConstrainedTypeShape>::CONSTRAINTS,
521 left_constraint_count,
522 tag_site,
523 };
524 <::uor_foundation::PartitionCoproductWitness
525 as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
526 }
527 }
528 };
529
530 expansion.into()
531}
532
533#[proc_macro]
564pub fn cartesian_product_shape(input: TokenStream) -> TokenStream {
565 let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
566 let iri = format!(
567 "urn:uor:cartesian:{}:{}",
568 lexically_earlier(&left, &right),
569 lexically_later(&left, &right)
570 );
571 let (l, r) = canonical_operand_pair(&left, &right);
572 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
573 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
574
575 let expansion = quote! {
580 pub struct #name;
583
584 const #raw_const:
585 [::uor_foundation::pipeline::ConstraintRef;
586 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
587 = ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>();
588
589 const #len_const: usize =
590 ::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>();
591
592 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
593 const IRI: &'static str = #iri;
594 const SITE_BUDGET: usize =
595 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
596 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
597 const SITE_COUNT: usize =
598 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
599 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
600 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
601 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
602 match buf.split_at_checked(#len_const) {
603 Some((head, _tail)) => head,
604 None => &[],
605 }
606 };
607 const CYCLE_SIZE: u64 = ::uor_foundation::pipeline::cycle_size_product(
613 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
614 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
615 );
616 }
617
618 impl ::uor_foundation::pipeline::CartesianProductShape for #name {
619 type Left = #l;
620 type Right = #r;
621 }
622
623 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
629 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
630 fn as_binding_value<const INLINE_BYTES: usize>(
631 &self,
632 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
633 ::uor_foundation::pipeline::TermValue::empty()
634 }
635 }
636 impl ::uor_foundation::enforcement::GroundedShape for #name {}
637
638 impl ::uor_foundation::pipeline::PartitionProductFields for #name {
645 const FIELDS: &'static [(u32, u32)] = &[
646 (0u32, <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
647 (<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32,
648 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
649 ];
650 const FIELD_NAMES: &'static [&'static str] = &["", ""];
651 }
652
653 impl ::uor_foundation::pipeline::PartitionProductFactor<0> for #name {
655 type Factor = #l;
656 }
657 impl ::uor_foundation::pipeline::PartitionProductFactor<1> for #name {
658 type Factor = #r;
659 }
660
661 impl #name {
662 #[allow(clippy::too_many_arguments)]
671 pub fn mint_cartesian_witness(
672 witt_bits: u16,
673 left_fingerprint: ::uor_foundation::ContentFingerprint,
674 right_fingerprint: ::uor_foundation::ContentFingerprint,
675 left_euler: i32,
676 right_euler: i32,
677 left_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
678 right_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
679 left_entropy_nats_bits: u64,
680 right_entropy_nats_bits: u64,
681 combined_euler: i32,
682 combined_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
683 combined_entropy_nats_bits: u64,
684 combined_fingerprint: ::uor_foundation::ContentFingerprint,
685 ) -> ::core::result::Result<
686 ::uor_foundation::CartesianProductWitness,
687 ::uor_foundation::enforcement::GenericImpossibilityWitness,
688 > {
689 use ::uor_foundation::pipeline::ConstrainedTypeShape;
690 let inputs = ::uor_foundation::CartesianProductMintInputs {
691 witt_bits,
692 left_fingerprint,
693 right_fingerprint,
694 left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
695 right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
696 left_total_site_count: <#l as ConstrainedTypeShape>::SITE_COUNT as u16,
697 right_total_site_count: <#r as ConstrainedTypeShape>::SITE_COUNT as u16,
698 left_euler,
699 right_euler,
700 left_betti,
701 right_betti,
702 left_entropy_nats_bits,
703 right_entropy_nats_bits,
704 combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
705 combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
706 combined_euler,
707 combined_betti,
708 combined_entropy_nats_bits,
709 combined_fingerprint,
710 };
711 <::uor_foundation::CartesianProductWitness
712 as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
713 }
714 }
715 };
716
717 expansion.into()
718}
719
720struct VariadicShapeArgs {
742 name: Ident,
743 operands: Vec<syn::Type>,
751 field_names: Vec<Option<Ident>>,
757 recurse_bounds: Vec<Option<proc_macro2::TokenStream>>,
768}
769
770impl Parse for VariadicShapeArgs {
771 fn parse(input: ParseStream) -> Result<Self> {
772 let name: Ident = input.parse()?;
773 input.parse::<Token![,]>()?;
774 let mut operands: Vec<syn::Type> = Vec::new();
775 let mut field_names: Vec<Option<Ident>> = Vec::new();
776 let mut recurse_bounds: Vec<Option<proc_macro2::TokenStream>> = Vec::new();
777 fn parse_one(
788 input: ParseStream,
789 ) -> Result<(Option<Ident>, syn::Type, Option<proc_macro2::TokenStream>)> {
790 let forked = input.fork();
805 let mut field_name: Option<Ident> = None;
806 if let Ok(maybe_name) = forked.parse::<Ident>() {
807 if maybe_name != "recurse" && forked.peek(Token![:]) {
808 field_name = Some(input.parse::<Ident>()?);
814 input.parse::<Token![:]>()?;
815 }
816 }
817 let recurse_bound = parse_optional_recurse_marker(input)?;
819 let ty: syn::Type = input.parse()?;
821 Ok((field_name, ty, recurse_bound))
822 }
823 let (n0, t0, r0) = parse_one(input)?;
824 field_names.push(n0);
825 operands.push(t0);
826 recurse_bounds.push(r0);
827 while input.peek(Token![,]) {
828 input.parse::<Token![,]>()?;
829 if input.is_empty() {
830 break;
831 }
832 let (ni, ti, ri) = parse_one(input)?;
833 field_names.push(ni);
834 operands.push(ti);
835 recurse_bounds.push(ri);
836 }
837 if operands.len() < 2 {
838 return Err(syn::Error::new(
839 name.span(),
840 "partition_product!/partition_coproduct! require at least two operands",
841 ));
842 }
843 let any_named = field_names.iter().any(|n| n.is_some());
846 let all_named = field_names.iter().all(|n| n.is_some());
847 if any_named && !all_named {
848 return Err(syn::Error::new(
849 name.span(),
850 "partition_product!/partition_coproduct!: named-field form must name every operand (or use the positional form for all)",
851 ));
852 }
853 Ok(Self {
854 name,
855 operands,
856 field_names,
857 recurse_bounds,
858 })
859 }
860}
861
862fn parse_optional_recurse_marker(input: ParseStream) -> Result<Option<proc_macro2::TokenStream>> {
867 let forked = input.fork();
874 let Ok(marker_ident) = forked.parse::<Ident>() else {
875 return Ok(None);
876 };
877 if marker_ident != "recurse" {
878 return Ok(None);
879 }
880 if forked.peek(syn::token::Paren) {
885 input.parse::<Ident>()?;
887 let content;
888 syn::parenthesized!(content in input);
889 let bound_expr: syn::Expr = content.parse()?;
890 input.parse::<Token![:]>()?;
891 Ok(Some(quote! { (#bound_expr) as u32 }))
892 } else if forked.peek(Token![:]) {
893 input.parse::<Ident>()?;
895 input.parse::<Token![:]>()?;
896 Ok(Some(quote! { u32::MAX }))
897 } else {
898 Ok(None)
904 }
905}
906
907#[proc_macro]
912pub fn partition_product(input: TokenStream) -> TokenStream {
913 let parsed = parse_macro_input!(input as VariadicShapeArgs);
914 expand_partition_product(
915 parsed.name,
916 &parsed.operands,
917 &parsed.field_names,
918 &parsed.recurse_bounds,
919 )
920}
921
922fn expand_partition_product(
923 name: Ident,
924 operands: &[syn::Type],
925 field_names: &[Option<Ident>],
926 recurse_bounds: &[Option<proc_macro2::TokenStream>],
927) -> TokenStream {
928 let any_recurse = recurse_bounds.iter().any(|b| b.is_some());
934
935 if operands.len() == 2 {
936 let l_orig = &operands[0];
940 let r_orig = &operands[1];
941 let l_recurse = &recurse_bounds[0];
942 let r_recurse = &recurse_bounds[1];
943 let iri = format!(
944 "urn:uor:product:{}:{}",
945 lexically_earlier_ty(l_orig, r_orig),
946 lexically_later_ty(l_orig, r_orig),
947 );
948 let (l, r, l_recurse, r_recurse) = if type_token_string(l_orig) <= type_token_string(r_orig)
952 {
953 (
954 l_orig.clone(),
955 r_orig.clone(),
956 l_recurse.clone(),
957 r_recurse.clone(),
958 )
959 } else {
960 (
961 r_orig.clone(),
962 l_orig.clone(),
963 r_recurse.clone(),
964 l_recurse.clone(),
965 )
966 };
967 let _ = canonical_operand_pair_ty; let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
969 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
970 let l_name_lit = field_names
973 .first()
974 .and_then(|n| n.as_ref())
975 .map(|i| i.to_string())
976 .unwrap_or_default();
977 let r_name_lit = field_names
978 .get(1)
979 .and_then(|n| n.as_ref())
980 .map(|i| i.to_string())
981 .unwrap_or_default();
982 let (raw_const_init, len_const_init, cycle_size_init): (
991 proc_macro2::TokenStream,
992 proc_macro2::TokenStream,
993 proc_macro2::TokenStream,
994 ) = if any_recurse {
995 let l_recurse_expr = match &l_recurse {
996 Some(bound) => quote! { ::core::option::Option::Some(#bound) },
997 None => quote! { ::core::option::Option::None },
998 };
999 let r_recurse_expr = match &r_recurse {
1000 Some(bound) => quote! { ::core::option::Option::Some(#bound) },
1001 None => quote! { ::core::option::Option::None },
1002 };
1003 let l_recurse_flag = l_recurse.is_some();
1004 let r_recurse_flag = r_recurse.is_some();
1005 (
1006 quote! {
1007 ::uor_foundation::pipeline::sdk_concat_product_constraints_v2::<#l, #r>(
1008 #l_recurse_expr,
1009 #r_recurse_expr,
1010 )
1011 },
1012 quote! {
1013 ::uor_foundation::pipeline::sdk_product_constraints_v2_len::<#l, #r>(
1014 #l_recurse_flag,
1015 #r_recurse_flag,
1016 )
1017 },
1018 quote! { u64::MAX },
1019 )
1020 } else {
1021 (
1022 quote! { ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>() },
1023 quote! { ::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>() },
1024 quote! {
1025 ::uor_foundation::pipeline::cycle_size_product(
1026 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1027 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1028 )
1029 },
1030 )
1031 };
1032 let expansion = quote! {
1033 pub struct #name;
1035
1036 const #raw_const:
1037 [::uor_foundation::pipeline::ConstraintRef;
1038 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
1039 = #raw_const_init;
1040
1041 const #len_const: usize = #len_const_init;
1042
1043 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
1044 const IRI: &'static str = #iri;
1045 const SITE_BUDGET: usize =
1046 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
1047 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
1048 const SITE_COUNT: usize =
1049 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
1050 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1051 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
1052 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
1053 match buf.split_at_checked(#len_const) {
1054 Some((head, _tail)) => head,
1055 None => &[],
1056 }
1057 };
1058 const CYCLE_SIZE: u64 = #cycle_size_init;
1063 }
1064
1065 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
1066 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
1067 fn as_binding_value<const INLINE_BYTES: usize>(
1068 &self,
1069 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
1070 ::uor_foundation::pipeline::TermValue::empty()
1071 }
1072 }
1073 impl ::uor_foundation::enforcement::GroundedShape for #name {}
1074
1075 impl ::uor_foundation::pipeline::PartitionProductFields for #name {
1078 const FIELDS: &'static [(u32, u32)] = &[
1079 (0u32, <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
1080 (<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32,
1081 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
1082 ];
1083 const FIELD_NAMES: &'static [&'static str] = &[#l_name_lit, #r_name_lit];
1084 }
1085
1086 impl ::uor_foundation::pipeline::PartitionProductFactor<0> for #name {
1092 type Factor = #l;
1093 }
1094 impl ::uor_foundation::pipeline::PartitionProductFactor<1> for #name {
1095 type Factor = #r;
1096 }
1097 };
1098 expansion.into()
1099 } else {
1100 let any_named = field_names.iter().any(|n| n.is_some());
1102 if any_named {
1103 return expand_partition_product_named_flat(
1108 name,
1109 operands,
1110 field_names,
1111 recurse_bounds,
1112 );
1113 }
1114 let mut intermediate_names: Vec<Ident> = Vec::with_capacity(operands.len() - 1);
1120 for i in 0..operands.len() - 2 {
1121 intermediate_names.push(Ident::new(&format!("{}PpStep{}", name, i), name.span()));
1122 }
1123 let mut chain: Vec<proc_macro2::TokenStream> = Vec::with_capacity(operands.len() - 1);
1124 let mut prev = if operands.len() > 2 {
1126 intermediate_names[0].clone()
1127 } else {
1128 name.clone()
1129 };
1130 let first_a = &operands[0];
1131 let first_b = &operands[1];
1132 let first_step_call = expand_partition_product_helper(
1133 prev.clone(),
1134 first_a,
1135 first_b,
1136 &recurse_bounds[0],
1137 &recurse_bounds[1],
1138 );
1139 chain.push(first_step_call);
1140 for i in 2..operands.len() {
1146 let next = if i == operands.len() - 1 {
1147 name.clone()
1148 } else {
1149 intermediate_names[i - 1].clone()
1150 };
1151 let prev_ty: syn::Type = syn::parse_quote! { #prev };
1152 let next_call = expand_partition_product_helper(
1153 next.clone(),
1154 &prev_ty,
1155 &operands[i],
1156 &None,
1157 &recurse_bounds[i],
1158 );
1159 chain.push(next_call);
1160 prev = next;
1161 }
1162 let combined = quote! { #( #chain )* };
1163 combined.into()
1164 }
1165}
1166
1167fn expand_partition_product_named_flat(
1174 name: Ident,
1175 operands: &[syn::Type],
1176 field_names: &[Option<Ident>],
1177 recurse_bounds: &[Option<proc_macro2::TokenStream>],
1178) -> TokenStream {
1179 let any_recurse = recurse_bounds.iter().any(|b| b.is_some());
1180 let mut fields_entries: Vec<proc_macro2::TokenStream> = Vec::with_capacity(operands.len());
1182 for (i, op) in operands.iter().enumerate() {
1183 let running_sum: proc_macro2::TokenStream = if i == 0 {
1185 quote::quote! { 0u32 }
1186 } else {
1187 let prior = &operands[..i];
1188 quote::quote! {
1189 0u32 #(
1190 + <#prior as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32
1191 )*
1192 }
1193 };
1194 fields_entries.push(quote::quote! {
1195 (#running_sum, <#op as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32)
1196 });
1197 }
1198 let name_lits: Vec<String> = field_names
1200 .iter()
1201 .map(|n| n.as_ref().map(|i| i.to_string()).unwrap_or_default())
1202 .collect();
1203 let factor_impls: Vec<proc_macro2::TokenStream> = operands
1205 .iter()
1206 .enumerate()
1207 .map(|(i, op)| {
1208 quote::quote! {
1209 impl ::uor_foundation::pipeline::PartitionProductFactor<#i> for #name {
1210 type Factor = #op;
1211 }
1212 }
1213 })
1214 .collect();
1215 let site_count_sum: proc_macro2::TokenStream = quote::quote! {
1217 0usize #(
1218 + <#operands as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
1219 )*
1220 };
1221 let site_budget_sum: proc_macro2::TokenStream = quote::quote! {
1222 0usize #(
1223 + <#operands as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
1224 )*
1225 };
1226 let cycle_size_expr: proc_macro2::TokenStream = if any_recurse {
1230 quote::quote! { u64::MAX }
1231 } else {
1232 let mut acc = quote::quote! { 1u64 };
1233 for op in operands {
1234 acc = quote::quote! {
1235 ::uor_foundation::pipeline::cycle_size_product(
1236 #acc,
1237 <#op as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1238 )
1239 };
1240 }
1241 acc
1242 };
1243 let mut sorted: Vec<&syn::Type> = operands.iter().collect();
1246 sorted.sort_by_key(|t| type_token_string(t));
1247 let iri_body: String = sorted
1248 .iter()
1249 .map(|t| type_token_string(t))
1250 .collect::<Vec<_>>()
1251 .join(":");
1252 let iri = format!("urn:uor:product:{iri_body}");
1253 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
1254 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
1255 let l = &operands[0];
1262 let r = &operands[1];
1263 let l_recurse = &recurse_bounds[0];
1264 let r_recurse = &recurse_bounds[1];
1265 let (raw_const_init, len_const_init): (proc_macro2::TokenStream, proc_macro2::TokenStream) =
1266 if l_recurse.is_some() || r_recurse.is_some() {
1267 let l_recurse_expr = match l_recurse {
1268 Some(bound) => quote::quote! { ::core::option::Option::Some(#bound) },
1269 None => quote::quote! { ::core::option::Option::None },
1270 };
1271 let r_recurse_expr = match r_recurse {
1272 Some(bound) => quote::quote! { ::core::option::Option::Some(#bound) },
1273 None => quote::quote! { ::core::option::Option::None },
1274 };
1275 let l_recurse_flag = l_recurse.is_some();
1276 let r_recurse_flag = r_recurse.is_some();
1277 (
1278 quote::quote! {
1279 ::uor_foundation::pipeline::sdk_concat_product_constraints_v2::<#l, #r>(
1280 #l_recurse_expr,
1281 #r_recurse_expr,
1282 )
1283 },
1284 quote::quote! {
1285 ::uor_foundation::pipeline::sdk_product_constraints_v2_len::<#l, #r>(
1286 #l_recurse_flag,
1287 #r_recurse_flag,
1288 )
1289 },
1290 )
1291 } else {
1292 (
1293 quote::quote! {
1294 ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>()
1295 },
1296 quote::quote! {
1297 ::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>()
1298 },
1299 )
1300 };
1301 let expansion = quote::quote! {
1302 pub struct #name;
1304
1305 const #raw_const:
1306 [::uor_foundation::pipeline::ConstraintRef;
1307 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
1308 = #raw_const_init;
1309
1310 const #len_const: usize = #len_const_init;
1311
1312 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
1313 const IRI: &'static str = #iri;
1314 const SITE_BUDGET: usize = #site_budget_sum;
1315 const SITE_COUNT: usize = #site_count_sum;
1316 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
1317 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
1318 match buf.split_at_checked(#len_const) {
1319 Some((head, _tail)) => head,
1320 None => &[],
1321 }
1322 };
1323 const CYCLE_SIZE: u64 = #cycle_size_expr;
1324 }
1325
1326 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
1327 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
1328 fn as_binding_value<const INLINE_BYTES: usize>(
1329 &self,
1330 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
1331 ::uor_foundation::pipeline::TermValue::empty()
1332 }
1333 }
1334 impl ::uor_foundation::enforcement::GroundedShape for #name {}
1335
1336 impl ::uor_foundation::pipeline::PartitionProductFields for #name {
1337 const FIELDS: &'static [(u32, u32)] = &[ #( #fields_entries ),* ];
1338 const FIELD_NAMES: &'static [&'static str] = &[ #( #name_lits ),* ];
1339 }
1340
1341 #( #factor_impls )*
1342 };
1343 expansion.into()
1344}
1345
1346fn expand_partition_product_helper(
1350 name: Ident,
1351 left: &syn::Type,
1352 right: &syn::Type,
1353 left_recurse: &Option<proc_macro2::TokenStream>,
1354 right_recurse: &Option<proc_macro2::TokenStream>,
1355) -> proc_macro2::TokenStream {
1356 let iri = format!(
1357 "urn:uor:product:{}:{}",
1358 lexically_earlier_ty(left, right),
1359 lexically_later_ty(left, right),
1360 );
1361 let (l, r, l_recurse, r_recurse) = if type_token_string(left) <= type_token_string(right) {
1364 (
1365 left.clone(),
1366 right.clone(),
1367 left_recurse.clone(),
1368 right_recurse.clone(),
1369 )
1370 } else {
1371 (
1372 right.clone(),
1373 left.clone(),
1374 right_recurse.clone(),
1375 left_recurse.clone(),
1376 )
1377 };
1378 let _ = canonical_operand_pair_ty; let any_recurse = l_recurse.is_some() || r_recurse.is_some();
1380 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
1381 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
1382 let (raw_const_init, len_const_init, cycle_size_init): (
1383 proc_macro2::TokenStream,
1384 proc_macro2::TokenStream,
1385 proc_macro2::TokenStream,
1386 ) = if any_recurse {
1387 let l_recurse_expr = match &l_recurse {
1388 Some(bound) => quote! { ::core::option::Option::Some(#bound) },
1389 None => quote! { ::core::option::Option::None },
1390 };
1391 let r_recurse_expr = match &r_recurse {
1392 Some(bound) => quote! { ::core::option::Option::Some(#bound) },
1393 None => quote! { ::core::option::Option::None },
1394 };
1395 let l_recurse_flag = l_recurse.is_some();
1396 let r_recurse_flag = r_recurse.is_some();
1397 (
1398 quote! {
1399 ::uor_foundation::pipeline::sdk_concat_product_constraints_v2::<#l, #r>(
1400 #l_recurse_expr,
1401 #r_recurse_expr,
1402 )
1403 },
1404 quote! {
1405 ::uor_foundation::pipeline::sdk_product_constraints_v2_len::<#l, #r>(
1406 #l_recurse_flag,
1407 #r_recurse_flag,
1408 )
1409 },
1410 quote! { u64::MAX },
1411 )
1412 } else {
1413 (
1414 quote! { ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>() },
1415 quote! { ::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>() },
1416 quote! {
1417 ::uor_foundation::pipeline::cycle_size_product(
1418 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1419 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1420 )
1421 },
1422 )
1423 };
1424 quote! {
1425 pub struct #name;
1427
1428 const #raw_const:
1429 [::uor_foundation::pipeline::ConstraintRef;
1430 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
1431 = #raw_const_init;
1432
1433 const #len_const: usize = #len_const_init;
1434
1435 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
1436 const IRI: &'static str = #iri;
1437 const SITE_BUDGET: usize =
1438 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
1439 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
1440 const SITE_COUNT: usize =
1441 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
1442 + <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1443 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
1444 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
1445 match buf.split_at_checked(#len_const) {
1446 Some((head, _tail)) => head,
1447 None => &[],
1448 }
1449 };
1450 const CYCLE_SIZE: u64 = #cycle_size_init;
1454 }
1455
1456 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
1457 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
1458 fn as_binding_value<const INLINE_BYTES: usize>(
1459 &self,
1460 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
1461 ::uor_foundation::pipeline::TermValue::empty()
1462 }
1463 }
1464 impl ::uor_foundation::enforcement::GroundedShape for #name {}
1465
1466 impl ::uor_foundation::pipeline::PartitionProductFields for #name {
1470 const FIELDS: &'static [(u32, u32)] = &[
1471 (0u32, <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
1472 (<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32,
1473 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT as u32),
1474 ];
1475 const FIELD_NAMES: &'static [&'static str] = &["", ""];
1476 }
1477
1478 impl ::uor_foundation::pipeline::PartitionProductFactor<0> for #name {
1481 type Factor = #l;
1482 }
1483 impl ::uor_foundation::pipeline::PartitionProductFactor<1> for #name {
1484 type Factor = #r;
1485 }
1486 }
1487}
1488
1489#[proc_macro]
1494pub fn partition_coproduct(input: TokenStream) -> TokenStream {
1495 let parsed = parse_macro_input!(input as VariadicShapeArgs);
1496 if parsed.field_names.iter().any(|n| n.is_some()) {
1497 return syn::Error::new(
1498 parsed.name.span(),
1499 "partition_coproduct! does not accept named-field form (coproducts discriminate by tag, not by named field; ADR-033 G3 named-field form applies to partition_product!)",
1500 )
1501 .to_compile_error()
1502 .into();
1503 }
1504 expand_partition_coproduct(parsed.name, &parsed.operands, &parsed.recurse_bounds)
1505}
1506
1507fn expand_partition_coproduct(
1508 name: Ident,
1509 operands: &[syn::Type],
1510 recurse_bounds: &[Option<proc_macro2::TokenStream>],
1511) -> TokenStream {
1512 if operands.len() == 2 {
1513 let combined = expand_partition_coproduct_helper(
1514 name,
1515 &operands[0],
1516 &operands[1],
1517 &recurse_bounds[0],
1518 &recurse_bounds[1],
1519 );
1520 return combined.into();
1521 }
1522 let mut intermediate_names: Vec<Ident> = Vec::with_capacity(operands.len() - 1);
1523 for i in 0..operands.len() - 2 {
1524 intermediate_names.push(Ident::new(&format!("{}PcStep{}", name, i), name.span()));
1525 }
1526 let mut chain: Vec<proc_macro2::TokenStream> = Vec::with_capacity(operands.len() - 1);
1527 let mut prev = if operands.len() > 2 {
1528 intermediate_names[0].clone()
1529 } else {
1530 name.clone()
1531 };
1532 let first_step = expand_partition_coproduct_helper(
1533 prev.clone(),
1534 &operands[0],
1535 &operands[1],
1536 &recurse_bounds[0],
1537 &recurse_bounds[1],
1538 );
1539 chain.push(first_step);
1540 for i in 2..operands.len() {
1541 let next = if i == operands.len() - 1 {
1542 name.clone()
1543 } else {
1544 intermediate_names[i - 1].clone()
1545 };
1546 let prev_ty: syn::Type = syn::parse_quote! { #prev };
1547 let step = expand_partition_coproduct_helper(
1549 next.clone(),
1550 &prev_ty,
1551 &operands[i],
1552 &None,
1553 &recurse_bounds[i],
1554 );
1555 chain.push(step);
1556 prev = next;
1557 }
1558 let combined = quote! { #( #chain )* };
1559 combined.into()
1560}
1561
1562fn expand_partition_coproduct_helper(
1563 name: Ident,
1564 left: &syn::Type,
1565 right: &syn::Type,
1566 left_recurse: &Option<proc_macro2::TokenStream>,
1567 right_recurse: &Option<proc_macro2::TokenStream>,
1568) -> proc_macro2::TokenStream {
1569 let iri = format!(
1570 "urn:uor:coproduct:{}:{}",
1571 lexically_earlier_ty(left, right),
1572 lexically_later_ty(left, right),
1573 );
1574 let (l, r, l_recurse, r_recurse) = if type_token_string(left) <= type_token_string(right) {
1576 (
1577 left.clone(),
1578 right.clone(),
1579 left_recurse.clone(),
1580 right_recurse.clone(),
1581 )
1582 } else {
1583 (
1584 right.clone(),
1585 left.clone(),
1586 right_recurse.clone(),
1587 left_recurse.clone(),
1588 )
1589 };
1590 let _ = canonical_operand_pair_ty;
1591 let any_recurse = l_recurse.is_some() || r_recurse.is_some();
1592 let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
1593 let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
1594 let tag_coeffs_l = format_ident_suffix(&name, "__TAG_COEFFS_L");
1595 let tag_coeffs_r = format_ident_suffix(&name, "__TAG_COEFFS_R");
1596 let tag_coeff_count = format_ident_suffix(&name, "__TAG_COEFF_COUNT");
1597
1598 let left_block: proc_macro2::TokenStream = match &l_recurse {
1603 Some(bound) => quote! {
1604 out[i] = ::uor_foundation::pipeline::ConstraintRef::Recurse {
1605 shape_iri: <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::IRI,
1606 descent_bound: #bound,
1607 };
1608 i += 1;
1609 },
1610 None => quote! {
1611 let left_arr = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
1612 let mut li = 0;
1613 while li < left_arr.len() {
1614 out[i] = left_arr[li];
1615 i += 1;
1616 li += 1;
1617 }
1618 },
1619 };
1620 let right_block: proc_macro2::TokenStream = match &r_recurse {
1621 Some(bound) => quote! {
1622 out[i] = ::uor_foundation::pipeline::ConstraintRef::Recurse {
1623 shape_iri: <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::IRI,
1624 descent_bound: #bound,
1625 };
1626 i += 1;
1627 },
1628 None => quote! {
1629 let right_arr = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
1630 let mut ri = 0;
1631 while ri < right_arr.len() {
1632 out[i] = right_arr[ri];
1633 i += 1;
1634 ri += 1;
1635 }
1636 },
1637 };
1638 let len_const_init: proc_macro2::TokenStream = {
1640 let left_len = match &l_recurse {
1641 Some(_) => quote! { 1usize },
1642 None => quote! {
1643 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
1644 },
1645 };
1646 let right_len = match &r_recurse {
1647 Some(_) => quote! { 1usize },
1648 None => quote! {
1649 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
1650 },
1651 };
1652 quote! { #left_len + #right_len + 2 }
1653 };
1654 let cycle_size_init: proc_macro2::TokenStream = if any_recurse {
1655 quote! { u64::MAX }
1656 } else {
1657 quote! {
1658 ::uor_foundation::pipeline::cycle_size_coproduct(
1659 <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1660 <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
1661 )
1662 }
1663 };
1664
1665 quote! {
1666 pub struct #name;
1668
1669 const #tag_coeffs_l: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = {
1670 let mut out = [0i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS];
1671 let tag_site = {
1672 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1673 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1674 if a > b { a } else { b }
1675 };
1676 if tag_site < ::uor_foundation::pipeline::AFFINE_MAX_COEFFS {
1677 out[tag_site] = 1;
1678 }
1679 out
1680 };
1681 const #tag_coeffs_r: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = #tag_coeffs_l;
1682 const #tag_coeff_count: u32 = {
1683 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1684 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1685 let tag_site = if a > b { a } else { b };
1686 (tag_site as u32).saturating_add(1)
1687 };
1688
1689 const #raw_const:
1690 [::uor_foundation::pipeline::ConstraintRef;
1691 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2]
1692 = {
1693 let mut out =
1694 [::uor_foundation::pipeline::ConstraintRef::Site { position: u32::MAX };
1695 2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2];
1696 let mut i = 0;
1700 #left_block
1701 out[i] = ::uor_foundation::pipeline::ConstraintRef::Affine {
1702 coefficients: #tag_coeffs_l,
1703 coefficient_count: #tag_coeff_count,
1704 bias: 0,
1705 };
1706 i += 1;
1707 #right_block
1708 out[i] = ::uor_foundation::pipeline::ConstraintRef::Affine {
1709 coefficients: #tag_coeffs_r,
1710 coefficient_count: #tag_coeff_count,
1711 bias: -1,
1712 };
1713 out
1714 };
1715 const #len_const: usize = #len_const_init;
1716
1717 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
1718 const IRI: &'static str = #iri;
1719 const SITE_BUDGET: usize = {
1720 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
1721 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
1722 if a > b { a } else { b }
1723 };
1724 const SITE_COUNT: usize = {
1725 let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1726 let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
1727 (if a > b { a } else { b }) + 1
1728 };
1729 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
1730 let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
1731 match buf.split_at_checked(#len_const) {
1732 Some((head, _tail)) => head,
1733 None => &[],
1734 }
1735 };
1736 const CYCLE_SIZE: u64 = #cycle_size_init;
1740 }
1741
1742 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
1743 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #name {
1744 fn as_binding_value<const INLINE_BYTES: usize>(
1745 &self,
1746 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
1747 ::uor_foundation::pipeline::TermValue::empty()
1748 }
1749 }
1750 impl ::uor_foundation::enforcement::GroundedShape for #name {}
1751 }
1752}
1753
1754struct PrismModelInput {
1805 model_vis: syn::Visibility,
1806 model_name: Ident,
1807 route_vis: syn::Visibility,
1808 route_name: Ident,
1809 h_ty: syn::Type,
1810 b_ty: syn::Type,
1811 a_ty: syn::Type,
1812 r_ty: Option<syn::Type>,
1816 c_ty: Option<syn::Type>,
1821 input_ty: syn::Type,
1822 output_ty: syn::Type,
1823 route_input_ident: Ident,
1824 route_body: syn::Block,
1825 resolvers_body: Option<syn::Block>,
1831 commitment_body: Option<syn::Block>,
1837}
1838
1839impl Parse for PrismModelInput {
1840 fn parse(input: ParseStream) -> Result<Self> {
1841 let model_vis: syn::Visibility = input.parse()?;
1843 input.parse::<Token![struct]>()?;
1844 let model_name: Ident = input.parse()?;
1845 input.parse::<Token![;]>()?;
1846
1847 let route_vis: syn::Visibility = input.parse()?;
1849 input.parse::<Token![struct]>()?;
1850 let route_name: Ident = input.parse()?;
1851 input.parse::<Token![;]>()?;
1852
1853 input.parse::<Token![impl]>()?;
1855 let trait_ident: Ident = input.parse()?;
1856 if trait_ident != "PrismModel" {
1857 return Err(syn::Error::new(
1858 trait_ident.span(),
1859 "prism_model! expects an `impl PrismModel<H, B, A[, R[, C]]> for <Model>` block",
1860 ));
1861 }
1862 input.parse::<Token![<]>()?;
1863 let h_ty: syn::Type = input.parse()?;
1864 input.parse::<Token![,]>()?;
1865 let b_ty: syn::Type = input.parse()?;
1866 input.parse::<Token![,]>()?;
1867 let a_ty: syn::Type = input.parse()?;
1868 let r_ty: Option<syn::Type> = if input.peek(Token![,]) {
1870 input.parse::<Token![,]>()?;
1871 Some(input.parse::<syn::Type>()?)
1872 } else {
1873 None
1874 };
1875 let c_ty: Option<syn::Type> = if input.peek(Token![,]) {
1877 input.parse::<Token![,]>()?;
1878 Some(input.parse::<syn::Type>()?)
1879 } else {
1880 None
1881 };
1882 input.parse::<Token![>]>()?;
1883 input.parse::<Token![for]>()?;
1884 let impl_target: Ident = input.parse()?;
1885 if impl_target != model_name {
1886 return Err(syn::Error::new(
1887 impl_target.span(),
1888 "prism_model!'s `impl PrismModel<…> for <Model>` target must match the declared model struct",
1889 ));
1890 }
1891
1892 let body;
1896 syn::braced!(body in input);
1897
1898 body.parse::<Token![type]>()?;
1900 let input_kw: Ident = body.parse()?;
1901 if input_kw != "Input" {
1902 return Err(syn::Error::new(
1903 input_kw.span(),
1904 "expected `type Input = …;`",
1905 ));
1906 }
1907 body.parse::<Token![=]>()?;
1908 let input_ty: syn::Type = body.parse()?;
1909 body.parse::<Token![;]>()?;
1910
1911 body.parse::<Token![type]>()?;
1913 let output_kw: Ident = body.parse()?;
1914 if output_kw != "Output" {
1915 return Err(syn::Error::new(
1916 output_kw.span(),
1917 "expected `type Output = …;`",
1918 ));
1919 }
1920 body.parse::<Token![=]>()?;
1921 let output_ty: syn::Type = body.parse()?;
1922 body.parse::<Token![;]>()?;
1923
1924 body.parse::<Token![type]>()?;
1926 let route_kw: Ident = body.parse()?;
1927 if route_kw != "Route" {
1928 return Err(syn::Error::new(
1929 route_kw.span(),
1930 "expected `type Route = …;`",
1931 ));
1932 }
1933 body.parse::<Token![=]>()?;
1934 let route_ty_ident: Ident = body.parse()?;
1935 if route_ty_ident != route_name {
1936 return Err(syn::Error::new(
1937 route_ty_ident.span(),
1938 "prism_model!'s `type Route = <RouteName>;` must match the declared route struct",
1939 ));
1940 }
1941 body.parse::<Token![;]>()?;
1942
1943 body.parse::<Token![fn]>()?;
1945 let fn_kw: Ident = body.parse()?;
1946 if fn_kw != "route" {
1947 return Err(syn::Error::new(
1948 fn_kw.span(),
1949 "expected `fn route(input: Self::Input) -> Self::Output { … }`",
1950 ));
1951 }
1952 let params;
1953 syn::parenthesized!(params in body);
1954 let route_input_ident: Ident = params.parse()?;
1955 params.parse::<Token![:]>()?;
1956 let _input_param_ty: syn::Type = params.parse()?;
1957 body.parse::<Token![->]>()?;
1959 let _output_param_ty: syn::Type = body.parse()?;
1960 let route_body: syn::Block = body.parse()?;
1961
1962 let mut resolvers_body: Option<syn::Block> = None;
1967 let mut commitment_body: Option<syn::Block> = None;
1968 while body.peek(Token![fn]) {
1969 body.parse::<Token![fn]>()?;
1970 let method_kw: Ident = body.parse()?;
1971 let method_name = method_kw.to_string();
1972 if method_name != "resolvers" && method_name != "commitment" {
1973 return Err(syn::Error::new(
1974 method_kw.span(),
1975 "the only optional methods after `fn route` are `fn resolvers() -> R { … }` (ADR-036) and `fn commitment() -> C { … }` (ADR-048)",
1976 ));
1977 }
1978 let method_params;
1979 syn::parenthesized!(method_params in body);
1980 if !method_params.is_empty() {
1981 return Err(syn::Error::new(
1982 method_kw.span(),
1983 "`fn resolvers()` / `fn commitment()` take no parameters — their return value is the per-call substrate instance",
1984 ));
1985 }
1986 body.parse::<Token![->]>()?;
1987 let _ret_ty: syn::Type = body.parse()?;
1988 let block: syn::Block = body.parse()?;
1989 if method_name == "resolvers" {
1990 if resolvers_body.is_some() {
1991 return Err(syn::Error::new(
1992 method_kw.span(),
1993 "duplicate `fn resolvers()` clause in `prism_model!` body",
1994 ));
1995 }
1996 resolvers_body = Some(block);
1997 } else {
1998 if commitment_body.is_some() {
1999 return Err(syn::Error::new(
2000 method_kw.span(),
2001 "duplicate `fn commitment()` clause in `prism_model!` body",
2002 ));
2003 }
2004 commitment_body = Some(block);
2005 }
2006 }
2007
2008 Ok(Self {
2009 model_vis,
2010 model_name,
2011 route_vis,
2012 route_name,
2013 h_ty,
2014 b_ty,
2015 a_ty,
2016 r_ty,
2017 c_ty,
2018 input_ty,
2019 output_ty,
2020 route_input_ident,
2021 route_body,
2022 resolvers_body,
2023 commitment_body,
2024 })
2025 }
2026}
2027
2028#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2036enum PsiShape {
2037 Byte,
2042 SimplicialComplex,
2044 ChainComplex,
2046 HomologyGroups,
2048 CochainComplex,
2050 CohomologyGroups,
2052 PostnikovTower,
2054 HomotopyGroups,
2056}
2057
2058impl PsiShape {
2059 fn describe(self) -> &'static str {
2062 match self {
2063 PsiShape::Byte => "byte-shaped",
2064 PsiShape::SimplicialComplex => "simplicial-complex-shaped",
2065 PsiShape::ChainComplex => "chain-complex-shaped",
2066 PsiShape::HomologyGroups => "homology-groups-shaped",
2067 PsiShape::CochainComplex => "cochain-complex-shaped",
2068 PsiShape::CohomologyGroups => "cohomology-groups-shaped",
2069 PsiShape::PostnikovTower => "Postnikov-tower-shaped",
2070 PsiShape::HomotopyGroups => "homotopy-groups-shaped",
2071 }
2072 }
2073}
2074
2075fn term_spec_shape(spec: &TermSpec) -> PsiShape {
2079 match spec {
2080 TermSpec::Nerve { .. } => PsiShape::SimplicialComplex,
2081 TermSpec::ChainComplex { .. } => PsiShape::ChainComplex,
2082 TermSpec::HomologyGroups { .. } => PsiShape::HomologyGroups,
2083 TermSpec::CochainComplex { .. } => PsiShape::CochainComplex,
2084 TermSpec::CohomologyGroups { .. } => PsiShape::CohomologyGroups,
2085 TermSpec::PostnikovTower { .. } => PsiShape::PostnikovTower,
2086 TermSpec::HomotopyGroups { .. } => PsiShape::HomotopyGroups,
2087 _ => PsiShape::Byte,
2093 }
2094}
2095
2096fn psi_shape_compatible(expected: PsiShape, actual: PsiShape) -> bool {
2101 expected == actual
2102}
2103
2104#[allow(dead_code)]
2115enum TermSpec {
2116 Literal(u64),
2118 Variable,
2121 Application {
2123 operator: proc_macro2::TokenStream,
2124 args_start: u32,
2125 args_len: u32,
2126 },
2127 AxisInvocation {
2132 axis_index: u32,
2133 kernel_id: u32,
2134 input_index: u32,
2135 },
2136 ProjectField {
2143 source_index: u32,
2144 byte_offset: proc_macro2::TokenStream,
2145 byte_length: proc_macro2::TokenStream,
2146 },
2147 FirstAdmit {
2153 domain_size_index: u32,
2154 predicate_index: u32,
2155 },
2156 FirstAdmitIdxPlaceholder,
2162 RecurseIdxPlaceholder,
2168 Nerve { value_index: u32 },
2170 ChainComplex { simplicial_index: u32 },
2172 HomologyGroups { chain_index: u32 },
2174 Betti { homology_index: u32 },
2176 CochainComplex { chain_index: u32 },
2178 CohomologyGroups { cochain_index: u32 },
2180 PostnikovTower { simplicial_index: u32 },
2182 HomotopyGroups { postnikov_index: u32 },
2184 KInvariants { homotopy_index: u32 },
2186 LiteralExpr {
2192 value: proc_macro2::TokenStream,
2193 level: proc_macro2::TokenStream,
2194 },
2195 LiteralBytesExpr {
2206 bytes: proc_macro2::TokenStream,
2207 level: proc_macro2::TokenStream,
2208 },
2209 VerbSplice {
2224 arg_root_idx: u32,
2225 fragment_path: proc_macro2::TokenStream,
2226 },
2227 Lift {
2231 operand_index: u32,
2232 target_witt: proc_macro2::TokenStream,
2233 },
2234 Project {
2238 operand_index: u32,
2239 target_witt: proc_macro2::TokenStream,
2240 },
2241 Try { body_index: u32 },
2246 Recurse {
2249 measure_index: u32,
2250 base_index: u32,
2251 step_index: u32,
2252 },
2253 Unfold { seed_index: u32, step_index: u32 },
2256 Match {
2260 scrutinee_index: u32,
2261 arms_start: u32,
2262 arms_len: u32,
2263 },
2264 WildcardSentinel,
2267 RecursePlaceholder,
2273 UnfoldPlaceholder,
2282}
2283
2284#[derive(Default, Clone)]
2292struct BindingScope {
2293 bindings: Vec<(Ident, usize)>,
2294 route_input_ty: Option<syn::Type>,
2300 in_route_body: bool,
2313}
2314
2315impl BindingScope {
2316 fn lookup(&self, ident: &Ident) -> Option<usize> {
2317 self.bindings
2320 .iter()
2321 .rev()
2322 .find(|(name, _)| name == ident)
2323 .map(|(_, idx)| *idx)
2324 }
2325
2326 fn shadow_check(&self, ident: &Ident) -> Result<()> {
2327 if self.bindings.iter().any(|(name, _)| name == ident) {
2328 return Err(syn::Error::new(
2329 ident.span(),
2330 format!(
2331 "closure violation: shadowing `{ident}` (ADR-022 D3 G10 forbids declaring two `let`s with the same identifier in the same scope)"
2332 ),
2333 ));
2334 }
2335 Ok(())
2336 }
2337
2338 fn push(&mut self, ident: Ident, root_idx: usize) {
2339 self.bindings.push((ident, root_idx));
2340 }
2341}
2342
2343fn emit_term_for_expr(
2349 expr: &syn::Expr,
2350 route_input: &Ident,
2351 arena: &mut Vec<TermSpec>,
2352 scope: &mut BindingScope,
2353) -> Result<usize> {
2354 match expr {
2355 syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => {
2356 let value: u64 = int_lit.base10_parse().map_err(|e| {
2357 syn::Error::new(int_lit.span(), format!("integer literal out of u64 range: {e}"))
2358 })?;
2359 let idx = arena.len();
2360 arena.push(TermSpec::Literal(value));
2361 Ok(idx)
2362 }
2363 syn::Expr::Path(path_expr) if path_expr.path.get_ident() == Some(route_input) => {
2364 let idx = arena.len();
2365 arena.push(TermSpec::Variable);
2366 Ok(idx)
2367 }
2368 syn::Expr::Path(path_expr) => {
2369 if let Some(name) = path_expr.path.get_ident() {
2375 if let Some(root) = scope.lookup(name) {
2376 return Ok(root);
2377 }
2378 }
2379 Err(syn::Error::new_spanned(
2380 path_expr,
2381 "closure violation: identifier is not a foundation-vocabulary name (only the route's `input` parameter, `let`-introduced bindings, and reserved macro-vocabulary identifiers are recognised)",
2382 ))
2383 }
2384 syn::Expr::Call(call_expr) => emit_term_for_call(call_expr, route_input, arena, scope),
2385 syn::Expr::Binary(bin_expr) => emit_term_for_binary(bin_expr, route_input, arena, scope),
2388 syn::Expr::Block(block_expr) => emit_term_for_block(&block_expr.block, route_input, arena, scope),
2389 syn::Expr::Paren(paren_expr) => {
2390 emit_term_for_expr(&paren_expr.expr, route_input, arena, scope)
2391 }
2392 syn::Expr::Try(try_expr) => {
2396 let body_root = emit_term_for_expr(&try_expr.expr, route_input, arena, scope)?;
2397 let idx = arena.len();
2398 arena.push(TermSpec::Try { body_index: body_root as u32 });
2399 Ok(idx)
2400 }
2401 syn::Expr::Match(match_expr) => emit_term_for_match(match_expr, route_input, arena, scope),
2403 syn::Expr::Field(field_expr) => emit_term_for_field(field_expr, route_input, arena, scope),
2409 other => Err(syn::Error::new_spanned(
2410 other,
2411 "closure violation: expression form is not in foundation vocabulary (recognised forms: integer literals, the route's `input` parameter, `let`-introduced bindings, postfix `?`, `match`, field access on partition_product inputs, and macro-vocabulary function calls — PrimitiveOps, hash, lift, project, recurse, unfold, plus implementation verbs)",
2412 )),
2413 }
2414}
2415
2416fn emit_term_for_field(
2435 field_expr: &syn::ExprField,
2436 route_input: &Ident,
2437 arena: &mut Vec<TermSpec>,
2438 scope: &mut BindingScope,
2439) -> Result<usize> {
2440 let source_root = emit_term_for_expr(&field_expr.base, route_input, arena, scope)?;
2442 let source_ty: syn::Type = resolve_field_receiver_type(&field_expr.base, route_input, scope)?;
2444 let index_expr = field_index_expr(&field_expr.member, &source_ty);
2448 let offset_expr = quote::quote! {
2449 <#source_ty as ::uor_foundation::pipeline::PartitionProductFields>::FIELDS[#index_expr].0
2450 };
2451 let length_expr = quote::quote! {
2452 <#source_ty as ::uor_foundation::pipeline::PartitionProductFields>::FIELDS[#index_expr].1
2453 };
2454 let idx = arena.len();
2455 arena.push(TermSpec::ProjectField {
2456 source_index: source_root as u32,
2457 byte_offset: offset_expr,
2458 byte_length: length_expr,
2459 });
2460 Ok(idx)
2461}
2462
2463fn field_index_expr(member: &syn::Member, source_ty: &syn::Type) -> proc_macro2::TokenStream {
2469 match member {
2470 syn::Member::Unnamed(idx) => {
2471 let i = idx.index as usize;
2472 quote::quote! { #i }
2473 }
2474 syn::Member::Named(name) => {
2475 let name_lit = name.to_string();
2476 quote::quote! {
2480 ::uor_foundation::pipeline::field_index_by_name_in(
2481 <#source_ty as ::uor_foundation::pipeline::PartitionProductFields>::FIELD_NAMES,
2482 #name_lit,
2483 )
2484 }
2485 }
2486 }
2487}
2488
2489fn resolve_field_receiver_type(
2495 base: &syn::Expr,
2496 route_input: &Ident,
2497 scope: &BindingScope,
2498) -> Result<syn::Type> {
2499 match base {
2500 syn::Expr::Path(path_expr) if path_expr.path.get_ident() == Some(route_input) => {
2501 match &scope.route_input_ty {
2502 Some(ty) => Ok(ty.clone()),
2503 None => Err(syn::Error::new_spanned(
2504 base,
2505 "closure violation: ADR-033 G20 field access requires the route input type to be known (only `prism_model!` and macros that pin `route_input_ty` admit field-access expressions)",
2506 )),
2507 }
2508 }
2509 syn::Expr::Paren(paren_expr) => {
2510 resolve_field_receiver_type(&paren_expr.expr, route_input, scope)
2511 }
2512 syn::Expr::Field(inner) => {
2513 let inner_ty = resolve_field_receiver_type(&inner.base, route_input, scope)?;
2516 let inner_index = field_index_expr(&inner.member, &inner_ty);
2517 let synth: syn::Type = syn::parse_quote! {
2521 <#inner_ty as ::uor_foundation::pipeline::PartitionProductFactor<{#inner_index}>>::Factor
2522 };
2523 Ok(synth)
2524 }
2525 other => Err(syn::Error::new_spanned(
2526 other,
2527 "closure violation: ADR-033 G20 field access receiver must be the route's `input` parameter or another field-access (let-binding receivers are not supported)",
2528 )),
2529 }
2530}
2531
2532fn emit_term_for_match(
2542 match_expr: &syn::ExprMatch,
2543 route_input: &Ident,
2544 arena: &mut Vec<TermSpec>,
2545 scope: &mut BindingScope,
2546) -> Result<usize> {
2547 let scrutinee_root = emit_term_for_expr(&match_expr.expr, route_input, arena, scope)?;
2548 if match_expr.arms.is_empty() {
2549 return Err(syn::Error::new_spanned(
2550 match_expr,
2551 "closure violation: `match` (G6) must have at least one arm; non-exhaustive matches are closure violations",
2552 ));
2553 }
2554 let last_arm = &match_expr.arms[match_expr.arms.len() - 1];
2555 let last_is_wildcard = matches!(last_arm.pat, syn::Pat::Wild(_));
2556 if !last_is_wildcard {
2557 return Err(syn::Error::new_spanned(
2558 &last_arm.pat,
2559 "closure violation: `match` (G6) MUST end with a wildcard arm `_ => <default>`; non-exhaustive matches are closure violations",
2560 ));
2561 }
2562
2563 let mut arm_pairs: Vec<(TermSpec, usize)> = Vec::with_capacity(match_expr.arms.len());
2567 for arm in &match_expr.arms {
2568 if arm.guard.is_some() {
2569 return Err(syn::Error::new_spanned(
2570 arm,
2571 "closure violation: match arm guards are not in the closure-body grammar (G6)",
2572 ));
2573 }
2574 let pattern_spec = match &arm.pat {
2575 syn::Pat::Lit(lit_pat) => {
2576 if let syn::Lit::Int(int_lit) = &lit_pat.lit {
2577 let value: u64 = int_lit.base10_parse().map_err(|e| {
2578 syn::Error::new(
2579 int_lit.span(),
2580 format!("integer literal out of u64 range: {e}"),
2581 )
2582 })?;
2583 TermSpec::Literal(value)
2584 } else {
2585 return Err(syn::Error::new_spanned(
2586 lit_pat,
2587 "closure violation: match patterns must be integer literals or `_` (G6)",
2588 ));
2589 }
2590 }
2591 syn::Pat::Wild(_) => TermSpec::WildcardSentinel,
2592 other => {
2593 return Err(syn::Error::new_spanned(
2594 other,
2595 "closure violation: match patterns must be integer literals or `_` (G6)",
2596 ));
2597 }
2598 };
2599 let body_root = emit_term_for_expr(&arm.body, route_input, arena, scope)?;
2600 arm_pairs.push((pattern_spec, body_root));
2601 }
2602
2603 let arms_start = arena.len();
2609 for (pattern_spec, body_root_idx) in arm_pairs {
2610 arena.push(pattern_spec);
2611 let body_copy = clone_term_spec(&arena[body_root_idx]);
2612 arena.push(body_copy);
2613 }
2614 let arms_len = arena.len() - arms_start;
2615 let idx = arena.len();
2616 arena.push(TermSpec::Match {
2617 scrutinee_index: scrutinee_root as u32,
2618 arms_start: arms_start as u32,
2619 arms_len: arms_len as u32,
2620 });
2621 Ok(idx)
2622}
2623
2624fn reject_psi_residual_op<E: quote::ToTokens>(
2629 expr: &E,
2630 op_syntax: &str,
2631 op_name: &str,
2632) -> Result<usize> {
2633 let message = format!(
2634 "ψ-residual violation (wiki ADR-035): byte-comparison `{op_syntax}` \
2635 (`PrimitiveOp::{op_name}`) is excluded from verb-body composition. \
2636 The canonical compiled form is structural — admission is a property \
2637 of the value's k-invariant signature, not a comparison predicate. \
2638 Express the admission relation through the ψ-chain (G21..G29) \
2639 instead: e.g. `k_invariants(homotopy_groups(postnikov_tower(nerve(input))))` \
2640 produces the κ-label that classifies the input's homotopy type."
2641 );
2642 Err(syn::Error::new_spanned(expr, message))
2643}
2644
2645fn reject_psi_residual_call<E: quote::ToTokens>(
2647 expr: &E,
2648 form: &str,
2649 term_variant: &str,
2650 rationale: &str,
2651) -> Result<usize> {
2652 let message = format!(
2653 "ψ-residual violation (wiki ADR-035): `{form}` lowers to `Term::{term_variant}` \
2654 and is excluded from verb-body composition. {rationale}"
2655 );
2656 Err(syn::Error::new_spanned(expr, message))
2657}
2658
2659fn emit_term_for_binary(
2666 expr: &syn::ExprBinary,
2667 route_input: &Ident,
2668 arena: &mut Vec<TermSpec>,
2669 scope: &mut BindingScope,
2670) -> Result<usize> {
2671 let operator = match expr.op {
2672 syn::BinOp::Le(_) if scope.in_route_body => {
2681 return reject_psi_residual_op(expr, "<=", "Le")
2682 }
2683 syn::BinOp::Lt(_) if scope.in_route_body => return reject_psi_residual_op(expr, "<", "Lt"),
2684 syn::BinOp::Ge(_) if scope.in_route_body => {
2685 return reject_psi_residual_op(expr, ">=", "Ge")
2686 }
2687 syn::BinOp::Gt(_) if scope.in_route_body => return reject_psi_residual_op(expr, ">", "Gt"),
2688 syn::BinOp::Le(_) => quote! { ::uor_foundation::PrimitiveOp::Le },
2689 syn::BinOp::Lt(_) => quote! { ::uor_foundation::PrimitiveOp::Lt },
2690 syn::BinOp::Ge(_) => quote! { ::uor_foundation::PrimitiveOp::Ge },
2691 syn::BinOp::Gt(_) => quote! { ::uor_foundation::PrimitiveOp::Gt },
2692 syn::BinOp::Add(_) => quote! { ::uor_foundation::PrimitiveOp::Add },
2693 syn::BinOp::Sub(_) => quote! { ::uor_foundation::PrimitiveOp::Sub },
2694 syn::BinOp::Mul(_) => quote! { ::uor_foundation::PrimitiveOp::Mul },
2695 syn::BinOp::BitXor(_) => quote! { ::uor_foundation::PrimitiveOp::Xor },
2696 syn::BinOp::BitAnd(_) => quote! { ::uor_foundation::PrimitiveOp::And },
2697 syn::BinOp::BitOr(_) => quote! { ::uor_foundation::PrimitiveOp::Or },
2698 _ => {
2699 return Err(syn::Error::new_spanned(
2700 expr,
2701 "closure violation: binary operator is not in the closure-body grammar; recognised operators are arithmetic (+, -, *) and bitwise (^, &, |). Byte-level comparison (<=, <, >=, >) is admissible in verb bodies and axis impl bodies per ADR-056; it is excluded only from the route body's syntactic surface (per ADR-035/ADR-056).",
2702 ));
2703 }
2704 };
2705 let lhs_root = emit_term_for_expr(&expr.left, route_input, arena, scope)?;
2706 let rhs_root = emit_term_for_expr(&expr.right, route_input, arena, scope)?;
2707 let already_contiguous = rhs_root == lhs_root + 1;
2708 let (args_start, args_len) = if already_contiguous {
2709 (lhs_root as u32, 2u32)
2710 } else {
2711 let start = arena.len();
2712 let lhs_dup = clone_term_spec(&arena[lhs_root]);
2713 arena.push(lhs_dup);
2714 let rhs_dup = clone_term_spec(&arena[rhs_root]);
2715 arena.push(rhs_dup);
2716 (start as u32, 2u32)
2717 };
2718 let idx = arena.len();
2719 arena.push(TermSpec::Application {
2720 operator,
2721 args_start,
2722 args_len,
2723 });
2724 Ok(idx)
2725}
2726
2727fn clone_term_spec(spec: &TermSpec) -> TermSpec {
2731 match spec {
2732 TermSpec::Literal(v) => TermSpec::Literal(*v),
2733 TermSpec::Variable => TermSpec::Variable,
2734 TermSpec::Application {
2735 operator,
2736 args_start,
2737 args_len,
2738 } => TermSpec::Application {
2739 operator: operator.clone(),
2740 args_start: *args_start,
2741 args_len: *args_len,
2742 },
2743 TermSpec::AxisInvocation {
2744 axis_index,
2745 kernel_id,
2746 input_index,
2747 } => TermSpec::AxisInvocation {
2748 axis_index: *axis_index,
2749 kernel_id: *kernel_id,
2750 input_index: *input_index,
2751 },
2752 TermSpec::ProjectField {
2753 source_index,
2754 byte_offset,
2755 byte_length,
2756 } => TermSpec::ProjectField {
2757 source_index: *source_index,
2758 byte_offset: byte_offset.clone(),
2759 byte_length: byte_length.clone(),
2760 },
2761 TermSpec::LiteralExpr { value, level } => TermSpec::LiteralExpr {
2762 value: value.clone(),
2763 level: level.clone(),
2764 },
2765 TermSpec::LiteralBytesExpr { bytes, level } => TermSpec::LiteralBytesExpr {
2766 bytes: bytes.clone(),
2767 level: level.clone(),
2768 },
2769 TermSpec::VerbSplice {
2770 arg_root_idx,
2771 fragment_path,
2772 } => TermSpec::VerbSplice {
2773 arg_root_idx: *arg_root_idx,
2774 fragment_path: fragment_path.clone(),
2775 },
2776 TermSpec::Lift {
2777 operand_index,
2778 target_witt,
2779 } => TermSpec::Lift {
2780 operand_index: *operand_index,
2781 target_witt: target_witt.clone(),
2782 },
2783 TermSpec::Project {
2784 operand_index,
2785 target_witt,
2786 } => TermSpec::Project {
2787 operand_index: *operand_index,
2788 target_witt: target_witt.clone(),
2789 },
2790 TermSpec::Try { body_index } => TermSpec::Try {
2791 body_index: *body_index,
2792 },
2793 TermSpec::Recurse {
2794 measure_index,
2795 base_index,
2796 step_index,
2797 } => TermSpec::Recurse {
2798 measure_index: *measure_index,
2799 base_index: *base_index,
2800 step_index: *step_index,
2801 },
2802 TermSpec::Unfold {
2803 seed_index,
2804 step_index,
2805 } => TermSpec::Unfold {
2806 seed_index: *seed_index,
2807 step_index: *step_index,
2808 },
2809 TermSpec::Match {
2810 scrutinee_index,
2811 arms_start,
2812 arms_len,
2813 } => TermSpec::Match {
2814 scrutinee_index: *scrutinee_index,
2815 arms_start: *arms_start,
2816 arms_len: *arms_len,
2817 },
2818 TermSpec::WildcardSentinel => TermSpec::WildcardSentinel,
2819 TermSpec::RecursePlaceholder => TermSpec::RecursePlaceholder,
2820 TermSpec::UnfoldPlaceholder => TermSpec::UnfoldPlaceholder,
2821 TermSpec::FirstAdmit {
2822 domain_size_index,
2823 predicate_index,
2824 } => TermSpec::FirstAdmit {
2825 domain_size_index: *domain_size_index,
2826 predicate_index: *predicate_index,
2827 },
2828 TermSpec::FirstAdmitIdxPlaceholder => TermSpec::FirstAdmitIdxPlaceholder,
2829 TermSpec::RecurseIdxPlaceholder => TermSpec::RecurseIdxPlaceholder,
2830 TermSpec::Nerve { value_index } => TermSpec::Nerve {
2831 value_index: *value_index,
2832 },
2833 TermSpec::ChainComplex { simplicial_index } => TermSpec::ChainComplex {
2834 simplicial_index: *simplicial_index,
2835 },
2836 TermSpec::HomologyGroups { chain_index } => TermSpec::HomologyGroups {
2837 chain_index: *chain_index,
2838 },
2839 TermSpec::Betti { homology_index } => TermSpec::Betti {
2840 homology_index: *homology_index,
2841 },
2842 TermSpec::CochainComplex { chain_index } => TermSpec::CochainComplex {
2843 chain_index: *chain_index,
2844 },
2845 TermSpec::CohomologyGroups { cochain_index } => TermSpec::CohomologyGroups {
2846 cochain_index: *cochain_index,
2847 },
2848 TermSpec::PostnikovTower { simplicial_index } => TermSpec::PostnikovTower {
2849 simplicial_index: *simplicial_index,
2850 },
2851 TermSpec::HomotopyGroups { postnikov_index } => TermSpec::HomotopyGroups {
2852 postnikov_index: *postnikov_index,
2853 },
2854 TermSpec::KInvariants { homotopy_index } => TermSpec::KInvariants {
2855 homotopy_index: *homotopy_index,
2856 },
2857 }
2858}
2859
2860fn emit_term_for_block(
2864 block: &syn::Block,
2865 route_input: &Ident,
2866 arena: &mut Vec<TermSpec>,
2867 scope: &mut BindingScope,
2868) -> Result<usize> {
2869 if block.stmts.is_empty() {
2870 return Err(syn::Error::new_spanned(
2871 block,
2872 "closure violation: block expressions must contain at least one statement (G11) — empty blocks are unreachable in the closure-body grammar",
2873 ));
2874 }
2875 let mut local_scope = scope.clone();
2876 let last = block.stmts.len() - 1;
2877 for (i, stmt) in block.stmts.iter().enumerate() {
2878 match stmt {
2879 syn::Stmt::Local(local) => {
2880 if i == last {
2881 return Err(syn::Error::new_spanned(
2882 stmt,
2883 "closure violation: block must end with an expression statement (G11), not a `let` binding",
2884 ));
2885 }
2886 let ident = match &local.pat {
2887 syn::Pat::Ident(pat_ident) => {
2888 if pat_ident.by_ref.is_some() || pat_ident.mutability.is_some() {
2889 return Err(syn::Error::new_spanned(
2890 pat_ident,
2891 "closure violation: `let` binding patterns must be plain identifiers (no `ref`, no `mut`) per ADR-022 D3 G10",
2892 ));
2893 }
2894 if pat_ident.subpat.is_some() {
2895 return Err(syn::Error::new_spanned(
2896 pat_ident,
2897 "closure violation: `let` binding patterns must be plain identifiers per ADR-022 D3 G10",
2898 ));
2899 }
2900 pat_ident.ident.clone()
2901 }
2902 other => {
2903 return Err(syn::Error::new_spanned(
2904 other,
2905 "closure violation: `let` binding patterns must be plain identifiers per ADR-022 D3 G10",
2906 ));
2907 }
2908 };
2909 local_scope.shadow_check(&ident)?;
2910 let init = local.init.as_ref().ok_or_else(|| {
2911 syn::Error::new_spanned(
2912 local,
2913 "closure violation: `let` bindings must have an initializer (`let <name> = <expr>;`)",
2914 )
2915 })?;
2916 if init.diverge.is_some() {
2917 return Err(syn::Error::new_spanned(
2918 local,
2919 "closure violation: `let ... else` is not in the closure-body grammar (G10)",
2920 ));
2921 }
2922 let value_root =
2923 emit_term_for_expr(&init.expr, route_input, arena, &mut local_scope)?;
2924 local_scope.push(ident, value_root);
2925 }
2926 syn::Stmt::Expr(inner, semi) => {
2927 if i == last {
2928 if semi.is_some() {
2929 return Err(syn::Error::new_spanned(
2930 stmt,
2931 "closure violation: block must end with a tail expression (G11), no trailing `;`",
2932 ));
2933 }
2934 return emit_term_for_expr(inner, route_input, arena, &mut local_scope);
2935 }
2936 return Err(syn::Error::new_spanned(
2937 stmt,
2938 "closure violation: only `let` statements may precede the block's tail expression (G10/G11)",
2939 ));
2940 }
2941 other => {
2942 return Err(syn::Error::new_spanned(
2943 other,
2944 "closure violation: block statement is not in the closure-body grammar (G10/G11 admits only `let` and a final expression)",
2945 ));
2946 }
2947 }
2948 }
2949 Err(syn::Error::new_spanned(
2951 block,
2952 "closure violation: block lacks a tail expression (G11)",
2953 ))
2954}
2955
2956fn emit_term_for_call(
2964 call: &syn::ExprCall,
2965 route_input: &Ident,
2966 arena: &mut Vec<TermSpec>,
2967 scope: &mut BindingScope,
2968) -> Result<usize> {
2969 if let syn::Expr::Path(path_expr) = call.func.as_ref() {
2972 let segments = &path_expr.path.segments;
2973 if segments.len() == 1 {
2974 let segment = &segments[0];
2975 let last_ident = &segment.ident;
2976 if last_ident == "lift" || last_ident == "project" {
2977 let target_witt = match &segment.arguments {
2978 syn::PathArguments::AngleBracketed(args) if args.args.len() == 1 => {
2979 match &args.args[0] {
2980 syn::GenericArgument::Type(syn::Type::Path(tp)) => tp.path.clone(),
2981 other => {
2982 return Err(syn::Error::new_spanned(
2983 other,
2984 "closure violation: lift/project's generic argument must be a Witt-level type (e.g., `WittLevel::W32`)",
2985 ));
2986 }
2987 }
2988 }
2989 _ => {
2990 return Err(syn::Error::new_spanned(
2991 segment,
2992 format!(
2993 "closure violation: `{last_ident}` requires a generic Witt-level argument: `{last_ident}::<WittLevel::W{{n}}>(operand)`"
2994 ),
2995 ));
2996 }
2997 };
2998 if call.args.len() != 1 {
2999 return Err(syn::Error::new(
3000 last_ident.span(),
3001 format!(
3002 "closure violation: `{last_ident}` (G4/G5) expects 1 argument, got {}",
3003 call.args.len()
3004 ),
3005 ));
3006 }
3007 let operand_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3008 let target_witt_ts = quote! { #target_witt };
3009 let idx = arena.len();
3010 let spec = if last_ident == "lift" {
3011 TermSpec::Lift {
3012 operand_index: operand_root as u32,
3013 target_witt: target_witt_ts,
3014 }
3015 } else {
3016 TermSpec::Project {
3017 operand_index: operand_root as u32,
3018 target_witt: target_witt_ts,
3019 }
3020 };
3021 arena.push(spec);
3022 return Ok(idx);
3023 }
3024 }
3025 }
3026
3027 let func_ident = match call.func.as_ref() {
3029 syn::Expr::Path(p) => p.path.get_ident().cloned().ok_or_else(|| {
3030 syn::Error::new_spanned(
3031 &call.func,
3032 "closure violation: call target must be a bare identifier matching a PrimitiveOp name, the `hash` verb form, or a declared verb identifier",
3033 )
3034 })?,
3035 other => {
3036 return Err(syn::Error::new_spanned(
3037 other,
3038 "closure violation: call target must be a bare identifier",
3039 ));
3040 }
3041 };
3042
3043 let unraw_name = func_ident.unraw().to_string();
3055 let verb_resolution = match unraw_name.as_str() {
3056 "add" | "sub" | "mul" | "xor" | "and" | "or" | "neg" | "bnot" | "succ" | "pred"
3059 | "div" | "mod" | "pow"
3061 | "hash" | "parallel" | "fold_n" | "tree_fold" | "first_admit"
3062 | "recurse" | "unfold" | "concat"
3063 | "literal_u64" | "literal_bytes"
3065 | "nerve" | "chain_complex" | "homology_groups" | "betti" | "cochain_complex"
3067 | "cohomology_groups" | "postnikov_tower" | "homotopy_groups" | "k_invariants" => false,
3068 _ => true,
3069 };
3070 if verb_resolution {
3071 if call.args.len() != 1 {
3072 return Err(syn::Error::new(
3073 func_ident.span(),
3074 format!(
3075 "verb invocation `{}` expects 1 argument (the verb's input value), got {}",
3076 func_ident,
3077 call.args.len()
3078 ),
3079 ));
3080 }
3081 let arg_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3082 let const_name = Ident::new(
3083 &format!("VERB_TERMS_{}", to_screaming_snake(&func_ident.to_string())),
3084 func_ident.span(),
3085 );
3086 let fragment_path = quote! { #const_name };
3087 let idx = arena.len();
3088 arena.push(TermSpec::VerbSplice {
3089 arg_root_idx: arg_root as u32,
3090 fragment_path,
3091 });
3092 return Ok(idx);
3093 }
3094
3095 if func_ident == "fold_n" {
3100 if call.args.len() != 3 {
3101 return Err(syn::Error::new(
3102 func_ident.span(),
3103 format!(
3104 "closure violation: `fold_n` (G14) expects 3 arguments (count, init, step closure), got {}",
3105 call.args.len()
3106 ),
3107 ));
3108 }
3109 let count_lit: Option<u64> = match &call.args[0] {
3114 syn::Expr::Lit(syn::ExprLit {
3115 lit: syn::Lit::Int(int_lit),
3116 ..
3117 }) => int_lit.base10_parse::<u64>().ok(),
3118 _ => None,
3119 };
3120 let init_root = emit_term_for_expr(&call.args[1], route_input, arena, scope)?;
3121 let step_closure = match &call.args[2] {
3122 syn::Expr::Closure(c) => c,
3123 other => {
3124 return Err(syn::Error::new_spanned(
3125 other,
3126 "closure violation: `fold_n`'s third argument must be a closure `|state, idx| <step_expr>` (G14)",
3127 ));
3128 }
3129 };
3130 if step_closure.inputs.len() != 2 {
3131 return Err(syn::Error::new_spanned(
3132 step_closure,
3133 "closure violation: `fold_n`'s step closure expects exactly 2 parameters (state, idx) per G14",
3134 ));
3135 }
3136 let state_ident = match &step_closure.inputs[0] {
3137 syn::Pat::Ident(p) => p.ident.clone(),
3138 other => {
3139 return Err(syn::Error::new_spanned(
3140 other,
3141 "closure violation: `fold_n`'s state parameter must be a plain identifier (G14)",
3142 ));
3143 }
3144 };
3145 let idx_ident = match &step_closure.inputs[1] {
3146 syn::Pat::Ident(p) => p.ident.clone(),
3147 other => {
3148 return Err(syn::Error::new_spanned(
3149 other,
3150 "closure violation: `fold_n`'s idx parameter must be a plain identifier (G14)",
3151 ));
3152 }
3153 };
3154 const FOLD_UNROLL_THRESHOLD: u64 = 8;
3156 if let Some(n) = count_lit {
3157 if n <= FOLD_UNROLL_THRESHOLD {
3158 let mut state_root = init_root;
3159 for i in 0..n {
3160 let idx_root = arena.len();
3161 arena.push(TermSpec::Literal(i));
3162 let mut iter_scope = scope.clone();
3163 iter_scope.shadow_check(&state_ident)?;
3164 iter_scope.shadow_check(&idx_ident)?;
3165 iter_scope.push(state_ident.clone(), state_root);
3166 iter_scope.push(idx_ident.clone(), idx_root);
3167 state_root = emit_term_for_expr(
3168 &step_closure.body,
3169 route_input,
3170 arena,
3171 &mut iter_scope,
3172 )?;
3173 }
3174 return Ok(state_root);
3175 }
3176 }
3177 let measure_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3183 let mut step_scope = scope.clone();
3184 step_scope.shadow_check(&state_ident)?;
3185 step_scope.shadow_check(&idx_ident)?;
3186 let placeholder_idx = arena.len();
3187 arena.push(TermSpec::RecursePlaceholder);
3188 step_scope.push(state_ident, placeholder_idx);
3189 step_scope.push(idx_ident, measure_root);
3190 let step_root =
3191 emit_term_for_expr(&step_closure.body, route_input, arena, &mut step_scope)?;
3192 let idx = arena.len();
3193 arena.push(TermSpec::Recurse {
3194 measure_index: measure_root as u32,
3195 base_index: init_root as u32,
3196 step_index: step_root as u32,
3197 });
3198 return Ok(idx);
3199 }
3200
3201 if func_ident == "recurse" {
3203 if call.args.len() != 3 {
3204 return Err(syn::Error::new(
3205 func_ident.span(),
3206 format!(
3207 "closure violation: `recurse` (G7) expects 3 arguments (measure, base, step closure), got {}",
3208 call.args.len()
3209 ),
3210 ));
3211 }
3212 let measure_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3213 let base_root = emit_term_for_expr(&call.args[1], route_input, arena, scope)?;
3214 let step_closure = match &call.args[2] {
3216 syn::Expr::Closure(c) => c,
3217 other => {
3218 return Err(syn::Error::new_spanned(
3219 other,
3220 "closure violation: `recurse`'s third argument must be a closure `|self_ident| <step_expr>` (G7)",
3221 ));
3222 }
3223 };
3224 if step_closure.inputs.is_empty() || step_closure.inputs.len() > 2 {
3229 return Err(syn::Error::new_spanned(
3230 step_closure,
3231 "closure violation: `recurse`'s step closure expects 1 parameter (`|self_ident| <step>` per G7) or 2 parameters (`|self_ident, idx_ident| <step>` per ADR-034 Mechanism 1)",
3232 ));
3233 }
3234 let self_ident = match &step_closure.inputs[0] {
3235 syn::Pat::Ident(p) => p.ident.clone(),
3236 other => {
3237 return Err(syn::Error::new_spanned(
3238 other,
3239 "closure violation: `recurse`'s step parameter must be a plain identifier (G7)",
3240 ));
3241 }
3242 };
3243 let idx_ident_opt: Option<Ident> = if step_closure.inputs.len() == 2 {
3244 match &step_closure.inputs[1] {
3245 syn::Pat::Ident(p) => Some(p.ident.clone()),
3246 other => {
3247 return Err(syn::Error::new_spanned(
3248 other,
3249 "closure violation: `recurse`'s iteration-counter parameter must be a plain identifier (ADR-034 Mechanism 1)",
3250 ));
3251 }
3252 }
3253 } else {
3254 None
3255 };
3256 let mut step_scope = scope.clone();
3263 step_scope.shadow_check(&self_ident)?;
3264 let self_placeholder_idx = arena.len();
3265 arena.push(TermSpec::RecursePlaceholder);
3266 step_scope.push(self_ident, self_placeholder_idx);
3267 if let Some(idx_ident) = idx_ident_opt {
3268 step_scope.shadow_check(&idx_ident)?;
3269 let idx_placeholder_idx = arena.len();
3270 arena.push(TermSpec::RecurseIdxPlaceholder);
3271 step_scope.push(idx_ident, idx_placeholder_idx);
3272 }
3273 let step_root =
3274 emit_term_for_expr(&step_closure.body, route_input, arena, &mut step_scope)?;
3275 let idx = arena.len();
3276 arena.push(TermSpec::Recurse {
3277 measure_index: measure_root as u32,
3278 base_index: base_root as u32,
3279 step_index: step_root as u32,
3280 });
3281 return Ok(idx);
3282 }
3283
3284 if func_ident == "unfold" {
3286 if call.args.len() != 2 {
3287 return Err(syn::Error::new(
3288 func_ident.span(),
3289 format!(
3290 "closure violation: `unfold` (G8) expects 2 arguments (seed, step closure), got {}",
3291 call.args.len()
3292 ),
3293 ));
3294 }
3295 let seed_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3296 let step_closure = match &call.args[1] {
3297 syn::Expr::Closure(c) => c,
3298 other => {
3299 return Err(syn::Error::new_spanned(
3300 other,
3301 "closure violation: `unfold`'s second argument must be a closure `|state_ident| <step_expr>` (G8)",
3302 ));
3303 }
3304 };
3305 if step_closure.inputs.len() != 1 {
3306 return Err(syn::Error::new_spanned(
3307 step_closure,
3308 "closure violation: `unfold`'s step closure expects exactly 1 parameter (the state placeholder, G8)",
3309 ));
3310 }
3311 let state_ident = match &step_closure.inputs[0] {
3312 syn::Pat::Ident(p) => p.ident.clone(),
3313 other => {
3314 return Err(syn::Error::new_spanned(
3315 other,
3316 "closure violation: `unfold`'s step parameter must be a plain identifier (G8)",
3317 ));
3318 }
3319 };
3320 let mut step_scope = scope.clone();
3328 step_scope.shadow_check(&state_ident)?;
3329 let placeholder_idx = arena.len();
3330 arena.push(TermSpec::UnfoldPlaceholder);
3331 step_scope.push(state_ident, placeholder_idx);
3332 let step_root =
3333 emit_term_for_expr(&step_closure.body, route_input, arena, &mut step_scope)?;
3334 let idx = arena.len();
3335 arena.push(TermSpec::Unfold {
3336 seed_index: seed_root as u32,
3337 step_index: step_root as u32,
3338 });
3339 return Ok(idx);
3340 }
3341
3342 if func_ident == "parallel" {
3353 if call.args.len() != 2 {
3354 return Err(syn::Error::new(
3355 func_ident.span(),
3356 format!(
3357 "closure violation: `parallel` (G13) expects 2 routes (left, right), got {}",
3358 call.args.len()
3359 ),
3360 ));
3361 }
3362 let lhs_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3363 let rhs_root = emit_term_for_expr(&call.args[1], route_input, arena, scope)?;
3364 let already_contiguous = rhs_root == lhs_root + 1;
3368 let (args_start, args_len) = if already_contiguous {
3369 (lhs_root as u32, 2u32)
3370 } else {
3371 let start = arena.len();
3372 let lhs_dup = clone_term_spec(&arena[lhs_root]);
3373 arena.push(lhs_dup);
3374 let rhs_dup = clone_term_spec(&arena[rhs_root]);
3375 arena.push(rhs_dup);
3376 (start as u32, 2u32)
3377 };
3378 let idx = arena.len();
3379 arena.push(TermSpec::Application {
3380 operator: quote! { ::uor_foundation::PrimitiveOp::Or },
3381 args_start,
3382 args_len,
3383 });
3384 return Ok(idx);
3385 }
3386
3387 if func_ident == "tree_fold" {
3394 if call.args.len() != 2 {
3395 return Err(syn::Error::new(
3396 func_ident.span(),
3397 format!(
3398 "closure violation: `tree_fold` (G15) expects 2 arguments (reducer, leaves array), got {}",
3399 call.args.len()
3400 ),
3401 ));
3402 }
3403 let reducer_ident = match &call.args[0] {
3405 syn::Expr::Path(p) => p.path.get_ident().cloned().ok_or_else(|| {
3406 syn::Error::new_spanned(
3407 &call.args[0],
3408 "closure violation: `tree_fold`'s reducer must be a bare identifier (PrimitiveOp or verb)",
3409 )
3410 })?,
3411 other => {
3412 return Err(syn::Error::new_spanned(
3413 other,
3414 "closure violation: `tree_fold`'s reducer must be a bare identifier",
3415 ));
3416 }
3417 };
3418 let leaves_array = match &call.args[1] {
3420 syn::Expr::Array(a) => a,
3421 other => {
3422 return Err(syn::Error::new_spanned(
3423 other,
3424 "closure violation: `tree_fold`'s leaves must be an array literal `[a, b, c, …]`",
3425 ));
3426 }
3427 };
3428 if leaves_array.elems.is_empty() {
3429 return Err(syn::Error::new_spanned(
3430 leaves_array,
3431 "closure violation: `tree_fold`'s leaves array must be non-empty (G15)",
3432 ));
3433 }
3434 let mut current_level: Vec<usize> = Vec::with_capacity(leaves_array.elems.len());
3436 for leaf_expr in &leaves_array.elems {
3437 current_level.push(emit_term_for_expr(leaf_expr, route_input, arena, scope)?);
3438 }
3439 let reducer_op_or_verb = match reducer_ident.unraw().to_string().as_str() {
3443 "add" => Some(quote! { ::uor_foundation::PrimitiveOp::Add }),
3444 "sub" => Some(quote! { ::uor_foundation::PrimitiveOp::Sub }),
3445 "mul" => Some(quote! { ::uor_foundation::PrimitiveOp::Mul }),
3446 "xor" => Some(quote! { ::uor_foundation::PrimitiveOp::Xor }),
3447 "and" => Some(quote! { ::uor_foundation::PrimitiveOp::And }),
3448 "or" => Some(quote! { ::uor_foundation::PrimitiveOp::Or }),
3449 "div" => Some(quote! { ::uor_foundation::PrimitiveOp::Div }),
3451 "mod" => Some(quote! { ::uor_foundation::PrimitiveOp::Mod }),
3452 "pow" => Some(quote! { ::uor_foundation::PrimitiveOp::Pow }),
3453 _ => None,
3454 };
3455 while current_level.len() > 1 {
3457 let mut next_level: Vec<usize> = Vec::with_capacity(current_level.len().div_ceil(2));
3458 let mut i = 0;
3459 while i + 1 < current_level.len() {
3460 let l_idx = current_level[i];
3461 let r_idx = current_level[i + 1];
3462 let already_contiguous = r_idx == l_idx + 1;
3464 let (args_start, args_len) = if already_contiguous {
3465 (l_idx as u32, 2u32)
3466 } else {
3467 let start = arena.len();
3468 let l_dup = clone_term_spec(&arena[l_idx]);
3469 arena.push(l_dup);
3470 let r_dup = clone_term_spec(&arena[r_idx]);
3471 arena.push(r_dup);
3472 (start as u32, 2u32)
3473 };
3474 let app_idx = arena.len();
3475 if let Some(op) = &reducer_op_or_verb {
3476 arena.push(TermSpec::Application {
3477 operator: op.clone(),
3478 args_start,
3479 args_len,
3480 });
3481 } else {
3482 let const_name = Ident::new(
3490 &format!(
3491 "VERB_TERMS_{}",
3492 to_screaming_snake(&reducer_ident.to_string())
3493 ),
3494 reducer_ident.span(),
3495 );
3496 let fragment_path = quote! { #const_name };
3497 arena.push(TermSpec::VerbSplice {
3498 arg_root_idx: l_idx as u32,
3499 fragment_path,
3500 });
3501 let _ = (args_start, args_len, r_idx);
3502 }
3503 next_level.push(app_idx);
3504 i += 2;
3505 }
3506 if i < current_level.len() {
3508 next_level.push(current_level[i]);
3509 }
3510 current_level = next_level;
3511 }
3512 return Ok(current_level[0]);
3513 }
3514
3515 if unraw_name == "literal_u64" {
3523 if call.args.len() != 2 {
3524 return Err(syn::Error::new(
3525 func_ident.span(),
3526 format!(
3527 "`literal_u64(<value>, <level>)` expects 2 arguments, got {}",
3528 call.args.len()
3529 ),
3530 ));
3531 }
3532 let value_tokens = quote::ToTokens::to_token_stream(&call.args[0]);
3533 let level_tokens = quote::ToTokens::to_token_stream(&call.args[1]);
3534 let idx = arena.len();
3535 arena.push(TermSpec::LiteralExpr {
3536 value: value_tokens,
3537 level: level_tokens,
3538 });
3539 return Ok(idx);
3540 }
3541
3542 if unraw_name == "literal_bytes" {
3551 if call.args.len() != 2 {
3552 return Err(syn::Error::new(
3553 func_ident.span(),
3554 format!(
3555 "`literal_bytes(<bytes>, <level>)` expects 2 arguments, got {}",
3556 call.args.len()
3557 ),
3558 ));
3559 }
3560 let bytes_tokens = quote::ToTokens::to_token_stream(&call.args[0]);
3561 let level_tokens = quote::ToTokens::to_token_stream(&call.args[1]);
3562 let idx = arena.len();
3563 arena.push(TermSpec::LiteralBytesExpr {
3564 bytes: bytes_tokens,
3565 level: level_tokens,
3566 });
3567 return Ok(idx);
3568 }
3569
3570 if func_ident == "first_admit" && scope.in_route_body {
3577 return reject_psi_residual_call(
3578 call,
3579 "first_admit(<domain>, |idx| <pred>)",
3580 "FirstAdmit",
3581 "The canonical compiled form is structural — admission is a \
3582 property of the value's k-invariant signature, not a search \
3583 predicate enumerated over a counter domain. Express the \
3584 admission relation through the ψ-chain (G21..G29): \
3585 e.g. `k_invariants(homotopy_groups(postnikov_tower(nerve(input))))` \
3586 produces the κ-label that classifies the input's homotopy type. \
3587 Per ADR-056 this restriction applies only to the route body's \
3588 syntactic surface; verb and axis impl bodies admit `first_admit` \
3589 directly.",
3590 );
3591 }
3592
3593 if func_ident == "concat" {
3601 if scope.in_route_body {
3602 return reject_psi_residual_call(
3603 call,
3604 "concat(<lhs>, <rhs>)",
3605 "Application { operator: PrimitiveOp::Concat }",
3606 "The canonical compiled form is structural — admission is a \
3607 property of the value's k-invariant signature, not a \
3608 byte-shape predicate. If byte-concatenation is the input's \
3609 structural decomposition, use `partition_product!` to \
3610 declare the typed shape and `Term::ProjectField` (G20) to \
3611 extract sub-byte ranges; pipe each component through the \
3612 ψ-chain instead of byte-manipulating before admission. \
3613 Per ADR-056 this restriction applies only to the route \
3614 body's syntactic surface; verb and axis impl bodies admit \
3615 `concat` directly.",
3616 );
3617 }
3618 if call.args.len() != 2 {
3620 return Err(syn::Error::new(
3621 func_ident.span(),
3622 format!(
3623 "PrimitiveOp `concat` expects 2 arguments (lhs, rhs), got {}",
3624 call.args.len()
3625 ),
3626 ));
3627 }
3628 let lhs_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3629 let rhs_root = emit_term_for_expr(&call.args[1], route_input, arena, scope)?;
3630 let already_contiguous = rhs_root == lhs_root + 1;
3631 let (args_start, args_len) = if already_contiguous {
3632 (lhs_root as u32, 2u32)
3633 } else {
3634 let start = arena.len();
3635 let lhs_dup = clone_term_spec(&arena[lhs_root]);
3636 arena.push(lhs_dup);
3637 let rhs_dup = clone_term_spec(&arena[rhs_root]);
3638 arena.push(rhs_dup);
3639 (start as u32, 2u32)
3640 };
3641 let idx = arena.len();
3642 arena.push(TermSpec::Application {
3643 operator: quote! { ::uor_foundation::PrimitiveOp::Concat },
3644 args_start,
3645 args_len,
3646 });
3647 return Ok(idx);
3648 }
3649
3650 if func_ident == "hash" {
3657 if scope.in_route_body {
3658 return reject_psi_residual_call(
3659 call,
3660 "hash(<value>)",
3661 "AxisInvocation",
3662 "The canonical hash axis is consumed by resolvers and verb \
3663 bodies, not directly by the route body (ADR-036 + ADR-056). \
3664 Move the `hash(...)` call into a `NerveResolver` (or other \
3665 resolver-trait impl) where the per-value content fingerprint \
3666 is computed as part of the resolver's internal resolution \
3667 semantics — or into a verb body, which admits `hash(...)` \
3668 freely per ADR-056. The route body composes ψ-chain forms \
3669 (G21..G29) over the input's structural decomposition.",
3670 );
3671 }
3672 if call.args.len() != 1 {
3674 return Err(syn::Error::new(
3675 func_ident.span(),
3676 format!(
3677 "`hash(<value>)` expects 1 argument, got {}",
3678 call.args.len()
3679 ),
3680 ));
3681 }
3682 let value_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3683 let idx = arena.len();
3684 arena.push(TermSpec::AxisInvocation {
3685 axis_index: 0,
3686 kernel_id: 0,
3687 input_index: value_root as u32,
3688 });
3689 return Ok(idx);
3690 }
3691
3692 let psi_chain: &[(&str, &str, PsiShape)] = &[
3702 ("nerve", "G21", PsiShape::Byte),
3703 ("chain_complex", "G22", PsiShape::SimplicialComplex),
3704 ("homology_groups", "G23", PsiShape::ChainComplex),
3705 ("betti", "G24", PsiShape::HomologyGroups),
3706 ("cochain_complex", "G25", PsiShape::ChainComplex),
3707 ("cohomology_groups", "G26", PsiShape::CochainComplex),
3708 ("postnikov_tower", "G27", PsiShape::SimplicialComplex),
3709 ("homotopy_groups", "G28", PsiShape::PostnikovTower),
3710 ("k_invariants", "G29", PsiShape::HomotopyGroups),
3711 ];
3712 for (name, grammar, expected_shape) in psi_chain {
3713 if func_ident == name {
3714 if call.args.len() != 1 {
3715 return Err(syn::Error::new(
3716 func_ident.span(),
3717 format!(
3718 "closure violation: `{name}` (ADR-035 {grammar}) expects 1 argument, got {}",
3719 call.args.len()
3720 ),
3721 ));
3722 }
3723 let operand_root = emit_term_for_expr(&call.args[0], route_input, arena, scope)?;
3724 let operand_shape = term_spec_shape(&arena[operand_root]);
3725 if !psi_shape_compatible(*expected_shape, operand_shape) {
3726 return Err(syn::Error::new_spanned(
3727 &call.args[0],
3728 format!(
3729 "receiver-shape violation (wiki ADR-035 {grammar}): `{name}` expects \
3730 a {expected} receiver, but the operand produces a {actual} value. \
3731 The ψ-chain stages compose along the ontology's identity maps \
3732 (ψ_1 → ψ_2 → … → ψ_9); receiver-shape mismatches break the \
3733 canonical compiled form's structural-witness chain.",
3734 expected = expected_shape.describe(),
3735 actual = operand_shape.describe(),
3736 ),
3737 ));
3738 }
3739 let idx = arena.len();
3740 let spec = match *name {
3741 "nerve" => TermSpec::Nerve {
3742 value_index: operand_root as u32,
3743 },
3744 "chain_complex" => TermSpec::ChainComplex {
3745 simplicial_index: operand_root as u32,
3746 },
3747 "homology_groups" => TermSpec::HomologyGroups {
3748 chain_index: operand_root as u32,
3749 },
3750 "betti" => TermSpec::Betti {
3751 homology_index: operand_root as u32,
3752 },
3753 "cochain_complex" => TermSpec::CochainComplex {
3754 chain_index: operand_root as u32,
3755 },
3756 "cohomology_groups" => TermSpec::CohomologyGroups {
3757 cochain_index: operand_root as u32,
3758 },
3759 "postnikov_tower" => TermSpec::PostnikovTower {
3760 simplicial_index: operand_root as u32,
3761 },
3762 "homotopy_groups" => TermSpec::HomotopyGroups {
3763 postnikov_index: operand_root as u32,
3764 },
3765 "k_invariants" => TermSpec::KInvariants {
3766 homotopy_index: operand_root as u32,
3767 },
3768 _ => unreachable!(),
3769 };
3770 arena.push(spec);
3771 return Ok(idx);
3772 }
3773 }
3774
3775 let (operator, expected_arity) = match func_ident.unraw().to_string().as_str() {
3776 "add" => (quote! { ::uor_foundation::PrimitiveOp::Add }, 2usize),
3777 "sub" => (quote! { ::uor_foundation::PrimitiveOp::Sub }, 2),
3778 "mul" => (quote! { ::uor_foundation::PrimitiveOp::Mul }, 2),
3779 "xor" => (quote! { ::uor_foundation::PrimitiveOp::Xor }, 2),
3780 "and" => (quote! { ::uor_foundation::PrimitiveOp::And }, 2),
3781 "or" => (quote! { ::uor_foundation::PrimitiveOp::Or }, 2),
3782 "neg" => (quote! { ::uor_foundation::PrimitiveOp::Neg }, 1),
3783 "bnot" => (quote! { ::uor_foundation::PrimitiveOp::Bnot }, 1),
3784 "succ" => (quote! { ::uor_foundation::PrimitiveOp::Succ }, 1),
3785 "pred" => (quote! { ::uor_foundation::PrimitiveOp::Pred }, 1),
3786 "div" => (quote! { ::uor_foundation::PrimitiveOp::Div }, 2),
3796 "mod" => (quote! { ::uor_foundation::PrimitiveOp::Mod }, 2),
3797 "pow" => (quote! { ::uor_foundation::PrimitiveOp::Pow }, 2),
3798 "partition_product" | "partition_coproduct" => {
3809 return Err(syn::Error::new(
3810 func_ident.span(),
3811 format!(
3812 "closure violation: `{}` (ADR-026 G17/G18) is a type-level shape constructor — invoke it at item position via the named SDK form `partition_product!(<Name>, <A>, <B>)` or `partition_coproduct!(<Name>, <A>, <B>)`, then reference `<Name>` in `type Input` / `type Output`",
3813 func_ident
3814 ),
3815 ));
3816 }
3817 other => {
3818 return Err(syn::Error::new(
3819 func_ident.span(),
3820 format!(
3821 "closure violation: `{other}` is not a foundation PrimitiveOp (recognised: add, sub, mul, div, r#mod, pow, xor, and, or, neg, bnot, succ, pred, concat), nor an ADR-026 macro-vocabulary identifier (hash/parallel/fold_n/tree_fold/first_admit/recurse/unfold), nor a declared verb"
3822 ),
3823 ));
3824 }
3825 };
3826
3827 if call.args.len() != expected_arity {
3828 return Err(syn::Error::new(
3829 func_ident.span(),
3830 format!(
3831 "PrimitiveOp `{}` expects {} argument(s), got {}",
3832 func_ident,
3833 expected_arity,
3834 call.args.len()
3835 ),
3836 ));
3837 }
3838
3839 let mut arg_root_indices: Vec<usize> = Vec::with_capacity(call.args.len());
3841 for arg in call.args.iter() {
3842 arg_root_indices.push(emit_term_for_expr(arg, route_input, arena, scope)?);
3843 }
3844
3845 let already_contiguous = arg_root_indices.windows(2).all(|w| w[1] == w[0] + 1);
3852
3853 let (args_start, args_len) = if already_contiguous && !arg_root_indices.is_empty() {
3854 let len = arg_root_indices.len();
3855 let start = arg_root_indices[0];
3856 (start as u32, len as u32)
3857 } else {
3858 let start = arena.len();
3860 for &idx in &arg_root_indices {
3861 let dup = match &arena[idx] {
3866 TermSpec::Literal(v) => TermSpec::Literal(*v),
3867 TermSpec::Variable => TermSpec::Variable,
3868 TermSpec::Application {
3869 operator,
3870 args_start,
3871 args_len,
3872 } => TermSpec::Application {
3873 operator: operator.clone(),
3874 args_start: *args_start,
3875 args_len: *args_len,
3876 },
3877 TermSpec::AxisInvocation {
3878 axis_index,
3879 kernel_id,
3880 input_index,
3881 } => TermSpec::AxisInvocation {
3882 axis_index: *axis_index,
3883 kernel_id: *kernel_id,
3884 input_index: *input_index,
3885 },
3886 TermSpec::ProjectField {
3887 source_index,
3888 byte_offset,
3889 byte_length,
3890 } => TermSpec::ProjectField {
3891 source_index: *source_index,
3892 byte_offset: byte_offset.clone(),
3893 byte_length: byte_length.clone(),
3894 },
3895 TermSpec::LiteralExpr { value, level } => TermSpec::LiteralExpr {
3896 value: value.clone(),
3897 level: level.clone(),
3898 },
3899 TermSpec::LiteralBytesExpr { bytes, level } => TermSpec::LiteralBytesExpr {
3900 bytes: bytes.clone(),
3901 level: level.clone(),
3902 },
3903 TermSpec::VerbSplice {
3904 arg_root_idx,
3905 fragment_path,
3906 } => TermSpec::VerbSplice {
3907 arg_root_idx: *arg_root_idx,
3908 fragment_path: fragment_path.clone(),
3909 },
3910 TermSpec::Lift {
3911 operand_index,
3912 target_witt,
3913 } => TermSpec::Lift {
3914 operand_index: *operand_index,
3915 target_witt: target_witt.clone(),
3916 },
3917 TermSpec::Project {
3918 operand_index,
3919 target_witt,
3920 } => TermSpec::Project {
3921 operand_index: *operand_index,
3922 target_witt: target_witt.clone(),
3923 },
3924 TermSpec::Try { body_index } => TermSpec::Try {
3925 body_index: *body_index,
3926 },
3927 TermSpec::Recurse {
3928 measure_index,
3929 base_index,
3930 step_index,
3931 } => TermSpec::Recurse {
3932 measure_index: *measure_index,
3933 base_index: *base_index,
3934 step_index: *step_index,
3935 },
3936 TermSpec::Unfold {
3937 seed_index,
3938 step_index,
3939 } => TermSpec::Unfold {
3940 seed_index: *seed_index,
3941 step_index: *step_index,
3942 },
3943 TermSpec::Match {
3944 scrutinee_index,
3945 arms_start,
3946 arms_len,
3947 } => TermSpec::Match {
3948 scrutinee_index: *scrutinee_index,
3949 arms_start: *arms_start,
3950 arms_len: *arms_len,
3951 },
3952 TermSpec::WildcardSentinel => TermSpec::WildcardSentinel,
3953 TermSpec::RecursePlaceholder => TermSpec::RecursePlaceholder,
3954 TermSpec::UnfoldPlaceholder => TermSpec::UnfoldPlaceholder,
3955 TermSpec::FirstAdmit {
3956 domain_size_index,
3957 predicate_index,
3958 } => TermSpec::FirstAdmit {
3959 domain_size_index: *domain_size_index,
3960 predicate_index: *predicate_index,
3961 },
3962 TermSpec::FirstAdmitIdxPlaceholder => TermSpec::FirstAdmitIdxPlaceholder,
3963 TermSpec::RecurseIdxPlaceholder => TermSpec::RecurseIdxPlaceholder,
3964 TermSpec::Nerve { value_index } => TermSpec::Nerve {
3965 value_index: *value_index,
3966 },
3967 TermSpec::ChainComplex { simplicial_index } => TermSpec::ChainComplex {
3968 simplicial_index: *simplicial_index,
3969 },
3970 TermSpec::HomologyGroups { chain_index } => TermSpec::HomologyGroups {
3971 chain_index: *chain_index,
3972 },
3973 TermSpec::Betti { homology_index } => TermSpec::Betti {
3974 homology_index: *homology_index,
3975 },
3976 TermSpec::CochainComplex { chain_index } => TermSpec::CochainComplex {
3977 chain_index: *chain_index,
3978 },
3979 TermSpec::CohomologyGroups { cochain_index } => TermSpec::CohomologyGroups {
3980 cochain_index: *cochain_index,
3981 },
3982 TermSpec::PostnikovTower { simplicial_index } => TermSpec::PostnikovTower {
3983 simplicial_index: *simplicial_index,
3984 },
3985 TermSpec::HomotopyGroups { postnikov_index } => TermSpec::HomotopyGroups {
3986 postnikov_index: *postnikov_index,
3987 },
3988 TermSpec::KInvariants { homotopy_index } => TermSpec::KInvariants {
3989 homotopy_index: *homotopy_index,
3990 },
3991 };
3992 arena.push(dup);
3993 }
3994 (start as u32, arg_root_indices.len() as u32)
3995 };
3996
3997 let app_idx = arena.len();
3998 arena.push(TermSpec::Application {
3999 operator,
4000 args_start,
4001 args_len,
4002 });
4003 Ok(app_idx)
4004}
4005
4006fn render_arena(arena: &[TermSpec]) -> Vec<proc_macro2::TokenStream> {
4010 arena
4011 .iter()
4012 .map(|spec| match spec {
4013 TermSpec::Literal(value) => quote! {
4014 ::uor_foundation::pipeline::literal_u64(
4015 #value,
4016 ::uor_foundation::WittLevel::W8,
4017 )
4018 },
4019 TermSpec::LiteralExpr { value, level } => quote! {
4020 ::uor_foundation::pipeline::literal_u64(
4021 #value,
4022 #level,
4023 )
4024 },
4025 TermSpec::LiteralBytesExpr { bytes, level } => quote! {
4026 ::uor_foundation::pipeline::literal_bytes(
4027 #bytes,
4028 #level,
4029 )
4030 },
4031 TermSpec::Variable => quote! {
4032 ::uor_foundation::enforcement::Term::Variable { name_index: 0u32 }
4033 },
4034 TermSpec::Application {
4035 operator,
4036 args_start,
4037 args_len,
4038 } => {
4039 let s = *args_start;
4040 let l = *args_len;
4041 quote! {
4042 ::uor_foundation::enforcement::Term::Application {
4043 operator: #operator,
4044 args: ::uor_foundation::enforcement::TermList {
4045 start: #s,
4046 len: #l,
4047 },
4048 }
4049 }
4050 }
4051 TermSpec::AxisInvocation {
4052 axis_index,
4053 kernel_id,
4054 input_index,
4055 } => {
4056 let a = *axis_index;
4057 let k = *kernel_id;
4058 let i = *input_index;
4059 quote! {
4060 ::uor_foundation::enforcement::Term::AxisInvocation {
4061 axis_index: #a,
4062 kernel_id: #k,
4063 input_index: #i,
4064 }
4065 }
4066 }
4067 TermSpec::ProjectField {
4068 source_index,
4069 byte_offset,
4070 byte_length,
4071 } => {
4072 let s = *source_index;
4073 quote! {
4074 ::uor_foundation::enforcement::Term::ProjectField {
4075 source_index: #s,
4076 byte_offset: (#byte_offset) as u32,
4077 byte_length: (#byte_length) as u32,
4078 }
4079 }
4080 }
4081 TermSpec::VerbSplice { .. } => {
4082 quote! {
4090 compile_error!(
4091 "internal error: VerbSplice reached the slice-literal renderer; \
4092 render_const_fn_arena_builder should have been chosen"
4093 )
4094 }
4095 }
4096 TermSpec::Lift {
4097 operand_index,
4098 target_witt,
4099 } => {
4100 let i = *operand_index;
4101 quote! {
4102 ::uor_foundation::enforcement::Term::Lift {
4103 operand_index: #i,
4104 target: #target_witt,
4105 }
4106 }
4107 }
4108 TermSpec::Project {
4109 operand_index,
4110 target_witt,
4111 } => {
4112 let i = *operand_index;
4113 quote! {
4114 ::uor_foundation::enforcement::Term::Project {
4115 operand_index: #i,
4116 target: #target_witt,
4117 }
4118 }
4119 }
4120 TermSpec::Try { body_index } => {
4121 let i = *body_index;
4122 quote! {
4123 ::uor_foundation::enforcement::Term::Try {
4124 body_index: #i,
4125 handler_index: u32::MAX,
4126 }
4127 }
4128 }
4129 TermSpec::Recurse {
4130 measure_index,
4131 base_index,
4132 step_index,
4133 } => {
4134 let m = *measure_index;
4135 let b = *base_index;
4136 let s = *step_index;
4137 quote! {
4138 ::uor_foundation::enforcement::Term::Recurse {
4139 measure_index: #m,
4140 base_index: #b,
4141 step_index: #s,
4142 }
4143 }
4144 }
4145 TermSpec::Unfold {
4146 seed_index,
4147 step_index,
4148 } => {
4149 let s = *seed_index;
4150 let st = *step_index;
4151 quote! {
4152 ::uor_foundation::enforcement::Term::Unfold {
4153 seed_index: #s,
4154 step_index: #st,
4155 }
4156 }
4157 }
4158 TermSpec::Match {
4159 scrutinee_index,
4160 arms_start,
4161 arms_len,
4162 } => {
4163 let s = *scrutinee_index;
4164 let st = *arms_start;
4165 let l = *arms_len;
4166 quote! {
4167 ::uor_foundation::enforcement::Term::Match {
4168 scrutinee_index: #s,
4169 arms: ::uor_foundation::enforcement::TermList {
4170 start: #st,
4171 len: #l,
4172 },
4173 }
4174 }
4175 }
4176 TermSpec::WildcardSentinel => quote! {
4177 ::uor_foundation::enforcement::Term::Variable {
4178 name_index: u32::MAX,
4179 }
4180 },
4181 TermSpec::RecursePlaceholder => quote! {
4182 ::uor_foundation::enforcement::Term::Variable {
4183 name_index: ::uor_foundation::pipeline::RECURSE_PLACEHOLDER_NAME_INDEX,
4184 }
4185 },
4186 TermSpec::UnfoldPlaceholder => quote! {
4187 ::uor_foundation::enforcement::Term::Variable {
4188 name_index: ::uor_foundation::pipeline::UNFOLD_PLACEHOLDER_NAME_INDEX,
4189 }
4190 },
4191 TermSpec::FirstAdmit {
4192 domain_size_index,
4193 predicate_index,
4194 } => {
4195 let d = *domain_size_index;
4196 let p = *predicate_index;
4197 quote! {
4198 ::uor_foundation::enforcement::Term::FirstAdmit {
4199 domain_size_index: #d,
4200 predicate_index: #p,
4201 }
4202 }
4203 }
4204 TermSpec::FirstAdmitIdxPlaceholder => quote! {
4205 ::uor_foundation::enforcement::Term::Variable {
4206 name_index: ::uor_foundation::pipeline::FIRST_ADMIT_IDX_NAME_INDEX,
4207 }
4208 },
4209 TermSpec::RecurseIdxPlaceholder => quote! {
4210 ::uor_foundation::enforcement::Term::Variable {
4211 name_index: ::uor_foundation::pipeline::RECURSE_IDX_NAME_INDEX,
4212 }
4213 },
4214 TermSpec::Nerve { value_index } => {
4215 let v = *value_index;
4216 quote! {
4217 ::uor_foundation::enforcement::Term::Nerve { value_index: #v }
4218 }
4219 }
4220 TermSpec::ChainComplex { simplicial_index } => {
4221 let s = *simplicial_index;
4222 quote! {
4223 ::uor_foundation::enforcement::Term::ChainComplex { simplicial_index: #s }
4224 }
4225 }
4226 TermSpec::HomologyGroups { chain_index } => {
4227 let c = *chain_index;
4228 quote! {
4229 ::uor_foundation::enforcement::Term::HomologyGroups { chain_index: #c }
4230 }
4231 }
4232 TermSpec::Betti { homology_index } => {
4233 let h = *homology_index;
4234 quote! {
4235 ::uor_foundation::enforcement::Term::Betti { homology_index: #h }
4236 }
4237 }
4238 TermSpec::CochainComplex { chain_index } => {
4239 let c = *chain_index;
4240 quote! {
4241 ::uor_foundation::enforcement::Term::CochainComplex { chain_index: #c }
4242 }
4243 }
4244 TermSpec::CohomologyGroups { cochain_index } => {
4245 let c = *cochain_index;
4246 quote! {
4247 ::uor_foundation::enforcement::Term::CohomologyGroups { cochain_index: #c }
4248 }
4249 }
4250 TermSpec::PostnikovTower { simplicial_index } => {
4251 let s = *simplicial_index;
4252 quote! {
4253 ::uor_foundation::enforcement::Term::PostnikovTower { simplicial_index: #s }
4254 }
4255 }
4256 TermSpec::HomotopyGroups { postnikov_index } => {
4257 let p = *postnikov_index;
4258 quote! {
4259 ::uor_foundation::enforcement::Term::HomotopyGroups { postnikov_index: #p }
4260 }
4261 }
4262 TermSpec::KInvariants { homotopy_index } => {
4263 let h = *homotopy_index;
4264 quote! {
4265 ::uor_foundation::enforcement::Term::KInvariants { homotopy_index: #h }
4266 }
4267 }
4268 })
4269 .collect()
4270}
4271
4272fn render_atomic_term_in_builder(
4279 spec: &TermSpec,
4280 spec_pos: &[proc_macro2::TokenStream],
4281) -> proc_macro2::TokenStream {
4282 let pos_at = |idx: u32| -> proc_macro2::TokenStream {
4283 let i = idx as usize;
4284 if i < spec_pos.len() {
4285 spec_pos[i].clone()
4286 } else {
4287 quote! { u32::MAX }
4290 }
4291 };
4292 match spec {
4293 TermSpec::Literal(value) => {
4294 let v = *value;
4295 quote! {
4296 ::uor_foundation::pipeline::literal_u64(
4297 #v,
4298 ::uor_foundation::WittLevel::W8,
4299 )
4300 }
4301 }
4302 TermSpec::LiteralExpr { value, level } => quote! {
4303 ::uor_foundation::pipeline::literal_u64(
4304 #value,
4305 #level,
4306 )
4307 },
4308 TermSpec::LiteralBytesExpr { bytes, level } => quote! {
4309 ::uor_foundation::pipeline::literal_bytes(
4310 #bytes,
4311 #level,
4312 )
4313 },
4314 TermSpec::Variable => quote! {
4315 ::uor_foundation::enforcement::Term::Variable { name_index: 0u32 }
4316 },
4317 TermSpec::Application {
4318 operator,
4319 args_start,
4320 args_len,
4321 } => {
4322 let s = pos_at(*args_start);
4323 let l = *args_len;
4324 quote! {
4325 ::uor_foundation::enforcement::Term::Application {
4326 operator: #operator,
4327 args: ::uor_foundation::enforcement::TermList {
4328 start: (#s) as u32,
4329 len: #l,
4330 },
4331 }
4332 }
4333 }
4334 TermSpec::AxisInvocation {
4335 axis_index,
4336 kernel_id,
4337 input_index,
4338 } => {
4339 let a = *axis_index;
4340 let k = *kernel_id;
4341 let i = pos_at(*input_index);
4342 quote! {
4343 ::uor_foundation::enforcement::Term::AxisInvocation {
4344 axis_index: #a,
4345 kernel_id: #k,
4346 input_index: (#i) as u32,
4347 }
4348 }
4349 }
4350 TermSpec::ProjectField {
4351 source_index,
4352 byte_offset,
4353 byte_length,
4354 } => {
4355 let s = pos_at(*source_index);
4356 quote! {
4357 ::uor_foundation::enforcement::Term::ProjectField {
4358 source_index: (#s) as u32,
4359 byte_offset: (#byte_offset) as u32,
4360 byte_length: (#byte_length) as u32,
4361 }
4362 }
4363 }
4364 TermSpec::Lift {
4365 operand_index,
4366 target_witt,
4367 } => {
4368 let i = pos_at(*operand_index);
4369 quote! {
4370 ::uor_foundation::enforcement::Term::Lift {
4371 operand_index: (#i) as u32,
4372 target: #target_witt,
4373 }
4374 }
4375 }
4376 TermSpec::Project {
4377 operand_index,
4378 target_witt,
4379 } => {
4380 let i = pos_at(*operand_index);
4381 quote! {
4382 ::uor_foundation::enforcement::Term::Project {
4383 operand_index: (#i) as u32,
4384 target: #target_witt,
4385 }
4386 }
4387 }
4388 TermSpec::Try { body_index } => {
4389 let i = pos_at(*body_index);
4390 quote! {
4391 ::uor_foundation::enforcement::Term::Try {
4392 body_index: (#i) as u32,
4393 handler_index: u32::MAX,
4394 }
4395 }
4396 }
4397 TermSpec::Recurse {
4398 measure_index,
4399 base_index,
4400 step_index,
4401 } => {
4402 let m = pos_at(*measure_index);
4403 let b = pos_at(*base_index);
4404 let s = pos_at(*step_index);
4405 quote! {
4406 ::uor_foundation::enforcement::Term::Recurse {
4407 measure_index: (#m) as u32,
4408 base_index: (#b) as u32,
4409 step_index: (#s) as u32,
4410 }
4411 }
4412 }
4413 TermSpec::Unfold {
4414 seed_index,
4415 step_index,
4416 } => {
4417 let s = pos_at(*seed_index);
4418 let st = pos_at(*step_index);
4419 quote! {
4420 ::uor_foundation::enforcement::Term::Unfold {
4421 seed_index: (#s) as u32,
4422 step_index: (#st) as u32,
4423 }
4424 }
4425 }
4426 TermSpec::Match {
4427 scrutinee_index,
4428 arms_start,
4429 arms_len,
4430 } => {
4431 let sc = pos_at(*scrutinee_index);
4432 let st = *arms_start;
4433 let l = *arms_len;
4434 let st_pos = if (st as usize) < spec_pos.len() {
4439 spec_pos[st as usize].clone()
4440 } else {
4441 quote! { u32::MAX }
4442 };
4443 quote! {
4444 ::uor_foundation::enforcement::Term::Match {
4445 scrutinee_index: (#sc) as u32,
4446 arms: ::uor_foundation::enforcement::TermList {
4447 start: (#st_pos) as u32,
4448 len: #l,
4449 },
4450 }
4451 }
4452 }
4453 TermSpec::WildcardSentinel => quote! {
4454 ::uor_foundation::enforcement::Term::Variable { name_index: u32::MAX }
4455 },
4456 TermSpec::RecursePlaceholder => quote! {
4457 ::uor_foundation::enforcement::Term::Variable {
4458 name_index: ::uor_foundation::pipeline::RECURSE_PLACEHOLDER_NAME_INDEX,
4459 }
4460 },
4461 TermSpec::UnfoldPlaceholder => quote! {
4462 ::uor_foundation::enforcement::Term::Variable {
4463 name_index: ::uor_foundation::pipeline::UNFOLD_PLACEHOLDER_NAME_INDEX,
4464 }
4465 },
4466 TermSpec::FirstAdmit {
4467 domain_size_index,
4468 predicate_index,
4469 } => {
4470 let d = pos_at(*domain_size_index);
4471 let p = pos_at(*predicate_index);
4472 quote! {
4473 ::uor_foundation::enforcement::Term::FirstAdmit {
4474 domain_size_index: (#d) as u32,
4475 predicate_index: (#p) as u32,
4476 }
4477 }
4478 }
4479 TermSpec::FirstAdmitIdxPlaceholder => quote! {
4480 ::uor_foundation::enforcement::Term::Variable {
4481 name_index: ::uor_foundation::pipeline::FIRST_ADMIT_IDX_NAME_INDEX,
4482 }
4483 },
4484 TermSpec::RecurseIdxPlaceholder => quote! {
4485 ::uor_foundation::enforcement::Term::Variable {
4486 name_index: ::uor_foundation::pipeline::RECURSE_IDX_NAME_INDEX,
4487 }
4488 },
4489 TermSpec::Nerve { value_index } => {
4490 let v = pos_at(*value_index);
4491 quote! {
4492 ::uor_foundation::enforcement::Term::Nerve { value_index: (#v) as u32 }
4493 }
4494 }
4495 TermSpec::ChainComplex { simplicial_index } => {
4496 let s = pos_at(*simplicial_index);
4497 quote! {
4498 ::uor_foundation::enforcement::Term::ChainComplex {
4499 simplicial_index: (#s) as u32,
4500 }
4501 }
4502 }
4503 TermSpec::HomologyGroups { chain_index } => {
4504 let c = pos_at(*chain_index);
4505 quote! {
4506 ::uor_foundation::enforcement::Term::HomologyGroups {
4507 chain_index: (#c) as u32,
4508 }
4509 }
4510 }
4511 TermSpec::Betti { homology_index } => {
4512 let h = pos_at(*homology_index);
4513 quote! {
4514 ::uor_foundation::enforcement::Term::Betti {
4515 homology_index: (#h) as u32,
4516 }
4517 }
4518 }
4519 TermSpec::CochainComplex { chain_index } => {
4520 let c = pos_at(*chain_index);
4521 quote! {
4522 ::uor_foundation::enforcement::Term::CochainComplex {
4523 chain_index: (#c) as u32,
4524 }
4525 }
4526 }
4527 TermSpec::CohomologyGroups { cochain_index } => {
4528 let c = pos_at(*cochain_index);
4529 quote! {
4530 ::uor_foundation::enforcement::Term::CohomologyGroups {
4531 cochain_index: (#c) as u32,
4532 }
4533 }
4534 }
4535 TermSpec::PostnikovTower { simplicial_index } => {
4536 let s = pos_at(*simplicial_index);
4537 quote! {
4538 ::uor_foundation::enforcement::Term::PostnikovTower {
4539 simplicial_index: (#s) as u32,
4540 }
4541 }
4542 }
4543 TermSpec::HomotopyGroups { postnikov_index } => {
4544 let p = pos_at(*postnikov_index);
4545 quote! {
4546 ::uor_foundation::enforcement::Term::HomotopyGroups {
4547 postnikov_index: (#p) as u32,
4548 }
4549 }
4550 }
4551 TermSpec::KInvariants { homotopy_index } => {
4552 let h = pos_at(*homotopy_index);
4553 quote! {
4554 ::uor_foundation::enforcement::Term::KInvariants {
4555 homotopy_index: (#h) as u32,
4556 }
4557 }
4558 }
4559 TermSpec::VerbSplice { .. } => quote! {
4560 compile_error!("VerbSplice handled separately in render_const_fn_arena_builder")
4561 },
4562 }
4563}
4564
4565fn render_const_fn_arena_builder(
4577 arena: &[TermSpec],
4578 inline_bytes: &proc_macro2::TokenStream,
4579) -> proc_macro2::TokenStream {
4580 let spec_pos: Vec<proc_macro2::TokenStream> = (0..arena.len())
4586 .map(|i| {
4587 let id = Ident::new(&format!("pos_{}", i), proc_macro2::Span::call_site());
4588 quote! { #id }
4589 })
4590 .collect();
4591
4592 let mut stmts: Vec<proc_macro2::TokenStream> = Vec::with_capacity(arena.len());
4594 for (i, spec) in arena.iter().enumerate() {
4595 let pos_id = &spec_pos[i];
4596 match spec {
4597 TermSpec::VerbSplice {
4598 arg_root_idx,
4599 fragment_path,
4600 } => {
4601 let arg_pos = &spec_pos[*arg_root_idx as usize];
4607 stmts.push(quote! {
4608 let __spliced = ::uor_foundation::enforcement::inline_verb_fragment(
4609 buf,
4610 len,
4611 #fragment_path::<#inline_bytes>(),
4612 (#arg_pos) as u32,
4613 );
4614 buf = __spliced.0;
4615 len = __spliced.1;
4616 let #pos_id: usize = len - 1;
4617 });
4618 }
4619 other => {
4620 let term_expr = render_atomic_term_in_builder(other, &spec_pos);
4621 stmts.push(quote! {
4622 buf[len] = #term_expr;
4623 let #pos_id: usize = len;
4624 len += 1;
4625 });
4626 }
4627 }
4628 }
4629
4630 quote! {
4633 {
4634 const ROUTE_ARENA_CAP: usize = 256;
4635 const fn __build_arena() -> ([::uor_foundation::enforcement::Term<'static, #inline_bytes>; ROUTE_ARENA_CAP], usize) {
4636 let mut buf: [::uor_foundation::enforcement::Term<'static, #inline_bytes>; ROUTE_ARENA_CAP] =
4637 [::uor_foundation::enforcement::Term::Variable { name_index: 0u32 }; ROUTE_ARENA_CAP];
4638 let mut len: usize = 0;
4639 #( #stmts )*
4640 (buf, len)
4641 }
4642 const ROUTE_BUILT: ([::uor_foundation::enforcement::Term<'static, #inline_bytes>; ROUTE_ARENA_CAP], usize) =
4643 __build_arena();
4644 const ROUTE_LEN: usize = ROUTE_BUILT.1;
4645 match ROUTE_BUILT.0.split_at_checked(ROUTE_LEN) {
4647 Some((head, _)) => head,
4648 None => &[],
4649 }
4650 }
4651 }
4652}
4653
4654#[proc_macro]
4673pub fn prism_model(input: TokenStream) -> TokenStream {
4674 let parsed = parse_macro_input!(input as PrismModelInput);
4675 let PrismModelInput {
4676 model_vis,
4677 model_name,
4678 route_vis,
4679 route_name,
4680 h_ty,
4681 b_ty,
4682 a_ty,
4683 r_ty,
4684 c_ty,
4685 input_ty,
4686 output_ty,
4687 route_input_ident,
4688 route_body,
4689 resolvers_body,
4690 commitment_body,
4691 } = parsed;
4692
4693 let (resolver_ty_tokens, resolver_construction) = match (&r_ty, &resolvers_body) {
4703 (Some(r), Some(block)) => (quote! { #r }, quote! { #block }),
4704 (Some(r), None) => (
4705 quote! { #r },
4706 quote! { <#r as ::core::default::Default>::default() },
4707 ),
4708 (None, _) => (
4709 quote! { ::uor_foundation::pipeline::NullResolverTuple },
4710 quote! { ::uor_foundation::pipeline::NullResolverTuple },
4711 ),
4712 };
4713
4714 let (commitment_ty_tokens, commitment_construction) = match (&c_ty, &commitment_body) {
4724 (Some(c), Some(block)) => (quote! { #c }, quote! { #block }),
4725 (Some(c), None) => (
4726 quote! { #c },
4727 quote! { <#c as ::core::default::Default>::default() },
4728 ),
4729 (None, _) => (
4730 quote! { ::uor_foundation::pipeline::EmptyCommitment },
4731 quote! { ::uor_foundation::pipeline::EmptyCommitment },
4732 ),
4733 };
4734
4735 let mut arena: Vec<TermSpec> = Vec::new();
4748 let mut scope = BindingScope {
4749 route_input_ty: Some(input_ty.clone()),
4750 in_route_body: true,
4751 ..BindingScope::default()
4752 };
4753 if let Err(e) = emit_term_for_block(&route_body, &route_input_ident, &mut arena, &mut scope) {
4754 return e.to_compile_error().into();
4755 }
4756
4757 let inline_bytes = quote! {
4764 { ::uor_foundation::pipeline::carrier_inline_bytes::<#b_ty>() }
4765 };
4766
4767 let fp_max = quote! {
4775 { <#b_ty as ::uor_foundation::HostBounds>::FINGERPRINT_MAX_BYTES }
4776 };
4777
4778 let route_has_verb_splices = arena
4783 .iter()
4784 .any(|s| matches!(s, TermSpec::VerbSplice { .. }));
4785 let route_arena_expr = if route_has_verb_splices {
4786 render_const_fn_arena_builder(&arena, &inline_bytes)
4787 } else {
4788 let term_specs = render_arena(&arena);
4789 quote! { &[ #( #term_specs ),* ] }
4790 };
4791
4792 let route_terms_const = Ident::new(
4795 &format!(
4796 "ROUTE_TERMS_FOR_{}",
4797 to_screaming_snake(&model_name.to_string())
4798 ),
4799 model_name.span(),
4800 );
4801
4802 let expansion = quote! {
4803 #model_vis struct #model_name;
4805 #route_vis struct #route_name;
4806
4807 #[allow(non_upper_case_globals, dead_code)]
4813 const #route_terms_const: &[::uor_foundation::enforcement::Term<'static, #inline_bytes>] =
4814 #route_arena_expr;
4815
4816 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #model_name {}
4819 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #route_name {}
4820
4821 impl ::uor_foundation::pipeline::FoundationClosed<#inline_bytes> for #route_name {
4824 fn arena_slice() -> &'static [::uor_foundation::enforcement::Term<'static, #inline_bytes>] {
4825 #route_terms_const
4826 }
4827 }
4828
4829 impl<'a> ::uor_foundation::pipeline::PrismModel<'a, #h_ty, #b_ty, #a_ty, #inline_bytes, #fp_max, #resolver_ty_tokens, #commitment_ty_tokens> for #model_name {
4837 type Input = #input_ty;
4838 type Output = #output_ty;
4839 type Route = #route_name;
4840
4841 fn forward(
4842 input: <Self as ::uor_foundation::pipeline::PrismModel<'a, #h_ty, #b_ty, #a_ty, #inline_bytes, #fp_max, #resolver_ty_tokens, #commitment_ty_tokens>>::Input,
4843 ) -> ::core::result::Result<
4844 ::uor_foundation::enforcement::Grounded<
4845 'a,
4846 <Self as ::uor_foundation::pipeline::PrismModel<'a, #h_ty, #b_ty, #a_ty, #inline_bytes, #fp_max, #resolver_ty_tokens, #commitment_ty_tokens>>::Output,
4847 #inline_bytes,
4848 #fp_max,
4849 >,
4850 ::uor_foundation::PipelineFailure,
4851 > {
4852 let __resolvers: #resolver_ty_tokens = #resolver_construction;
4853 let __commitment: #commitment_ty_tokens = #commitment_construction;
4854 ::uor_foundation::pipeline::run_route::<
4855 #h_ty,
4856 #b_ty,
4857 #a_ty,
4858 Self,
4859 #resolver_ty_tokens,
4860 #commitment_ty_tokens,
4861 #inline_bytes,
4862 #fp_max,
4863 >(input, &__resolvers, &__commitment)
4864 }
4865 }
4866 };
4867
4868 expansion.into()
4869}
4870
4871struct OutputShapeInput {
4906 struct_vis: syn::Visibility,
4907 struct_name: Ident,
4908 impl_iri: syn::LitStr,
4909 impl_site_count: syn::Expr,
4910 impl_constraints: syn::Expr,
4911 impl_cycle_size: Option<syn::Expr>,
4915}
4916
4917impl Parse for OutputShapeInput {
4918 fn parse(input: ParseStream) -> Result<Self> {
4919 let struct_vis: syn::Visibility = input.parse()?;
4921 input.parse::<Token![struct]>()?;
4922 let struct_name: Ident = input.parse()?;
4923 input.parse::<Token![;]>()?;
4924
4925 input.parse::<Token![impl]>()?;
4927 let trait_ident: Ident = input.parse()?;
4928 if trait_ident != "ConstrainedTypeShape" {
4929 return Err(syn::Error::new(
4930 trait_ident.span(),
4931 "output_shape! expects `impl ConstrainedTypeShape for <Name>`",
4932 ));
4933 }
4934 input.parse::<Token![for]>()?;
4935 let target: Ident = input.parse()?;
4936 if target != struct_name {
4937 return Err(syn::Error::new(
4938 target.span(),
4939 "output_shape!'s `impl ConstrainedTypeShape for <Name>` target must match the declared struct",
4940 ));
4941 }
4942
4943 let body;
4944 syn::braced!(body in input);
4945
4946 body.parse::<Token![const]>()?;
4948 let kw_iri: Ident = body.parse()?;
4949 if kw_iri != "IRI" {
4950 return Err(syn::Error::new(
4951 kw_iri.span(),
4952 "expected `const IRI: &'static str = ...`",
4953 ));
4954 }
4955 body.parse::<Token![:]>()?;
4956 let _ty: syn::Type = body.parse()?;
4957 body.parse::<Token![=]>()?;
4958 let impl_iri: syn::LitStr = body.parse()?;
4959 body.parse::<Token![;]>()?;
4960
4961 body.parse::<Token![const]>()?;
4963 let kw_sc: Ident = body.parse()?;
4964 if kw_sc != "SITE_COUNT" {
4965 return Err(syn::Error::new(
4966 kw_sc.span(),
4967 "expected `const SITE_COUNT: usize = ...`",
4968 ));
4969 }
4970 body.parse::<Token![:]>()?;
4971 let _ty: syn::Type = body.parse()?;
4972 body.parse::<Token![=]>()?;
4973 let impl_site_count: syn::Expr = body.parse()?;
4974 body.parse::<Token![;]>()?;
4975
4976 body.parse::<Token![const]>()?;
4978 let kw_cn: Ident = body.parse()?;
4979 if kw_cn != "CONSTRAINTS" {
4980 return Err(syn::Error::new(
4981 kw_cn.span(),
4982 "expected `const CONSTRAINTS: &'static [ConstraintRef] = ...`",
4983 ));
4984 }
4985 body.parse::<Token![:]>()?;
4986 let _ty: syn::Type = body.parse()?;
4987 body.parse::<Token![=]>()?;
4988 let impl_constraints: syn::Expr = body.parse()?;
4989 body.parse::<Token![;]>()?;
4990
4991 let impl_cycle_size: Option<syn::Expr> = if body.peek(Token![const]) {
4994 body.parse::<Token![const]>()?;
4995 let kw_cs: Ident = body.parse()?;
4996 if kw_cs != "CYCLE_SIZE" {
4997 return Err(syn::Error::new(
4998 kw_cs.span(),
4999 "expected `const CYCLE_SIZE: u64 = ...` (the only optional const recognised by output_shape! is CYCLE_SIZE per ADR-032)",
5000 ));
5001 }
5002 body.parse::<Token![:]>()?;
5003 let _ty: syn::Type = body.parse()?;
5004 body.parse::<Token![=]>()?;
5005 let expr: syn::Expr = body.parse()?;
5006 body.parse::<Token![;]>()?;
5007 Some(expr)
5008 } else {
5009 None
5010 };
5011
5012 Ok(Self {
5013 struct_vis,
5014 struct_name,
5015 impl_iri,
5016 impl_site_count,
5017 impl_constraints,
5018 impl_cycle_size,
5019 })
5020 }
5021}
5022
5023#[proc_macro]
5030pub fn output_shape(input: TokenStream) -> TokenStream {
5031 let parsed = parse_macro_input!(input as OutputShapeInput);
5032 let OutputShapeInput {
5033 struct_vis,
5034 struct_name,
5035 impl_iri,
5036 impl_site_count,
5037 impl_constraints,
5038 impl_cycle_size,
5039 } = parsed;
5040
5041 let cycle_size_tokens = match impl_cycle_size {
5044 Some(expr) => quote! { #expr },
5045 None => quote! {
5046 ::uor_foundation::pipeline::cycle_size_power(256, #impl_site_count)
5047 },
5048 };
5049
5050 let expansion = quote! {
5051 #struct_vis struct #struct_name;
5053
5054 impl ::uor_foundation::pipeline::ConstrainedTypeShape for #struct_name {
5055 const IRI: &'static str = #impl_iri;
5056 const SITE_COUNT: usize = #impl_site_count;
5057 const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] =
5058 #impl_constraints;
5059 const CYCLE_SIZE: u64 = #cycle_size_tokens;
5061 }
5062
5063 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #struct_name {}
5065 impl ::uor_foundation::enforcement::GroundedShape for #struct_name {}
5066 impl<'a> ::uor_foundation::pipeline::IntoBindingValue<'a> for #struct_name {
5067 fn as_binding_value<const INLINE_BYTES: usize>(
5068 &self,
5069 ) -> ::uor_foundation::pipeline::TermValue<'a, INLINE_BYTES> {
5070 ::uor_foundation::pipeline::TermValue::empty()
5076 }
5077 }
5078 };
5079
5080 expansion.into()
5081}
5082
5083struct VerbInput {
5118 fn_vis: syn::Visibility,
5119 fn_name: Ident,
5120 input_param: Ident,
5121 input_ty: syn::Type,
5122 output_ty: syn::Type,
5123 body: syn::Block,
5124}
5125
5126impl Parse for VerbInput {
5127 fn parse(input: ParseStream) -> Result<Self> {
5128 let fn_vis: syn::Visibility = input.parse()?;
5129 input.parse::<Token![fn]>()?;
5130 let fn_name: Ident = input.parse()?;
5131 let params;
5132 syn::parenthesized!(params in input);
5133 let input_param: Ident = params.parse()?;
5134 params.parse::<Token![:]>()?;
5135 let input_ty: syn::Type = params.parse()?;
5136 input.parse::<Token![->]>()?;
5137 let output_ty: syn::Type = input.parse()?;
5138 let body: syn::Block = input.parse()?;
5139 Ok(Self {
5140 fn_vis,
5141 fn_name,
5142 input_param,
5143 input_ty,
5144 output_ty,
5145 body,
5146 })
5147 }
5148}
5149
5150#[proc_macro]
5153pub fn verb(input: TokenStream) -> TokenStream {
5154 let parsed = parse_macro_input!(input as VerbInput);
5155 let VerbInput {
5156 fn_vis,
5157 fn_name,
5158 input_param,
5159 input_ty,
5160 output_ty,
5161 body,
5162 } = parsed;
5163
5164 let mut arena: Vec<TermSpec> = Vec::new();
5165 let mut scope = BindingScope {
5176 route_input_ty: Some(input_ty.clone()),
5177 in_route_body: false,
5178 ..BindingScope::default()
5179 };
5180 if let Err(e) = emit_term_for_block(&body, &input_param, &mut arena, &mut scope) {
5181 return e.to_compile_error().into();
5182 }
5183
5184 let self_const_name = format!("VERB_TERMS_{}", to_screaming_snake(&fn_name.to_string()));
5191 for spec in &arena {
5192 if let TermSpec::VerbSplice { fragment_path, .. } = spec {
5193 if fragment_path.to_string().trim() == self_const_name {
5194 return syn::Error::new(
5195 fn_name.span(),
5196 format!(
5197 "verb-closure violation (ADR-024): `{}`'s body references itself directly; the verb-reference graph through non-`recurse` operators must be acyclic. Lift the recursion through `recurse(...)` (G7) instead.",
5198 fn_name
5199 ),
5200 )
5201 .to_compile_error()
5202 .into();
5203 }
5204 }
5205 }
5206
5207 let body_has_verb_splices = arena
5212 .iter()
5213 .any(|s| matches!(s, TermSpec::VerbSplice { .. }));
5214 let verb_fragment_expr = if body_has_verb_splices {
5215 render_const_fn_arena_builder(&arena, "e! { INLINE_BYTES })
5216 } else {
5217 let term_specs = render_arena(&arena);
5218 quote! { &[ #( #term_specs ),* ] }
5219 };
5220
5221 let const_name = Ident::new(
5222 &format!("VERB_TERMS_{}", to_screaming_snake(&fn_name.to_string())),
5223 fn_name.span(),
5224 );
5225 let accessor_name = Ident::new(&format!("{}_term_arena", fn_name), fn_name.span());
5226 let frag_holder = Ident::new(
5233 &format!("__VerbFrag_{}", to_screaming_snake(&fn_name.to_string())),
5234 fn_name.span(),
5235 );
5236
5237 let expansion = quote! {
5238 #[doc(hidden)]
5245 #[allow(non_camel_case_types)]
5246 #fn_vis struct #frag_holder<const INLINE_BYTES: usize>;
5247 #[allow(dead_code)]
5248 impl<const INLINE_BYTES: usize> #frag_holder<INLINE_BYTES> {
5249 #fn_vis const TERMS: &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] =
5250 #verb_fragment_expr;
5251 }
5252
5253 #[allow(non_snake_case, dead_code)]
5256 #fn_vis const fn #const_name<const INLINE_BYTES: usize>(
5257 ) -> &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] {
5258 #frag_holder::<INLINE_BYTES>::TERMS
5259 }
5260
5261 #fn_vis const fn #accessor_name<const INLINE_BYTES: usize>(
5265 ) -> &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] {
5266 #frag_holder::<INLINE_BYTES>::TERMS
5267 }
5268
5269 #[allow(unused_variables, unreachable_code)]
5274 #fn_vis fn #fn_name(#input_param: #input_ty) -> #output_ty {
5275 let _ = #input_param;
5280 unimplemented!(
5281 "verb `{}` body is catamorphism-evaluated by foundation's pipeline; \
5282 callers reach it through the term-tree accessor `{}_term_arena()`, \
5283 not by direct Rust invocation",
5284 stringify!(#fn_name),
5285 stringify!(#fn_name),
5286 )
5287 }
5288 };
5289
5290 expansion.into()
5291}
5292
5293struct UseVerbsInput {
5314 crate_path: syn::Path,
5315 verb_names: Vec<Ident>,
5316}
5317
5318impl Parse for UseVerbsInput {
5319 fn parse(input: ParseStream) -> Result<Self> {
5320 let from_kw: Ident = input.parse()?;
5322 if from_kw != "from" {
5323 return Err(syn::Error::new(
5324 from_kw.span(),
5325 "expected `from <crate_path>`",
5326 ));
5327 }
5328 let crate_path: syn::Path = input.parse()?;
5329
5330 let body;
5332 syn::braced!(body in input);
5333 let mut verb_names: Vec<Ident> = Vec::new();
5334 while !body.is_empty() {
5335 verb_names.push(body.parse()?);
5336 if body.peek(Token![,]) {
5337 body.parse::<Token![,]>()?;
5338 }
5339 }
5340
5341 let _ = input.parse::<Token![;]>();
5343
5344 Ok(Self {
5345 crate_path,
5346 verb_names,
5347 })
5348 }
5349}
5350
5351#[proc_macro]
5353pub fn use_verbs(input: TokenStream) -> TokenStream {
5354 let parsed = parse_macro_input!(input as UseVerbsInput);
5355 let UseVerbsInput {
5356 crate_path,
5357 verb_names,
5358 } = parsed;
5359
5360 let mut imports: Vec<proc_macro2::TokenStream> = Vec::with_capacity(verb_names.len() * 3);
5361 for name in &verb_names {
5362 let arena_name = Ident::new(&format!("{}_term_arena", name), name.span());
5363 let const_name = Ident::new(
5364 &format!("VERB_TERMS_{}", to_screaming_snake(&name.to_string())),
5365 name.span(),
5366 );
5367 imports.push(quote! { pub use #crate_path::#name; });
5368 imports.push(quote! { pub use #crate_path::#arena_name; });
5369 imports.push(quote! { pub use #crate_path::#const_name; });
5370 }
5371
5372 let expansion = quote! {
5373 #( #imports )*
5374 };
5375
5376 expansion.into()
5377}
5378
5379struct RegisterShapeInput {
5414 registry_name: Ident,
5415 shapes: Vec<syn::Type>,
5416}
5417
5418impl Parse for RegisterShapeInput {
5419 fn parse(input: ParseStream) -> Result<Self> {
5420 let registry_name: Ident = input.parse()?;
5421 input.parse::<Token![,]>()?;
5422 let mut shapes: Vec<syn::Type> = Vec::new();
5423 let first: syn::Type = input.parse()?;
5424 shapes.push(first);
5425 while input.peek(Token![,]) {
5426 input.parse::<Token![,]>()?;
5427 if input.is_empty() {
5428 break;
5429 }
5430 let next: syn::Type = input.parse()?;
5431 shapes.push(next);
5432 }
5433 if shapes.is_empty() {
5434 return Err(syn::Error::new(
5435 registry_name.span(),
5436 "register_shape! requires at least one shape after the registry name",
5437 ));
5438 }
5439 Ok(Self {
5440 registry_name,
5441 shapes,
5442 })
5443 }
5444}
5445
5446#[proc_macro]
5451pub fn register_shape(input: TokenStream) -> TokenStream {
5452 let parsed = parse_macro_input!(input as RegisterShapeInput);
5453 let RegisterShapeInput {
5454 registry_name,
5455 shapes,
5456 } = parsed;
5457
5458 let entries: Vec<proc_macro2::TokenStream> = shapes
5459 .iter()
5460 .map(|shape| {
5461 quote! {
5462 ::uor_foundation::pipeline::shape_iri_registry::RegisteredShape {
5463 iri: <#shape as ::uor_foundation::pipeline::ConstrainedTypeShape>::IRI,
5464 site_count: <#shape as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT,
5465 constraints: <#shape as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS,
5466 cycle_size: <#shape as ::uor_foundation::pipeline::ConstrainedTypeShape>::CYCLE_SIZE,
5467 }
5468 }
5469 })
5470 .collect();
5471
5472 let registry_const = Ident::new(
5473 &format!("{}_SHAPES", to_screaming_snake(®istry_name.to_string())),
5474 registry_name.span(),
5475 );
5476
5477 let expansion = quote! {
5478 #[derive(::core::fmt::Debug, ::core::clone::Clone, ::core::marker::Copy, ::core::default::Default)]
5483 pub struct #registry_name;
5484
5485 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #registry_name {}
5486
5487 pub const #registry_const:
5489 &[::uor_foundation::pipeline::shape_iri_registry::RegisteredShape] = &[
5490 #( #entries ),*
5491 ];
5492
5493 impl ::uor_foundation::pipeline::shape_iri_registry::ShapeRegistryProvider for #registry_name {
5494 const REGISTRY:
5495 &'static [::uor_foundation::pipeline::shape_iri_registry::RegisteredShape] =
5496 #registry_const;
5497 }
5498 };
5499
5500 expansion.into()
5501}
5502
5503fn lexically_earlier(a: &Ident, b: &Ident) -> String {
5510 let a_s = a.to_string();
5511 let b_s = b.to_string();
5512 if a_s.as_str() <= b_s.as_str() {
5513 a_s
5514 } else {
5515 b_s
5516 }
5517}
5518
5519fn lexically_later(a: &Ident, b: &Ident) -> String {
5520 let a_s = a.to_string();
5521 let b_s = b.to_string();
5522 if a_s.as_str() > b_s.as_str() {
5523 a_s
5524 } else {
5525 b_s
5526 }
5527}
5528
5529fn canonical_operand_pair(a: &Ident, b: &Ident) -> (Ident, Ident) {
5534 let a_s = a.to_string();
5535 let b_s = b.to_string();
5536 if a_s.as_str() <= b_s.as_str() {
5537 (a.clone(), b.clone())
5538 } else {
5539 (b.clone(), a.clone())
5540 }
5541}
5542
5543fn type_token_string(ty: &syn::Type) -> String {
5550 quote::ToTokens::to_token_stream(ty).to_string()
5553}
5554
5555fn lexically_earlier_ty(a: &syn::Type, b: &syn::Type) -> String {
5556 let a_s = type_token_string(a);
5557 let b_s = type_token_string(b);
5558 if a_s.as_str() <= b_s.as_str() {
5559 a_s
5560 } else {
5561 b_s
5562 }
5563}
5564
5565fn lexically_later_ty(a: &syn::Type, b: &syn::Type) -> String {
5566 let a_s = type_token_string(a);
5567 let b_s = type_token_string(b);
5568 if a_s.as_str() > b_s.as_str() {
5569 a_s
5570 } else {
5571 b_s
5572 }
5573}
5574
5575fn canonical_operand_pair_ty(a: &syn::Type, b: &syn::Type) -> (syn::Type, syn::Type) {
5576 let a_s = type_token_string(a);
5577 let b_s = type_token_string(b);
5578 if a_s.as_str() <= b_s.as_str() {
5579 (a.clone(), b.clone())
5580 } else {
5581 (b.clone(), a.clone())
5582 }
5583}
5584
5585fn format_ident_suffix(base: &Ident, suffix: &str) -> Ident {
5592 let upper_base = to_screaming_snake(&base.to_string());
5593 let joined = format!("{upper_base}{suffix}");
5594 Ident::new(&joined, base.span())
5595}
5596
5597fn camel_to_snake(s: &str) -> String {
5603 to_screaming_snake(s).to_ascii_lowercase()
5604}
5605
5606fn to_screaming_snake(s: &str) -> String {
5607 let mut out = String::with_capacity(s.len() + 4);
5608 let chars: Vec<char> = s.chars().collect();
5609 for (i, ch) in chars.iter().enumerate() {
5610 if *ch == '_' {
5611 if !out.ends_with('_') && !out.is_empty() {
5612 out.push('_');
5613 }
5614 continue;
5615 }
5616 if ch.is_ascii_uppercase() {
5617 let prev_lower_or_digit = i > 0
5622 && chars[i - 1] != '_'
5623 && (chars[i - 1].is_ascii_lowercase() || chars[i - 1].is_ascii_digit());
5624 let run_ending = i > 0
5625 && chars[i - 1].is_ascii_uppercase()
5626 && i + 1 < chars.len()
5627 && chars[i + 1].is_ascii_lowercase();
5628 if (prev_lower_or_digit || run_ending) && !out.ends_with('_') && !out.is_empty() {
5629 out.push('_');
5630 }
5631 out.push(*ch);
5632 } else {
5633 out.push(ch.to_ascii_uppercase());
5634 }
5635 }
5636 out
5637}
5638
5639struct AxisInput {
5673 trait_decl: syn::ItemTrait,
5674 body: Option<syn::ExprClosure>,
5686}
5687
5688impl Parse for AxisInput {
5689 fn parse(input: ParseStream) -> Result<Self> {
5690 let trait_decl: syn::ItemTrait = input.parse()?;
5691 let mut body = None;
5692 if !input.is_empty() {
5693 let body_kw: Ident = input.parse()?;
5695 if body_kw != "body" {
5696 return Err(syn::Error::new(
5697 body_kw.span(),
5698 "expected `body = |input| { … };` clause after the axis trait declaration (ADR-055)",
5699 ));
5700 }
5701 input.parse::<Token![=]>()?;
5702 let closure: syn::ExprClosure = input.parse()?;
5703 input.parse::<Token![;]>()?;
5704 body = Some(closure);
5705 }
5706 Ok(Self { trait_decl, body })
5707 }
5708}
5709
5710#[proc_macro]
5712pub fn axis(input: TokenStream) -> TokenStream {
5713 let parsed = parse_macro_input!(input as AxisInput);
5714 let mut trait_decl = parsed.trait_decl;
5715 trait_decl.supertraits = syn::punctuated::Punctuated::new();
5722 trait_decl.colon_token = None;
5723 let trait_name = trait_decl.ident.clone();
5724 let body_clause = parsed.body;
5725 let mut kernel_idents: Vec<Ident> = Vec::new();
5727 for item in &trait_decl.items {
5728 if let syn::TraitItem::Fn(fn_item) = item {
5729 kernel_idents.push(fn_item.sig.ident.clone());
5730 }
5731 }
5732 let mut kernel_consts: Vec<proc_macro2::TokenStream> = Vec::new();
5734 for (i, ident) in kernel_idents.iter().enumerate() {
5735 let upper_name = ident.to_string().to_ascii_uppercase();
5736 let const_name = Ident::new(&format!("KERNEL_{upper_name}"), ident.span());
5737 let id = i as u32;
5738 kernel_consts.push(quote! {
5739 pub const #const_name: u32 = #id;
5740 });
5741 }
5742 let dollar = proc_macro2::Punct::new('$', proc_macro2::Spacing::Joint);
5749 let struct_ident_meta: proc_macro2::TokenStream = quote!(#dollar struct_ident);
5750 let struct_ty_meta: proc_macro2::TokenStream = quote!(#dollar struct_ty);
5751 let dispatch_arms: Vec<proc_macro2::TokenStream> = kernel_idents
5752 .iter()
5753 .enumerate()
5754 .map(|(i, ident)| {
5755 let id = i as u32;
5756 quote! {
5757 #id => <#struct_ident_meta as #trait_name>::#ident(input, out),
5758 }
5759 })
5760 .collect();
5761 let dispatch_arms_generic: Vec<proc_macro2::TokenStream> = kernel_idents
5762 .iter()
5763 .enumerate()
5764 .map(|(i, ident)| {
5765 let id = i as u32;
5766 quote! {
5767 #id => <#struct_ty_meta as #trait_name>::#ident(input, out),
5768 }
5769 })
5770 .collect();
5771
5772 let trait_name_lower = camel_to_snake(&trait_name.to_string());
5790 let companion_macro_ident = Ident::new(
5791 &format!("axis_extension_impl_for_{trait_name_lower}"),
5792 trait_name.span(),
5793 );
5794
5795 let body_arena_holder = Ident::new(
5813 &format!("__AxisBody_{}", to_screaming_snake(&trait_name.to_string())),
5814 trait_name.span(),
5815 );
5816 let (body_arena_const, body_arena_expr): (proc_macro2::TokenStream, proc_macro2::TokenStream) =
5817 if let Some(closure) = body_clause {
5818 if closure.inputs.len() != 1 {
5820 let err = syn::Error::new_spanned(
5821 &closure,
5822 "ADR-055 body clause: closure must have exactly one input named `input`",
5823 );
5824 return err.to_compile_error().into();
5825 }
5826 let input_pat = &closure.inputs[0];
5827 let input_ident: Ident = match input_pat {
5828 syn::Pat::Ident(pi) => pi.ident.clone(),
5829 other => {
5830 let err = syn::Error::new_spanned(
5831 other,
5832 "ADR-055 body clause: closure input must be a bare identifier `input`",
5833 );
5834 return err.to_compile_error().into();
5835 }
5836 };
5837 let body_block: syn::Block = match closure.body.as_ref() {
5841 syn::Expr::Block(eb) => eb.block.clone(),
5842 other => {
5843 let span = quote::ToTokens::to_token_stream(other);
5844 let parsed: syn::Block = match syn::parse2(quote! { { #span } }) {
5845 Ok(b) => b,
5846 Err(e) => return e.to_compile_error().into(),
5847 };
5848 parsed
5849 }
5850 };
5851 let mut arena: Vec<TermSpec> = Vec::new();
5852 let mut scope = BindingScope::default();
5853 if let Err(e) = emit_term_for_block(&body_block, &input_ident, &mut arena, &mut scope) {
5854 return e.to_compile_error().into();
5855 }
5856 let has_verb_splices = arena
5857 .iter()
5858 .any(|s| matches!(s, TermSpec::VerbSplice { .. }));
5859 let arena_expr = if has_verb_splices {
5860 render_const_fn_arena_builder(&arena, "e! { INLINE_BYTES })
5861 } else {
5862 let term_specs = render_arena(&arena);
5863 quote! { &[ #( #term_specs ),* ] }
5864 };
5865 (
5866 quote! {
5867 #[doc(hidden)]
5868 #[allow(non_camel_case_types)]
5869 struct #body_arena_holder<const INLINE_BYTES: usize>;
5870 #[allow(dead_code)]
5871 impl<const INLINE_BYTES: usize> #body_arena_holder<INLINE_BYTES> {
5872 const TERMS: &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] =
5873 #arena_expr;
5874 }
5875 },
5876 quote! { #body_arena_holder::<INLINE_BYTES>::TERMS },
5877 )
5878 } else {
5879 (quote! {}, quote! { &[] })
5881 };
5882 let expansion = quote! {
5883 #trait_decl
5884
5885 #(#kernel_consts)*
5886
5887 #body_arena_const
5891
5892 #[macro_export]
5902 macro_rules! #companion_macro_ident {
5903 ($struct_ident:ident) => {
5905 impl ::uor_foundation::pipeline::__sdk_seal::Sealed for $struct_ident {}
5911 impl<const INLINE_BYTES: usize> ::uor_foundation::pipeline::SubstrateTermBody<INLINE_BYTES> for $struct_ident {
5912 fn body_arena() -> &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] {
5913 #body_arena_expr
5914 }
5915 }
5916 impl<const INLINE_BYTES: usize, const FP_MAX: usize> ::uor_foundation::pipeline::AxisExtension<INLINE_BYTES, FP_MAX> for $struct_ident {
5917 const AXIS_ADDRESS: &'static str =
5918 <$struct_ident as #trait_name>::AXIS_ADDRESS;
5919 const MAX_OUTPUT_BYTES: usize =
5920 <$struct_ident as #trait_name>::MAX_OUTPUT_BYTES;
5921 fn dispatch_kernel(
5922 kernel_id: u32,
5923 input: &[u8],
5924 out: &mut [u8],
5925 ) -> ::core::result::Result<
5926 usize,
5927 ::uor_foundation::enforcement::ShapeViolation,
5928 > {
5929 match kernel_id {
5930 #(#dispatch_arms)*
5931 _ => Err(::uor_foundation::enforcement::ShapeViolation {
5932 shape_iri:
5933 "https://uor.foundation/axis/AxisExtensionShape",
5934 constraint_iri:
5935 "https://uor.foundation/axis/AxisExtensionShape/kernelId",
5936 property_iri:
5937 "https://uor.foundation/axis/kernelId",
5938 expected_range:
5939 "https://uor.foundation/axis/RecognisedKernelId",
5940 min_count: 0,
5941 max_count: 0,
5942 kind: ::uor_foundation::ViolationKind::ValueCheck,
5943 }),
5944 }
5945 }
5946 }
5947 };
5948 (@generic $struct_ty:ty, [$($generic_params:tt)*] $(, where [$($where_clauses:tt)*])?) => {
5961 impl<$($generic_params)*> ::uor_foundation::pipeline::__sdk_seal::Sealed for $struct_ty
5962 $(where $($where_clauses)*)?
5963 {}
5964 impl<const INLINE_BYTES: usize, $($generic_params)*> ::uor_foundation::pipeline::SubstrateTermBody<INLINE_BYTES> for $struct_ty
5965 $(where $($where_clauses)*)?
5966 {
5967 fn body_arena() -> &'static [::uor_foundation::enforcement::Term<'static, INLINE_BYTES>] {
5968 #body_arena_expr
5969 }
5970 }
5971 impl<const INLINE_BYTES: usize, const FP_MAX: usize, $($generic_params)*> ::uor_foundation::pipeline::AxisExtension<INLINE_BYTES, FP_MAX> for $struct_ty
5972 $(where $($where_clauses)*)?
5973 {
5974 const AXIS_ADDRESS: &'static str =
5975 <$struct_ty as #trait_name>::AXIS_ADDRESS;
5976 const MAX_OUTPUT_BYTES: usize =
5977 <$struct_ty as #trait_name>::MAX_OUTPUT_BYTES;
5978 fn dispatch_kernel(
5979 kernel_id: u32,
5980 input: &[u8],
5981 out: &mut [u8],
5982 ) -> ::core::result::Result<
5983 usize,
5984 ::uor_foundation::enforcement::ShapeViolation,
5985 > {
5986 match kernel_id {
5987 #(#dispatch_arms_generic)*
5988 _ => Err(::uor_foundation::enforcement::ShapeViolation {
5989 shape_iri:
5990 "https://uor.foundation/axis/AxisExtensionShape",
5991 constraint_iri:
5992 "https://uor.foundation/axis/AxisExtensionShape/kernelId",
5993 property_iri:
5994 "https://uor.foundation/axis/kernelId",
5995 expected_range:
5996 "https://uor.foundation/axis/RecognisedKernelId",
5997 min_count: 0,
5998 max_count: 0,
5999 kind: ::uor_foundation::ViolationKind::ValueCheck,
6000 }),
6001 }
6002 }
6003 }
6004 };
6005 }
6006 };
6007 expansion.into()
6008}
6009
6010struct ResolverInput {
6048 struct_vis: syn::Visibility,
6049 struct_name: Ident,
6050 hasher_param: Ident,
6051 fields: Vec<(Ident, syn::Type)>,
6052 shape_registry: Option<syn::Type>,
6060}
6061
6062impl Parse for ResolverInput {
6063 fn parse(input: ParseStream) -> Result<Self> {
6064 let struct_vis: syn::Visibility = input.parse()?;
6065 input.parse::<Token![struct]>()?;
6066 let struct_name: Ident = input.parse()?;
6067 input.parse::<Token![<]>()?;
6072 let hasher_param: Ident = input.parse()?;
6073 if input.peek(Token![:]) {
6075 input.parse::<Token![:]>()?;
6076 let _: syn::Type = input.parse()?;
6077 }
6078 input.parse::<Token![>]>()?;
6079 let body;
6080 syn::braced!(body in input);
6081 let mut fields: Vec<(Ident, syn::Type)> = Vec::new();
6082 let mut shape_registry: Option<syn::Type> = None;
6083 while !body.is_empty() {
6084 if body.peek(Token![pub]) {
6086 let _: syn::Visibility = body.parse()?;
6087 }
6088 let field_name: Ident = body.parse()?;
6089 body.parse::<Token![:]>()?;
6090 let field_ty: syn::Type = body.parse()?;
6091 if field_name == "shape_registry" {
6095 if shape_registry.is_some() {
6096 return Err(syn::Error::new_spanned(
6097 &field_name,
6098 "`resolver!` shape_registry clause specified twice",
6099 ));
6100 }
6101 shape_registry = Some(field_ty);
6102 } else {
6103 fields.push((field_name, field_ty));
6104 }
6105 if body.peek(Token![,]) {
6106 body.parse::<Token![,]>()?;
6107 }
6108 }
6109 Ok(Self {
6110 struct_vis,
6111 struct_name,
6112 hasher_param,
6113 fields,
6114 shape_registry,
6115 })
6116 }
6117}
6118
6119const RESOLVER_FIELD_TABLE: &[(&str, &str, &str, &str, &str)] = &[
6120 (
6122 "nerve",
6123 "Nerve",
6124 "NerveResolver",
6125 "HasNerveResolver",
6126 "nerve_resolver",
6127 ),
6128 (
6129 "chain_complex",
6130 "ChainComplex",
6131 "ChainComplexResolver",
6132 "HasChainComplexResolver",
6133 "chain_complex_resolver",
6134 ),
6135 (
6136 "homology_groups",
6137 "HomologyGroup",
6138 "HomologyGroupResolver",
6139 "HasHomologyGroupResolver",
6140 "homology_group_resolver",
6141 ),
6142 (
6143 "cochain_complex",
6144 "CochainComplex",
6145 "CochainComplexResolver",
6146 "HasCochainComplexResolver",
6147 "cochain_complex_resolver",
6148 ),
6149 (
6150 "cohomology_groups",
6151 "CohomologyGroup",
6152 "CohomologyGroupResolver",
6153 "HasCohomologyGroupResolver",
6154 "cohomology_group_resolver",
6155 ),
6156 (
6157 "postnikov",
6158 "Postnikov",
6159 "PostnikovResolver",
6160 "HasPostnikovResolver",
6161 "postnikov_resolver",
6162 ),
6163 (
6164 "homotopy_groups",
6165 "HomotopyGroup",
6166 "HomotopyGroupResolver",
6167 "HasHomotopyGroupResolver",
6168 "homotopy_group_resolver",
6169 ),
6170 (
6171 "k_invariants",
6172 "KInvariant",
6173 "KInvariantResolver",
6174 "HasKInvariantResolver",
6175 "k_invariant_resolver",
6176 ),
6177];
6178
6179#[proc_macro]
6193pub fn resolver(input: TokenStream) -> TokenStream {
6194 let parsed = parse_macro_input!(input as ResolverInput);
6195 let ResolverInput {
6196 struct_vis,
6197 struct_name,
6198 hasher_param,
6199 fields,
6200 shape_registry,
6201 } = parsed;
6202 let recognised: Vec<&str> = RESOLVER_FIELD_TABLE.iter().map(|t| t.0).collect();
6204 for (name, _) in &fields {
6205 let name_str = name.to_string();
6206 if !recognised.contains(&name_str.as_str()) {
6207 let recognised_csv = recognised.join(", ");
6208 return syn::Error::new_spanned(
6209 name,
6210 format!(
6211 "closure violation: `resolver!` field `{name_str}` is not a recognised resolver category. Recognised: {recognised_csv}"
6212 ),
6213 )
6214 .to_compile_error()
6215 .into();
6216 }
6217 }
6218 let struct_fields: Vec<proc_macro2::TokenStream> = fields
6220 .iter()
6221 .map(|(name, ty)| quote::quote! { pub #name: #ty, })
6222 .collect();
6223 let category_idents: Vec<proc_macro2::TokenStream> = fields
6225 .iter()
6226 .map(|(name, _)| {
6227 let entry = RESOLVER_FIELD_TABLE
6231 .iter()
6232 .find(|t| t.0 == name.to_string().as_str())
6233 .unwrap_or(&RESOLVER_FIELD_TABLE[0]);
6234 let cat = syn::Ident::new(entry.1, name.span());
6235 quote::quote! { ::uor_foundation::pipeline::ResolverCategory::#cat }
6236 })
6237 .collect();
6238 let arity = fields.len();
6239 let has_impls: Vec<proc_macro2::TokenStream> = RESOLVER_FIELD_TABLE
6249 .iter()
6250 .map(|entry| {
6251 let cat_field = entry.0;
6252 let resolver_trait = syn::Ident::new(entry.2, struct_name.span());
6253 let marker = syn::Ident::new(entry.3, struct_name.span());
6254 let accessor = syn::Ident::new(entry.4, struct_name.span());
6255 if let Some((field_name, field_ty)) = fields
6256 .iter()
6257 .find(|(n, _)| *n == cat_field)
6258 {
6259 quote::quote! {
6265 impl<const INLINE_BYTES: usize, #hasher_param: ::uor_foundation::enforcement::Hasher>
6266 ::uor_foundation::pipeline::#marker<INLINE_BYTES, #hasher_param>
6267 for #struct_name<#hasher_param>
6268 where
6269 #field_ty: ::uor_foundation::pipeline::#resolver_trait<INLINE_BYTES, #hasher_param>,
6270 {
6271 fn #accessor(&self) -> &dyn ::uor_foundation::pipeline::#resolver_trait<INLINE_BYTES, #hasher_param> {
6272 &self.#field_name
6273 }
6274 }
6275 }
6276 } else {
6277 quote::quote! {
6283 impl<const INLINE_BYTES: usize, #hasher_param: ::uor_foundation::enforcement::Hasher>
6284 ::uor_foundation::pipeline::#marker<INLINE_BYTES, #hasher_param>
6285 for #struct_name<#hasher_param>
6286 {
6287 fn #accessor(&self) -> &dyn ::uor_foundation::pipeline::#resolver_trait<INLINE_BYTES, #hasher_param> {
6288 &::uor_foundation::pipeline::NullResolverTuple
6289 }
6290 }
6291 }
6292 }
6293 })
6294 .collect();
6295 let default_field_inits: Vec<proc_macro2::TokenStream> = fields
6302 .iter()
6303 .map(|(name, _)| quote::quote! { #name: ::core::default::Default::default(), })
6304 .collect();
6305 let default_where_clauses: Vec<proc_macro2::TokenStream> = fields
6306 .iter()
6307 .map(|(_, ty)| quote::quote! { #ty: ::core::default::Default, })
6308 .collect();
6309 let shape_registry_ty: proc_macro2::TokenStream = match shape_registry {
6313 Some(ty) => quote::quote! { #ty },
6314 None => quote::quote! {
6315 ::uor_foundation::pipeline::shape_iri_registry::EmptyShapeRegistry
6316 },
6317 };
6318 let expansion = quote::quote! {
6319 #struct_vis struct #struct_name<#hasher_param: ::uor_foundation::enforcement::Hasher> {
6320 #(#struct_fields)*
6321 #[doc(hidden)]
6322 pub _phantom: ::core::marker::PhantomData<#hasher_param>,
6323 }
6324
6325 impl<#hasher_param: ::uor_foundation::enforcement::Hasher>
6326 ::uor_foundation::pipeline::__sdk_seal::Sealed
6327 for #struct_name<#hasher_param> {}
6328
6329 impl<#hasher_param: ::uor_foundation::enforcement::Hasher>
6330 ::uor_foundation::pipeline::ResolverTuple
6331 for #struct_name<#hasher_param>
6332 {
6333 const ARITY: usize = #arity;
6334 const CATEGORIES: &'static [::uor_foundation::pipeline::ResolverCategory] =
6335 &[#(#category_idents),*];
6336 type ShapeRegistry = #shape_registry_ty;
6341 }
6342
6343 impl<#hasher_param: ::uor_foundation::enforcement::Hasher>
6351 ::core::default::Default
6352 for #struct_name<#hasher_param>
6353 where
6354 #(#default_where_clauses)*
6355 {
6356 fn default() -> Self {
6357 Self {
6358 #(#default_field_inits)*
6359 _phantom: ::core::marker::PhantomData,
6360 }
6361 }
6362 }
6363
6364 #(#has_impls)*
6365 };
6366 expansion.into()
6367}