stringid_macros 0.1.0

Macros implementation for stringid crate.
Documentation
use proc_macro2::{Literal, Span, TokenStream};
use quote::quote;
use syn::{
	parse::{Parse, ParseStream},
	DeriveInput, Ident, Result, Token,
};

use crate::parse::{check_unittype, get_data_struct};

pub(crate) fn get_strlookup_type(input: &DeriveInput) -> Result<()> {
	let data_struct = get_data_struct(input)?;
	check_unittype(data_struct)
}

pub(crate) fn get_strlookup_hashmap_impl(
	name: &Ident,
	key_type: Ident,
	store_type: Ident,
	hashmap_size: Literal,
) -> TokenStream {
	quote! {
		struct #name;
		impl stringid::StrLookupHashMap for #name {
			type Key = #key_type;
			type StrStore = #store_type;

			fn hashmap() -> &'static std::sync::RwLock<cityhasher::HashMap<Self::Key, &'static str>> {
				static STRING_TABLE: once_cell::sync::Lazy<
					std::sync::RwLock<cityhasher::HashMap<#key_type, &'static str>>,
				> = once_cell::sync::Lazy::new(|| create_hash_map::<#key_type>(#hashmap_size));
				&STRING_TABLE
			}
		}
	}
}

/// Struct to parse and store [`StrLookupHashMap`] attributes
///
/// ```ignore
/// #[strlookup_hashmap(key = u32, store = BufferStrStore)]
/// ```
#[derive(Debug)]
pub(crate) struct StrLookupHashMap {
	pub key_type: Ident,
	pub store_type: Ident,
	pub hashmap_size: Literal,
}

impl Parse for StrLookupHashMap {
	fn parse(input: ParseStream) -> syn::Result<Self> {
		let mut key_type: Option<Ident> = None;
		let mut store_type: Option<Ident> = None;
		let mut hashmap_size: Option<Literal> = None;
		while !input.is_empty() {
			if input.peek(Token![,]) {
				let _eq: Token![,] = input.parse()?;
			} else {
				let ident: Ident = input.parse()?;
				match ident.to_string().as_str() {
					"key" => {
						let msg = concat!(
							"`key` must be used with a assigned value.\n",
							"For example:\n",
							"    key = u32\n"
						);
						key_type = Some(parse_attr_value(input, &ident, msg)?);
					}
					"store" => {
						let msg = concat!(
							"`store` must be used with a assigned value .\n",
							"For example:\n",
							"    store = BufferStrStore\n\n"
						);
						store_type = Some(parse_attr_value(input, &ident, msg)?);
					}
					"size" => {
						let msg = concat!(
							"`size` must be used with a assigned value .\n",
							"For example:\n",
							"    size = 128\n\n"
						);
						hashmap_size = Some(parse_attr_value(input, &ident, msg)?);
					}
					_ => {
						let msg = format!("Unknown attribute `{ident}`, `key` and `store` are the only autorized and wanted attribute");
						return Err(syn::Error::new(ident.span(), msg));
					}
				}
			}
		}

		Ok(StrLookupHashMap {
			key_type: key_type.ok_or(syn::Error::new(
				Span::call_site(),
				"a 'key' type should be specified",
			))?,
			store_type: store_type.ok_or(syn::Error::new(
				Span::call_site(),
				"a 'store' type should be specified",
			))?,
			hashmap_size: hashmap_size.ok_or(syn::Error::new(
				Span::call_site(),
				"a 'size' for the hashmap should be specified",
			))?,
		})
	}
}

fn parse_attr_value<T: Parse>(
	input: &syn::parse::ParseBuffer<'_>,
	ident: &Ident,
	msg: &str,
) -> Result<T> {
	let _eq: Token![=] = input.parse().or(Err(syn::Error::new(ident.span(), msg)))?;
	input.parse()
}