summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-05-12 13:33:22 -0400
committerMica White <botahamec@outlook.com>2026-05-12 13:33:22 -0400
commit8b534ea0488701d3312894e3a0ee9531e2cd444c (patch)
tree69205b29b22219fd7de56e70f93a99273120c19b
parentfd835b4ce895d83940cca0107fd03c7b035506ac (diff)
Implement Tuple for all tuple lengths
-rw-r--r--Cargo.lock24
-rw-r--r--src/lib.rs152
-rw-r--r--varihappy-macros/Cargo.toml1
-rw-r--r--varihappy-macros/src/lib.rs283
4 files changed, 280 insertions, 180 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7aee7ef..2bd1caa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,12 @@
version = 4
[[package]]
+name = "mutants"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
+
+[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -21,6 +27,12 @@ dependencies = [
]
[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -38,6 +50,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
+name = "unsynn"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501a7adf1a4bd9951501e5c66621e972ef8874d787628b7f90e64f936ef7ec0a"
+dependencies = [
+ "mutants",
+ "proc-macro2",
+ "rustc-hash",
+]
+
+[[package]]
name = "varihappy"
version = "0.1.0"
dependencies = [
@@ -51,4 +74,5 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
+ "unsynn",
]
diff --git a/src/lib.rs b/src/lib.rs
index 7bd5342..6d93428 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,5 @@
use varihappy_macros::{
- reverse_expr_type, reverse_tuple_type, tuple_expr_len_rest, tuple_expr_mut, tuple_expr_ref,
- tuple_type_len, tuple_type_len_rest,
+ repeat, tuple_expr_reverse, tuple_new, tuple_reverse, tuple_trait_def, type_to_field_access,
};
pub trait Tuple {
@@ -53,17 +52,18 @@ impl<A> Tuple for (A,) {
= (&'a mut A,)
where
Self: 'a;
-
type Reversed = (A,);
fn as_ref(&self) -> Self::AsRef<'_> {
(&self.0,)
}
+
fn as_mut(&mut self) -> Self::AsMut<'_> {
(&mut self.0,)
}
+
fn rev(self) -> Self::Reversed {
- self
+ (self.0,)
}
}
@@ -74,79 +74,112 @@ impl<A> Tuple1 for (A,) {
fn into_0(self) -> Self::Item0 {
self.0
}
-
- fn split_into(self) -> (Self::Item0, <Self as Tuple1>::Rest) {
+ fn split_into(self) -> (Self::Item0, Self::Rest) {
(self.0, ())
}
fn into_rest(self) -> Self::Rest {}
}
-macro_rules! impl_tuple {
- ($length: expr; $($letter: ident),*) => {
- impl<$($letter),*> Tuple for tuple_type_len!($length) {
+macro_rules! tuple_apply {
+ (($($letter: ident,)*), $other: ident) => {
+ $other!(($($letter,)*))
+ };
+}
+
+macro_rules! tuple_rest {
+ ((A, $($letter: ident,)*)) => {
+ ($($letter,)*)
+ };
+}
+
+macro_rules! tuple_expr_apply {
+ ($this: ident, ($($letter: ident,)*), $other: ident) => {
+ $other!($this, ($($letter,)*))
+ };
+}
+
+macro_rules! tuple_expr_ref {
+ ($this: ident, ($($letter: ident,)*)) => {
+ {
+ let this = $this;
+ ($(&type_to_field_access!($letter),)*)
+ }
+ }
+}
+
+macro_rules! tuple_expr_mut {
+ ($this: ident, ($($letter: ident,)*)) => {
+ {
+ let this = $this;
+ ($(&mut type_to_field_access!($letter),)*)
+ }
+ }
+}
+
+macro_rules! tuple_expr_rest {
+ ($this: ident, (A, $($letter: ident,)*)) => {
+ {
+ let this = $this;
+ ($(type_to_field_access!($letter),)*)
+ }
+ }
+}
+
+macro_rules! tuple_expr_split {
+ ($this: ident, (A, $($letter: ident,)*)) => {
+ {
+ let this = $this;
+ (this.0, ($(type_to_field_access!($letter),)*))
+ }
+ }
+}
+
+macro_rules! impl_tuple_inner {
+ (($($letter: ident,)*)) => {
+ impl<$($letter),*> Tuple for ($($letter,)*) {
type AsRef<'a> = ($(&'a $letter,)*) where Self: 'a;
type AsMut<'a> = ($(&'a mut $letter,)*) where Self: 'a;
- type Reversed = reverse_tuple_type!($length);
+ type Reversed = tuple_apply!(($($letter,)*), tuple_reverse);
fn as_ref(&self) -> Self::AsRef<'_> {
- tuple_expr_ref!($length)
+ tuple_expr_apply!(self, ($($letter,)*), tuple_expr_ref)
}
fn as_mut(&mut self) -> Self::AsMut<'_> {
- tuple_expr_mut!($length)
+ tuple_expr_apply!(self, ($($letter,)*), tuple_expr_mut)
}
fn rev(self) -> Self::Reversed {
- reverse_expr_type!($length)
+ tuple_expr_apply!(self, ($($letter,)*), tuple_expr_reverse)
}
}
- impl<$($letter),*> Tuple1 for tuple_type_len!($length) {
+ impl<$($letter),*> Tuple1 for ($($letter,)*) {
type Item0 = A;
- type Rest = tuple_type_len_rest!($length);
+ type Rest = tuple_apply!(($($letter,)*), tuple_rest);
fn into_0(self) -> Self::Item0 {
self.0
}
fn split_into(self) -> (Self::Item0, Self::Rest) {
- (self.0, tuple_expr_len_rest!($length))
+ tuple_expr_apply!(self, ($($letter,)*), tuple_expr_split)
}
fn into_rest(self) -> Self::Rest {
- tuple_expr_len_rest!($length)
+ tuple_expr_apply!(self, ($($letter,)*), tuple_expr_rest)
}
}
};
}
-impl_tuple!(2; A, B);
-impl_tuple!(3; A, B, C);
-impl_tuple!(4; A, B, C, D);
-impl_tuple!(5; A, B, C, D, E);
-impl_tuple!(6; A, B, C, D, E, F);
-impl_tuple!(7; A, B, C, D, E, F, G);
-impl_tuple!(8; A, B, C, D, E, F, G, H);
-impl_tuple!(9; A, B, C, D, E, F, G, H, I);
-impl_tuple!(10; A, B, C, D, E, F, G, H, I, J);
-impl_tuple!(11; A, B, C, D, E, F, G, H, I, J, K);
-impl_tuple!(12; A, B, C, D, E, F, G, H, I, J, K, L);
-impl_tuple!(13; A, B, C, D, E, F, G, H, I, J, K, L, M);
-impl_tuple!(14; A, B, C, D, E, F, G, H, I, J, K, L, M, N);
-impl_tuple!(15; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
-impl_tuple!(16; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
-impl_tuple!(17; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
-impl_tuple!(18; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
-impl_tuple!(19; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
-impl_tuple!(20; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
-impl_tuple!(21; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
-impl_tuple!(22; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
-impl_tuple!(23; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
-impl_tuple!(24; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
-impl_tuple!(25; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
-impl_tuple!(26; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
+macro_rules! impl_tuple {
+ ($number: literal) => {
+ tuple_new! { $number, impl_tuple_inner }
+ };
+}
-macro_rules! tuple_trait {
+macro_rules! tuple_trait_inner {
(trait $trait_name: ident for $trait_minus1: ident {
type $item_name: ident;
fn $item_getter: ident;
@@ -174,28 +207,11 @@ macro_rules! tuple_trait {
};
}
-tuple_trait!(trait Tuple2 for Tuple1 { type Item1; fn into_1; });
-tuple_trait!(trait Tuple3 for Tuple2 { type Item2; fn into_2; });
-tuple_trait!(trait Tuple4 for Tuple3 { type Item3; fn into_3; });
-tuple_trait!(trait Tuple5 for Tuple4 { type Item4; fn into_4; });
-tuple_trait!(trait Tuple6 for Tuple5 { type Item5; fn into_5; });
-tuple_trait!(trait Tuple7 for Tuple6 { type Item6; fn into_6; });
-tuple_trait!(trait Tuple8 for Tuple7 { type Item7; fn into_7; });
-tuple_trait!(trait Tuple9 for Tuple8 { type Item8; fn into_8; });
-tuple_trait!(trait Tuple10 for Tuple9 { type Item9; fn into_9; });
-tuple_trait!(trait Tuple11 for Tuple10 { type Item10; fn into_10; });
-tuple_trait!(trait Tuple12 for Tuple11 { type Item11; fn into_11; });
-tuple_trait!(trait Tuple13 for Tuple12 { type Item12; fn into_12; });
-tuple_trait!(trait Tuple14 for Tuple13 { type Item13; fn into_13; });
-tuple_trait!(trait Tuple15 for Tuple14 { type Item14; fn into_14; });
-tuple_trait!(trait Tuple16 for Tuple15 { type Item15; fn into_15; });
-tuple_trait!(trait Tuple17 for Tuple16 { type Item16; fn into_16; });
-tuple_trait!(trait Tuple18 for Tuple17 { type Item17; fn into_17; });
-tuple_trait!(trait Tuple19 for Tuple18 { type Item18; fn into_18; });
-tuple_trait!(trait Tuple20 for Tuple19 { type Item19; fn into_19; });
-tuple_trait!(trait Tuple21 for Tuple20 { type Item20; fn into_20; });
-tuple_trait!(trait Tuple22 for Tuple21 { type Item21; fn into_21; });
-tuple_trait!(trait Tuple23 for Tuple22 { type Item22; fn into_22; });
-tuple_trait!(trait Tuple24 for Tuple23 { type Item23; fn into_23; });
-tuple_trait!(trait Tuple25 for Tuple24 { type Item24; fn into_24; });
-tuple_trait!(trait Tuple26 for Tuple25 { type Item25; fn into_25; });
+macro_rules! tuple_trait {
+ ($number: literal) => {
+ tuple_trait_def! { $number, tuple_trait_inner }
+ };
+}
+
+repeat!(64, impl_tuple);
+repeat!(64, tuple_trait);
diff --git a/varihappy-macros/Cargo.toml b/varihappy-macros/Cargo.toml
index 3999ad5..0f98af9 100644
--- a/varihappy-macros/Cargo.toml
+++ b/varihappy-macros/Cargo.toml
@@ -10,3 +10,4 @@ proc-macro = true
proc-macro2 = "1"
syn = "2"
quote = "1"
+unsynn = "0.3"
diff --git a/varihappy-macros/src/lib.rs b/varihappy-macros/src/lib.rs
index 522d333..3931899 100644
--- a/varihappy-macros/src/lib.rs
+++ b/varihappy-macros/src/lib.rs
@@ -2,139 +2,198 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal, Span};
-use quote::quote;
-use syn::{Field, LitInt};
+use quote::{format_ident, quote};
+use syn::LitInt;
+use unsynn::{
+ Comma, CommaDelimitedVec, Cons, Either, GroupContaining, IParse, LiteralInteger, TokenIter,
+};
-#[proc_macro]
-pub fn concat_idents(input: TokenStream) -> TokenStream {
+fn number_to_type(mut number: u128) -> String {
let mut buffer = String::new();
- for token in input {
- buffer.push_str(&token.to_string());
+ 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"),
+ }
+ });
}
- let mut stream = proc_macro2::TokenStream::new();
- stream.extend([Ident::new(&buffer, Span::call_site())]);
- stream.into()
-}
-
-#[proc_macro]
-pub fn number_to_generic(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let identifier = Ident::new(
- 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"),
- },
- Span::call_site(),
- );
-
- quote! { #identifier }.into()
-}
-
-#[proc_macro]
-pub fn tuple_type_len(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = 0..number;
- quote! { (#(::varihappy_macros::number_to_generic!(#num),)*) }.into()
-}
-
-#[proc_macro]
-pub fn tuple_type_ref(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = 0..number;
- quote! { (#(&'a ::varihappy_macros::number_to_generic!(#num),)*) }.into()
+ 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_type_mut(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = 0..number;
- quote! { (#(&'a mut ::varihappy_macros::number_to_generic!(#num),)*) }.into()
+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 reverse_tuple_type(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = (0..number).rev();
- quote! { (#(::varihappy_macros::number_to_generic!(#num),)*) }.into()
+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_type_len_rest(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = 1..number;
- quote! { (#(::varihappy_macros::number_to_generic!(#num),)*) }.into()
+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 tuple_expr_ref(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = (0..number).map(Literal::u128_unsuffixed);
- quote! { (#(&self.#num,)*) }.into()
-}
-
-#[proc_macro]
-pub fn tuple_expr_mut(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
+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 num = (0..number).map(Literal::u128_unsuffixed);
- quote! { (#(&mut self.#num,)*) }.into()
+ let number = Literal::u128_unsuffixed(number);
+ quote! { this.#number }.into()
}
#[proc_macro]
-pub fn reverse_expr_type(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = (0..number).rev().map(Literal::u128_unsuffixed);
- quote! { (#(self.#num,)*) }.into()
+pub fn repeat(input: TokenStream) -> TokenStream {
+ let input: proc_macro2::TokenStream = input.into();
+ let mut token_iter = TokenIter::new(input);
+ let ast: Cons<LiteralInteger, Comma, unsynn::Ident> = token_iter.parse().unwrap();
+
+ let numbers = (2..=ast.first.value()).map(Literal::u128_unsuffixed);
+ let macro_name = ast.third;
+ quote! { #(#macro_name!(#numbers);)* }.into()
}
#[proc_macro]
-pub fn tuple_expr_len_rest(input: TokenStream) -> TokenStream {
- let number = syn::parse_macro_input!(input as LitInt);
- let number: u128 = number.base10_parse().expect("a number 0..=u128::MAX");
-
- let num = (1..number).map(Literal::u128_unsuffixed);
- quote! { (#(self.#num,)*) }.into()
+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()
}