use std::fmt;
use syn::{
Attribute, Expr, Ident, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Repr {
ProperList,
List,
Curry,
Transparent,
Atom,
}
impl Repr {
pub fn expect(repr: Option<Repr>) -> Repr {
repr.expect(
"missing either `list`, `proper_list`, `curry`, `transparent`, or `atom` in `clvm` attribute options",
)
}
}
impl fmt::Display for Repr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::List => "list",
Self::ProperList => "proper_list",
Self::Curry => "curry",
Self::Transparent => "transparent",
Self::Atom => "atom",
})
}
}
pub struct ClvmOptions {
pub repr: Option<Repr>,
pub constant: Option<Expr>,
pub untagged: bool,
pub enum_repr: Option<Ident>,
pub crate_name: Option<Ident>,
pub default: Option<Option<Expr>>,
pub rest: bool,
}
enum ClvmOption {
Repr(Repr),
Constant(Expr),
CrateName(Ident),
Untagged,
Default(Option<Expr>),
Rest,
}
impl Parse for ClvmOption {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let ident = input.parse::<Ident>()?;
match ident.to_string().as_str() {
"list" => Ok(Self::Repr(Repr::List)),
"proper_list" => Ok(Self::Repr(Repr::ProperList)),
"curry" => Ok(Self::Repr(Repr::Curry)),
"transparent" => Ok(Self::Repr(Repr::Transparent)),
"atom" => Ok(Self::Repr(Repr::Atom)),
"untagged" => Ok(Self::Untagged),
"constant" => {
input.parse::<Token![=]>()?;
Ok(Self::Constant(input.parse()?))
}
"crate_name" => {
input.parse::<Token![=]>()?;
Ok(Self::CrateName(input.parse()?))
}
"default" => {
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
Ok(Self::Default(Some(input.parse()?)))
} else {
Ok(Self::Default(None))
}
}
"rest" => Ok(Self::Rest),
_ => Err(syn::Error::new(ident.span(), "unknown argument")),
}
}
}
pub fn parse_clvm_options(attrs: &[Attribute]) -> ClvmOptions {
let mut options = ClvmOptions {
repr: None,
constant: None,
untagged: false,
enum_repr: None,
crate_name: None,
default: None,
rest: false,
};
for attr in attrs {
let Some(ident) = attr.path().get_ident() else {
continue;
};
if ident == "repr" {
let repr = attr.parse_args::<Ident>().unwrap();
let text = repr.to_string();
let text = text.as_str();
let is_unsigned_int = matches!(text, "u8" | "u16" | "u32" | "u64" | "u128" | "usize");
let is_signed_int = matches!(text, "i8" | "i16" | "i32" | "i64" | "i128" | "isize");
if !is_unsigned_int && !is_signed_int {
continue;
}
options.enum_repr = Some(repr);
}
if ident != "clvm" {
continue;
}
let parsed_options = attr
.parse_args_with(Punctuated::<ClvmOption, Token![,]>::parse_terminated)
.unwrap_or_else(|error| panic!("failed to parse `clvm` attribute options: {error}"));
for option in parsed_options {
match option {
ClvmOption::Untagged => {
assert!(!options.untagged, "duplicate `untagged` option");
options.untagged = true;
}
ClvmOption::Repr(repr) => {
assert!(options.repr.is_none(), "duplicate repr option `{repr}`");
options.repr = Some(repr);
}
ClvmOption::Constant(value) => {
assert!(options.constant.is_none(), "duplicate `constant` option");
options.constant = Some(value);
}
ClvmOption::CrateName(crate_name) => {
assert!(
options.crate_name.is_none(),
"duplicate `crate_name` option"
);
options.crate_name = Some(crate_name);
}
ClvmOption::Default(default) => {
assert!(options.default.is_none(), "duplicate `default` option");
options.default = Some(default);
}
ClvmOption::Rest => {
assert!(!options.rest, "duplicate `rest` option");
options.rest = true;
}
}
}
}
options
}