summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2025-08-25 16:09:03 +0200
committerEugen Wissner <belka@caraus.de>2025-08-25 16:09:03 +0200
commitb8fa670c5a1e6e856ced5d1be7f04360e91e0c7d (patch)
tree14cbbbc198a6bc2c0fb56219af375c6ddf95a4e1 /source
parent720d259cfc1db61d2deadca8b3e7751182d789b2 (diff)
downloadtanya-b8fa670c5a1e6e856ced5d1be7f04360e91e0c7d.tar.gz
Merge os, middle and meta subpackages
Diffstat (limited to 'source')
-rw-r--r--source/tanya/algorithm/iteration.d4
-rw-r--r--source/tanya/algorithm/mutation.d5
-rw-r--r--source/tanya/container/array.d4
-rw-r--r--source/tanya/container/buffer.d2
-rw-r--r--source/tanya/container/entry.d4
-rw-r--r--source/tanya/container/hashtable.d4
-rw-r--r--source/tanya/container/list.d4
-rw-r--r--source/tanya/container/set.d4
-rw-r--r--source/tanya/container/string.d4
-rw-r--r--source/tanya/conv.d5
-rw-r--r--source/tanya/format.d9
-rw-r--r--source/tanya/hash/lookup.d4
-rw-r--r--source/tanya/math/package.d434
-rw-r--r--source/tanya/net/iface.d4
-rw-r--r--source/tanya/net/inet.d4
-rw-r--r--source/tanya/net/ip.d4
-rw-r--r--source/tanya/range/adapter.d4
-rw-r--r--source/tanya/range/primitive.d4
-rw-r--r--source/tanya/test/assertion.d106
-rw-r--r--source/tanya/test/package.d18
-rw-r--r--source/tanya/test/stub.d397
21 files changed, 559 insertions, 469 deletions
diff --git a/source/tanya/algorithm/iteration.d b/source/tanya/algorithm/iteration.d
index 41c5f8b..df1027d 100644
--- a/source/tanya/algorithm/iteration.d
+++ b/source/tanya/algorithm/iteration.d
@@ -20,10 +20,10 @@
*/
module tanya.algorithm.iteration;
-import std.traits : Unqual, isMutable;
+import std.traits;
import std.typecons;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
private struct SingletonByValue(E)
diff --git a/source/tanya/algorithm/mutation.d b/source/tanya/algorithm/mutation.d
index e1e7c22..1a1bb56 100644
--- a/source/tanya/algorithm/mutation.d
+++ b/source/tanya/algorithm/mutation.d
@@ -14,11 +14,10 @@
*/
module tanya.algorithm.mutation;
-import std.traits : Unqual, hasElaborateAssign, hasElaborateCopyConstructor, hasElaborateDestructor, isAssignable,
- isDynamicArray;
+import std.traits;
static import tanya.memory.lifetime;
static import tanya.memory.op;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
/**
diff --git a/source/tanya/container/array.d b/source/tanya/container/array.d
index b3b3455..b6c9c5d 100644
--- a/source/tanya/container/array.d
+++ b/source/tanya/container/array.d
@@ -18,11 +18,11 @@ import core.checkedint;
import std.algorithm.comparison;
import std.algorithm.iteration;
import std.algorithm.mutation : bringToFront;
-import std.traits : PointerTarget, Unqual, hasElaborateDestructor, isImplicitlyConvertible, isCopyable;
+import std.traits;
import tanya.algorithm.mutation;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
/**
diff --git a/source/tanya/container/buffer.d b/source/tanya/container/buffer.d
index 35d83a5..ec8cbf0 100644
--- a/source/tanya/container/buffer.d
+++ b/source/tanya/container/buffer.d
@@ -16,7 +16,7 @@ module tanya.container.buffer;
import std.traits : isScalarType;
import tanya.memory.allocator;
-import tanya.meta.trait;
+import tanya.meta;
version (unittest)
{
diff --git a/source/tanya/container/entry.d b/source/tanya/container/entry.d
index 52b3e6a..c4d74c2 100644
--- a/source/tanya/container/entry.d
+++ b/source/tanya/container/entry.d
@@ -14,11 +14,11 @@
*/
module tanya.container.entry;
-import std.traits : Unqual, hasElaborateDestructor;
+import std.traits;
import tanya.container.array;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
package struct SEntry(T)
{
diff --git a/source/tanya/container/hashtable.d b/source/tanya/container/hashtable.d
index d74783c..5e9a4e4 100644
--- a/source/tanya/container/hashtable.d
+++ b/source/tanya/container/hashtable.d
@@ -15,14 +15,14 @@
module tanya.container.hashtable;
import std.algorithm.iteration;
-import std.traits : CopyConstness, Unqual, ifTestable, isMutable;
+import std.traits;
import tanya.algorithm.mutation;
import tanya.container.array;
import tanya.container.entry;
import tanya.hash.lookup;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range.primitive;
/**
diff --git a/source/tanya/container/list.d b/source/tanya/container/list.d
index 23af88d..ddef391 100644
--- a/source/tanya/container/list.d
+++ b/source/tanya/container/list.d
@@ -17,11 +17,11 @@ module tanya.container.list;
import std.algorithm.comparison;
import std.algorithm.iteration;
-import std.traits : Unqual, isImplicitlyConvertible, isCopyable;
+import std.traits;
import tanya.container.entry;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range.array;
import tanya.range.primitive;
diff --git a/source/tanya/container/set.d b/source/tanya/container/set.d
index 67fc66a..5a4f64c 100644
--- a/source/tanya/container/set.d
+++ b/source/tanya/container/set.d
@@ -15,13 +15,13 @@
*/
module tanya.container.set;
-import std.traits : CopyConstness, Unqual, ifTestable, isImplicitlyConvertible, isMutable;
+import std.traits;
import tanya.container.array;
import tanya.container.entry;
import tanya.hash.lookup;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range.primitive;
/**
diff --git a/source/tanya/container/string.d b/source/tanya/container/string.d
index 71328f2..10db553 100644
--- a/source/tanya/container/string.d
+++ b/source/tanya/container/string.d
@@ -28,12 +28,12 @@ module tanya.container.string;
import std.algorithm.comparison;
import std.algorithm.mutation : bringToFront;
-import std.traits : CopyConstness, Unqual, isInstanceOf, isSomeChar, isNarrowString;
+import std.traits;
import tanya.algorithm.mutation;
import tanya.hash.lookup;
import tanya.memory.allocator;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range.array;
import tanya.range.primitive;
diff --git a/source/tanya/conv.d b/source/tanya/conv.d
index 8f46bd3..3a75d62 100644
--- a/source/tanya/conv.d
+++ b/source/tanya/conv.d
@@ -14,11 +14,10 @@
*/
module tanya.conv;
-import std.traits : Unsigned, isNumeric, Largest, Unqual, EnumMembers, isFloatingPoint, isSomeChar, isSigned,
- isUnsigned, isIntegral, isSomeString;
+import std.traits;
import tanya.container.string;
import tanya.memory.allocator;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
/**
diff --git a/source/tanya/format.d b/source/tanya/format.d
index 0b031d1..089ed49 100644
--- a/source/tanya/format.d
+++ b/source/tanya/format.d
@@ -49,12 +49,13 @@ module tanya.format;
import std.algorithm.comparison;
import std.ascii;
-import std.traits : Unqual, isPointer, isSomeChar, isFloatingPoint, isSomeFunction, isIntegral, isSomeString;
+import std.math : signbit;
+import std.meta;
+import std.traits;
import tanya.container.string;
import tanya.math;
static import tanya.memory.op;
-import tanya.meta.metafunction;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
// Returns the last part of buffer with converted number.
@@ -1952,7 +1953,7 @@ private const(char)[] real2String(double value,
const FloatBits!double bits = { value };
exponent = (bits.integral >> 52) & 0x7ff;
- sign = signBit(value);
+ sign = !!signbit(value);
if (sign)
{
value = -value;
diff --git a/source/tanya/hash/lookup.d b/source/tanya/hash/lookup.d
index 706cca5..eb26caa 100644
--- a/source/tanya/hash/lookup.d
+++ b/source/tanya/hash/lookup.d
@@ -14,8 +14,8 @@
*/
module tanya.hash.lookup;
-import std.traits : isScalarType, isPointer, isSomeChar, isArray, isIntegral, isBoolean;
-import tanya.meta.trait;
+import std.traits;
+import tanya.meta;
import tanya.range.primitive;
private struct Hasher
diff --git a/source/tanya/math/package.d b/source/tanya/math/package.d
index ef0592e..66923b6 100644
--- a/source/tanya/math/package.d
+++ b/source/tanya/math/package.d
@@ -22,8 +22,8 @@
module tanya.math;
import std.math;
-import std.traits : Unqual, isFloatingPoint;
-import tanya.meta.trait;
+import std.traits;
+import tanya.meta;
/// Floating-point number precisions according to IEEE-754.
enum IEEEPrecision : ubyte
@@ -113,433 +113,3 @@ package(tanya) union FloatBits(F)
static assert(false, "Unsupported IEEE 754 floating point precision");
}
}
-
-/**
- * Floating-point number classifications.
- */
-enum FloatingPointClass : ubyte
-{
- /**
- * Not a Number.
- *
- * See_Also: $(D_PSYMBOL isNaN).
- */
- nan,
-
- /// Zero.
- zero,
-
- /**
- * Infinity.
- *
- * See_Also: $(D_PSYMBOL isInfinity).
- */
- infinite,
-
- /**
- * Denormalized number.
- *
- * See_Also: $(D_PSYMBOL isSubnormal).
- */
- subnormal,
-
- /**
- * Normalized number.
- *
- * See_Also: $(D_PSYMBOL isNormal).
- */
- normal,
-}
-
-/**
- * Returns whether $(D_PARAM x) is a NaN, zero, infinity, subnormal or
- * normalized number.
- *
- * This function doesn't distinguish between negative and positive infinity,
- * negative and positive NaN or negative and positive zero.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: Classification of $(D_PARAM x).
- */
-FloatingPointClass classify(F)(F x)
-if (isFloatingPoint!F)
-{
- if (x == 0)
- {
- return FloatingPointClass.zero;
- }
- FloatBits!F bits;
- bits.floating = abs(x);
-
- static if (ieeePrecision!F == IEEEPrecision.single)
- {
- if (bits.integral > bits.expMask)
- {
- return FloatingPointClass.nan;
- }
- else if (bits.integral == bits.expMask)
- {
- return FloatingPointClass.infinite;
- }
- else if (bits.integral < (1 << 23))
- {
- return FloatingPointClass.subnormal;
- }
- }
- else static if (ieeePrecision!F == IEEEPrecision.double_)
- {
- if (bits.integral > bits.expMask)
- {
- return FloatingPointClass.nan;
- }
- else if (bits.integral == bits.expMask)
- {
- return FloatingPointClass.infinite;
- }
- else if (bits.integral < (1L << 52))
- {
- return FloatingPointClass.subnormal;
- }
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- if (bits.exp == bits.expMask)
- {
- if ((bits.mantissa & bits.mantissaMask) == 0)
- {
- return FloatingPointClass.infinite;
- }
- else
- {
- return FloatingPointClass.nan;
- }
- }
- else if (bits.exp == 0)
- {
- return FloatingPointClass.subnormal;
- }
- else if (bits.mantissa < (1L << 63)) // "Unnormal".
- {
- return FloatingPointClass.nan;
- }
- }
-
- return FloatingPointClass.normal;
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(classify(0.0) == FloatingPointClass.zero);
- assert(classify(double.nan) == FloatingPointClass.nan);
- assert(classify(double.infinity) == FloatingPointClass.infinite);
- assert(classify(-double.infinity) == FloatingPointClass.infinite);
- assert(classify(1.4) == FloatingPointClass.normal);
- assert(classify(1.11254e-307 / 10) == FloatingPointClass.subnormal);
-
- assert(classify(0.0f) == FloatingPointClass.zero);
- assert(classify(float.nan) == FloatingPointClass.nan);
- assert(classify(float.infinity) == FloatingPointClass.infinite);
- assert(classify(-float.infinity) == FloatingPointClass.infinite);
- assert(classify(0.3) == FloatingPointClass.normal);
- assert(classify(5.87747e-38f / 10) == FloatingPointClass.subnormal);
-
- assert(classify(0.0L) == FloatingPointClass.zero);
- assert(classify(real.nan) == FloatingPointClass.nan);
- assert(classify(real.infinity) == FloatingPointClass.infinite);
- assert(classify(-real.infinity) == FloatingPointClass.infinite);
-}
-
-/**
- * Determines whether $(D_PARAM x) is a finite number.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a finite number,
- * $(D_KEYWORD false) otherwise.
- *
- * See_Also: $(D_PSYMBOL isInfinity).
- */
-bool isFinite(F)(F x)
-if (isFloatingPoint!F)
-{
- FloatBits!F bits;
- static if (ieeePrecision!F == IEEEPrecision.single
- || ieeePrecision!F == IEEEPrecision.double_)
- {
- bits.floating = x;
- bits.integral &= bits.expMask;
- return bits.integral != bits.expMask;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- bits.floating = abs(x);
- return (bits.exp != bits.expMask)
- && (bits.exp == 0 || bits.mantissa >= (1L << 63));
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(!isFinite(float.infinity));
- assert(!isFinite(-double.infinity));
- assert(isFinite(0.0));
- assert(!isFinite(float.nan));
- assert(isFinite(5.87747e-38f / 10));
- assert(isFinite(1.11254e-307 / 10));
- assert(isFinite(0.5));
-}
-
-/**
- * Determines whether $(D_PARAM x) is $(B n)ot $(B a) $(B n)umber (NaN).
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if $(D_PARAM x) is not a number,
- * $(D_KEYWORD false) otherwise.
- */
-bool isNaN(F)(F x)
-if (isFloatingPoint!F)
-{
- FloatBits!F bits;
- bits.floating = abs(x);
-
- static if (ieeePrecision!F == IEEEPrecision.single
- || ieeePrecision!F == IEEEPrecision.double_)
- {
- return bits.integral > bits.expMask;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- const maskedMantissa = bits.mantissa & bits.mantissaMask;
- if ((bits.exp == bits.expMask && maskedMantissa != 0)
- || ((bits.exp != 0) && (bits.mantissa < (1L << 63))))
- {
- return true;
- }
- return false;
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(isNaN(float.init));
- assert(isNaN(double.init));
- assert(isNaN(real.init));
-}
-
-/**
- * Determines whether $(D_PARAM x) is a positive or negative infinity.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if $(D_PARAM x) is infinity, $(D_KEYWORD false)
- * otherwise.
- *
- * See_Also: $(D_PSYMBOL isFinite).
- */
-bool isInfinity(F)(F x)
-if (isFloatingPoint!F)
-{
- FloatBits!F bits;
- bits.floating = abs(x);
- static if (ieeePrecision!F == IEEEPrecision.single
- || ieeePrecision!F == IEEEPrecision.double_)
- {
- return bits.integral == bits.expMask;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- return (bits.exp == bits.expMask)
- && ((bits.mantissa & bits.mantissaMask) == 0);
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(isInfinity(float.infinity));
- assert(isInfinity(-float.infinity));
- assert(isInfinity(double.infinity));
- assert(isInfinity(-double.infinity));
- assert(isInfinity(real.infinity));
- assert(isInfinity(-real.infinity));
-}
-
-/**
- * Determines whether $(D_PARAM x) is a denormilized number or not.
- *
- * Denormalized number is a number between `0` and `1` that cannot be
- * represented as
- *
- * <pre>
- * m*2<sup>e</sup>
- * </pre>
- *
- * where $(I m) is the mantissa and $(I e) is an exponent that fits into the
- * exponent field of the type $(D_PARAM F).
- *
- * `0` is neither normalized nor denormalized.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a denormilized number,
- * $(D_KEYWORD false) otherwise.
- *
- * See_Also: $(D_PSYMBOL isNormal).
- */
-bool isSubnormal(F)(F x)
-if (isFloatingPoint!F)
-{
- FloatBits!F bits;
- bits.floating = abs(x);
- static if (ieeePrecision!F == IEEEPrecision.single)
- {
- return bits.integral < (1 << 23) && bits.integral > 0;
- }
- else static if (ieeePrecision!F == IEEEPrecision.double_)
- {
- return bits.integral < (1L << 52) && bits.integral > 0;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- return bits.exp == 0 && bits.mantissa != 0;
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(!isSubnormal(0.0f));
- assert(!isSubnormal(float.nan));
- assert(!isSubnormal(float.infinity));
- assert(!isSubnormal(0.3f));
- assert(isSubnormal(5.87747e-38f / 10));
-
- assert(!isSubnormal(0.0));
- assert(!isSubnormal(double.nan));
- assert(!isSubnormal(double.infinity));
- assert(!isSubnormal(1.4));
- assert(isSubnormal(1.11254e-307 / 10));
-
- assert(!isSubnormal(0.0L));
- assert(!isSubnormal(real.nan));
- assert(!isSubnormal(real.infinity));
-}
-
-/**
- * Determines whether $(D_PARAM x) is a normilized number or not.
- *
- * Normalized number is a number that can be represented as
- *
- * <pre>
- * m*2<sup>e</sup>
- * </pre>
- *
- * where $(I m) is the mantissa and $(I e) is an exponent that fits into the
- * exponent field of the type $(D_PARAM F).
- *
- * `0` is neither normalized nor denormalized.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a normilized number,
- * $(D_KEYWORD false) otherwise.
- *
- * See_Also: $(D_PSYMBOL isSubnormal).
- */
-bool isNormal(F)(F x)
-if (isFloatingPoint!F)
-{
- static if (ieeePrecision!F == IEEEPrecision.single
- || ieeePrecision!F == IEEEPrecision.double_)
- {
- FloatBits!F bits;
- bits.floating = x;
- bits.integral &= bits.expMask;
- return bits.integral != 0 && bits.integral != bits.expMask;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- return classify(x) == FloatingPointClass.normal;
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(!isNormal(0.0f));
- assert(!isNormal(float.nan));
- assert(!isNormal(float.infinity));
- assert(isNormal(0.3f));
- assert(!isNormal(5.87747e-38f / 10));
-
- assert(!isNormal(0.0));
- assert(!isNormal(double.nan));
- assert(!isNormal(double.infinity));
- assert(isNormal(1.4));
- assert(!isNormal(1.11254e-307 / 10));
-
- assert(!isNormal(0.0L));
- assert(!isNormal(real.nan));
- assert(!isNormal(real.infinity));
-}
-
-/**
- * Determines whether the sign bit of $(D_PARAM x) is set or not.
- *
- * If the sign bit, $(D_PARAM x) is a negative number, otherwise positive.
- *
- * Params:
- * F = Type of the floating point number.
- * x = Floating point number.
- *
- * Returns: $(D_KEYWORD true) if the sign bit of $(D_PARAM x) is set,
- * $(D_KEYWORD false) otherwise.
- */
-bool signBit(F)(F x)
-if (isFloatingPoint!F)
-{
- FloatBits!F bits;
- bits.floating = x;
- static if (ieeePrecision!F == IEEEPrecision.single)
- {
- return (bits.integral & (1 << 31)) != 0;
- }
- else static if (ieeePrecision!F == IEEEPrecision.double_)
- {
- return (bits.integral & (1L << 63)) != 0;
- }
- else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
- {
- return (bits.exp & (1 << 15)) != 0;
- }
-}
-
-///
-@nogc nothrow pure @safe unittest
-{
- assert(signBit(-1.0f));
- assert(!signBit(1.0f));
-
- assert(signBit(-1.0));
- assert(!signBit(1.0));
-
- assert(signBit(-1.0L));
- assert(!signBit(1.0L));
-}
diff --git a/source/tanya/net/iface.d b/source/tanya/net/iface.d
index 8443cb4..c910b29 100644
--- a/source/tanya/net/iface.d
+++ b/source/tanya/net/iface.d
@@ -14,10 +14,10 @@
*/
module tanya.net.iface;
-import std.traits : Unqual;
+import std.traits;
import tanya.algorithm.mutation;
import tanya.container.string;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
version (Windows)
diff --git a/source/tanya/net/inet.d b/source/tanya/net/inet.d
index 314a90c..141fd77 100644
--- a/source/tanya/net/inet.d
+++ b/source/tanya/net/inet.d
@@ -14,8 +14,8 @@
*/
module tanya.net.inet;
-import std.traits : Unqual, isUnsigned;
-import tanya.meta.trait;
+import std.traits;
+import tanya.meta;
import tanya.range;
/**
diff --git a/source/tanya/net/ip.d b/source/tanya/net/ip.d
index 73ef23d..87268bd 100644
--- a/source/tanya/net/ip.d
+++ b/source/tanya/net/ip.d
@@ -18,14 +18,14 @@ import std.algorithm.comparison;
import std.ascii;
import std.sumtype;
import std.typecons;
-import std.traits : Unqual;
+import std.traits;
import tanya.algorithm.iteration;
import tanya.algorithm.mutation;
import tanya.container.string;
import tanya.conv;
import tanya.format;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.net.iface;
import tanya.net.inet;
import tanya.range;
diff --git a/source/tanya/range/adapter.d b/source/tanya/range/adapter.d
index d30b24c..287e868 100644
--- a/source/tanya/range/adapter.d
+++ b/source/tanya/range/adapter.d
@@ -14,10 +14,10 @@
*/
module tanya.range.adapter;
-import std.traits : hasMember, isArray;
+import std.traits;
import tanya.algorithm.mutation;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range;
private mixin template InserterCtor()
diff --git a/source/tanya/range/primitive.d b/source/tanya/range/primitive.d
index aa8c0cf..d8abe3d 100644
--- a/source/tanya/range/primitive.d
+++ b/source/tanya/range/primitive.d
@@ -15,9 +15,9 @@
module tanya.range.primitive;
import std.algorithm.comparison;
-import std.traits : FunctionAttribute, ReturnType, hasElaborateCopyConstructor, functionAttributes;
+import std.traits;
import tanya.memory.lifetime;
-import tanya.meta.trait;
+import tanya.meta;
import tanya.range.array;
/**
diff --git a/source/tanya/test/assertion.d b/source/tanya/test/assertion.d
new file mode 100644
index 0000000..9b146c8
--- /dev/null
+++ b/source/tanya/test/assertion.d
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Additional assertions.
+ *
+ * This module provides functions that assert whether a given expression
+ * satisfies some complex condition, that can't be tested with
+ * $(D_KEYWORD assert) in a single line. Internally all the functions
+ * just evaluate the expression and call $(D_KEYWORD assert).
+ *
+ * The functions can cause segmentation fault if the module is compiled
+ * in production mode and the condition fails.
+ *
+ * Copyright: Eugene Wissner 2017-2025.
+ * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
+ * Mozilla Public License, v. 2.0).
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/assertion.d,
+ * tanya/test/assertion.d)
+ */
+module tanya.test.assertion;
+
+import std.traits;
+import tanya.memory.allocator;
+import tanya.meta;
+
+/**
+ * Asserts whether the function $(D_PARAM expr) throws an exception of type
+ * $(D_PARAM E). If it does, the exception is catched and properly destroyed.
+ * If it doesn't, an assertion error is thrown. If the exception doesn't match
+ * $(D_PARAM E) type, it isn't catched and escapes.
+ *
+ * Params:
+ * E = Expected exception type.
+ * T = Throwing function type.
+ * Args = Argument types of the throwing function.
+ * expr = Throwing function.
+ * args = Arguments for $(D_PARAM expr).
+ */
+void assertThrown(E : Exception, T, Args...)(T expr, auto ref Args args)
+if (isSomeFunction!T)
+{
+ try
+ {
+ cast(void) expr(args);
+ assert(false, "Expected exception not thrown");
+ }
+ catch (E exception)
+ {
+ defaultAllocator.dispose(exception);
+ }
+}
+
+///
+@nogc nothrow pure @safe unittest
+{
+ // If you want to test that an expression throws, you can wrap it into an
+ // arrow function.
+ static struct CtorThrows
+ {
+ this(int i) @nogc pure @safe
+ {
+ throw defaultAllocator.make!Exception();
+ }
+ }
+ assertThrown!Exception(() => CtorThrows(8));
+}
+
+/**
+ * Asserts that the function $(D_PARAM expr) doesn't throw.
+ *
+ * If it does, the thrown exception is catched, properly destroyed and an
+ * assertion error is thrown instead.
+ *
+ * Params:
+ * T = Tested function type.
+ * Args = Argument types of $(D_PARAM expr).
+ * expr = Tested function.
+ * args = Arguments for $(D_PARAM expr).
+ */
+void assertNotThrown(T, Args...)(T expr, auto ref Args args)
+if (isSomeFunction!T)
+{
+ try
+ {
+ cast(void) expr(args);
+ }
+ catch (Exception exception)
+ {
+ defaultAllocator.dispose(exception);
+ assert(false, "Unexpected exception thrown");
+ }
+}
+
+///
+@nogc nothrow pure @safe unittest
+{
+ // If you want to test that an expression doesn't throw, you can wrap it
+ // into an arrow function.
+ static struct S
+ {
+ }
+ assertNotThrown(() => S());
+}
diff --git a/source/tanya/test/package.d b/source/tanya/test/package.d
new file mode 100644
index 0000000..f316ddf
--- /dev/null
+++ b/source/tanya/test/package.d
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test suite for $(D_KEYWORD unittest)-blocks.
+ *
+ * Copyright: Eugene Wissner 2017-2025.
+ * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
+ * Mozilla Public License, v. 2.0).
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/package.d,
+ * tanya/test/package.d)
+ */
+module tanya.test;
+
+public import tanya.test.assertion;
+public import tanya.test.stub;
diff --git a/source/tanya/test/stub.d b/source/tanya/test/stub.d
new file mode 100644
index 0000000..1741bbb
--- /dev/null
+++ b/source/tanya/test/stub.d
@@ -0,0 +1,397 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Range and generic type generators.
+ *
+ * Copyright: Eugene Wissner 2018-2025.
+ * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
+ * Mozilla Public License, v. 2.0).
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/stub.d,
+ * tanya/test/stub.d)
+ */
+module tanya.test.stub;
+
+/**
+ * Attribute signalizing that the generated range should contain the given
+ * number of elements.
+ *
+ * $(D_PSYMBOL Count) should be always specified with some value and not as a
+ * type, so $(D_INLINECODE Count(1)) instead just $(D_INLINECODE Count),
+ * otherwise you can just omit $(D_PSYMBOL Count) and it will default to 0.
+ *
+ * $(D_PSYMBOL Count) doesn't generate `.length` property - use
+ * $(D_PSYMBOL Length) for that.
+ *
+ * If neither $(D_PSYMBOL Length) nor $(D_PSYMBOL Infinite) is given,
+ * $(D_ILNINECODE Count(0)) is assumed.
+ *
+ * This attribute conflicts with $(D_PSYMBOL Infinite) and $(D_PSYMBOL Length).
+ */
+struct Count
+{
+ /// Original range length.
+ size_t count = 0;
+
+ @disable this();
+
+ /**
+ * Constructs the attribute with the given length.
+ *
+ * Params:
+ * count = Original range length.
+ */
+ this(size_t count) @nogc nothrow pure @safe
+ {
+ this.count = count;
+ }
+}
+
+/**
+ * Attribute signalizing that the generated range should be infinite.
+ *
+ * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Length).
+ */
+struct Infinite
+{
+}
+
+/**
+ * Generates `.length` property for the range.
+ *
+ * The length of the range can be specified as a constructor argument,
+ * otherwise it is 0.
+ *
+ * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Infinite).
+ */
+struct Length
+{
+ /// Original range length.
+ size_t length = 0;
+}
+
+/**
+ * Attribute signalizing that the generated range should return values by
+ * reference.
+ *
+ * This atribute affects the return values of `.front`, `.back` and `[]`.
+ */
+struct WithLvalueElements
+{
+}
+
+/**
+ * Generates an input range.
+ *
+ * Params:
+ * E = Element type.
+ */
+mixin template InputRangeStub(E = int)
+{
+ import std.traits : hasUDA, getUDAs;
+ import std.meta : Alias;
+
+ /*
+ * Aliases for the attribute lookups to access them faster
+ */
+ private enum bool infinite = hasUDA!(typeof(this), Infinite);
+ private enum bool withLvalueElements = hasUDA!(typeof(this),
+ WithLvalueElements);
+ private alias Count = getUDAs!(typeof(this), .Count);
+ private alias Length = getUDAs!(typeof(this), .Length);
+
+ static if (Count.length != 0)
+ {
+ private enum size_t count = Count[0].count;
+
+ static assert (!infinite,
+ "Range cannot have count and be infinite at the same time");
+ static assert (Length.length == 0,
+ "Range cannot have count and length at the same time");
+ }
+ else static if (Length.length != 0)
+ {
+ private enum size_t count = Length[0]().length;
+
+ static assert (!infinite,
+ "Range cannot have length and be infinite at the same time");
+ }
+ else static if (!infinite)
+ {
+ private enum size_t count = 0;
+ }
+
+ /*
+ * Member generation
+ */
+ static if (infinite)
+ {
+ enum bool empty = false;
+ }
+ else
+ {
+ private size_t length_ = count;
+
+ @property bool empty() const @nogc nothrow pure @safe
+ {
+ return this.length_ == 0;
+ }
+ }
+
+ static if (withLvalueElements)
+ {
+ private E* element; // Pointer to enable range copying in save()
+ }
+
+ void popFront() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ static if (!infinite)
+ {
+ --this.length_;
+ }
+ }
+
+ static if (withLvalueElements)
+ {
+ ref E front() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ return *this.element;
+ }
+ }
+ else
+ {
+ E front() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ return E.init;
+ }
+ }
+
+ static if (Length.length != 0)
+ {
+ size_t length() const @nogc nothrow pure @safe
+ {
+ return this.length_;
+ }
+ }
+}
+
+/**
+ * Generates a forward range.
+ *
+ * This mixin includes input range primitives as well, but can be combined with
+ * $(D_PSYMBOL RandomAccessRangeStub).
+ *
+ * Params:
+ * E = Element type.
+ */
+mixin template ForwardRangeStub(E = int)
+{
+ static if (!is(typeof(this.InputRangeMixin) == void))
+ {
+ mixin InputRangeStub!E InputRangeMixin;
+ }
+
+ auto save() @nogc nothrow pure @safe
+ {
+ return this;
+ }
+}
+
+/**
+ * Generates a bidirectional range.
+ *
+ * This mixin includes forward range primitives as well, but can be combined with
+ * $(D_PSYMBOL RandomAccessRangeStub).
+ *
+ * Params:
+ * E = Element type.
+ */
+mixin template BidirectionalRangeStub(E = int)
+{
+ mixin ForwardRangeStub!E;
+
+ void popBack() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ static if (!infinite)
+ {
+ --this.length_;
+ }
+ }
+
+ static if (withLvalueElements)
+ {
+ ref E back() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ return *this.element;
+ }
+ }
+ else
+ {
+ E back() @nogc nothrow pure @safe
+ in
+ {
+ assert(!empty);
+ }
+ do
+ {
+ return E.init;
+ }
+ }
+}
+
+/**
+ * Generates a random-access range.
+ *
+ * This mixin includes input range primitives as well, but can be combined with
+ * $(D_PSYMBOL ForwardRangeStub) or $(D_PSYMBOL BidirectionalRangeStub).
+ *
+ * Note that a random-access range also requires $(D_PSYMBOL Length) or
+ * $(D_PARAM Infinite) by definition.
+ *
+ * Params:
+ * E = Element type.
+ */
+mixin template RandomAccessRangeStub(E = int)
+{
+ static if (!is(typeof(this.InputRangeMixin) == void))
+ {
+ mixin InputRangeStub!E InputRangeMixin;
+ }
+
+ static if (withLvalueElements)
+ {
+ ref E opIndex(size_t) @nogc nothrow pure @safe
+ {
+ return *this.element;
+ }
+ }
+ else
+ {
+ E opIndex(size_t) @nogc nothrow pure @safe
+ {
+ return E.init;
+ }
+ }
+}
+
+/**
+ * Struct with a disabled postblit constructor.
+ *
+ * $(D_PSYMBOL NonCopyable) can be used as an attribute for
+ * $(D_PSYMBOL StructStub) or as a standalone type.
+ */
+struct NonCopyable
+{
+ @disable this(this);
+}
+
+/**
+ * Struct with an elaborate destructor.
+ *
+ * $(D_PSYMBOL WithDtor) can be used as an attribute for
+ * $(D_PSYMBOL StructStub) or as a standalone type.
+ *
+ * When used as a standalone object the constructor of $(D_PSYMBOL WithDtor)
+ * accepts an additional `counter` argument, which is incremented by the
+ * destructor. $(D_PSYMBOL WithDtor) stores a pointer to the passed variable,
+ * so the variable can be investigated after the struct isn't available
+ * anymore.
+ */
+struct WithDtor
+{
+ size_t* counter;
+
+ this(ref size_t counter) @nogc nothrow pure @trusted
+ {
+ this.counter = &counter;
+ }
+
+ ~this() @nogc nothrow pure @safe
+ {
+ if (this.counter !is null)
+ {
+ ++*this.counter;
+ }
+ }
+}
+
+/**
+ * Struct supporting hashing.
+ *
+ * $(D_PSYMBOL Hashable) can be used as an attribute for
+ * $(D_PSYMBOL StructStub) or as a standalone type.
+ *
+ * The constructor accepts an additional parameter, which is returned by the
+ * `toHash()`-function. `0U` is returned if no hash value is given.
+ */
+struct Hashable
+{
+ size_t hash;
+
+ size_t toHash() const @nogc nothrow pure @safe
+ {
+ return this.hash;
+ }
+}
+
+/**
+ * Generates a $(D_KEYWORD struct) with common functionality.
+ *
+ * To specify the needed functionality use user-defined attributes on the
+ * $(D_KEYWORD struct) $(D_PSYMBOL StructStub) is mixed in.
+ *
+ * Supported attributes are: $(D_PSYMBOL NonCopyable), $(D_PSYMBOL Hashable),
+ * $(D_PSYMBOL WithDtor).
+ */
+mixin template StructStub()
+{
+ import std.traits : hasUDA, getUDAs;
+
+ static if (hasUDA!(typeof(this), NonCopyable))
+ {
+ @disable this(this);
+ }
+
+ private alias Hashable = getUDAs!(typeof(this), .Hashable);
+ static if (Hashable.length > 0)
+ {
+ size_t toHash() const @nogc nothrow pure @safe
+ {
+ return Hashable[0]().hash;
+ }
+ }
+
+ static if (hasUDA!(typeof(this), WithDtor))
+ {
+ ~this() @nogc nothrow pure @safe
+ {
+ }
+ }
+}