summaryrefslogtreecommitdiff
path: root/varihappy-macros/src/lib.rs
blob: 3b1cc2930c33ea973ba8e52a335fc4bf0161008a (plain)
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal};
use quote::{format_ident, quote};
use unsynn::{
	Comma, CommaDelimitedVec, Cons, Either, GroupContaining, IParse, LiteralInteger, TokenIter,
	unsynn,
};

fn number_to_type(mut number: u128) -> String {
	let mut buffer = String::new();
	while number > 25 {
		buffer.push({
			let digit = number % 26;
			number /= 26;
			match digit {
				0 => 'A',
				1 => 'B',
				2 => 'C',
				3 => 'D',
				4 => 'E',
				5 => 'F',
				6 => 'G',
				7 => 'H',
				8 => 'I',
				9 => 'J',
				10 => 'K',
				11 => 'L',
				12 => 'M',
				13 => 'N',
				14 => 'O',
				15 => 'P',
				16 => 'Q',
				17 => 'R',
				18 => 'S',
				19 => 'T',
				20 => 'U',
				21 => 'V',
				22 => 'W',
				23 => 'X',
				24 => 'Y',
				25 => 'Z',
				_ => panic!("Expected a number less than 26"),
			}
		});
	}

	buffer.push(match number {
		0 => 'A',
		1 => 'B',
		2 => 'C',
		3 => 'D',
		4 => 'E',
		5 => 'F',
		6 => 'G',
		7 => 'H',
		8 => 'I',
		9 => 'J',
		10 => 'K',
		11 => 'L',
		12 => 'M',
		13 => 'N',
		14 => 'O',
		15 => 'P',
		16 => 'Q',
		17 => 'R',
		18 => 'S',
		19 => 'T',
		20 => 'U',
		21 => 'V',
		22 => 'W',
		23 => 'X',
		24 => 'Y',
		25 => 'Z',
		_ => panic!("Expected a number less than 26"),
	});

	buffer.chars().rev().collect()
}

#[proc_macro]
pub fn tuple_new(input: TokenStream) -> TokenStream {
	let input: proc_macro2::TokenStream = input.into();
	let mut token_iter = TokenIter::new(input);
	let ast: Cons<Either<LiteralInteger, GroupContaining<LiteralInteger>>, Comma, unsynn::Ident> =
		token_iter.parse().unwrap();

	let number = ast.first.fold2(|x| x, |x| x.content);
	let number_value = number.value();
	let number_token = number.into_inner();
	let types =
		(0..number_value).map(|number| Ident::new(&number_to_type(number), number_token.span()));
	let ident = Ident::new(&ast.third.to_string(), ast.third.span());
	quote! { #ident! { (#(#types,)*) } }.into()
}

#[proc_macro]
pub fn tuple_reverse(input: TokenStream) -> TokenStream {
	let tuple = syn::parse_macro_input!(input as syn::TypeTuple);
	let reversed_tuple = tuple.elems.iter().rev();
	quote! { (#(#reversed_tuple,)*) }.into()
}

#[proc_macro]
pub fn tuple_expr_reverse(input: TokenStream) -> TokenStream {
	let input: proc_macro2::TokenStream = input.into();
	let mut token_iter = TokenIter::new(input);
	let ast: Cons<
		unsynn::Ident,
		Comma,
		unsynn::ParenthesisGroupContaining<CommaDelimitedVec<unsynn::Ident>>,
	> = token_iter.parse().unwrap();

	let self_id = ast.first;
	let reversed_tuple = ast.third.content.into_iter().rev().map(|x| x.value);
	quote! {{
		let this = #self_id;
		(#(::varihappy_macros::type_to_field_access!(#reversed_tuple),)*)
	}}
	.into()
}

#[proc_macro]
pub fn type_to_field_access(input: TokenStream) -> TokenStream {
	let input: proc_macro2::TokenStream = input.into();
	let mut token_iter = TokenIter::new(input);
	let type_name: unsynn::Ident = token_iter.parse().unwrap();
	let mut type_name = type_name.to_string();
	type_name.make_ascii_lowercase();

	let mut number = 0;
	for char in type_name.chars() {
		number *= 26;
		number += match char {
			'a' => 0,
			'b' => 1,
			'c' => 2,
			'd' => 3,
			'e' => 4,
			'f' => 5,
			'g' => 6,
			'h' => 7,
			'i' => 8,
			'j' => 9,
			'k' => 10,
			'l' => 11,
			'm' => 12,
			'n' => 13,
			'o' => 14,
			'p' => 15,
			'q' => 16,
			'r' => 17,
			's' => 18,
			't' => 19,
			'u' => 20,
			'v' => 21,
			'w' => 22,
			'x' => 23,
			'y' => 24,
			'z' => 25,
			_ => panic!("invalid type name"),
		};
	}

	let number = Literal::u128_unsuffixed(number);
	quote! { this.#number }.into()
}

#[proc_macro]
pub fn repeat(input: TokenStream) -> TokenStream {
	unsynn! {
		struct RangeLiteral {
			start: LiteralInteger,
			dotdot: unsynn::DotDot,
			end: LiteralInteger,
		}
	}

	let input: proc_macro2::TokenStream = input.into();
	let mut token_iter = TokenIter::new(input);
	let ast: Cons<RangeLiteral, Comma, unsynn::Ident> = token_iter.parse().unwrap();

	let numbers = (ast.first.start.value()..ast.first.end.value()).map(Literal::u128_unsuffixed);
	let macro_name = ast.third;
	quote! { #(#macro_name!(#numbers);)* }.into()
}

#[proc_macro]
pub fn tuple_trait_def(input: TokenStream) -> TokenStream {
	let input: proc_macro2::TokenStream = input.into();
	let mut token_iter = TokenIter::new(input);
	let ast: Cons<Either<LiteralInteger, GroupContaining<LiteralInteger>>, Comma, unsynn::Ident> =
		token_iter.parse().unwrap();

	let number = ast.first.fold2(|x| x, |x| x.content);
	let number = number.value();
	let minus1 = number - 1;

	let trait_name = format_ident!("Tuple{number}");
	let other_trait = format_ident!("Tuple{minus1}");
	let item_type = format_ident!("Item{minus1}");
	let item_fn = format_ident!("into_{minus1}");
	let ident = ast.third;
	quote! { #ident! { trait #trait_name for #other_trait { type #item_type; fn #item_fn; }} }
		.into()
}

#[proc_macro]
pub fn tuple_len(input: TokenStream) -> TokenStream {
	let tuple = syn::parse_macro_input!(input as syn::TypeTuple);
	let len = Literal::usize_unsuffixed(tuple.elems.len());
	quote! { #len }.into()
}