use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{braced, Ident, Token, Type};
use crate::generated::keywords::KNOWN_KEYWORDS;
use crate::generated::shape_requirements::SHAPE_REQUIRED_KEYS;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ConformanceDecl {
pub keyword: String,
pub identifier: String,
pub body: String,
pub grounded_type: Option<Type>,
}
#[derive(Debug, Clone)]
struct BodyEntry {
key: String,
value: String,
}
fn parse_body(body: &str) -> Vec<BodyEntry> {
let mut out = Vec::new();
let bytes = body.as_bytes();
let mut i = 0;
while i < bytes.len() {
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
if i >= bytes.len() {
break;
}
let key_start = i;
while i < bytes.len() && bytes[i] != b':' && bytes[i] != b';' {
i += 1;
}
if i >= bytes.len() || bytes[i] == b';' {
break;
}
let key = body[key_start..i].trim().to_string();
i += 1; while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
let value_start = i;
let mut depth = 0i32;
while i < bytes.len() {
match bytes[i] {
b'{' => depth += 1,
b'}' => depth -= 1,
b';' if depth == 0 => break,
_ => {}
}
i += 1;
}
let value = body[value_start..i].trim().to_string();
if !key.is_empty() {
out.push(BodyEntry { key, value });
}
if i < bytes.len() {
i += 1; }
}
out
}
fn body_lookup<'a>(entries: &'a [BodyEntry], key: &str) -> Option<&'a str> {
entries
.iter()
.find(|e| e.key == key)
.map(|e| e.value.as_str())
}
fn validate_required_keys(keyword: &str, entries: &[BodyEntry]) -> Option<TokenStream> {
let required = SHAPE_REQUIRED_KEYS
.iter()
.find(|(k, _)| *k == keyword)
.map(|(_, reqs)| *reqs)?;
for req in required {
if !entries.iter().any(|e| e.key == *req) {
let msg = format!("uor_ground! {keyword} declaration missing required key `{req}`");
return Some(quote! { compile_error!(#msg); }.into());
}
}
None
}
struct MacroInput {
keyword: Ident,
ident: Ident,
brace_body: proc_macro2::TokenStream,
grounded_type: Option<Type>,
}
impl Parse for MacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let keyword: Ident = input.parse()?;
let ident: Ident = input.parse()?;
let brace_content;
let _ = braced!(brace_content in input);
let body_tokens: proc_macro2::TokenStream = brace_content.parse()?;
let grounded_type = if input.peek(Token![as]) {
let _: Token![as] = input.parse()?;
let ty: Type = input.parse()?;
Some(ty)
} else {
None
};
Ok(MacroInput {
keyword,
ident,
brace_body: body_tokens,
grounded_type,
})
}
}
pub fn parse(input: &str) -> Result<ConformanceDecl, String> {
let tokens: proc_macro2::TokenStream =
input.parse().map_err(|e| format!("lexer error: {e}"))?;
let macro_input: MacroInput = syn::parse2(tokens).map_err(|e| format!("parse error: {e}"))?;
let keyword = macro_input.keyword.to_string();
if !KNOWN_KEYWORDS.contains(&keyword.as_str()) {
return Err(format!(
"unknown declaration keyword `{keyword}`; expected one of {KNOWN_KEYWORDS:?}"
));
}
Ok(ConformanceDecl {
keyword,
identifier: macro_input.ident.to_string(),
body: macro_input.brace_body.to_string(),
grounded_type: macro_input.grounded_type,
})
}
#[must_use]
pub fn emit_grounded(decl: &ConformanceDecl) -> TokenStream {
match decl.keyword.as_str() {
"compile_unit" => emit_compile_unit(decl),
"dispatch_rule" => emit_dispatch_rule(decl),
"witt_level" => emit_witt_level(decl),
"predicate" => emit_predicate(decl),
"parallel" => emit_parallel(decl),
"stream" => emit_stream(decl),
"lease" => emit_lease(decl),
other => {
let msg = format!("unknown uor_ground! keyword `{other}`");
quote! { compile_error!(#msg); }.into()
}
}
}
fn emit_compile_unit(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let grounded_type = match &decl.grounded_type {
Some(t) => t,
None => {
let msg = "uor_ground! compile_unit requires a trailing \
`as Grounded<T>` type ascription so the macro can \
recover the type parameter at expansion time.";
return quote! { compile_error!(#msg); }.into();
}
};
let inner_type: Type =
extract_grounded_inner(grounded_type).unwrap_or_else(|| grounded_type.clone());
let witt_bits: u16 = body_lookup(&entries, "witt_level_ceiling")
.and_then(parse_witt_level_name_to_bits)
.unwrap_or(32);
let expanded = quote! {
{
let __uor_input = <#inner_type as ::core::default::Default>::default();
match ::uor_foundation::pipeline::run_pipeline::<#inner_type>(
&__uor_input,
#witt_bits,
) {
Ok(g) => g,
Err(_) => {
::core::panic!(
"uor_ground! pipeline failure: reduction:ConvergenceStall"
)
}
}
}
};
expanded.into()
}
fn parse_witt_level_name_to_bits(value: &str) -> Option<u16> {
let v = value.trim();
if let Some(rest) = v.strip_prefix('W') {
return rest.parse::<u16>().ok();
}
None
}
fn emit_dispatch_rule(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let predicate_iri = body_lookup(&entries, "predicate")
.unwrap_or("https://uor.foundation/predicate/always")
.trim_matches('"')
.to_string();
let target_raw = body_lookup(&entries, "target_resolver")
.unwrap_or("ResidualVerdictResolver")
.to_string();
let target_iri = if target_raw.starts_with('"') {
target_raw.trim_matches('"').to_string()
} else {
format!("https://uor.foundation/resolver/{target_raw}")
};
let priority: u32 = body_lookup(&entries, "priority")
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
#[doc = "Generated dispatch rule marker. Carries the predicate IRI, target resolver IRI, and priority as associated constants."]
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types)]
pub struct #ident;
impl #ident {
pub const PREDICATE_IRI: &'static str = #predicate_iri;
pub const TARGET_RESOLVER_IRI: &'static str = #target_iri;
pub const PRIORITY: u32 = #priority;
}
};
expanded.into()
}
fn emit_witt_level(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let bit_width: u32 = body_lookup(&entries, "bit_width")
.and_then(|s| s.trim().parse().ok())
.unwrap_or(8);
if bit_width == 0 || bit_width % 8 != 0 {
let msg = format!("witt_level bit_width must be 8·(k+1); got {bit_width}");
return quote! { compile_error!(#msg); }.into();
}
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
pub const #ident: ::uor_foundation::WittLevel =
::uor_foundation::WittLevel::new(#bit_width);
};
expanded.into()
}
fn emit_predicate(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let termination_witness = body_lookup(&entries, "termination_witness")
.unwrap_or("https://uor.foundation/proof/AxiomaticDerivation")
.trim_matches('"')
.to_string();
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
#[doc = "Generated predicate marker. Carries the termination-witness IRI as an associated constant."]
#[derive(Debug, Default, Clone, Copy)]
pub struct #ident;
impl #ident {
pub const TERMINATION_WITNESS_IRI: &'static str = #termination_witness;
}
};
expanded.into()
}
fn emit_parallel(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let site_partition = body_lookup(&entries, "site_partition")
.unwrap_or("https://uor.foundation/partition/TrivialPartition")
.trim_matches('"')
.to_string();
let disjointness = body_lookup(&entries, "disjointness_witness")
.unwrap_or("")
.trim_matches('"')
.to_string();
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
#[doc = "Generated parallel marker. Carries the site-partition and disjointness-witness IRIs as associated constants."]
#[derive(Debug, Default, Clone, Copy)]
pub struct #ident;
impl #ident {
pub const SITE_PARTITION_IRI: &'static str = #site_partition;
pub const DISJOINTNESS_WITNESS_IRI: &'static str = #disjointness;
}
};
expanded.into()
}
fn emit_stream(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let productivity = body_lookup(&entries, "productivity_witness")
.unwrap_or("")
.trim_matches('"')
.to_string();
let seed = body_lookup(&entries, "unfold_seed")
.unwrap_or("0")
.to_string();
let step = body_lookup(&entries, "step").unwrap_or("").to_string();
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
#[doc = "Generated stream marker. Carries the unfold seed, step term source, and productivity-witness IRI as associated constants."]
#[derive(Debug, Default, Clone, Copy)]
pub struct #ident;
impl #ident {
pub const UNFOLD_SEED_SRC: &'static str = #seed;
pub const STEP_SRC: &'static str = #step;
pub const PRODUCTIVITY_WITNESS_IRI: &'static str = #productivity;
}
};
expanded.into()
}
fn emit_lease(decl: &ConformanceDecl) -> TokenStream {
let entries = parse_body(&decl.body);
if let Some(err) = validate_required_keys(&decl.keyword, &entries) {
return err;
}
let linear_site: u32 = body_lookup(&entries, "linear_site")
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
let lease_scope = body_lookup(&entries, "lease_scope")
.unwrap_or("")
.trim_matches('"')
.to_string();
let ident = syn::Ident::new(&decl.identifier, proc_macro2::Span::call_site());
let expanded = quote! {
#[doc = "Generated lease marker. Carries the linear-site index and lease-scope IRI as associated constants."]
#[derive(Debug, Default, Clone, Copy)]
pub struct #ident;
impl #ident {
pub const LINEAR_SITE: u32 = #linear_site;
pub const LEASE_SCOPE: &'static str = #lease_scope;
}
};
expanded.into()
}
fn extract_grounded_inner(ty: &Type) -> Option<Type> {
use syn::{GenericArgument, PathArguments};
if let Type::Path(tp) = ty {
let last = tp.path.segments.last()?;
if last.ident != "Grounded" {
return None;
}
if let PathArguments::AngleBracketed(args) = &last.arguments {
for arg in &args.args {
if let GenericArgument::Type(inner) = arg {
return Some(inner.clone());
}
}
}
}
None
}