#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
missing_docs,
clippy::missing_errors_doc
)]
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, Ident, Result, Token};
struct ShapeArgs {
name: Ident,
left: Ident,
right: Ident,
}
impl Parse for ShapeArgs {
fn parse(input: ParseStream) -> Result<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let left: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let right: Ident = input.parse()?;
let _ = input.parse::<Token![,]>();
Ok(Self { name, left, right })
}
}
#[proc_macro]
pub fn product_shape(input: TokenStream) -> TokenStream {
let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
let iri = format!(
"urn:uor:product:{}:{}",
lexically_earlier(&left, &right),
lexically_later(&left, &right)
);
let (l, r) = canonical_operand_pair(&left, &right);
let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
let expansion = quote! {
pub struct #name;
const #raw_const:
[::uor_foundation::pipeline::ConstraintRef;
2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
= ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>();
const #len_const: usize =
::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>();
impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
const IRI: &'static str = #iri;
const SITE_BUDGET: usize =
<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
+ <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
const SITE_COUNT: usize =
<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
+ <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
match buf.split_at_checked(#len_const) {
Some((head, _tail)) => head,
None => &[],
}
};
}
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
impl ::uor_foundation::pipeline::IntoBindingValue for #name {
const MAX_BYTES: usize = 0;
fn into_binding_bytes(
&self,
_out: &mut [u8],
) -> ::core::result::Result<usize, ::uor_foundation::enforcement::ShapeViolation> {
Ok(0)
}
}
impl #name {
#[allow(clippy::too_many_arguments)]
pub fn mint_product_witness(
witt_bits: u16,
left_fingerprint: ::uor_foundation::ContentFingerprint,
right_fingerprint: ::uor_foundation::ContentFingerprint,
left_euler: i32,
right_euler: i32,
left_entropy_nats_bits: u64,
right_entropy_nats_bits: u64,
combined_euler: i32,
combined_entropy_nats_bits: u64,
combined_fingerprint: ::uor_foundation::ContentFingerprint,
) -> ::core::result::Result<
::uor_foundation::PartitionProductWitness,
::uor_foundation::enforcement::GenericImpossibilityWitness,
> {
use ::uor_foundation::pipeline::ConstrainedTypeShape;
let inputs = ::uor_foundation::PartitionProductMintInputs {
witt_bits,
left_fingerprint,
right_fingerprint,
left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
left_total_site_count: <#l as ConstrainedTypeShape>::SITE_COUNT as u16,
right_total_site_count: <#r as ConstrainedTypeShape>::SITE_COUNT as u16,
left_euler,
right_euler,
left_entropy_nats_bits,
right_entropy_nats_bits,
combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
combined_euler,
combined_entropy_nats_bits,
combined_fingerprint,
};
<::uor_foundation::PartitionProductWitness
as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
}
}
};
expansion.into()
}
#[proc_macro]
pub fn coproduct_shape(input: TokenStream) -> TokenStream {
let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
let iri = format!(
"urn:uor:coproduct:{}:{}",
lexically_earlier(&left, &right),
lexically_later(&left, &right)
);
let (l, r) = canonical_operand_pair(&left, &right);
let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
let tag_coeffs_l = format_ident_suffix(&name, "__TAG_COEFFS_L");
let tag_coeffs_r = format_ident_suffix(&name, "__TAG_COEFFS_R");
let tag_coeff_count = format_ident_suffix(&name, "__TAG_COEFF_COUNT");
let expansion = quote! {
pub struct #name;
const #tag_coeffs_l: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = {
let mut out = [0i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS];
let tag_site = {
let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
if a > b { a } else { b }
};
if tag_site < ::uor_foundation::pipeline::AFFINE_MAX_COEFFS {
out[tag_site] = 1;
}
out
};
const #tag_coeffs_r: [i64; ::uor_foundation::pipeline::AFFINE_MAX_COEFFS] = #tag_coeffs_l;
const #tag_coeff_count: u32 = {
let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
let tag_site = if a > b { a } else { b };
(tag_site as u32).saturating_add(1)
};
const #raw_const:
[::uor_foundation::pipeline::ConstraintRef;
2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2]
= {
let mut out =
[::uor_foundation::pipeline::ConstraintRef::Site { position: u32::MAX };
2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP + 2];
let left_arr = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
let right_arr = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS;
let mut i = 0;
while i < left_arr.len() {
out[i] = left_arr[i];
i += 1;
}
out[i] = ::uor_foundation::pipeline::ConstraintRef::Affine {
coefficients: #tag_coeffs_l,
coefficient_count: #tag_coeff_count,
bias: 0,
};
let left_boundary = i + 1;
let mut j = 0;
while j < right_arr.len() {
out[left_boundary + j] = right_arr[j];
j += 1;
}
out[left_boundary + right_arr.len()] = ::uor_foundation::pipeline::ConstraintRef::Affine {
coefficients: #tag_coeffs_r,
coefficient_count: #tag_coeff_count,
bias: -1,
};
out
};
const #len_const: usize =
<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
+ <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::CONSTRAINTS.len()
+ 2;
impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
const IRI: &'static str = #iri;
const SITE_BUDGET: usize = {
let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
if a > b { a } else { b }
};
const SITE_COUNT: usize = {
let a = <#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
let b = <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
(if a > b { a } else { b }) + 1
};
const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
match buf.split_at_checked(#len_const) {
Some((head, _tail)) => head,
None => &[],
}
};
}
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
impl ::uor_foundation::pipeline::IntoBindingValue for #name {
const MAX_BYTES: usize = 0;
fn into_binding_bytes(
&self,
_out: &mut [u8],
) -> ::core::result::Result<usize, ::uor_foundation::enforcement::ShapeViolation> {
Ok(0)
}
}
impl #name {
#[allow(clippy::too_many_arguments)]
pub fn mint_coproduct_witness(
witt_bits: u16,
left_fingerprint: ::uor_foundation::ContentFingerprint,
right_fingerprint: ::uor_foundation::ContentFingerprint,
left_euler: i32,
right_euler: i32,
left_entropy_nats_bits: u64,
right_entropy_nats_bits: u64,
left_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
right_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
combined_euler: i32,
combined_entropy_nats_bits: u64,
combined_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
combined_fingerprint: ::uor_foundation::ContentFingerprint,
) -> ::core::result::Result<
::uor_foundation::PartitionCoproductWitness,
::uor_foundation::enforcement::GenericImpossibilityWitness,
> {
use ::uor_foundation::pipeline::ConstrainedTypeShape;
let left_total_site_count = <#l as ConstrainedTypeShape>::SITE_COUNT as u16;
let right_total_site_count = <#r as ConstrainedTypeShape>::SITE_COUNT as u16;
let tag_site = if left_total_site_count > right_total_site_count {
left_total_site_count
} else {
right_total_site_count
};
let left_constraint_count =
<#l as ConstrainedTypeShape>::CONSTRAINTS.len() + 1;
let inputs = ::uor_foundation::PartitionCoproductMintInputs {
witt_bits,
left_fingerprint,
right_fingerprint,
left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
left_total_site_count,
right_total_site_count,
left_euler,
right_euler,
left_entropy_nats_bits,
right_entropy_nats_bits,
left_betti,
right_betti,
combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
combined_euler,
combined_entropy_nats_bits,
combined_betti,
combined_fingerprint,
combined_constraints: <#name as ConstrainedTypeShape>::CONSTRAINTS,
left_constraint_count,
tag_site,
};
<::uor_foundation::PartitionCoproductWitness
as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
}
}
};
expansion.into()
}
#[proc_macro]
pub fn cartesian_product_shape(input: TokenStream) -> TokenStream {
let ShapeArgs { name, left, right } = parse_macro_input!(input as ShapeArgs);
let iri = format!(
"urn:uor:cartesian:{}:{}",
lexically_earlier(&left, &right),
lexically_later(&left, &right)
);
let (l, r) = canonical_operand_pair(&left, &right);
let raw_const = format_ident_suffix(&name, "__CONSTRAINTS_RAW");
let len_const = format_ident_suffix(&name, "__CONSTRAINTS_LEN");
let expansion = quote! {
pub struct #name;
const #raw_const:
[::uor_foundation::pipeline::ConstraintRef;
2 * ::uor_foundation::enforcement::NERVE_CONSTRAINTS_CAP]
= ::uor_foundation::pipeline::sdk_concat_product_constraints::<#l, #r>();
const #len_const: usize =
::uor_foundation::pipeline::sdk_product_constraints_len::<#l, #r>();
impl ::uor_foundation::pipeline::ConstrainedTypeShape for #name {
const IRI: &'static str = #iri;
const SITE_BUDGET: usize =
<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET
+ <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_BUDGET;
const SITE_COUNT: usize =
<#l as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT
+ <#r as ::uor_foundation::pipeline::ConstrainedTypeShape>::SITE_COUNT;
const CONSTRAINTS: &'static [::uor_foundation::pipeline::ConstraintRef] = {
let buf: &'static [::uor_foundation::pipeline::ConstraintRef] = &#raw_const;
match buf.split_at_checked(#len_const) {
Some((head, _tail)) => head,
None => &[],
}
};
}
impl ::uor_foundation::pipeline::CartesianProductShape for #name {
type Left = #l;
type Right = #r;
}
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #name {}
impl ::uor_foundation::pipeline::IntoBindingValue for #name {
const MAX_BYTES: usize = 0;
fn into_binding_bytes(
&self,
_out: &mut [u8],
) -> ::core::result::Result<usize, ::uor_foundation::enforcement::ShapeViolation> {
Ok(0)
}
}
impl #name {
#[allow(clippy::too_many_arguments)]
pub fn mint_cartesian_witness(
witt_bits: u16,
left_fingerprint: ::uor_foundation::ContentFingerprint,
right_fingerprint: ::uor_foundation::ContentFingerprint,
left_euler: i32,
right_euler: i32,
left_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
right_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
left_entropy_nats_bits: u64,
right_entropy_nats_bits: u64,
combined_euler: i32,
combined_betti: [u32; ::uor_foundation::enforcement::MAX_BETTI_DIMENSION],
combined_entropy_nats_bits: u64,
combined_fingerprint: ::uor_foundation::ContentFingerprint,
) -> ::core::result::Result<
::uor_foundation::CartesianProductWitness,
::uor_foundation::enforcement::GenericImpossibilityWitness,
> {
use ::uor_foundation::pipeline::ConstrainedTypeShape;
let inputs = ::uor_foundation::CartesianProductMintInputs {
witt_bits,
left_fingerprint,
right_fingerprint,
left_site_budget: <#l as ConstrainedTypeShape>::SITE_BUDGET as u16,
right_site_budget: <#r as ConstrainedTypeShape>::SITE_BUDGET as u16,
left_total_site_count: <#l as ConstrainedTypeShape>::SITE_COUNT as u16,
right_total_site_count: <#r as ConstrainedTypeShape>::SITE_COUNT as u16,
left_euler,
right_euler,
left_betti,
right_betti,
left_entropy_nats_bits,
right_entropy_nats_bits,
combined_site_budget: <#name as ConstrainedTypeShape>::SITE_BUDGET as u16,
combined_site_count: <#name as ConstrainedTypeShape>::SITE_COUNT as u16,
combined_euler,
combined_betti,
combined_entropy_nats_bits,
combined_fingerprint,
};
<::uor_foundation::CartesianProductWitness
as ::uor_foundation::VerifiedMint>::mint_verified(inputs)
}
}
};
expansion.into()
}
struct PrismModelInput {
model_vis: syn::Visibility,
model_name: Ident,
route_vis: syn::Visibility,
route_name: Ident,
h_ty: syn::Type,
b_ty: syn::Type,
a_ty: syn::Type,
input_ty: syn::Type,
output_ty: syn::Type,
route_input_ident: Ident,
route_body: syn::Block,
}
impl Parse for PrismModelInput {
fn parse(input: ParseStream) -> Result<Self> {
let model_vis: syn::Visibility = input.parse()?;
input.parse::<Token![struct]>()?;
let model_name: Ident = input.parse()?;
input.parse::<Token![;]>()?;
let route_vis: syn::Visibility = input.parse()?;
input.parse::<Token![struct]>()?;
let route_name: Ident = input.parse()?;
input.parse::<Token![;]>()?;
input.parse::<Token![impl]>()?;
let trait_ident: Ident = input.parse()?;
if trait_ident != "PrismModel" {
return Err(syn::Error::new(
trait_ident.span(),
"prism_model! expects an `impl PrismModel<H, B, A> for <Model>` block",
));
}
input.parse::<Token![<]>()?;
let h_ty: syn::Type = input.parse()?;
input.parse::<Token![,]>()?;
let b_ty: syn::Type = input.parse()?;
input.parse::<Token![,]>()?;
let a_ty: syn::Type = input.parse()?;
input.parse::<Token![>]>()?;
input.parse::<Token![for]>()?;
let impl_target: Ident = input.parse()?;
if impl_target != model_name {
return Err(syn::Error::new(
impl_target.span(),
"prism_model!'s `impl PrismModel<…> for <Model>` target must match the declared model struct",
));
}
let body;
syn::braced!(body in input);
body.parse::<Token![type]>()?;
let input_kw: Ident = body.parse()?;
if input_kw != "Input" {
return Err(syn::Error::new(
input_kw.span(),
"expected `type Input = …;`",
));
}
body.parse::<Token![=]>()?;
let input_ty: syn::Type = body.parse()?;
body.parse::<Token![;]>()?;
body.parse::<Token![type]>()?;
let output_kw: Ident = body.parse()?;
if output_kw != "Output" {
return Err(syn::Error::new(
output_kw.span(),
"expected `type Output = …;`",
));
}
body.parse::<Token![=]>()?;
let output_ty: syn::Type = body.parse()?;
body.parse::<Token![;]>()?;
body.parse::<Token![type]>()?;
let route_kw: Ident = body.parse()?;
if route_kw != "Route" {
return Err(syn::Error::new(
route_kw.span(),
"expected `type Route = …;`",
));
}
body.parse::<Token![=]>()?;
let route_ty_ident: Ident = body.parse()?;
if route_ty_ident != route_name {
return Err(syn::Error::new(
route_ty_ident.span(),
"prism_model!'s `type Route = <RouteName>;` must match the declared route struct",
));
}
body.parse::<Token![;]>()?;
body.parse::<Token![fn]>()?;
let fn_kw: Ident = body.parse()?;
if fn_kw != "route" {
return Err(syn::Error::new(
fn_kw.span(),
"expected `fn route(input: Self::Input) -> Self::Output { … }`",
));
}
let params;
syn::parenthesized!(params in body);
let route_input_ident: Ident = params.parse()?;
params.parse::<Token![:]>()?;
let _input_param_ty: syn::Type = params.parse()?;
body.parse::<Token![->]>()?;
let _output_param_ty: syn::Type = body.parse()?;
let route_body: syn::Block = body.parse()?;
Ok(Self {
model_vis,
model_name,
route_vis,
route_name,
h_ty,
b_ty,
a_ty,
input_ty,
output_ty,
route_input_ident,
route_body,
})
}
}
enum TermSpec {
Literal(u64),
Variable,
Application {
operator: proc_macro2::TokenStream,
args_start: u32,
args_len: u32,
},
}
fn emit_term_for_expr(
expr: &syn::Expr,
route_input: &Ident,
arena: &mut Vec<TermSpec>,
) -> Result<usize> {
match expr {
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => {
let value: u64 = int_lit.base10_parse().map_err(|e| {
syn::Error::new(int_lit.span(), format!("integer literal out of u64 range: {e}"))
})?;
let idx = arena.len();
arena.push(TermSpec::Literal(value));
Ok(idx)
}
syn::Expr::Path(path_expr) if path_expr.path.get_ident() == Some(route_input) => {
let idx = arena.len();
arena.push(TermSpec::Variable);
Ok(idx)
}
syn::Expr::Path(path_expr) => Err(syn::Error::new_spanned(
path_expr,
"closure violation: identifier is not a foundation-vocabulary name (only the route's `input` parameter is recognised as a variable reference)",
)),
syn::Expr::Call(call_expr) => emit_term_for_call(call_expr, route_input, arena),
syn::Expr::Block(block_expr) => {
let stmts = &block_expr.block.stmts;
if stmts.len() == 1 {
if let syn::Stmt::Expr(inner, _) = &stmts[0] {
return emit_term_for_expr(inner, route_input, arena);
}
}
Err(syn::Error::new_spanned(
block_expr,
"closure violation: block expressions must contain exactly one expression statement",
))
}
syn::Expr::Paren(paren_expr) => emit_term_for_expr(&paren_expr.expr, route_input, arena),
other => Err(syn::Error::new_spanned(
other,
"closure violation: expression form is not in foundation vocabulary (recognised forms: integer literals, the route's `input` parameter, and lowercase PrimitiveOp function calls — add, sub, mul, xor, and, or, neg, bnot, succ, pred)",
)),
}
}
fn emit_term_for_call(
call: &syn::ExprCall,
route_input: &Ident,
arena: &mut Vec<TermSpec>,
) -> Result<usize> {
let func_ident = match call.func.as_ref() {
syn::Expr::Path(p) => p.path.get_ident().cloned().ok_or_else(|| {
syn::Error::new_spanned(
&call.func,
"closure violation: call target must be a bare identifier matching a PrimitiveOp name",
)
})?,
other => {
return Err(syn::Error::new_spanned(
other,
"closure violation: call target must be a bare identifier matching a PrimitiveOp name",
));
}
};
let (operator, expected_arity) = match func_ident.to_string().as_str() {
"add" => (quote! { ::uor_foundation::PrimitiveOp::Add }, 2usize),
"sub" => (quote! { ::uor_foundation::PrimitiveOp::Sub }, 2),
"mul" => (quote! { ::uor_foundation::PrimitiveOp::Mul }, 2),
"xor" => (quote! { ::uor_foundation::PrimitiveOp::Xor }, 2),
"and" => (quote! { ::uor_foundation::PrimitiveOp::And }, 2),
"or" => (quote! { ::uor_foundation::PrimitiveOp::Or }, 2),
"neg" => (quote! { ::uor_foundation::PrimitiveOp::Neg }, 1),
"bnot" => (quote! { ::uor_foundation::PrimitiveOp::Bnot }, 1),
"succ" => (quote! { ::uor_foundation::PrimitiveOp::Succ }, 1),
"pred" => (quote! { ::uor_foundation::PrimitiveOp::Pred }, 1),
other => {
return Err(syn::Error::new(
func_ident.span(),
format!(
"closure violation: `{other}` is not a foundation PrimitiveOp (recognised: add, sub, mul, xor, and, or, neg, bnot, succ, pred)"
),
));
}
};
if call.args.len() != expected_arity {
return Err(syn::Error::new(
func_ident.span(),
format!(
"PrimitiveOp `{}` expects {} argument(s), got {}",
func_ident,
expected_arity,
call.args.len()
),
));
}
let mut arg_root_indices: Vec<usize> = Vec::with_capacity(call.args.len());
for arg in call.args.iter() {
arg_root_indices.push(emit_term_for_expr(arg, route_input, arena)?);
}
let already_contiguous = arg_root_indices.windows(2).all(|w| w[1] == w[0] + 1);
let (args_start, args_len) = if already_contiguous && !arg_root_indices.is_empty() {
let len = arg_root_indices.len();
let start = arg_root_indices[0];
(start as u32, len as u32)
} else {
let start = arena.len();
for &idx in &arg_root_indices {
let dup = match &arena[idx] {
TermSpec::Literal(v) => TermSpec::Literal(*v),
TermSpec::Variable => TermSpec::Variable,
TermSpec::Application {
operator,
args_start,
args_len,
} => TermSpec::Application {
operator: operator.clone(),
args_start: *args_start,
args_len: *args_len,
},
};
arena.push(dup);
}
(start as u32, arg_root_indices.len() as u32)
};
let app_idx = arena.len();
arena.push(TermSpec::Application {
operator,
args_start,
args_len,
});
Ok(app_idx)
}
fn render_arena(arena: &[TermSpec]) -> Vec<proc_macro2::TokenStream> {
arena
.iter()
.map(|spec| match spec {
TermSpec::Literal(value) => quote! {
::uor_foundation::enforcement::Term::Literal {
value: #value,
level: ::uor_foundation::WittLevel::W8,
}
},
TermSpec::Variable => quote! {
::uor_foundation::enforcement::Term::Variable { name_index: 0u32 }
},
TermSpec::Application {
operator,
args_start,
args_len,
} => {
let s = *args_start;
let l = *args_len;
quote! {
::uor_foundation::enforcement::Term::Application {
operator: #operator,
args: ::uor_foundation::enforcement::TermList {
start: #s,
len: #l,
},
}
}
}
})
.collect()
}
#[proc_macro]
pub fn prism_model(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as PrismModelInput);
let PrismModelInput {
model_vis,
model_name,
route_vis,
route_name,
h_ty,
b_ty,
a_ty,
input_ty,
output_ty,
route_input_ident,
route_body,
} = parsed;
let final_expr = match route_body.stmts.last() {
Some(syn::Stmt::Expr(e, _)) => e.clone(),
_ => {
return syn::Error::new_spanned(
&route_body,
"closure violation: route body must end with an expression statement (no `;`)",
)
.to_compile_error()
.into();
}
};
let mut arena: Vec<TermSpec> = Vec::new();
if let Err(e) = emit_term_for_expr(&final_expr, &route_input_ident, &mut arena) {
return e.to_compile_error().into();
}
let term_specs = render_arena(&arena);
let route_terms_const = Ident::new(
&format!(
"ROUTE_TERMS_FOR_{}",
to_screaming_snake(&model_name.to_string())
),
model_name.span(),
);
let expansion = quote! {
#model_vis struct #model_name;
#route_vis struct #route_name;
#[allow(non_upper_case_globals, dead_code)]
const #route_terms_const: &[::uor_foundation::enforcement::Term] = &[
#( #term_specs ),*
];
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #model_name {}
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for #route_name {}
impl ::uor_foundation::pipeline::FoundationClosed for #route_name {
fn arena_slice() -> &'static [::uor_foundation::enforcement::Term] {
#route_terms_const
}
}
impl ::uor_foundation::pipeline::PrismModel<#h_ty, #b_ty, #a_ty> for #model_name {
type Input = #input_ty;
type Output = #output_ty;
type Route = #route_name;
fn forward(
input: <Self as ::uor_foundation::pipeline::PrismModel<#h_ty, #b_ty, #a_ty>>::Input,
) -> ::core::result::Result<
::uor_foundation::enforcement::Grounded<
<Self as ::uor_foundation::pipeline::PrismModel<#h_ty, #b_ty, #a_ty>>::Output,
>,
::uor_foundation::PipelineFailure,
> {
::uor_foundation::pipeline::run_route::<#h_ty, #b_ty, #a_ty, Self>(input)
}
}
};
expansion.into()
}
fn lexically_earlier(a: &Ident, b: &Ident) -> String {
let a_s = a.to_string();
let b_s = b.to_string();
if a_s.as_str() <= b_s.as_str() {
a_s
} else {
b_s
}
}
fn lexically_later(a: &Ident, b: &Ident) -> String {
let a_s = a.to_string();
let b_s = b.to_string();
if a_s.as_str() > b_s.as_str() {
a_s
} else {
b_s
}
}
fn canonical_operand_pair(a: &Ident, b: &Ident) -> (Ident, Ident) {
let a_s = a.to_string();
let b_s = b.to_string();
if a_s.as_str() <= b_s.as_str() {
(a.clone(), b.clone())
} else {
(b.clone(), a.clone())
}
}
fn format_ident_suffix(base: &Ident, suffix: &str) -> Ident {
let upper_base = to_screaming_snake(&base.to_string());
let joined = format!("{upper_base}{suffix}");
Ident::new(&joined, base.span())
}
fn to_screaming_snake(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
let chars: Vec<char> = s.chars().collect();
for (i, ch) in chars.iter().enumerate() {
if *ch == '_' {
if !out.ends_with('_') && !out.is_empty() {
out.push('_');
}
continue;
}
if ch.is_ascii_uppercase() {
let prev_lower_or_digit = i > 0
&& chars[i - 1] != '_'
&& (chars[i - 1].is_ascii_lowercase() || chars[i - 1].is_ascii_digit());
let run_ending = i > 0
&& chars[i - 1].is_ascii_uppercase()
&& i + 1 < chars.len()
&& chars[i + 1].is_ascii_lowercase();
if (prev_lower_or_digit || run_ending) && !out.ends_with('_') && !out.is_empty() {
out.push('_');
}
out.push(*ch);
} else {
out.push(ch.to_ascii_uppercase());
}
}
out
}