use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{
DeriveInput, Expr, Field, Fields, GenericParam, Generics, Ident, Lifetime, LifetimeParam,
Member, Variant,
};
const LOWERCASE_CHARS: &[char] = &[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z',
];
const UPPERCASE_CHARS: &[char] = &[
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];
const DIGIT_CHARS: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
pub(crate) fn to_ast(input: TokenStream) -> DeriveInput {
syn::parse(input).unwrap()
}
pub(crate) fn call_site_ident(s: &str) -> Ident {
Ident::new(s, Span::call_site())
}
fn build_nested<L: IntoIterator>(
items: L,
base: TokenStream2,
wrap: impl Fn(L::Item, TokenStream2) -> TokenStream2,
) -> TokenStream2
where
L::IntoIter: DoubleEndedIterator,
{
items
.into_iter()
.rev()
.fold(base, |acc, item| wrap(item, acc))
}
pub(crate) fn build_hlist_type<L: IntoIterator>(items: L) -> TokenStream2
where
L::Item: ToTokens,
L::IntoIter: DoubleEndedIterator,
{
build_nested(
items,
quote! { ::ordofp_core::hlist::Nihil },
|item, acc| {
quote! { ::ordofp_core::hlist::Coniunctio<#item, #acc> }
},
)
}
pub(crate) fn build_hlist_constr<L: IntoIterator>(items: L) -> TokenStream2
where
L::Item: ToTokens,
L::IntoIter: DoubleEndedIterator,
{
build_nested(
items,
quote! { ::ordofp_core::hlist::Nihil },
|item, acc| {
quote! { ::ordofp_core::hlist::Coniunctio { head: #item, tail: #acc }}
},
)
}
pub(crate) fn build_disiunctio_type<L: IntoIterator>(items: L) -> TokenStream2
where
L::Item: ToTokens,
L::IntoIter: DoubleEndedIterator,
{
build_nested(
items,
quote! { ::ordofp_core::disiunctio::Absurdum },
|item, acc| quote! { ::ordofp_core::disiunctio::Disiunctio<#item, #acc> },
)
}
pub(crate) fn build_disiunctio_constr(index: usize, item: impl ToTokens) -> TokenStream2 {
(0..index).fold(
quote! { ::ordofp_core::disiunctio::Disiunctio::Sinister(#item) },
|acc, _| quote! { ::ordofp_core::disiunctio::Disiunctio::Dexter(#acc) },
)
}
pub(crate) fn build_disiunctio_unreachable_arm(length: usize, _deref: bool) -> TokenStream2 {
let result = (0..length).fold(quote! { _ordofp_unreachable_ }, |acc, _| {
quote! { ::ordofp_core::disiunctio::Disiunctio::Dexter(#acc)}
});
quote! { #result => unreachable!() }
}
pub(crate) fn build_field_type(name: &Ident, inner_type: impl ToTokens) -> TokenStream2 {
let label_type = build_label_type(name);
quote! { ::ordofp_core::labelled::Field<#label_type, #inner_type> }
}
pub(crate) fn build_field_expr(name: &Ident, inner_expr: impl ToTokens) -> TokenStream2 {
let label_type = build_label_type(name);
let literal_name = name.to_string();
quote! { ::ordofp_core::labelled::field_with_name::<#label_type, _>(#literal_name, #inner_expr) }
}
pub(crate) fn build_field_pat(inner_pat: impl ToTokens) -> TokenStream2 {
quote! { ::ordofp_core::labelled::Field { value: #inner_pat, .. } }
}
pub(crate) fn build_label_type(ident: &Ident) -> impl ToTokens {
let as_string = ident.to_string();
let name = as_string.as_str();
let name_as_tokens: Vec<_> = name
.chars()
.flat_map(encode_as_ident)
.map(|ident| quote! { ::ordofp_core::labelled::chars::#ident })
.collect();
quote! { (#(#name_as_tokens),*) }
}
fn encode_as_ident(c: char) -> Vec<Ident> {
if LOWERCASE_CHARS.contains(&c) {
vec![call_site_ident(&format!("L{c}"))]
} else if UPPERCASE_CHARS.contains(&c) {
vec![call_site_ident(&format!("U{}", c.to_ascii_lowercase()))]
} else if DIGIT_CHARS.contains(&c) {
vec![call_site_ident(&format!("N{c}"))]
} else if c == '_' {
vec![call_site_ident("Underscore")]
} else {
let as_unicode = c.escape_unicode();
let delimited_hex = as_unicode.filter(|c| c.is_alphanumeric());
let mut hex_idents: Vec<Ident> = delimited_hex.flat_map(encode_as_ident).collect();
let mut book_ended: Vec<Ident> = vec![call_site_ident("UnderscoreUc")];
book_ended.append(&mut hex_idents);
book_ended.push(call_site_ident("UcUnderscore"));
book_ended
}
}
pub(crate) fn build_path_type(path_expr: Expr) -> syn::Result<TokenStream2> {
let idents = find_idents_in_expr(path_expr)?;
Ok(idents
.iter()
.map(build_label_type)
.fold(quote!(::ordofp_core::hlist::Nihil), |acc, t| {
quote! {
::ordofp_core::path::Path<
::ordofp_core::hlist::Coniunctio<
#t,
#acc
>
>
}
}))
}
pub(crate) fn find_idents_in_expr(path_expr: Expr) -> syn::Result<Vec<Ident>> {
fn go(current: Expr, mut v: Vec<Ident>) -> syn::Result<Vec<Ident>> {
match current {
Expr::Field(e) => {
let m = e.member;
match m {
Member::Named(i) => {
v.push(i);
}
Member::Unnamed(idx) => {
return Err(syn::Error::new_spanned(
&idx,
format!(
"Tuple field access (`.{}`) is not supported in path expressions. \
Use named struct fields instead.",
idx.index
),
));
}
}
go(*e.base, v)
}
Expr::Path(p) => {
if p.path.segments.len() == 1 {
let i = p.path.segments[0].ident.clone();
v.push(i);
Ok(v)
} else {
let msg = format!(
"Path `{}` contains `::` separators. \
Only simple field access chains are supported (e.g., `foo.bar.baz`).",
p.path.to_token_stream()
);
Err(syn::Error::new_spanned(&p.path, msg))
}
}
_ => Err(syn::Error::new_spanned(
¤t,
"Invalid path expression. Expected a field access chain like `foo.bar.baz`, \
but found an unsupported expression type.",
)),
}
}
go(path_expr, Vec::new())
}
pub(crate) enum StructType {
Named,
Tuple,
Unit,
}
pub(crate) struct FieldBinding {
pub field: Field,
pub binding: Ident,
}
impl FieldBinding {
pub(crate) fn build_type(&self) -> TokenStream2 {
let ty = &self.field.ty;
quote! { #ty }
}
pub(crate) fn build_type_ref(&self) -> TokenStream2 {
let ty = &self.field.ty;
quote! { &'_ordofp_ref_ #ty }
}
pub(crate) fn build_type_mut(&self) -> TokenStream2 {
let ty = &self.field.ty;
quote! { &'_ordofp_ref_ mut #ty }
}
pub(crate) fn build(&self) -> TokenStream2 {
let binding = &self.binding;
quote! { #binding }
}
pub(crate) fn build_pat_ref(&self) -> TokenStream2 {
let binding = &self.binding;
quote! { ref #binding }
}
pub(crate) fn build_pat_mut(&self) -> TokenStream2 {
let binding = &self.binding;
quote! { ref mut #binding }
}
pub(crate) fn build_field_type(&self) -> TokenStream2 {
build_field_type(&self.binding, self.build_type())
}
pub(crate) fn build_field_type_ref(&self) -> TokenStream2 {
build_field_type(&self.binding, self.build_type_ref())
}
pub(crate) fn build_field_type_mut(&self) -> TokenStream2 {
build_field_type(&self.binding, self.build_type_mut())
}
pub(crate) fn build_field_expr(&self) -> TokenStream2 {
build_field_expr(&self.binding, &self.binding)
}
pub(crate) fn build_field_pat(&self) -> TokenStream2 {
build_field_pat(&self.binding)
}
}
pub(crate) struct FieldBindings {
pub type_: StructType,
pub fields: Vec<FieldBinding>,
}
impl FieldBindings {
pub(crate) fn new(fields: &Fields) -> Self {
Self {
type_: match fields {
Fields::Named(_) => StructType::Named,
Fields::Unnamed(_) => StructType::Tuple,
Fields::Unit => StructType::Unit,
},
fields: fields
.iter()
.enumerate()
.map(|(index, field)| FieldBinding {
field: field.clone(),
binding: field
.ident
.clone()
.unwrap_or_else(|| Ident::new(&format!("_{index}"), field.span())),
})
.collect(),
}
}
pub(crate) fn build_type_constr<R: ToTokens>(
&self,
f: impl Fn(&FieldBinding) -> R,
) -> TokenStream2 {
let bindings: Vec<_> = self.fields.iter().map(f).collect();
match self.type_ {
StructType::Named => quote! { { #(#bindings,)* } },
StructType::Tuple => quote! { ( #(#bindings,)* ) },
StructType::Unit => TokenStream2::new(),
}
}
pub(crate) fn build_hlist_type<R: ToTokens>(
&self,
f: impl Fn(&FieldBinding) -> R,
) -> TokenStream2 {
build_hlist_type(self.fields.iter().map(f))
}
pub(crate) fn build_hlist_constr<R: ToTokens>(
&self,
f: impl Fn(&FieldBinding) -> R,
) -> TokenStream2 {
build_hlist_constr(self.fields.iter().map(f))
}
}
pub(crate) fn ref_generics(generics: &Generics) -> Generics {
let mut generics_ref = generics.clone();
let ref_lifetime = Lifetime::new("'_ordofp_ref_", Span::call_site());
let ref_lifetime_def = LifetimeParam::new(ref_lifetime.clone());
{
let generics_ref_lifetimes_mut = generics_ref.lifetimes_mut();
for existing_lifetime_mut in generics_ref_lifetimes_mut {
existing_lifetime_mut.bounds.push(ref_lifetime.clone());
}
}
let ref_lifetime_param = GenericParam::Lifetime(ref_lifetime_def);
generics_ref.params.push(ref_lifetime_param);
generics_ref
}
pub(crate) struct VariantBinding {
pub name: Ident,
pub fields: FieldBindings,
}
impl VariantBinding {
pub(crate) fn build_type_constr(&self) -> TokenStream2 {
let name = &self.name;
let constr = self.fields.build_type_constr(FieldBinding::build);
quote! { #name #constr }
}
pub(crate) fn build_type_pat_ref(&self) -> TokenStream2 {
let name = &self.name;
let constr = self.fields.build_type_constr(FieldBinding::build);
quote! { #name #constr }
}
pub(crate) fn build_type_pat_mut(&self) -> TokenStream2 {
let name = &self.name;
let constr = self.fields.build_type_constr(FieldBinding::build);
quote! { #name #constr }
}
pub(crate) fn build_hlist_field_type(&self) -> TokenStream2 {
build_field_type(
&self.name,
self.fields.build_hlist_type(FieldBinding::build_field_type),
)
}
pub(crate) fn build_hlist_field_type_ref(&self) -> TokenStream2 {
build_field_type(
&self.name,
self.fields
.build_hlist_type(FieldBinding::build_field_type_ref),
)
}
pub(crate) fn build_hlist_field_type_mut(&self) -> TokenStream2 {
build_field_type(
&self.name,
self.fields
.build_hlist_type(FieldBinding::build_field_type_mut),
)
}
pub(crate) fn build_hlist_field_expr(&self) -> TokenStream2 {
build_field_expr(
&self.name,
self.fields
.build_hlist_constr(FieldBinding::build_field_expr),
)
}
pub(crate) fn build_hlist_field_pat(&self) -> TokenStream2 {
build_field_pat(
self.fields
.build_hlist_constr(FieldBinding::build_field_pat),
)
}
}
pub(crate) struct VariantBindings {
pub variants: Vec<VariantBinding>,
}
impl VariantBindings {
pub(crate) fn new<'a>(data: impl IntoIterator<Item = &'a Variant>) -> Self {
VariantBindings {
variants: data
.into_iter()
.map(|variant| VariantBinding {
name: variant.ident.clone(),
fields: FieldBindings::new(&variant.fields),
})
.collect(),
}
}
pub(crate) fn build_disiunctio_type<R: ToTokens>(
&self,
f: impl Fn(&VariantBinding) -> R,
) -> TokenStream2 {
build_disiunctio_type(self.variants.iter().map(f))
}
pub(crate) fn build_disiunctio_constrs<R: ToTokens>(
&self,
f: impl Fn(&VariantBinding) -> R,
) -> Vec<TokenStream2> {
self.variants
.iter()
.enumerate()
.map(|(index, variant)| build_disiunctio_constr(index, f(variant)))
.collect()
}
pub(crate) fn build_variant_constrs<R: ToTokens>(
&self,
f: impl Fn(&VariantBinding) -> R,
) -> Vec<R> {
self.variants.iter().map(f).collect()
}
pub(crate) fn build_disiunctio_unreachable_arm(&self, deref: bool) -> TokenStream2 {
build_disiunctio_unreachable_arm(self.variants.len(), deref)
}
}