pm2 0.1.0

Useful proc macros.
Documentation
use proc_macro::{
  TokenStream,
  TokenTree::{self, *},
};

use cg2::{Generator, f};

use pm2_types::EnumRepr;

use crate::{get_idents, get_repr, seek_ident};

pub fn run(_attr: TokenStream, mut item: TokenStream) -> TokenStream {
  let item_tokens: Vec<TokenTree> = item.clone().into_iter().collect();

  let mut item_stream = item.clone().into_iter();

  let repr = if let Some(r) = get_repr(&item_tokens) {
    match EnumRepr::try_from(r.as_str()) {
      Ok(r) => Some(r),
      Err(r) => panic!("unknown repr: {r}"),
    }
  } else {
    None
  };

  if !seek_ident(&mut item_stream, "enum") {
    panic!("missing enum keyword");
  }

  let Ident(i) = item_stream.next().expect("missing name") else {
    panic!("name not an Ident");
  };

  let name = i.to_string();

  let Group(body) = item_stream.next().expect("missing body") else {
    panic!("generic params and where clauses are not allowed");
  };

  let body_tokens: Vec<TokenTree> = body.stream().into_iter().collect();

  let variants = get_enum_variants(&body_tokens);

  if variants.is_empty() {
    panic!("must have atleast 1 variant");
  }

  let mut g = Generator::new();

  // constants
  g.implement()
    .space()
    .ident(&name)
    .block(|g: &mut Generator| {
      cg2::bind!($g, declare_const);

      // REPR
      declare_const!(pub REPR: Option<&str>, |g: &mut Generator| {
        if let Some(r) = repr {
          g.out(f!("Some({:?})", r.as_str()));
        } else {
          g.out("None");
        }
      });

      // NUM_VARIANTS
      declare_const!(pub NUM_VARIANTS: usize = variants.len());

      // FIRST_VARIANT
      // LAST_VARIANT
      declare_const!(pub FIRST_VARIANT: Self = f!("Self::{}", variants.first().unwrap()));
      declare_const!(pub LAST_VARIANT: Self = f!("Self::{}", variants.last().unwrap()));

      // MAX_DISCRIMINANT
      if let Some(r) = repr
        && r.is_primitive()
      {
        declare_const!(pub MAX_DISCRIMINANT: r.as_str(), "Self::LAST_VARIANT as _");
      }
    });

  item.extend::<TokenStream>(g.into_code().parse().expect("failed to parse new code"));

  item
}

pub fn get_enum_variants(tt: &[TokenTree]) -> Vec<String> {
  get_idents(tt)
}