use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{DeriveInput, GenericParam, Ident, parse_quote};
use crate::{
crate_name,
helpers::{DiscriminantInfo, add_trait_bounds, variant_discriminants},
parser::{EnumInfo, FieldInfo, ParsedInfo, Repr, StructInfo, StructKind, VariantKind, parse},
};
pub fn from_clvm(ast: DeriveInput) -> TokenStream {
let parsed = parse("FromClvm", &ast);
let node_name = Ident::new("Node", Span::mixed_site());
let decoder_name = Ident::new("D", Span::mixed_site());
match parsed {
ParsedInfo::Struct(struct_info) => {
impl_for_struct(ast, struct_info, &node_name, &decoder_name)
}
ParsedInfo::Enum(enum_info) => impl_for_enum(ast, &enum_info, &node_name, &decoder_name),
}
}
struct ParsedFields {
decoded_names: Vec<Ident>,
decoded_values: Vec<TokenStream>,
body: TokenStream,
}
fn field_parser_fn_body(
crate_name: &Ident,
decoder_name: &Ident,
fields: &[FieldInfo],
repr: Repr,
) -> ParsedFields {
let mut body = TokenStream::new();
let temp_names: Vec<Ident> = (0..fields.len())
.map(|i| Ident::new(&format!("field_{i}"), Span::mixed_site()))
.collect();
let decode_next = match repr {
Repr::Atom | Repr::Transparent => unreachable!(),
Repr::List | Repr::ProperList => quote!(decode_pair),
Repr::Curry => quote!(decode_curried_arg),
};
let mut optional = false;
for (i, field) in fields.iter().enumerate() {
let ident = &temp_names[i];
if field.rest {
body.extend(quote! {
let #ident = node;
});
} else if field.optional_with_default.is_some() {
if !optional {
body.extend(quote! {
let optional_node = Some(decoder.clone_node(&node));
});
}
optional = true;
body.extend(quote! {
let (#ident, optional_node) = optional_node.and_then(|node| decoder.#decode_next(&node).ok())
.map(|(a, b)| (Some(a), Some(b))).unwrap_or((None, None));
if let Some(new_node) = optional_node.as_ref().map(|node| decoder.clone_node(node)) {
node = new_node;
}
});
} else {
body.extend(quote! {
let (#ident, new_node) = decoder.#decode_next(&node)?;
node = new_node;
});
}
}
if !fields.last().is_some_and(|field| field.rest) {
body.extend(check_rest_value(crate_name, repr));
}
let mut decoded_names = Vec::new();
let mut decoded_values = Vec::new();
for (i, field) in fields.iter().enumerate() {
let ident = &temp_names[i];
let ty = &field.ty;
let mut decoded_value = quote! {
<#ty as #crate_name::FromClvm<#decoder_name>>::from_clvm(decoder, #ident)
};
if let Some(default) = &field.optional_with_default {
let default = default.as_ref().map_or_else(
|| quote!(<#ty as ::std::default::Default>::default()),
ToTokens::to_token_stream,
);
decoded_value = quote! {
#ident.map(|#ident| #decoded_value).unwrap_or(Ok(#default))?
};
} else {
decoded_value = quote!(#decoded_value?);
}
let field_ident = field.ident.clone();
if let Some(value) = &field.constant {
body.extend(quote! {
let value: #ty = #value;
if #decoded_value != value {
return Err(#crate_name::FromClvmError::Custom(
format!(
"constant `{}` has an incorrect value",
stringify!(#field_ident),
)
));
}
});
} else {
decoded_names.push(field_ident);
decoded_values.push(decoded_value);
}
}
ParsedFields {
decoded_names,
decoded_values,
body,
}
}
fn check_rest_value(crate_name: &Ident, repr: Repr) -> TokenStream {
match repr {
Repr::Atom | Repr::Transparent => unreachable!(),
Repr::List => quote! {},
Repr::ProperList => {
quote! {
let atom = decoder.decode_atom(&node)?;
let atom_ref = atom.as_ref();
if !atom_ref.is_empty() {
return Err(#crate_name::FromClvmError::WrongAtomLength {
expected: 0,
found: atom_ref.len(),
});
}
}
}
Repr::Curry => {
quote! {
let atom = decoder.decode_atom(&node)?;
let atom_ref = atom.as_ref();
if atom_ref.len() != 1 {
return Err(#crate_name::FromClvmError::WrongAtomLength {
expected: 1,
found: atom_ref.len(),
});
}
if atom_ref != &[1] {
return Err(#crate_name::FromClvmError::Custom(
"expected curried argument terminator of 1".to_string(),
));
}
}
}
}
}
fn impl_for_struct(
ast: DeriveInput,
struct_info: StructInfo,
node_name: &Ident,
decoder_name: &Ident,
) -> TokenStream {
let crate_name = crate_name(struct_info.crate_name);
let ParsedFields {
decoded_names,
decoded_values,
mut body,
} = field_parser_fn_body(
&crate_name,
decoder_name,
&struct_info.fields,
struct_info.repr,
);
match struct_info.kind {
StructKind::Unit => {
body.extend(quote!(Ok(Self)));
}
StructKind::Unnamed => {
body.extend(quote! {
Ok(Self ( #( #decoded_values, )* ))
});
}
StructKind::Named => {
body.extend(quote! {
Ok(Self {
#( #decoded_names: #decoded_values, )*
})
});
}
}
trait_impl(ast, &crate_name, node_name, decoder_name, &body)
}
fn impl_for_enum(
ast: DeriveInput,
enum_info: &EnumInfo,
node_name: &Ident,
decoder_name: &Ident,
) -> TokenStream {
let crate_name = crate_name(enum_info.crate_name.clone());
let mut body = TokenStream::new();
if enum_info.is_untagged {
let variant_parsers = enum_variant_parsers(&crate_name, node_name, decoder_name, enum_info);
for parser in variant_parsers {
body.extend(quote! {
if let Ok(value) = (#parser)(decoder.clone_node(&node)) {
return Ok(value);
}
});
}
body.extend(quote! {
Err(#crate_name::FromClvmError::Custom(
"failed to parse any enum variant".to_string(),
))
});
} else {
let DiscriminantInfo {
discriminant_type,
discriminant_consts,
discriminant_names,
variant_names,
} = variant_discriminants(enum_info);
if enum_info.default_repr == Repr::Atom {
body.extend(quote! {
let discriminant = <#discriminant_type as #crate_name::FromClvm<#decoder_name>>::from_clvm(
decoder,
node,
)?;
#( #discriminant_consts )*
match discriminant {
#( #discriminant_names => Ok(Self::#variant_names), )*
_ => Err(#crate_name::FromClvmError::Custom(
format!("unknown enum variant discriminant: {}", discriminant),
)),
}
});
} else {
let variant_parsers =
enum_variant_parsers(&crate_name, node_name, decoder_name, enum_info);
let decode_next = match enum_info.default_repr {
Repr::Atom | Repr::Transparent => unreachable!(),
Repr::List | Repr::ProperList => quote!(decode_pair),
Repr::Curry => quote!(decode_curried_arg),
};
body.extend(quote! {
let (discriminant_node, node) = decoder.#decode_next(&node)?;
let discriminant = <#discriminant_type as #crate_name::FromClvm<#decoder_name>>::from_clvm(
decoder,
discriminant_node,
)?;
#( #discriminant_consts )*
match discriminant {
#( #discriminant_names => (#variant_parsers)(node), )*
_ => Err(#crate_name::FromClvmError::Custom(
format!("unknown enum variant discriminant: {}", discriminant),
)),
}
});
}
}
trait_impl(ast, &crate_name, node_name, decoder_name, &body)
}
fn enum_variant_parsers(
crate_name: &Ident,
node_name: &Ident,
decoder_name: &Ident,
enum_info: &EnumInfo,
) -> Vec<TokenStream> {
let mut variant_parsers = Vec::new();
for variant in &enum_info.variants {
let variant_name = &variant.name;
let repr = variant.repr.unwrap_or(enum_info.default_repr);
let ParsedFields {
decoded_names,
decoded_values,
mut body,
} = field_parser_fn_body(crate_name, decoder_name, &variant.fields, repr);
match variant.kind {
VariantKind::Unit => {
body.extend(quote!(Ok(Self::#variant_name)));
}
VariantKind::Unnamed => {
body.extend(quote! {
Ok(Self::#variant_name ( #( #decoded_values, )* ))
});
}
VariantKind::Named => {
body.extend(quote! {
Ok(Self::#variant_name {
#( #decoded_names: #decoded_values, )*
})
});
}
}
variant_parsers.push(quote! {
|mut node: #node_name| -> ::std::result::Result<Self, #crate_name::FromClvmError> {
#body
}
});
}
variant_parsers
}
fn trait_impl(
mut ast: DeriveInput,
crate_name: &Ident,
node_name: &Ident,
decoder_name: &Ident,
body: &TokenStream,
) -> TokenStream {
let type_name = ast.ident;
add_trait_bounds(
&mut ast.generics,
&parse_quote!(#crate_name::FromClvm<#decoder_name>),
);
let generics_clone = ast.generics.clone();
let (_, ty_generics, where_clause) = generics_clone.split_for_impl();
ast.generics
.params
.push(GenericParam::Type(node_name.clone().into()));
ast.generics.params.push(GenericParam::Type(
parse_quote!(#decoder_name: #crate_name::ClvmDecoder<Node = #node_name>),
));
let (impl_generics, _, _) = ast.generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_generics #crate_name::FromClvm<#decoder_name>
for #type_name #ty_generics #where_clause {
fn from_clvm(
decoder: &#decoder_name,
mut node: #node_name,
) -> ::std::result::Result<Self, #crate_name::FromClvmError> {
#body
}
}
}
}