diff --git a/source/tanya/meta/metafunction.d b/source/tanya/meta/metafunction.d index ac25768..6c46acf 100644 --- a/source/tanya/meta/metafunction.d +++ b/source/tanya/meta/metafunction.d @@ -15,9 +15,40 @@ */ module tanya.meta.metafunction; +import tanya.meta.trait; + version (unittest) { - import tanya.meta.trait; + import tanya.meta.transform; +} + +// Used for sorting. +private template lessEqual(alias cmp, Args...) +if (Args.length == 2) +{ + enum result = cmp!(Args[1], Args[0]); + static if (is(typeof(result) == bool)) + { + enum bool lessEqual = !result; + } + else + { + enum bool lessEqual = result >= 0; + } +} + +private template equalTo(Args...) +if (Args.length == 2) +{ + static if ((is(typeof(Args[0] == Args[1])) && (Args[0] == Args[1])) + || is(Args[0] == Args[1])) + { + enum bool equalTo = true; + } + else + { + enum bool equalTo = false; + } } /** @@ -165,8 +196,7 @@ if (Args.length > 0) { enum ptrdiff_t indexOf = -1; } - else static if (is(Args[0] == Args[1]) - || (is(typeof(Args[0] == Args[1])) && (Args[0] == Args[1]))) + else static if (equalTo!(Args[0 .. 2])) { enum ptrdiff_t indexOf = i; } @@ -185,7 +215,7 @@ if (Args.length > 0) * T = The type to search for. * L = Type list. * - * Returns: The index of the first occurence of $(D_PARAM T) in $(D_PARAM L). + * Returns: The index of the first occurrence of $(D_PARAM T) in $(D_PARAM L). */ template staticIndexOf(T, L...) { @@ -346,8 +376,8 @@ pure nothrow @safe @nogc unittest * ) * * Params: - * cmp = Comparison template. - * L = Arguments. + * cmp = Sorting template predicate. + * L = Elements to be tested. * * Returns: $(D_KEYWORD true) if $(D_PARAM L) is sorted, $(D_KEYWORD false) * if not. @@ -360,19 +390,10 @@ template staticIsSorted(alias cmp, L...) } else { - // `L` is sorted if the both halves are sorted. - enum bool halves = staticIsSorted!(cmp, L[0 .. $ / 2]) - && staticIsSorted!(cmp, L[$ / 2 .. $]); - // Compare the boundary values of the havles. - enum result = cmp!(L[$ / 2], L[$ / 2 - 1]); - static if (is(typeof(result) == bool)) - { - enum bool staticIsSorted = !result && halves; - } - else - { - enum bool staticIsSorted = result >= 0 && halves; - } + // `L` is sorted if the both halves and the boundary values are sorted. + enum bool staticIsSorted = lessEqual!(cmp, L[$ / 2 - 1], L[$ / 2]) + && staticIsSorted!(cmp, L[0 .. $ / 2]) + && staticIsSorted!(cmp, L[$ / 2 .. $]); } } @@ -451,3 +472,541 @@ pure nothrow @safe @nogc unittest static assert(intIs!(isIntegral)); static assert(!intIs!(isUnsigned)); } + +/** + * Params: + * n = The number of times to repeat $(D_PARAM L). + * L = The sequence to be repeated. + * + * Returns: $(D_PARAM L) repeated $(D_PARAM n) times. + */ +template Repeat(size_t n, L...) +if (n > 0) +{ + static if (n == 1) + { + alias Repeat = L; + } + else + { + alias Repeat = AliasSeq!(L, Repeat!(n - 1, L)); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(Repeat!(1, uint, int) == AliasSeq!(uint, int))); + static assert(is(Repeat!(2, uint, int) == AliasSeq!(uint, int, uint, int))); + static assert(is(Repeat!(3) == AliasSeq!())); +} + +private template ReplaceOne(L...) +{ + static if (L.length == 2) + { + alias ReplaceOne = AliasSeq!(); + } + else static if (equalTo!(L[0], L[2])) + { + alias ReplaceOne = AliasSeq!(L[1], L[3 .. $]); + } + else + { + alias ReplaceOne = AliasSeq!(L[2], ReplaceOne!(L[0], L[1], L[3 .. $])); + } +} + +/** + * Replaces the first occurrence of $(D_PARAM T) in $(D_PARAM L) with + * $(D_PARAM U). + * + * Params: + * T = The symbol to be replaced. + * U = Replacement. + * L = List of symbols. + * + * Returns: $(D_PARAM L) with the first occurrence of $(D_PARAM T) replaced. + */ +template Replace(T, U, L...) +{ + alias Replace = ReplaceOne!(T, U, L); +} + +/// Ditto. +template Replace(alias T, U, L...) +{ + alias Replace = ReplaceOne!(T, U, L); +} + +/// Ditto. +template Replace(T, alias U, L...) +{ + alias Replace = ReplaceOne!(T, U, L); +} + +/// Ditto. +template Replace(alias T, alias U, L...) +{ + alias Replace = ReplaceOne!(T, U, L); +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(Replace!(int, uint, int) == AliasSeq!(uint))); + static assert(is(Replace!(int, uint, short, int, int, ushort) + == AliasSeq!(short, uint, int, ushort))); + + static assert(Replace!(5, 8, 1, 2, 5, 5) == AliasSeq!(1, 2, 8, 5)); +} + +private template ReplaceAllImpl(L...) +{ + static if (L.length == 2) + { + alias ReplaceAllImpl = AliasSeq!(); + } + else + { + private alias Rest = ReplaceAllImpl!(L[0], L[1], L[3 .. $]); + static if (equalTo!(L[0], L[2])) + { + alias ReplaceAllImpl = AliasSeq!(L[1], Rest); + } + else + { + alias ReplaceAllImpl = AliasSeq!(L[2], Rest); + } + } +} + +/** + * Replaces all occurrences of $(D_PARAM T) in $(D_PARAM L) with $(D_PARAM U). + * + * Params: + * T = The symbol to be replaced. + * U = Replacement. + * L = List of symbols. + * + * Returns: $(D_PARAM L) with all occurrences of $(D_PARAM T) replaced. + */ +template ReplaceAll(T, U, L...) +{ + alias ReplaceAll = ReplaceAllImpl!(T, U, L); +} + +/// Ditto. +template ReplaceAll(alias T, U, L...) +{ + alias ReplaceAll = ReplaceAllImpl!(T, U, L); +} + +/// Ditto. +template ReplaceAll(T, alias U, L...) +{ + alias ReplaceAll = ReplaceAllImpl!(T, U, L); +} + +/// Ditto. +template ReplaceAll(alias T, alias U, L...) +{ + alias ReplaceAll = ReplaceAllImpl!(T, U, L); +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(ReplaceAll!(int, uint, int) == AliasSeq!(uint))); + static assert(is(ReplaceAll!(int, uint, short, int, int, ushort) + == AliasSeq!(short, uint, uint, ushort))); + + static assert(ReplaceAll!(5, 8, 1, 2, 5, 5) == AliasSeq!(1, 2, 8, 8)); +} + +/** + * Params: + * L = List of symbols. + * + * Returns: $(D_PARAM L) with elements in reversed order. + */ +template Reverse(L...) +{ + static if (L.length == 0) + { + alias Reverse = AliasSeq!(); + } + else + { + alias Reverse = AliasSeq!(L[$ - 1], Reverse!(L[0 .. $ - 1])); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(Reverse!(byte, short, int) == AliasSeq!(int, short, byte))); +} + +/** + * Applies $(D_PARAM F) to all elements of $(D_PARAM T). + * + * Params: + * F = Template predicate. + * T = List of symbols. + * + * Returns: Elements $(D_PARAM T) after applying $(D_PARAM F) to them. + */ +template staticMap(alias F, T...) +{ + static if (T.length == 0) + { + alias staticMap = AliasSeq!(); + } + else + { + alias staticMap = AliasSeq!(F!(T[0]), staticMap!(F, T[1 .. $])); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(staticMap!(Unqual, const int, immutable short) + == AliasSeq!(int, short))); +} + +/** + * Sorts $(D_PARAM L) in ascending order according to $(D_PARAM cmp). + * + * $(D_PARAM cmp) can evaluate to: + * $(UL + * $(LI $(D_KEYWORD bool): $(D_KEYWORD true) means + * $(D_INLINECODE a[i] < a[i + 1]).) + * $(LI $(D_KEYWORD int): a negative number means that + * $(D_INLINECODE a[i] < a[i + 1]), a positive number that + * $(D_INLINECODE a[i] > a[i + 1]), `0` if they equal.) + * ) + * + * Merge sort is used to sort the arguments. + * + * Params: + * cmp = Sorting template predicate. + * L = Elements to be sorted. + * + * Returns: Elements of $(D_PARAM L) in ascending order. + * + * See_Also: $(LINK2 https://en.wikipedia.org/wiki/Merge_sort, Merge sort). + */ +template staticSort(alias cmp, L...) +{ + private template merge(size_t A, size_t B) + { + static if (A + B == L.length) + { + alias merge = AliasSeq!(); + } + else static if (B >= Right.length + || (A < Left.length && lessEqual!(cmp, Left[A], Right[B]))) + { + alias merge = AliasSeq!(Left[A], merge!(A + 1, B)); + } + else + { + alias merge = AliasSeq!(Right[B], merge!(A, B + 1)); + } + } + + static if (L.length <= 1) + { + alias staticSort = L; + } + else + { + private alias Left = staticSort!(cmp, L[0 .. $ / 2]); + private alias Right = staticSort!(cmp, L[$ / 2 .. $]); + alias staticSort = merge!(0, 0); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + enum cmp(T, U) = T.sizeof < U.sizeof; + static assert(is(staticSort!(cmp, long, short, byte, int) + == AliasSeq!(byte, short, int, long))); +} + +private pure nothrow @safe @nogc unittest +{ + enum cmp(int T, int U) = T - U; + static assert(staticSort!(cmp, 5, 17, 9, 12, 2, 10, 14) + == AliasSeq!(2, 5, 9, 10, 12, 14, 17)); +} + +private enum bool DerivedToFrontCmp(A, B) = is(A : B); + +/** + * Returns $(D_PARAM L) sorted in such a way that the most derived types come + * first. + * + * Params: + * L = Type tuple. + * + * Returns: Sorted $(D_PARAM L). + */ +template DerivedToFront(L...) +{ + alias DerivedToFront = staticSort!(DerivedToFrontCmp, L); +} + +/// +pure nothrow @safe @nogc unittest +{ + class A + { + } + class B : A + { + } + class C : B + { + } + static assert(is(DerivedToFront!(B, A, C) == AliasSeq!(C, B, A))); +} + +/** + * Returns the type from the type tuple $(D_PARAM L) that is most derived from + * $(D_PARAM T). + * + * Params: + * T = The type to compare to. + * L = Type tuple. + * + * Returns: The type most derived from $(D_PARAM T). + */ +template MostDerived(T, L...) +{ + static if (L.length == 0) + { + alias MostDerived = T; + } + else static if (is(T : L[0])) + { + alias MostDerived = MostDerived!(T, L[1 .. $]); + } + else + { + alias MostDerived = MostDerived!(L[0], L[1 .. $]); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + class A + { + } + class B : A + { + } + class C : B + { + } + static assert(is(MostDerived!(A, C, B) == C)); +} + +private template EraseOne(L...) +if (L.length > 0) +{ + static if (L.length == 1) + { + alias EraseOne = AliasSeq!(); + } + else static if (equalTo!(L[0 .. 2])) + { + alias EraseOne = AliasSeq!(L[2 .. $]); + } + else + { + alias EraseOne = AliasSeq!(L[1], EraseOne!(L[0], L[2 .. $])); + } +} + +/** + * Removes the first occurrence of $(D_PARAM T) from the alias sequence + * $(D_PARAL L). + * + * Params: + * T = The item to be removed. + * L = Alias sequence. + * + * Returns: $(D_PARAM L) with the first occurrence of $(D_PARAM T) removed. + */ +template Erase(T, L...) +{ + alias Erase = EraseOne!(T, L); +} + +/// Ditto. +template Erase(alias T, L...) +{ + alias Erase = EraseOne!(T, L); +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(Erase!(int, short, int, int, uint) == AliasSeq!(short, int, uint))); + static assert(is(Erase!(int, short, uint) == AliasSeq!(short, uint))); +} + +private template EraseAllImpl(L...) +{ + static if (L.length == 1) + { + alias EraseAllImpl = AliasSeq!(); + } + else static if (equalTo!(L[0 .. 2])) + { + alias EraseAllImpl = EraseAllImpl!(L[0], L[2 .. $]); + } + else + { + alias EraseAllImpl = AliasSeq!(L[1], EraseAllImpl!(L[0], L[2 .. $])); + } +} + +/** + * Removes all occurrences of $(D_PARAM T) from the alias sequence $(D_PARAL L). + * + * Params: + * T = The item to be removed. + * L = Alias sequence. + * + * Returns: $(D_PARAM L) with all occurrences of $(D_PARAM T) removed. + */ +template EraseAll(T, L...) +{ + alias EraseAll = EraseAllImpl!(T, L); +} + +/// Ditto. +template EraseAll(alias T, L...) +{ + alias EraseAll = EraseAllImpl!(T, L); +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(EraseAll!(int, short, int, int, uint) == AliasSeq!(short, uint))); + static assert(is(EraseAll!(int, short, uint) == AliasSeq!(short, uint))); + static assert(is(EraseAll!(int, int, int) == AliasSeq!())); +} + +/** + * Returns an alias sequence which contains only items that satisfy the + * condition $(D_PARAM pred). + * + * Params: + * pred = Template predicate. + * L = Alias sequence. + * + * Returns: $(D_PARAM L) filtered so that it contains only items that satisfy + * $(D_PARAM pred). + */ +template Filter(alias pred, L...) +{ + static if (L.length == 0) + { + alias Filter = AliasSeq!(); + } + else static if (pred!(L[0])) + { + alias Filter = AliasSeq!(L[0], Filter!(pred, L[1 .. $])); + } + else + { + alias Filter = Filter!(pred, L[1 .. $]); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + static assert(is(Filter!(isIntegral, real, int, bool, uint) == AliasSeq!(int, uint))); +} + +/** + * Removes all duplicates from the alias sequence $(D_PARAM L). + * + * Params: + * L = Alias sequence. + * + * Returns: $(D_PARAM L) containing only unique items. + */ +template NoDuplicates(L...) +{ + static if (L.length == 0) + { + alias NoDuplicates = AliasSeq!(); + } + else + { + private alias Rest = NoDuplicates!(EraseAll!(L[0], L[1 .. $])); + alias NoDuplicates = AliasSeq!(L[0], Rest); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + alias Types = AliasSeq!(int, uint, int, short, short, uint); + static assert(is(NoDuplicates!Types == AliasSeq!(int, uint, short))); +} + +/** + * Converts an input range $(D_PARAM range) into an alias sequence. + * + * Params: + * range = Input range. + * + * Returns: Alias sequence with items from $(D_PARAM range). + */ +template aliasSeqOf(alias range) +{ + static if (isArray!(typeof(range))) + { + static if (range.length == 0) + { + alias aliasSeqOf = AliasSeq!(); + } + else + { + alias aliasSeqOf = AliasSeq!(range[0], aliasSeqOf!(range[1 .. $])); + } + } + else + { + ReturnType!(typeof(&range.front))[] toArray(typeof(range) range) + { + typeof(return) result; + foreach (r; range) + { + result ~= r; + } + return result; + } + alias aliasSeqOf = aliasSeqOf!(toArray(range)); + } +} + +/// +pure nothrow @safe @nogc unittest +{ + foreach (i, e; aliasSeqOf!([0, 1, 2, 3])) + { + static assert(i == e); + } +} diff --git a/source/tanya/meta/trait.d b/source/tanya/meta/trait.d index c4a79b8..7291aae 100644 --- a/source/tanya/meta/trait.d +++ b/source/tanya/meta/trait.d @@ -1624,7 +1624,7 @@ pure nothrow @safe @nogc unittest * Returns: Type of the function $(D_PARAM F). */ template FunctionTypeOf(F...) -if (F.length == 1 && isCallable!F) +if (isCallable!F) { static if ((is(typeof(F[0]) T : T*) && is(T == function)) || (is(F[0] T : T*) && is(T == function)) @@ -1705,3 +1705,22 @@ private pure nothrow @safe @nogc unittest static assert(is(FunctionTypeOf!S2 == function)); static assert(is(FunctionTypeOf!s2 == function)); } + +/** + * Params: + * F = A callable object. + * + * Returns: Return type of $(D_PARAM F). + */ +template ReturnType(F...) +if (isCallable!F) +{ + static if (is(FunctionTypeOf!(F[0]) T == return)) + { + alias ReturnType = T; + } + else + { + static assert(false, "Argument is not a callable"); + } +}