mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 21:22:20 +03:00
4720 lines
122 KiB
D
4720 lines
122 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
Bit-level manipulation facilities.
|
|
|
|
$(SCRIPT inhibitQuickIndex = 1;)
|
|
$(DIVC quickindex,
|
|
$(BOOKTABLE,
|
|
$(TR $(TH Category) $(TH Functions))
|
|
$(TR $(TD Bit constructs) $(TD
|
|
$(LREF BitArray)
|
|
$(LREF bitfields)
|
|
$(LREF bitsSet)
|
|
))
|
|
$(TR $(TD Endianness conversion) $(TD
|
|
$(LREF bigEndianToNative)
|
|
$(LREF littleEndianToNative)
|
|
$(LREF nativeToBigEndian)
|
|
$(LREF nativeToLittleEndian)
|
|
$(LREF swapEndian)
|
|
))
|
|
$(TR $(TD Integral ranges) $(TD
|
|
$(LREF append)
|
|
$(LREF peek)
|
|
$(LREF read)
|
|
$(LREF write)
|
|
))
|
|
$(TR $(TD Floating-Point manipulation) $(TD
|
|
$(LREF DoubleRep)
|
|
$(LREF FloatRep)
|
|
))
|
|
$(TR $(TD Tagging) $(TD
|
|
$(LREF taggedClassRef)
|
|
$(LREF taggedPointer)
|
|
))
|
|
))
|
|
|
|
Copyright: Copyright The D Language Foundation 2007 - 2011.
|
|
License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP digitalmars.com, Walter Bright),
|
|
$(HTTP erdani.org, Andrei Alexandrescu),
|
|
$(HTTP jmdavisprog.com, Jonathan M Davis),
|
|
Alex Rønne Petersen,
|
|
Damian Ziemba,
|
|
Amaury SECHET
|
|
Source: $(PHOBOSSRC std/bitmanip.d)
|
|
*/
|
|
/*
|
|
Copyright The D Language Foundation 2007 - 2012.
|
|
Distributed under the Boost Software License, Version 1.0.
|
|
(See accompanying file LICENSE_1_0.txt or copy at
|
|
http://www.boost.org/LICENSE_1_0.txt)
|
|
*/
|
|
module std.bitmanip;
|
|
|
|
import std.range.primitives;
|
|
public import std.system : Endian;
|
|
import std.traits;
|
|
|
|
private string myToString(ulong n) pure @safe
|
|
{
|
|
import core.internal.string : UnsignedStringBuf, unsignedToTempString;
|
|
UnsignedStringBuf buf;
|
|
auto s = unsignedToTempString(n, buf);
|
|
// pure allows implicit cast to string
|
|
return s ~ (n > uint.max ? "UL" : "U");
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
assert(myToString(5) == "5U");
|
|
assert(myToString(uint.max) == "4294967295U");
|
|
assert(myToString(uint.max + 1UL) == "4294967296UL");
|
|
}
|
|
|
|
|
|
private template createAccessors(
|
|
string store, T, string name, size_t len, size_t offset)
|
|
{
|
|
static if (!name.length)
|
|
{
|
|
// No need to create any accessor
|
|
enum createAccessors = "";
|
|
}
|
|
else static if (len == 0)
|
|
{
|
|
// Fields of length 0 are always zero
|
|
enum createAccessors = "enum "~T.stringof~" "~name~" = 0;\n";
|
|
}
|
|
else
|
|
{
|
|
enum ulong maskAllElse = ((~0uL) >> (64 - len)) << offset;
|
|
enum TSize = 8 * T.sizeof;
|
|
enum SignShift = TSize - len;
|
|
|
|
static if (T.min < 0)
|
|
{
|
|
enum long minVal = -(1uL << (len - 1));
|
|
enum ulong maxVal = (1uL << (len - 1)) - 1;
|
|
enum RightShiftOp = ">>=";
|
|
}
|
|
else
|
|
{
|
|
enum ulong minVal = 0;
|
|
enum ulong maxVal = (~0uL) >> (64 - len);
|
|
enum RightShiftOp = ">>>=";
|
|
}
|
|
|
|
static if (is(T == bool))
|
|
{
|
|
enum createAccessors =
|
|
// getter
|
|
"@property bool " ~ name ~ "() @safe pure nothrow @nogc const { return "
|
|
~"("~store~" & "~myToString(maskAllElse)~") != 0;}\n"
|
|
// setter
|
|
~"@property void " ~ name ~ "(bool v) @safe pure nothrow @nogc { "
|
|
~"if (v) "~store~" |= "~myToString(maskAllElse)~";"
|
|
~"else "~store~" &= cast(typeof("~store~"))(-1-cast(typeof("~store~"))"~myToString(maskAllElse)~");}\n";
|
|
}
|
|
else
|
|
{
|
|
// getter
|
|
enum createAccessors = "@property "~T.stringof~" "~name~"() @safe pure nothrow @nogc const {"
|
|
~ "auto result = cast("~T.stringof~") (" ~ store ~ " >>" ~ myToString(offset) ~ ");"
|
|
~ "result <<= " ~ myToString(SignShift) ~ ";"
|
|
~ "result " ~ RightShiftOp ~ myToString(SignShift) ~ ";"
|
|
~ " return result;}\n"
|
|
// setter
|
|
~"@property void "~name~"("~T.stringof~" v) @safe pure nothrow @nogc { "
|
|
~"assert(v >= "~name~`_min, "Value is smaller than the minimum value of bitfield '`~name~`'"); `
|
|
~"assert(v <= "~name~`_max, "Value is greater than the maximum value of bitfield '`~name~`'"); `
|
|
~store~" = cast(typeof("~store~"))"
|
|
~" (("~store~" & (-1-cast(typeof("~store~"))"~myToString(maskAllElse)~"))"
|
|
~" | ((cast(typeof("~store~")) v << "~myToString(offset)~")"
|
|
~" & "~myToString(maskAllElse)~"));}\n"
|
|
// constants
|
|
~"enum "~T.stringof~" "~name~"_min = cast("~T.stringof~")"
|
|
~myToString(minVal)~"; "
|
|
~" enum "~T.stringof~" "~name~"_max = cast("~T.stringof~")"
|
|
~myToString(maxVal)~"; ";
|
|
}
|
|
}
|
|
}
|
|
|
|
private template createStoreName(Ts...)
|
|
{
|
|
static if (Ts.length < 2)
|
|
enum createStoreName = "_bf";
|
|
else
|
|
enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]);
|
|
}
|
|
|
|
private template createStorageAndFields(Ts...)
|
|
{
|
|
enum Name = createStoreName!Ts;
|
|
enum Size = sizeOfBitField!Ts;
|
|
static if (Size == ubyte.sizeof * 8)
|
|
alias StoreType = ubyte;
|
|
else static if (Size == ushort.sizeof * 8)
|
|
alias StoreType = ushort;
|
|
else static if (Size == uint.sizeof * 8)
|
|
alias StoreType = uint;
|
|
else static if (Size == ulong.sizeof * 8)
|
|
alias StoreType = ulong;
|
|
else
|
|
{
|
|
import std.conv : to;
|
|
static assert(false, "Field widths must sum to 8, 16, 32, or 64, not " ~ to!string(Size));
|
|
alias StoreType = ulong; // just to avoid another error msg
|
|
}
|
|
|
|
enum createStorageAndFields
|
|
= "private " ~ StoreType.stringof ~ " " ~ Name ~ ";"
|
|
~ createFields!(Name, 0, Ts);
|
|
}
|
|
|
|
private template createFields(string store, size_t offset, Ts...)
|
|
{
|
|
static if (Ts.length > 0)
|
|
enum createFields
|
|
= createAccessors!(store, Ts[0], Ts[1], Ts[2], offset)
|
|
~ createFields!(store, offset + Ts[2], Ts[3 .. $]);
|
|
else
|
|
enum createFields = "";
|
|
}
|
|
|
|
private ulong getBitsForAlign(ulong a)
|
|
{
|
|
ulong bits = 0;
|
|
while ((a & 0x01) == 0)
|
|
{
|
|
bits++;
|
|
a >>= 1;
|
|
}
|
|
|
|
assert(a == 1, "alignment is not a power of 2");
|
|
return bits;
|
|
}
|
|
|
|
private template createReferenceAccessor(string store, T, ulong bits, string name)
|
|
{
|
|
enum storage = "private void* " ~ store ~ "_ptr;\n";
|
|
enum storage_accessor = "@property ref size_t " ~ store ~ "() return @trusted pure nothrow @nogc const { "
|
|
~ "return *cast(size_t*) &" ~ store ~ "_ptr;}\n"
|
|
~ "@property void " ~ store ~ "(size_t v) @trusted pure nothrow @nogc { "
|
|
~ "" ~ store ~ "_ptr = cast(void*) v;}\n";
|
|
|
|
enum mask = (1UL << bits) - 1;
|
|
// getter
|
|
enum ref_accessor = "@property "~T.stringof~" "~name~"() @trusted pure nothrow @nogc const { auto result = "
|
|
~ "("~store~" & "~myToString(~mask)~"); "
|
|
~ "return cast("~T.stringof~") cast(void*) result;}\n"
|
|
// setter
|
|
~"@property void "~name~"("~T.stringof~" v) @trusted pure nothrow @nogc { "
|
|
~"assert(((cast(typeof("~store~")) cast(void*) v) & "~myToString(mask)
|
|
~`) == 0, "Value not properly aligned for '`~name~`'"); `
|
|
~store~" = cast(typeof("~store~"))"
|
|
~" (("~store~" & (cast(typeof("~store~")) "~myToString(mask)~"))"
|
|
~" | ((cast(typeof("~store~")) cast(void*) v) & (cast(typeof("~store~")) "~myToString(~mask)~")));}\n";
|
|
|
|
enum createReferenceAccessor = storage ~ storage_accessor ~ ref_accessor;
|
|
}
|
|
|
|
private template sizeOfBitField(T...)
|
|
{
|
|
static if (T.length < 2)
|
|
enum sizeOfBitField = 0;
|
|
else
|
|
enum sizeOfBitField = T[2] + sizeOfBitField!(T[3 .. $]);
|
|
}
|
|
|
|
private template createTaggedReference(T, ulong a, string name, Ts...)
|
|
{
|
|
static assert(
|
|
sizeOfBitField!Ts <= getBitsForAlign(a),
|
|
"Fields must fit in the bits know to be zero because of alignment."
|
|
);
|
|
enum StoreName = createStoreName!(T, name, 0, Ts);
|
|
enum createTaggedReference
|
|
= createReferenceAccessor!(StoreName, T, sizeOfBitField!Ts, name)
|
|
~ createFields!(StoreName, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts);
|
|
}
|
|
|
|
/**
|
|
Allows creating `bitfields` inside `structs`, `classes` and `unions`.
|
|
|
|
A `bitfield` consists of one or more entries with a fixed number of
|
|
bits reserved for each of the entries. The types of the entries can
|
|
be `bool`s, integral types or enumerated types, arbitrarily mixed.
|
|
The most efficient type to store in `bitfields` is `bool`, followed
|
|
by unsigned types, followed by signed types.
|
|
|
|
Each non-`bool` entry of the `bitfield` will be represented by the
|
|
number of bits specified by the user. The minimum and the maximum
|
|
numbers that represent this domain can be queried by using the name
|
|
of the variable followed by `_min` or `_max`.
|
|
|
|
Limitation: The number of bits in a `bitfield` is limited to 8, 16,
|
|
32 or 64. If padding is needed, an entry should be explicitly
|
|
allocated with an empty name.
|
|
|
|
Implementation_details: `Bitfields` are internally stored in an
|
|
`ubyte`, `ushort`, `uint` or `ulong` depending on the number of bits
|
|
used. The bits are filled in the order given by the parameters,
|
|
starting with the lowest significant bit. The name of the (private)
|
|
variable used for saving the `bitfield` is created by concatenating
|
|
all of the variable names, each preceded by an underscore, and
|
|
a suffix `_bf`.
|
|
|
|
Params: T = A list of template parameters divided into chunks of 3
|
|
items. Each chunk consists (in this order) of a type, a
|
|
name and a number. Together they define an entry
|
|
of the `bitfield`: a variable of the given type and name,
|
|
which can hold as many bits as the number denotes.
|
|
|
|
Returns: A string that can be used in a `mixin` to add the `bitfield`.
|
|
|
|
See_Also: $(REF BitFlags, std,typecons)
|
|
*/
|
|
string bitfields(T...)()
|
|
{
|
|
static assert(T.length % 3 == 0,
|
|
"Wrong number of arguments (" ~ T.length.stringof ~ "): Must be a multiple of 3");
|
|
|
|
static foreach (i, ARG; T)
|
|
{
|
|
static if (i % 3 == 0)
|
|
static assert(is (typeof({ARG tmp = cast (ARG)0; if (ARG.min < 0) {} }())),
|
|
"Integral type or `bool` expected, found " ~ ARG.stringof);
|
|
|
|
// would be nice to check for valid variable names too
|
|
static if (i % 3 == 1)
|
|
static assert(is (typeof(ARG) : string),
|
|
"Variable name expected, found " ~ ARG.stringof);
|
|
|
|
static if (i % 3 == 2)
|
|
{
|
|
static assert(is (typeof({ulong tmp = ARG;}())),
|
|
"Integral value expected, found " ~ ARG.stringof);
|
|
|
|
static if (T[i-1] != "")
|
|
{
|
|
static assert(!is (T[i-2] : bool) || ARG <= 1,
|
|
"Type `bool` is only allowed for single-bit fields");
|
|
|
|
static assert(ARG >= 0 && ARG <= T[i-2].sizeof * 8,
|
|
"Wrong size of bitfield: " ~ ARG.stringof);
|
|
}
|
|
}
|
|
}
|
|
|
|
return createStorageAndFields!T;
|
|
}
|
|
|
|
/**
|
|
Create a `bitfield` pack of eight bits, which fit in
|
|
one `ubyte`. The `bitfields` are allocated starting from the
|
|
least significant bit, i.e. `x` occupies the two least significant bits
|
|
of the `bitfields` storage.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
struct A
|
|
{
|
|
int a;
|
|
mixin(bitfields!(
|
|
uint, "x", 2,
|
|
int, "y", 3,
|
|
uint, "z", 2,
|
|
bool, "flag", 1));
|
|
}
|
|
|
|
A obj;
|
|
obj.x = 2;
|
|
obj.z = obj.x;
|
|
|
|
assert(obj.x == 2);
|
|
assert(obj.y == 0);
|
|
assert(obj.z == 2);
|
|
assert(obj.flag == false);
|
|
}
|
|
|
|
/**
|
|
The sum of all bit lengths in one `bitfield` instantiation
|
|
must be exactly 8, 16, 32, or 64. If padding is needed, just allocate
|
|
one bitfield with an empty name.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
struct A
|
|
{
|
|
mixin(bitfields!(
|
|
bool, "flag1", 1,
|
|
bool, "flag2", 1,
|
|
uint, "", 6));
|
|
}
|
|
|
|
A a;
|
|
assert(a.flag1 == 0);
|
|
a.flag1 = 1;
|
|
assert(a.flag1 == 1);
|
|
a.flag1 = 0;
|
|
assert(a.flag1 == 0);
|
|
}
|
|
|
|
/// enums can be used too
|
|
@safe unittest
|
|
{
|
|
enum ABC { A, B, C }
|
|
struct EnumTest
|
|
{
|
|
mixin(bitfields!(
|
|
ABC, "x", 2,
|
|
bool, "y", 1,
|
|
ubyte, "z", 5));
|
|
}
|
|
}
|
|
|
|
@safe pure nothrow @nogc
|
|
unittest
|
|
{
|
|
// Degenerate bitfields tests mixed with range tests
|
|
// https://issues.dlang.org/show_bug.cgi?id=8474
|
|
// https://issues.dlang.org/show_bug.cgi?id=11160
|
|
struct Test1
|
|
{
|
|
mixin(bitfields!(uint, "a", 32,
|
|
uint, "b", 4,
|
|
uint, "c", 4,
|
|
uint, "d", 8,
|
|
uint, "e", 16,));
|
|
|
|
static assert(Test1.b_min == 0);
|
|
static assert(Test1.b_max == 15);
|
|
}
|
|
|
|
struct Test2
|
|
{
|
|
mixin(bitfields!(bool, "a", 0,
|
|
ulong, "b", 64));
|
|
|
|
static assert(Test2.b_min == ulong.min);
|
|
static assert(Test2.b_max == ulong.max);
|
|
}
|
|
|
|
struct Test1b
|
|
{
|
|
mixin(bitfields!(bool, "a", 0,
|
|
int, "b", 8));
|
|
}
|
|
|
|
struct Test2b
|
|
{
|
|
mixin(bitfields!(int, "a", 32,
|
|
int, "b", 4,
|
|
int, "c", 4,
|
|
int, "d", 8,
|
|
int, "e", 16,));
|
|
|
|
static assert(Test2b.b_min == -8);
|
|
static assert(Test2b.b_max == 7);
|
|
}
|
|
|
|
struct Test3b
|
|
{
|
|
mixin(bitfields!(bool, "a", 0,
|
|
long, "b", 64));
|
|
|
|
static assert(Test3b.b_min == long.min);
|
|
static assert(Test3b.b_max == long.max);
|
|
}
|
|
|
|
struct Test4b
|
|
{
|
|
mixin(bitfields!(long, "a", 32,
|
|
int, "b", 32));
|
|
}
|
|
|
|
// Sign extension tests
|
|
Test2b t2b;
|
|
Test4b t4b;
|
|
t2b.b = -5; assert(t2b.b == -5);
|
|
t2b.d = -5; assert(t2b.d == -5);
|
|
t2b.e = -5; assert(t2b.e == -5);
|
|
t4b.a = -5; assert(t4b.a == -5L);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=6686
|
|
@safe unittest
|
|
{
|
|
union S {
|
|
ulong bits = ulong.max;
|
|
mixin (bitfields!(
|
|
ulong, "back", 31,
|
|
ulong, "front", 33)
|
|
);
|
|
}
|
|
S num;
|
|
|
|
num.bits = ulong.max;
|
|
num.back = 1;
|
|
assert(num.bits == 0xFFFF_FFFF_8000_0001uL);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5942
|
|
@safe unittest
|
|
{
|
|
struct S
|
|
{
|
|
mixin(bitfields!(
|
|
int, "a" , 32,
|
|
int, "b" , 32
|
|
));
|
|
}
|
|
|
|
S data;
|
|
data.b = 42;
|
|
data.a = 1;
|
|
assert(data.b == 42);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct Test
|
|
{
|
|
mixin(bitfields!(bool, "a", 1,
|
|
uint, "b", 3,
|
|
short, "c", 4));
|
|
}
|
|
|
|
@safe void test() pure nothrow
|
|
{
|
|
Test t;
|
|
|
|
t.a = true;
|
|
t.b = 5;
|
|
t.c = 2;
|
|
|
|
assert(t.a);
|
|
assert(t.b == 5);
|
|
assert(t.c == 2);
|
|
}
|
|
|
|
test();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
{
|
|
static struct Integrals {
|
|
bool checkExpectations(bool eb, int ei, short es) { return b == eb && i == ei && s == es; }
|
|
|
|
mixin(bitfields!(
|
|
bool, "b", 1,
|
|
uint, "i", 3,
|
|
short, "s", 4));
|
|
}
|
|
Integrals i;
|
|
assert(i.checkExpectations(false, 0, 0));
|
|
i.b = true;
|
|
assert(i.checkExpectations(true, 0, 0));
|
|
i.i = 7;
|
|
assert(i.checkExpectations(true, 7, 0));
|
|
i.s = -8;
|
|
assert(i.checkExpectations(true, 7, -8));
|
|
i.s = 7;
|
|
assert(i.checkExpectations(true, 7, 7));
|
|
}
|
|
|
|
//https://issues.dlang.org/show_bug.cgi?id=8876
|
|
{
|
|
struct MoreIntegrals {
|
|
bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; }
|
|
|
|
mixin(bitfields!(
|
|
uint, "u", 24,
|
|
short, "s", 16,
|
|
int, "i", 24));
|
|
}
|
|
|
|
MoreIntegrals i;
|
|
assert(i.checkExpectations(0, 0, 0));
|
|
i.s = 20;
|
|
assert(i.checkExpectations(0, 20, 0));
|
|
i.i = 72;
|
|
assert(i.checkExpectations(0, 20, 72));
|
|
i.u = 8;
|
|
assert(i.checkExpectations(8, 20, 72));
|
|
i.s = 7;
|
|
assert(i.checkExpectations(8, 7, 72));
|
|
}
|
|
|
|
enum A { True, False }
|
|
enum B { One, Two, Three, Four }
|
|
static struct Enums {
|
|
bool checkExpectations(A ea, B eb) { return a == ea && b == eb; }
|
|
|
|
mixin(bitfields!(
|
|
A, "a", 1,
|
|
B, "b", 2,
|
|
uint, "", 5));
|
|
}
|
|
Enums e;
|
|
assert(e.checkExpectations(A.True, B.One));
|
|
e.a = A.False;
|
|
assert(e.checkExpectations(A.False, B.One));
|
|
e.b = B.Three;
|
|
assert(e.checkExpectations(A.False, B.Three));
|
|
|
|
static struct SingleMember {
|
|
bool checkExpectations(bool eb) { return b == eb; }
|
|
|
|
mixin(bitfields!(
|
|
bool, "b", 1,
|
|
uint, "", 7));
|
|
}
|
|
SingleMember f;
|
|
assert(f.checkExpectations(false));
|
|
f.b = true;
|
|
assert(f.checkExpectations(true));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=12477
|
|
@system unittest
|
|
{
|
|
import core.exception : AssertError;
|
|
import std.algorithm.searching : canFind;
|
|
|
|
static struct S
|
|
{
|
|
mixin(bitfields!(
|
|
uint, "a", 6,
|
|
int, "b", 2));
|
|
}
|
|
|
|
S s;
|
|
|
|
try { s.a = uint.max; assert(0); }
|
|
catch (AssertError ae)
|
|
{ assert(ae.msg.canFind("Value is greater than the maximum value of bitfield 'a'"), ae.msg); }
|
|
|
|
try { s.b = int.min; assert(0); }
|
|
catch (AssertError ae)
|
|
{ assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); }
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15305
|
|
@safe unittest
|
|
{
|
|
struct S {
|
|
mixin(bitfields!(
|
|
bool, "alice", 1,
|
|
ulong, "bob", 63,
|
|
));
|
|
}
|
|
|
|
S s;
|
|
s.bob = long.max - 1;
|
|
s.alice = false;
|
|
assert(s.bob == long.max - 1);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21634
|
|
@safe unittest
|
|
{
|
|
struct A
|
|
{
|
|
mixin(bitfields!(int, "", 1,
|
|
int, "gshared", 7));
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21725
|
|
@safe unittest
|
|
{
|
|
struct S
|
|
{
|
|
mixin(bitfields!(
|
|
uint, q{foo}, 4,
|
|
uint, null, 4,
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
This string mixin generator allows one to create tagged pointers inside $(D_PARAM struct)s and $(D_PARAM class)es.
|
|
|
|
A tagged pointer uses the bits known to be zero in a normal pointer or class reference to store extra information.
|
|
For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
|
|
One can store a 2-bit integer there.
|
|
|
|
The example above creates a tagged pointer in the struct A. The pointer is of type
|
|
`uint*` as specified by the first argument, and is named x, as specified by the second
|
|
argument.
|
|
|
|
Following arguments works the same way as `bitfield`'s. The bitfield must fit into the
|
|
bits known to be zero because of the pointer alignment.
|
|
*/
|
|
|
|
template taggedPointer(T : T*, string name, Ts...) {
|
|
enum taggedPointer = createTaggedReference!(T*, T.alignof, name, Ts);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
struct A
|
|
{
|
|
int a;
|
|
mixin(taggedPointer!(
|
|
uint*, "x",
|
|
bool, "b1", 1,
|
|
bool, "b2", 1));
|
|
}
|
|
A obj;
|
|
obj.x = new uint;
|
|
obj.b1 = true;
|
|
obj.b2 = false;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
struct Test5
|
|
{
|
|
mixin(taggedPointer!(
|
|
int*, "a",
|
|
uint, "b", 2));
|
|
}
|
|
|
|
Test5 t5;
|
|
t5.a = null;
|
|
t5.b = 3;
|
|
assert(t5.a is null);
|
|
assert(t5.b == 3);
|
|
|
|
int myint = 42;
|
|
t5.a = &myint;
|
|
assert(t5.a is &myint);
|
|
assert(t5.b == 3);
|
|
}
|
|
|
|
/**
|
|
This string mixin generator allows one to create tagged class reference inside $(D_PARAM struct)s and $(D_PARAM class)es.
|
|
|
|
A tagged class reference uses the bits known to be zero in a normal class reference to store extra information.
|
|
For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero.
|
|
One can store a 2-bit integer there.
|
|
|
|
The example above creates a tagged reference to an Object in the struct A. This expects the same parameters
|
|
as `taggedPointer`, except the first argument which must be a class type instead of a pointer type.
|
|
*/
|
|
|
|
template taggedClassRef(T, string name, Ts...)
|
|
if (is(T == class))
|
|
{
|
|
enum taggedClassRef = createTaggedReference!(T, 8, name, Ts);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
struct A
|
|
{
|
|
int a;
|
|
mixin(taggedClassRef!(
|
|
Object, "o",
|
|
uint, "i", 2));
|
|
}
|
|
A obj;
|
|
obj.o = new Object();
|
|
obj.i = 3;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
struct Test6
|
|
{
|
|
mixin(taggedClassRef!(
|
|
Object, "o",
|
|
bool, "b", 1));
|
|
}
|
|
|
|
Test6 t6;
|
|
t6.o = null;
|
|
t6.b = false;
|
|
assert(t6.o is null);
|
|
assert(t6.b == false);
|
|
|
|
auto o = new Object();
|
|
t6.o = o;
|
|
t6.b = true;
|
|
assert(t6.o is o);
|
|
assert(t6.b == true);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(!__traits(compiles,
|
|
taggedPointer!(
|
|
int*, "a",
|
|
uint, "b", 3)));
|
|
|
|
static assert(!__traits(compiles,
|
|
taggedClassRef!(
|
|
Object, "a",
|
|
uint, "b", 4)));
|
|
|
|
struct S {
|
|
mixin(taggedClassRef!(
|
|
Object, "a",
|
|
bool, "b", 1));
|
|
}
|
|
|
|
const S s;
|
|
void bar(S s) {}
|
|
|
|
static assert(!__traits(compiles, bar(s)));
|
|
}
|
|
|
|
private struct FloatingPointRepresentation(T)
|
|
{
|
|
static if (is(T == float))
|
|
{
|
|
enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1;
|
|
alias FractionType = uint;
|
|
alias ExponentType = ubyte;
|
|
}
|
|
else
|
|
{
|
|
enum uint bias = 1023, fractionBits = 52, exponentBits = 11, signBits = 1;
|
|
alias FractionType = ulong;
|
|
alias ExponentType = ushort;
|
|
}
|
|
|
|
union
|
|
{
|
|
T value;
|
|
mixin(bitfields!(
|
|
FractionType, "fraction", fractionBits,
|
|
ExponentType, "exponent", exponentBits,
|
|
bool, "sign", signBits));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Allows manipulating the fraction, exponent, and sign parts of a
|
|
`float` separately. The definition is:
|
|
|
|
----
|
|
struct FloatRep
|
|
{
|
|
union
|
|
{
|
|
float value;
|
|
mixin(bitfields!(
|
|
uint, "fraction", 23,
|
|
ubyte, "exponent", 8,
|
|
bool, "sign", 1));
|
|
}
|
|
enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1;
|
|
}
|
|
----
|
|
*/
|
|
alias FloatRep = FloatingPointRepresentation!float;
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
FloatRep rep = {value: 0};
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 0);
|
|
assert(!rep.sign);
|
|
|
|
rep.value = 42;
|
|
assert(rep.fraction == 2621440);
|
|
assert(rep.exponent == 132);
|
|
assert(!rep.sign);
|
|
|
|
rep.value = 10;
|
|
assert(rep.fraction == 2097152);
|
|
assert(rep.exponent == 130);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
FloatRep rep = {value: 1};
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 127);
|
|
assert(!rep.sign);
|
|
|
|
rep.exponent = 126;
|
|
assert(rep.value == 0.5);
|
|
|
|
rep.exponent = 130;
|
|
assert(rep.value == 8);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
FloatRep rep = {value: 1};
|
|
rep.value = -0.5;
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 126);
|
|
assert(rep.sign);
|
|
|
|
rep.value = -1. / 3;
|
|
assert(rep.fraction == 2796203);
|
|
assert(rep.exponent == 125);
|
|
assert(rep.sign);
|
|
}
|
|
|
|
/**
|
|
Allows manipulating the fraction, exponent, and sign parts of a
|
|
`double` separately. The definition is:
|
|
|
|
----
|
|
struct DoubleRep
|
|
{
|
|
union
|
|
{
|
|
double value;
|
|
mixin(bitfields!(
|
|
ulong, "fraction", 52,
|
|
ushort, "exponent", 11,
|
|
bool, "sign", 1));
|
|
}
|
|
enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11;
|
|
}
|
|
----
|
|
*/
|
|
alias DoubleRep = FloatingPointRepresentation!double;
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
DoubleRep rep = {value: 0};
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 0);
|
|
assert(!rep.sign);
|
|
|
|
rep.value = 42;
|
|
assert(rep.fraction == 1407374883553280);
|
|
assert(rep.exponent == 1028);
|
|
assert(!rep.sign);
|
|
|
|
rep.value = 10;
|
|
assert(rep.fraction == 1125899906842624);
|
|
assert(rep.exponent == 1026);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
DoubleRep rep = {value: 1};
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 1023);
|
|
assert(!rep.sign);
|
|
|
|
rep.exponent = 1022;
|
|
assert(rep.value == 0.5);
|
|
|
|
rep.exponent = 1026;
|
|
assert(rep.value == 8);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
DoubleRep rep = {value: 1};
|
|
rep.value = -0.5;
|
|
assert(rep.fraction == 0);
|
|
assert(rep.exponent == 1022);
|
|
assert(rep.sign);
|
|
|
|
rep.value = -1. / 3;
|
|
assert(rep.fraction == 1501199875790165);
|
|
assert(rep.exponent == 1021);
|
|
assert(rep.sign);
|
|
}
|
|
|
|
/// Reading
|
|
@safe unittest
|
|
{
|
|
DoubleRep x;
|
|
x.value = 1.0;
|
|
assert(x.fraction == 0 && x.exponent == 1023 && !x.sign);
|
|
x.value = -0.5;
|
|
assert(x.fraction == 0 && x.exponent == 1022 && x.sign);
|
|
x.value = 0.5;
|
|
assert(x.fraction == 0 && x.exponent == 1022 && !x.sign);
|
|
}
|
|
|
|
/// Writing
|
|
@safe unittest
|
|
{
|
|
DoubleRep x;
|
|
x.fraction = 1125899906842624;
|
|
x.exponent = 1025;
|
|
x.sign = true;
|
|
assert(x.value == -5.0);
|
|
}
|
|
|
|
/**
|
|
A dynamic array of bits. Each bit in a `BitArray` can be manipulated individually
|
|
or by the standard bitwise operators `&`, `|`, `^`, `~`, `>>`, `<<` and also by
|
|
other effective member functions; most of them work relative to the `BitArray`'s
|
|
dimension (see $(LREF dim)), instead of its $(LREF length).
|
|
*/
|
|
struct BitArray
|
|
{
|
|
private:
|
|
|
|
import core.bitop : btc, bts, btr, bsf, bt;
|
|
import std.format.spec : FormatSpec;
|
|
|
|
size_t _len;
|
|
size_t* _ptr;
|
|
enum bitsPerSizeT = size_t.sizeof * 8;
|
|
|
|
@property size_t fullWords() const scope @safe @nogc pure nothrow
|
|
{
|
|
return _len / bitsPerSizeT;
|
|
}
|
|
// Number of bits after the last full word
|
|
@property size_t endBits() const scope @safe @nogc pure nothrow
|
|
{
|
|
return _len % bitsPerSizeT;
|
|
}
|
|
// Bit mask to extract the bits after the last full word
|
|
@property size_t endMask() const scope @safe @nogc pure nothrow
|
|
{
|
|
return (size_t(1) << endBits) - 1;
|
|
}
|
|
static size_t lenToDim(size_t len) @nogc pure nothrow @safe
|
|
{
|
|
return (len + (bitsPerSizeT-1)) / bitsPerSizeT;
|
|
}
|
|
|
|
public:
|
|
/**
|
|
Creates a `BitArray` from a `bool` array, such that `bool` values read
|
|
from left to right correspond to subsequent bits in the `BitArray`.
|
|
|
|
Params: ba = Source array of `bool` values.
|
|
*/
|
|
this(in bool[] ba) nothrow pure
|
|
{
|
|
length = ba.length;
|
|
foreach (i, b; ba)
|
|
{
|
|
this[i] = b;
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
bool[] input = [true, false, false, true, true];
|
|
auto a = BitArray(input);
|
|
assert(a.length == 5);
|
|
assert(a.bitsSet.equal([0, 3, 4]));
|
|
|
|
// This also works because an implicit cast to bool[] occurs for this array.
|
|
auto b = BitArray([0, 0, 1]);
|
|
assert(b.length == 3);
|
|
assert(b.bitsSet.equal([2]));
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.array : array;
|
|
import std.range : iota, repeat;
|
|
|
|
BitArray a = true.repeat(70).array;
|
|
assert(a.length == 70);
|
|
assert(a.bitsSet.equal(iota(0, 70)));
|
|
}
|
|
|
|
/**
|
|
Creates a `BitArray` from the raw contents of the source array. The
|
|
source array is not copied but simply acts as the underlying array
|
|
of bits, which stores data as `size_t` units.
|
|
|
|
That means a particular care should be taken when passing an array
|
|
of a type different than `size_t`, firstly because its length should
|
|
be a multiple of `size_t.sizeof`, and secondly because how the bits
|
|
are mapped:
|
|
---
|
|
size_t[] source = [1, 2, 3, 3424234, 724398, 230947, 389492];
|
|
enum sbits = size_t.sizeof * 8;
|
|
auto ba = BitArray(source, source.length * sbits);
|
|
foreach (n; 0 .. source.length * sbits)
|
|
{
|
|
auto nth_bit = cast(bool) (source[n / sbits] & (1L << (n % sbits)));
|
|
assert(ba[n] == nth_bit);
|
|
}
|
|
---
|
|
The least significant bit in any `size_t` unit is the starting bit of this
|
|
unit, and the most significant bit is the last bit of this unit. Therefore,
|
|
passing e.g. an array of `int`s may result in a different `BitArray`
|
|
depending on the processor's endianness.
|
|
|
|
This constructor is the inverse of $(LREF opCast).
|
|
|
|
Params:
|
|
v = Source array. `v.length` must be a multple of `size_t.sizeof`.
|
|
numbits = Number of bits to be mapped from the source array, i.e.
|
|
length of the created `BitArray`.
|
|
*/
|
|
this(void[] v, size_t numbits) @nogc nothrow pure
|
|
in
|
|
{
|
|
assert(numbits <= v.length * 8,
|
|
"numbits must be less than or equal to v.length * 8");
|
|
assert(v.length % size_t.sizeof == 0,
|
|
"v.length must be a multiple of the size of size_t");
|
|
}
|
|
do
|
|
{
|
|
_ptr = cast(size_t*) v.ptr;
|
|
_len = numbits;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto a = BitArray([1, 0, 0, 1, 1]);
|
|
|
|
// Inverse of the cast.
|
|
auto v = cast(void[]) a;
|
|
auto b = BitArray(v, a.length);
|
|
|
|
assert(b.length == 5);
|
|
assert(b.bitsSet.equal([0, 3, 4]));
|
|
|
|
// a and b share the underlying data.
|
|
a[0] = 0;
|
|
assert(b[0] == 0);
|
|
assert(a == b);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
size_t[] source = [0b1100, 0b0011];
|
|
enum sbits = size_t.sizeof * 8;
|
|
auto ba = BitArray(source, source.length * sbits);
|
|
// The least significant bit in each unit is this unit's starting bit.
|
|
assert(ba.bitsSet.equal([2, 3, sbits, sbits + 1]));
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
// Example from the doc for this constructor.
|
|
static immutable size_t[] sourceData = [1, 0b101, 3, 3424234, 724398, 230947, 389492];
|
|
size_t[] source = sourceData.dup;
|
|
enum sbits = size_t.sizeof * 8;
|
|
auto ba = BitArray(source, source.length * sbits);
|
|
foreach (n; 0 .. source.length * sbits)
|
|
{
|
|
auto nth_bit = cast(bool) (source[n / sbits] & (1L << (n % sbits)));
|
|
assert(ba[n] == nth_bit);
|
|
}
|
|
|
|
// Example of mapping only part of the array.
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto bc = BitArray(source, sbits + 1);
|
|
assert(bc.bitsSet.equal([0, sbits]));
|
|
// Source array has not been modified.
|
|
assert(source == sourceData);
|
|
}
|
|
|
|
// Deliberately undocumented: raw initialization of bit array.
|
|
this(size_t len, size_t* ptr) @nogc nothrow pure
|
|
{
|
|
_len = len;
|
|
_ptr = ptr;
|
|
}
|
|
|
|
/**
|
|
Returns: Dimension i.e. the number of native words backing this `BitArray`.
|
|
|
|
Technically, this is the length of the underlying array storing bits, which
|
|
is equal to `ceil(length / (size_t.sizeof * 8))`, as bits are packed into
|
|
`size_t` units.
|
|
*/
|
|
@property size_t dim() const @nogc nothrow pure @safe
|
|
{
|
|
return lenToDim(_len);
|
|
}
|
|
|
|
/**
|
|
Returns: Number of bits in the `BitArray`.
|
|
*/
|
|
@property size_t length() const @nogc nothrow pure @safe
|
|
{
|
|
return _len;
|
|
}
|
|
|
|
/**********************************************
|
|
* Sets the amount of bits in the `BitArray`.
|
|
* $(RED Warning: increasing length may overwrite bits in
|
|
* the final word of the current underlying data regardless
|
|
* of whether it is shared between BitArray objects. i.e. D
|
|
* dynamic array extension semantics are not followed.)
|
|
*/
|
|
@property size_t length(size_t newlen) pure nothrow @system
|
|
{
|
|
if (newlen != _len)
|
|
{
|
|
size_t olddim = dim;
|
|
immutable newdim = lenToDim(newlen);
|
|
|
|
if (newdim != olddim)
|
|
{
|
|
// Create a fake array so we can use D's realloc machinery
|
|
auto b = _ptr[0 .. olddim];
|
|
b.length = newdim; // realloc
|
|
_ptr = b.ptr;
|
|
}
|
|
|
|
auto oldlen = _len;
|
|
_len = newlen;
|
|
if (oldlen < newlen)
|
|
{
|
|
auto end = ((oldlen / bitsPerSizeT) + 1) * bitsPerSizeT;
|
|
if (end > newlen)
|
|
end = newlen;
|
|
this[oldlen .. end] = 0;
|
|
}
|
|
}
|
|
return _len;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20240
|
|
@system unittest
|
|
{
|
|
BitArray ba;
|
|
|
|
ba.length = 1;
|
|
ba[0] = 1;
|
|
ba.length = 0;
|
|
ba.length = 1;
|
|
assert(ba[0] == 0); // OK
|
|
|
|
ba.length = 2;
|
|
ba[1] = 1;
|
|
ba.length = 1;
|
|
ba.length = 2;
|
|
assert(ba[1] == 0); // Fail
|
|
}
|
|
|
|
/**********************************************
|
|
* Gets the `i`'th bit in the `BitArray`.
|
|
*/
|
|
bool opIndex(size_t i) const @nogc pure nothrow
|
|
in
|
|
{
|
|
assert(i < _len, "i must be less than the length");
|
|
}
|
|
do
|
|
{
|
|
return cast(bool) bt(_ptr, i);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static void fun(const BitArray arr)
|
|
{
|
|
auto x = arr[0];
|
|
assert(x == 1);
|
|
}
|
|
BitArray a;
|
|
a.length = 3;
|
|
a[0] = 1;
|
|
fun(a);
|
|
}
|
|
|
|
/**********************************************
|
|
* Sets the `i`'th bit in the `BitArray`.
|
|
*/
|
|
bool opIndexAssign(bool b, size_t i) @nogc pure nothrow
|
|
in
|
|
{
|
|
assert(i < _len, "i must be less than the length");
|
|
}
|
|
do
|
|
{
|
|
if (b)
|
|
bts(_ptr, i);
|
|
else
|
|
btr(_ptr, i);
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
Sets all the values in the `BitArray` to the
|
|
value specified by `val`.
|
|
*/
|
|
void opSliceAssign(bool val) @nogc pure nothrow
|
|
{
|
|
_ptr[0 .. fullWords] = val ? ~size_t(0) : 0;
|
|
if (endBits)
|
|
{
|
|
if (val)
|
|
_ptr[fullWords] |= endMask;
|
|
else
|
|
_ptr[fullWords] &= ~endMask;
|
|
}
|
|
}
|
|
|
|
///
|
|
@system pure nothrow unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto b = BitArray([1, 0, 1, 0, 1, 1]);
|
|
|
|
b[] = true;
|
|
// all bits are set
|
|
assert(b.bitsSet.equal([0, 1, 2, 3, 4, 5]));
|
|
|
|
b[] = false;
|
|
// none of the bits are set
|
|
assert(b.bitsSet.empty);
|
|
}
|
|
|
|
/**
|
|
Sets the bits of a slice of `BitArray` starting
|
|
at index `start` and ends at index ($D end - 1)
|
|
with the values specified by `val`.
|
|
*/
|
|
void opSliceAssign(bool val, size_t start, size_t end) @nogc pure nothrow
|
|
in
|
|
{
|
|
assert(start <= end, "start must be less or equal to end");
|
|
assert(end <= length, "end must be less or equal to the length");
|
|
}
|
|
do
|
|
{
|
|
size_t startBlock = start / bitsPerSizeT;
|
|
size_t endBlock = end / bitsPerSizeT;
|
|
size_t startOffset = start % bitsPerSizeT;
|
|
size_t endOffset = end % bitsPerSizeT;
|
|
|
|
if (startBlock == endBlock)
|
|
{
|
|
size_t startBlockMask = ~((size_t(1) << startOffset) - 1);
|
|
size_t endBlockMask = (size_t(1) << endOffset) - 1;
|
|
size_t joinMask = startBlockMask & endBlockMask;
|
|
if (val)
|
|
_ptr[startBlock] |= joinMask;
|
|
else
|
|
_ptr[startBlock] &= ~joinMask;
|
|
return;
|
|
}
|
|
|
|
if (startOffset != 0)
|
|
{
|
|
size_t startBlockMask = ~((size_t(1) << startOffset) - 1);
|
|
if (val)
|
|
_ptr[startBlock] |= startBlockMask;
|
|
else
|
|
_ptr[startBlock] &= ~startBlockMask;
|
|
++startBlock;
|
|
}
|
|
if (endOffset != 0)
|
|
{
|
|
size_t endBlockMask = (size_t(1) << endOffset) - 1;
|
|
if (val)
|
|
_ptr[endBlock] |= endBlockMask;
|
|
else
|
|
_ptr[endBlock] &= ~endBlockMask;
|
|
}
|
|
_ptr[startBlock .. endBlock] = size_t(0) - size_t(val);
|
|
}
|
|
|
|
///
|
|
@system pure nothrow unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
import std.stdio;
|
|
|
|
auto b = BitArray([1, 0, 0, 0, 1, 1, 0]);
|
|
b[1 .. 3] = true;
|
|
assert(b.bitsSet.equal([0, 1, 2, 4, 5]));
|
|
|
|
bool[72] bitArray;
|
|
auto b1 = BitArray(bitArray);
|
|
b1[63 .. 67] = true;
|
|
assert(b1.bitsSet.equal([63, 64, 65, 66]));
|
|
b1[63 .. 67] = false;
|
|
assert(b1.bitsSet.empty);
|
|
b1[0 .. 64] = true;
|
|
assert(b1.bitsSet.equal(iota(0, 64)));
|
|
b1[0 .. 64] = false;
|
|
assert(b1.bitsSet.empty);
|
|
|
|
bool[256] bitArray2;
|
|
auto b2 = BitArray(bitArray2);
|
|
b2[3 .. 245] = true;
|
|
assert(b2.bitsSet.equal(iota(3, 245)));
|
|
b2[3 .. 245] = false;
|
|
assert(b2.bitsSet.empty);
|
|
}
|
|
|
|
/**
|
|
Flips all the bits in the `BitArray`
|
|
*/
|
|
void flip() @nogc pure nothrow
|
|
{
|
|
foreach (i; 0 .. fullWords)
|
|
_ptr[i] = ~_ptr[i];
|
|
|
|
if (endBits)
|
|
_ptr[fullWords] = (~_ptr[fullWords]) & endMask;
|
|
}
|
|
|
|
///
|
|
@system pure nothrow unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
// positions 0, 2, 4 are set
|
|
auto b = BitArray([1, 0, 1, 0, 1, 0]);
|
|
b.flip();
|
|
// after flipping, positions 1, 3, 5 are set
|
|
assert(b.bitsSet.equal([1, 3, 5]));
|
|
|
|
bool[270] bits;
|
|
auto b1 = BitArray(bits);
|
|
b1.flip();
|
|
assert(b1.bitsSet.equal(iota(0, 270)));
|
|
}
|
|
|
|
/**
|
|
Flips a single bit, specified by `pos`
|
|
*/
|
|
void flip(size_t pos) @nogc pure nothrow
|
|
{
|
|
bt(_ptr, pos) ? btr(_ptr, pos) : bts(_ptr, pos);
|
|
}
|
|
|
|
///
|
|
@system pure nothrow unittest
|
|
{
|
|
auto ax = BitArray([1, 0, 0, 1]);
|
|
ax.flip(0);
|
|
assert(ax[0] == 0);
|
|
|
|
bool[200] y;
|
|
y[90 .. 130] = true;
|
|
auto ay = BitArray(y);
|
|
ay.flip(100);
|
|
assert(ay[100] == 0);
|
|
}
|
|
|
|
/**********************************************
|
|
* Counts all the set bits in the `BitArray`
|
|
*/
|
|
size_t count() const scope @safe @nogc pure nothrow
|
|
{
|
|
if (_ptr)
|
|
{
|
|
size_t bitCount;
|
|
foreach (i; 0 .. fullWords)
|
|
bitCount += (() @trusted => countBitsSet(_ptr[i]))();
|
|
if (endBits)
|
|
bitCount += (() @trusted => countBitsSet(_ptr[fullWords] & endMask))();
|
|
return bitCount;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
///
|
|
@system pure nothrow unittest
|
|
{
|
|
auto a = BitArray([0, 1, 1, 0, 0, 1, 1]);
|
|
assert(a.count == 4);
|
|
|
|
BitArray b;
|
|
assert(b.count == 0);
|
|
|
|
bool[200] boolArray;
|
|
boolArray[45 .. 130] = true;
|
|
auto c = BitArray(boolArray);
|
|
assert(c.count == 85);
|
|
}
|
|
|
|
/**********************************************
|
|
* Duplicates the `BitArray` and its contents.
|
|
*/
|
|
@property BitArray dup() const pure nothrow
|
|
{
|
|
BitArray ba;
|
|
|
|
auto b = _ptr[0 .. dim].dup;
|
|
ba._len = _len;
|
|
ba._ptr = b.ptr;
|
|
return ba;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
BitArray a;
|
|
BitArray b;
|
|
|
|
a.length = 3;
|
|
a[0] = 1; a[1] = 0; a[2] = 1;
|
|
b = a.dup;
|
|
assert(b.length == 3);
|
|
foreach (i; 0 .. 3)
|
|
assert(b[i] == (((i ^ 1) & 1) ? true : false));
|
|
}
|
|
|
|
/**********************************************
|
|
* Support for `foreach` loops for `BitArray`.
|
|
*/
|
|
int opApply(scope int delegate(ref bool) dg)
|
|
{
|
|
int result;
|
|
|
|
foreach (i; 0 .. _len)
|
|
{
|
|
bool b = opIndex(i);
|
|
result = dg(b);
|
|
this[i] = b;
|
|
if (result)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** ditto */
|
|
int opApply(scope int delegate(bool) dg) const
|
|
{
|
|
int result;
|
|
|
|
foreach (i; 0 .. _len)
|
|
{
|
|
immutable b = opIndex(i);
|
|
result = dg(b);
|
|
if (result)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** ditto */
|
|
int opApply(scope int delegate(size_t, ref bool) dg)
|
|
{
|
|
int result;
|
|
|
|
foreach (i; 0 .. _len)
|
|
{
|
|
bool b = opIndex(i);
|
|
result = dg(i, b);
|
|
this[i] = b;
|
|
if (result)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** ditto */
|
|
int opApply(scope int delegate(size_t, bool) dg) const
|
|
{
|
|
int result;
|
|
|
|
foreach (i; 0 .. _len)
|
|
{
|
|
immutable b = opIndex(i);
|
|
result = dg(i, b);
|
|
if (result)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1];
|
|
|
|
auto a = BitArray(ba);
|
|
|
|
int i;
|
|
foreach (b;a)
|
|
{
|
|
switch (i)
|
|
{
|
|
case 0: assert(b == true); break;
|
|
case 1: assert(b == false); break;
|
|
case 2: assert(b == true); break;
|
|
default: assert(0);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
foreach (j,b;a)
|
|
{
|
|
switch (j)
|
|
{
|
|
case 0: assert(b == true); break;
|
|
case 1: assert(b == false); break;
|
|
case 2: assert(b == true); break;
|
|
default: assert(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************************
|
|
* Reverses the bits of the `BitArray`.
|
|
*/
|
|
@property BitArray reverse() @nogc pure nothrow return
|
|
out (result)
|
|
{
|
|
assert(result == this, "the result must be equal to this");
|
|
}
|
|
do
|
|
{
|
|
if (_len >= 2)
|
|
{
|
|
bool t;
|
|
size_t lo, hi;
|
|
|
|
lo = 0;
|
|
hi = _len - 1;
|
|
for (; lo < hi; lo++, hi--)
|
|
{
|
|
t = this[lo];
|
|
this[lo] = this[hi];
|
|
this[hi] = t;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
BitArray b;
|
|
bool[5] data = [1,0,1,1,0];
|
|
|
|
b = BitArray(data);
|
|
b.reverse;
|
|
foreach (i; 0 .. data.length)
|
|
assert(b[i] == data[4 - i]);
|
|
}
|
|
|
|
|
|
/**********************************************
|
|
* Sorts the `BitArray`'s elements.
|
|
*/
|
|
@property BitArray sort() @nogc pure nothrow return
|
|
out (result)
|
|
{
|
|
assert(result == this, "the result must be equal to this");
|
|
}
|
|
do
|
|
{
|
|
if (_len >= 2)
|
|
{
|
|
size_t lo, hi;
|
|
|
|
lo = 0;
|
|
hi = _len - 1;
|
|
while (1)
|
|
{
|
|
while (1)
|
|
{
|
|
if (lo >= hi)
|
|
goto Ldone;
|
|
if (this[lo] == true)
|
|
break;
|
|
lo++;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
if (lo >= hi)
|
|
goto Ldone;
|
|
if (this[hi] == false)
|
|
break;
|
|
hi--;
|
|
}
|
|
|
|
this[lo] = false;
|
|
this[hi] = true;
|
|
|
|
lo++;
|
|
hi--;
|
|
}
|
|
}
|
|
Ldone:
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
size_t x = 0b1100011000;
|
|
auto ba = BitArray(10, &x);
|
|
ba.sort;
|
|
foreach (i; 0 .. 6)
|
|
assert(ba[i] == false);
|
|
foreach (i; 6 .. 10)
|
|
assert(ba[i] == true);
|
|
}
|
|
|
|
|
|
/***************************************
|
|
* Support for operators == and != for `BitArray`.
|
|
*/
|
|
bool opEquals(const ref BitArray a2) const @nogc pure nothrow
|
|
{
|
|
if (this.length != a2.length)
|
|
return false;
|
|
auto p1 = this._ptr;
|
|
auto p2 = a2._ptr;
|
|
|
|
if (p1[0 .. fullWords] != p2[0 .. fullWords])
|
|
return false;
|
|
|
|
if (!endBits)
|
|
return true;
|
|
|
|
auto i = fullWords;
|
|
return (p1[i] & endMask) == (p2[i] & endMask);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1];
|
|
bool[] bc = [1,0,1,0,1,0,1];
|
|
bool[] bd = [1,0,1,1,1];
|
|
bool[] be = [1,0,1,0,1];
|
|
bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
|
bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
auto c = BitArray(bc);
|
|
auto d = BitArray(bd);
|
|
auto e = BitArray(be);
|
|
auto f = BitArray(bf);
|
|
auto g = BitArray(bg);
|
|
|
|
assert(a != b);
|
|
assert(a != c);
|
|
assert(a != d);
|
|
assert(a == e);
|
|
assert(f != g);
|
|
}
|
|
|
|
/***************************************
|
|
* Supports comparison operators for `BitArray`.
|
|
*/
|
|
int opCmp(BitArray a2) const @nogc pure nothrow
|
|
{
|
|
const lesser = this.length < a2.length ? &this : &a2;
|
|
immutable fullWords = lesser.fullWords;
|
|
immutable endBits = lesser.endBits;
|
|
auto p1 = this._ptr;
|
|
auto p2 = a2._ptr;
|
|
|
|
foreach (i; 0 .. fullWords)
|
|
{
|
|
if (p1[i] != p2[i])
|
|
{
|
|
return p1[i] & (size_t(1) << bsf(p1[i] ^ p2[i])) ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
if (endBits)
|
|
{
|
|
immutable i = fullWords;
|
|
immutable diff = p1[i] ^ p2[i];
|
|
if (diff)
|
|
{
|
|
immutable index = bsf(diff);
|
|
if (index < endBits)
|
|
{
|
|
return p1[i] & (size_t(1) << index) ? 1 : -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Standard:
|
|
// A bool value can be implicitly converted to any integral type,
|
|
// with false becoming 0 and true becoming 1
|
|
return (this.length > a2.length) - (this.length < a2.length);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1];
|
|
bool[] bc = [1,0,1,0,1,0,1];
|
|
bool[] bd = [1,0,1,1,1];
|
|
bool[] be = [1,0,1,0,1];
|
|
bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
|
|
bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
auto c = BitArray(bc);
|
|
auto d = BitArray(bd);
|
|
auto e = BitArray(be);
|
|
auto f = BitArray(bf);
|
|
auto g = BitArray(bg);
|
|
|
|
assert(a > b);
|
|
assert(a >= b);
|
|
assert(a < c);
|
|
assert(a <= c);
|
|
assert(a < d);
|
|
assert(a <= d);
|
|
assert(a == e);
|
|
assert(a <= e);
|
|
assert(a >= e);
|
|
assert(f < g);
|
|
assert(g <= g);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
bool[] v;
|
|
foreach (i; 1 .. 256)
|
|
{
|
|
v.length = i;
|
|
v[] = false;
|
|
auto x = BitArray(v);
|
|
v[i-1] = true;
|
|
auto y = BitArray(v);
|
|
assert(x < y);
|
|
assert(x <= y);
|
|
}
|
|
|
|
BitArray a1, a2;
|
|
|
|
for (size_t len = 4; len <= 256; len <<= 1)
|
|
{
|
|
a1.length = a2.length = len;
|
|
a1[len-2] = a2[len-1] = true;
|
|
assert(a1 > a2);
|
|
a1[len-2] = a2[len-1] = false;
|
|
}
|
|
|
|
foreach (j; 1 .. a1.length)
|
|
{
|
|
a1[j-1] = a2[j] = true;
|
|
assert(a1 > a2);
|
|
a1[j-1] = a2[j] = false;
|
|
}
|
|
}
|
|
|
|
/***************************************
|
|
* Support for hashing for `BitArray`.
|
|
*/
|
|
size_t toHash() const @nogc pure nothrow
|
|
{
|
|
size_t hash = 3557;
|
|
auto fullBytes = _len / 8;
|
|
foreach (i; 0 .. fullBytes)
|
|
{
|
|
hash *= 3559;
|
|
hash += (cast(byte*) this._ptr)[i];
|
|
}
|
|
foreach (i; 8*fullBytes .. _len)
|
|
{
|
|
hash *= 3571;
|
|
hash += this[i];
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/***************************************
|
|
* Convert to `void[]`.
|
|
*/
|
|
inout(void)[] opCast(T : const void[])() inout @nogc pure nothrow
|
|
{
|
|
return cast(inout void[]) _ptr[0 .. dim];
|
|
}
|
|
|
|
/***************************************
|
|
* Convert to `size_t[]`.
|
|
*/
|
|
inout(size_t)[] opCast(T : const size_t[])() inout @nogc pure nothrow
|
|
{
|
|
return _ptr[0 .. dim];
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.array : array;
|
|
import std.range : repeat, take;
|
|
|
|
// bit array with 300 elements
|
|
auto a = BitArray(true.repeat.take(300).array);
|
|
size_t[] v = cast(size_t[]) a;
|
|
const blockSize = size_t.sizeof * 8;
|
|
assert(v.length == (a.length + blockSize - 1) / blockSize);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20606
|
|
@system unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
static foreach (alias T; AliasSeq!(void, size_t))
|
|
{{
|
|
BitArray m;
|
|
T[] ma = cast(T[]) m;
|
|
|
|
const BitArray c;
|
|
const(T)[] ca = cast(const T[]) c;
|
|
|
|
immutable BitArray i;
|
|
immutable(T)[] ia = cast(immutable T[]) i;
|
|
|
|
// Cross-mutability
|
|
ca = cast(const T[]) m;
|
|
ca = cast(const T[]) i;
|
|
|
|
// Invalid cast don't compile
|
|
static assert(!is(typeof(cast(T[]) c)));
|
|
static assert(!is(typeof(cast(T[]) i)));
|
|
static assert(!is(typeof(cast(immutable T[]) m)));
|
|
static assert(!is(typeof(cast(immutable T[]) c)));
|
|
}}
|
|
}
|
|
|
|
/***************************************
|
|
* Support for unary operator ~ for `BitArray`.
|
|
*/
|
|
BitArray opUnary(string op)() const pure nothrow
|
|
if (op == "~")
|
|
{
|
|
auto dim = this.dim;
|
|
|
|
BitArray result;
|
|
result.length = _len;
|
|
|
|
result._ptr[0 .. dim] = ~this._ptr[0 .. dim];
|
|
|
|
// Avoid putting garbage in extra bits
|
|
// Remove once we zero on length extension
|
|
if (endBits)
|
|
result._ptr[dim - 1] &= endMask;
|
|
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
|
|
auto a = BitArray(ba);
|
|
BitArray b = ~a;
|
|
|
|
assert(b[0] == 0);
|
|
assert(b[1] == 1);
|
|
assert(b[2] == 0);
|
|
assert(b[3] == 1);
|
|
assert(b[4] == 0);
|
|
}
|
|
|
|
|
|
/***************************************
|
|
* Support for binary bitwise operators for `BitArray`.
|
|
*/
|
|
BitArray opBinary(string op)(const BitArray e2) const pure nothrow
|
|
if (op == "-" || op == "&" || op == "|" || op == "^")
|
|
in
|
|
{
|
|
assert(e2.length == _len, "e2 must have the same length as this");
|
|
}
|
|
do
|
|
{
|
|
auto dim = this.dim;
|
|
|
|
BitArray result;
|
|
result.length = _len;
|
|
|
|
static if (op == "-")
|
|
result._ptr[0 .. dim] = this._ptr[0 .. dim] & ~e2._ptr[0 .. dim];
|
|
else
|
|
mixin("result._ptr[0 .. dim] = this._ptr[0 .. dim]"~op~" e2._ptr[0 .. dim];");
|
|
|
|
// Avoid putting garbage in extra bits
|
|
// Remove once we zero on length extension
|
|
if (endBits)
|
|
result._ptr[dim - 1] &= endMask;
|
|
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static bool[] ba = [1,0,1,0,1];
|
|
static bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
BitArray c = a & b;
|
|
|
|
assert(c[0] == 1);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 1);
|
|
assert(c[3] == 0);
|
|
assert(c[4] == 0);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
BitArray c = a | b;
|
|
|
|
assert(c[0] == 1);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 1);
|
|
assert(c[3] == 1);
|
|
assert(c[4] == 1);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
BitArray c = a ^ b;
|
|
|
|
assert(c[0] == 0);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 0);
|
|
assert(c[3] == 1);
|
|
assert(c[4] == 1);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
BitArray c = a - b;
|
|
|
|
assert(c[0] == 0);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 0);
|
|
assert(c[3] == 0);
|
|
assert(c[4] == 1);
|
|
}
|
|
|
|
|
|
/***************************************
|
|
* Support for operator op= for `BitArray`.
|
|
*/
|
|
BitArray opOpAssign(string op)(const BitArray e2) @nogc pure nothrow return scope
|
|
if (op == "-" || op == "&" || op == "|" || op == "^")
|
|
in
|
|
{
|
|
assert(e2.length == _len, "e2 must have the same length as this");
|
|
}
|
|
do
|
|
{
|
|
foreach (i; 0 .. fullWords)
|
|
{
|
|
static if (op == "-")
|
|
_ptr[i] &= ~e2._ptr[i];
|
|
else
|
|
mixin("_ptr[i] "~op~"= e2._ptr[i];");
|
|
}
|
|
if (!endBits)
|
|
return this;
|
|
|
|
size_t i = fullWords;
|
|
size_t endWord = _ptr[i];
|
|
static if (op == "-")
|
|
endWord &= ~e2._ptr[i];
|
|
else
|
|
mixin("endWord "~op~"= e2._ptr[i];");
|
|
_ptr[i] = (_ptr[i] & ~endMask) | (endWord & endMask);
|
|
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1,1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
BitArray c = a;
|
|
c.length = 5;
|
|
c &= b;
|
|
assert(a[5] == 1);
|
|
assert(a[6] == 0);
|
|
assert(a[7] == 1);
|
|
assert(a[8] == 0);
|
|
assert(a[9] == 1);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
a &= b;
|
|
assert(a[0] == 1);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 1);
|
|
assert(a[3] == 0);
|
|
assert(a[4] == 0);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
a |= b;
|
|
assert(a[0] == 1);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 1);
|
|
assert(a[3] == 1);
|
|
assert(a[4] == 1);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
a ^= b;
|
|
assert(a[0] == 0);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 0);
|
|
assert(a[3] == 1);
|
|
assert(a[4] == 1);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
bool[] bb = [1,0,1,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
|
|
a -= b;
|
|
assert(a[0] == 0);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 0);
|
|
assert(a[3] == 0);
|
|
assert(a[4] == 1);
|
|
}
|
|
|
|
/***************************************
|
|
* Support for operator ~= for `BitArray`.
|
|
* $(RED Warning: This will overwrite a bit in the final word
|
|
* of the current underlying data regardless of whether it is
|
|
* shared between BitArray objects. i.e. D dynamic array
|
|
* concatenation semantics are not followed)
|
|
*/
|
|
BitArray opOpAssign(string op)(bool b) pure nothrow return scope
|
|
if (op == "~")
|
|
{
|
|
length = _len + 1;
|
|
this[_len - 1] = b;
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0,1,0,1];
|
|
|
|
auto a = BitArray(ba);
|
|
BitArray b;
|
|
|
|
b = (a ~= true);
|
|
assert(a[0] == 1);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 1);
|
|
assert(a[3] == 0);
|
|
assert(a[4] == 1);
|
|
assert(a[5] == 1);
|
|
|
|
assert(b == a);
|
|
}
|
|
|
|
/***************************************
|
|
* ditto
|
|
*/
|
|
BitArray opOpAssign(string op)(BitArray b) pure nothrow return scope
|
|
if (op == "~")
|
|
{
|
|
auto istart = _len;
|
|
length = _len + b.length;
|
|
for (auto i = istart; i < _len; i++)
|
|
this[i] = b[i - istart];
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0];
|
|
bool[] bb = [0,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
BitArray c;
|
|
|
|
c = (a ~= b);
|
|
assert(a.length == 5);
|
|
assert(a[0] == 1);
|
|
assert(a[1] == 0);
|
|
assert(a[2] == 0);
|
|
assert(a[3] == 1);
|
|
assert(a[4] == 0);
|
|
|
|
assert(c == a);
|
|
}
|
|
|
|
/***************************************
|
|
* Support for binary operator ~ for `BitArray`.
|
|
*/
|
|
BitArray opBinary(string op)(bool b) const pure nothrow
|
|
if (op == "~")
|
|
{
|
|
BitArray r;
|
|
|
|
r = this.dup;
|
|
r.length = _len + 1;
|
|
r[_len] = b;
|
|
return r;
|
|
}
|
|
|
|
/** ditto */
|
|
BitArray opBinaryRight(string op)(bool b) const pure nothrow
|
|
if (op == "~")
|
|
{
|
|
BitArray r;
|
|
|
|
r.length = _len + 1;
|
|
r[0] = b;
|
|
foreach (i; 0 .. _len)
|
|
r[1 + i] = this[i];
|
|
return r;
|
|
}
|
|
|
|
/** ditto */
|
|
BitArray opBinary(string op)(BitArray b) const pure nothrow
|
|
if (op == "~")
|
|
{
|
|
BitArray r;
|
|
|
|
r = this.dup;
|
|
r ~= b;
|
|
return r;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
bool[] ba = [1,0];
|
|
bool[] bb = [0,1,0];
|
|
|
|
auto a = BitArray(ba);
|
|
auto b = BitArray(bb);
|
|
BitArray c;
|
|
|
|
c = (a ~ b);
|
|
assert(c.length == 5);
|
|
assert(c[0] == 1);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 0);
|
|
assert(c[3] == 1);
|
|
assert(c[4] == 0);
|
|
|
|
c = (a ~ true);
|
|
assert(c.length == 3);
|
|
assert(c[0] == 1);
|
|
assert(c[1] == 0);
|
|
assert(c[2] == 1);
|
|
|
|
c = (false ~ a);
|
|
assert(c.length == 3);
|
|
assert(c[0] == 0);
|
|
assert(c[1] == 1);
|
|
assert(c[2] == 0);
|
|
}
|
|
|
|
// Rolls double word (upper, lower) to the right by n bits and returns the
|
|
// lower word of the result.
|
|
private static size_t rollRight()(size_t upper, size_t lower, size_t nbits)
|
|
pure @safe nothrow @nogc
|
|
in
|
|
{
|
|
assert(nbits < bitsPerSizeT, "nbits must be less than bitsPerSizeT");
|
|
}
|
|
do
|
|
{
|
|
if (nbits == 0)
|
|
return lower;
|
|
return (upper << (bitsPerSizeT - nbits)) | (lower >> nbits);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static if (size_t.sizeof == 8)
|
|
{
|
|
size_t x = 0x12345678_90ABCDEF;
|
|
size_t y = 0xFEDBCA09_87654321;
|
|
|
|
assert(rollRight(x, y, 32) == 0x90ABCDEF_FEDBCA09);
|
|
assert(rollRight(y, x, 4) == 0x11234567_890ABCDE);
|
|
}
|
|
else static if (size_t.sizeof == 4)
|
|
{
|
|
size_t x = 0x12345678;
|
|
size_t y = 0x90ABCDEF;
|
|
|
|
assert(rollRight(x, y, 16) == 0x567890AB);
|
|
assert(rollRight(y, x, 4) == 0xF1234567);
|
|
}
|
|
else
|
|
static assert(0, "Unsupported size_t width");
|
|
}
|
|
|
|
// Rolls double word (upper, lower) to the left by n bits and returns the
|
|
// upper word of the result.
|
|
private static size_t rollLeft()(size_t upper, size_t lower, size_t nbits)
|
|
pure @safe nothrow @nogc
|
|
in
|
|
{
|
|
assert(nbits < bitsPerSizeT, "nbits must be less than bitsPerSizeT");
|
|
}
|
|
do
|
|
{
|
|
if (nbits == 0)
|
|
return upper;
|
|
return (upper << nbits) | (lower >> (bitsPerSizeT - nbits));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static if (size_t.sizeof == 8)
|
|
{
|
|
size_t x = 0x12345678_90ABCDEF;
|
|
size_t y = 0xFEDBCA09_87654321;
|
|
|
|
assert(rollLeft(x, y, 32) == 0x90ABCDEF_FEDBCA09);
|
|
assert(rollLeft(y, x, 4) == 0xEDBCA098_76543211);
|
|
}
|
|
else static if (size_t.sizeof == 4)
|
|
{
|
|
size_t x = 0x12345678;
|
|
size_t y = 0x90ABCDEF;
|
|
|
|
assert(rollLeft(x, y, 16) == 0x567890AB);
|
|
assert(rollLeft(y, x, 4) == 0x0ABCDEF1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Operator `<<=` support.
|
|
*
|
|
* Shifts all the bits in the array to the left by the given number of
|
|
* bits. The leftmost bits are dropped, and 0's are appended to the end
|
|
* to fill up the vacant bits.
|
|
*
|
|
* $(RED Warning: unused bits in the final word up to the next word
|
|
* boundary may be overwritten by this operation. It does not attempt to
|
|
* preserve bits past the end of the array.)
|
|
*/
|
|
void opOpAssign(string op)(size_t nbits) @nogc pure nothrow
|
|
if (op == "<<")
|
|
{
|
|
size_t wordsToShift = nbits / bitsPerSizeT;
|
|
size_t bitsToShift = nbits % bitsPerSizeT;
|
|
|
|
if (wordsToShift < dim)
|
|
{
|
|
foreach_reverse (i; 1 .. dim - wordsToShift)
|
|
{
|
|
_ptr[i + wordsToShift] = rollLeft(_ptr[i], _ptr[i-1],
|
|
bitsToShift);
|
|
}
|
|
_ptr[wordsToShift] = rollLeft(_ptr[0], 0, bitsToShift);
|
|
}
|
|
|
|
import std.algorithm.comparison : min;
|
|
foreach (i; 0 .. min(wordsToShift, dim))
|
|
{
|
|
_ptr[i] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Operator `>>=` support.
|
|
*
|
|
* Shifts all the bits in the array to the right by the given number of
|
|
* bits. The rightmost bits are dropped, and 0's are inserted at the back
|
|
* to fill up the vacant bits.
|
|
*
|
|
* $(RED Warning: unused bits in the final word up to the next word
|
|
* boundary may be overwritten by this operation. It does not attempt to
|
|
* preserve bits past the end of the array.)
|
|
*/
|
|
void opOpAssign(string op)(size_t nbits) @nogc pure nothrow
|
|
if (op == ">>")
|
|
{
|
|
size_t wordsToShift = nbits / bitsPerSizeT;
|
|
size_t bitsToShift = nbits % bitsPerSizeT;
|
|
|
|
if (wordsToShift + 1 < dim)
|
|
{
|
|
foreach (i; 0 .. dim - wordsToShift - 1)
|
|
{
|
|
_ptr[i] = rollRight(_ptr[i + wordsToShift + 1],
|
|
_ptr[i + wordsToShift], bitsToShift);
|
|
}
|
|
}
|
|
|
|
// The last word needs some care, as it must shift in 0's from past the
|
|
// end of the array.
|
|
if (wordsToShift < dim)
|
|
{
|
|
if (bitsToShift == 0)
|
|
_ptr[dim - wordsToShift - 1] = _ptr[dim - 1];
|
|
else
|
|
{
|
|
// Special case: if endBits == 0, then also endMask == 0.
|
|
size_t lastWord = (endBits ? (_ptr[fullWords] & endMask) : _ptr[fullWords - 1]);
|
|
_ptr[dim - wordsToShift - 1] = rollRight(0, lastWord, bitsToShift);
|
|
}
|
|
}
|
|
|
|
import std.algorithm.comparison : min;
|
|
foreach (i; 0 .. min(wordsToShift, dim))
|
|
{
|
|
_ptr[dim - i - 1] = 0;
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17467
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
bool[] buf = new bool[64*3];
|
|
buf[0 .. 64] = true;
|
|
BitArray b = BitArray(buf);
|
|
assert(equal(b.bitsSet, iota(0, 64)));
|
|
b <<= 64;
|
|
assert(equal(b.bitsSet, iota(64, 128)));
|
|
|
|
buf = new bool[64*3];
|
|
buf[64*2 .. 64*3] = true;
|
|
b = BitArray(buf);
|
|
assert(equal(b.bitsSet, iota(64*2, 64*3)));
|
|
b >>= 64;
|
|
assert(equal(b.bitsSet, iota(64, 128)));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=18134
|
|
// shifting right when length is a multiple of 8 * size_t.sizeof.
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.array : array;
|
|
import std.range : repeat, iota;
|
|
|
|
immutable r = size_t.sizeof * 8;
|
|
|
|
BitArray a = true.repeat(r / 2).array;
|
|
a >>= 0;
|
|
assert(a.bitsSet.equal(iota(0, r / 2)));
|
|
a >>= 1;
|
|
assert(a.bitsSet.equal(iota(0, r / 2 - 1)));
|
|
|
|
BitArray b = true.repeat(r).array;
|
|
b >>= 0;
|
|
assert(b.bitsSet.equal(iota(0, r)));
|
|
b >>= 1;
|
|
assert(b.bitsSet.equal(iota(0, r - 1)));
|
|
|
|
BitArray c = true.repeat(2 * r).array;
|
|
c >>= 0;
|
|
assert(c.bitsSet.equal(iota(0, 2 * r)));
|
|
c >>= 10;
|
|
assert(c.bitsSet.equal(iota(0, 2 * r - 10)));
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
auto b = BitArray([1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]);
|
|
|
|
b <<= 1;
|
|
assert(format("%b", b) == "01100_10101101");
|
|
|
|
b >>= 1;
|
|
assert(format("%b", b) == "11001_01011010");
|
|
|
|
b <<= 4;
|
|
assert(format("%b", b) == "00001_10010101");
|
|
|
|
b >>= 5;
|
|
assert(format("%b", b) == "10010_10100000");
|
|
|
|
b <<= 13;
|
|
assert(format("%b", b) == "00000_00000000");
|
|
|
|
b = BitArray([1, 0, 1, 1, 0, 1, 1, 1]);
|
|
b >>= 8;
|
|
assert(format("%b", b) == "00000000");
|
|
|
|
}
|
|
|
|
// Test multi-word case
|
|
@system unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
// This has to be long enough to occupy more than one size_t. On 64-bit
|
|
// machines, this would be at least 64 bits.
|
|
auto b = BitArray([
|
|
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1,
|
|
]);
|
|
b <<= 8;
|
|
assert(format("%b", b) ==
|
|
"00000000_10000000_"~
|
|
"11000000_11100000_"~
|
|
"11110000_11111000_"~
|
|
"11111100_11111110_"~
|
|
"11111111_10101010");
|
|
|
|
// Test right shift of more than one size_t's worth of bits
|
|
b <<= 68;
|
|
assert(format("%b", b) ==
|
|
"00000000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00001000");
|
|
|
|
b = BitArray([
|
|
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1,
|
|
]);
|
|
b >>= 8;
|
|
assert(format("%b", b) ==
|
|
"11000000_11100000_"~
|
|
"11110000_11111000_"~
|
|
"11111100_11111110_"~
|
|
"11111111_10101010_"~
|
|
"01010101_00000000");
|
|
|
|
// Test left shift of more than 1 size_t's worth of bits
|
|
b >>= 68;
|
|
assert(format("%b", b) ==
|
|
"01010000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00000000_"~
|
|
"00000000_00000000");
|
|
}
|
|
|
|
/***************************************
|
|
* Return a string representation of this BitArray.
|
|
*
|
|
* Two format specifiers are supported:
|
|
* $(LI $(B %s) which prints the bits as an array, and)
|
|
* $(LI $(B %b) which prints the bits as 8-bit byte packets)
|
|
* separated with an underscore.
|
|
*
|
|
* Params:
|
|
* sink = A `char` accepting
|
|
* $(REF_ALTTEXT output range, isOutputRange, std, range, primitives).
|
|
* fmt = A $(REF FormatSpec, std,format) which controls how the data
|
|
* is displayed.
|
|
*/
|
|
void toString(W)(ref W sink, scope const ref FormatSpec!char fmt) const
|
|
if (isOutputRange!(W, char))
|
|
{
|
|
const spec = fmt.spec;
|
|
switch (spec)
|
|
{
|
|
case 'b':
|
|
return formatBitString(sink);
|
|
case 's':
|
|
return formatBitArray(sink);
|
|
default:
|
|
throw new Exception("Unknown format specifier: %" ~ spec);
|
|
}
|
|
}
|
|
|
|
///
|
|
@system pure unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
auto b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
|
|
auto s1 = format("%s", b);
|
|
assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]");
|
|
|
|
auto s2 = format("%b", b);
|
|
assert(s2 == "00001111_00001111");
|
|
}
|
|
|
|
/***************************************
|
|
* Return a lazy range of the indices of set bits.
|
|
*/
|
|
@property auto bitsSet() const nothrow
|
|
{
|
|
import std.algorithm.iteration : filter, map, joiner;
|
|
import std.range : iota, chain;
|
|
|
|
return chain(
|
|
iota(fullWords)
|
|
.filter!(i => _ptr[i])()
|
|
.map!(i => BitsSet!size_t(_ptr[i], i * bitsPerSizeT))()
|
|
.joiner(),
|
|
iota(fullWords * bitsPerSizeT, _len)
|
|
.filter!(i => this[i])()
|
|
);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto b1 = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(b1.bitsSet.equal([4, 5, 6, 7, 12, 13, 14, 15]));
|
|
|
|
BitArray b2;
|
|
b2.length = 1000;
|
|
b2[333] = true;
|
|
b2[666] = true;
|
|
b2[999] = true;
|
|
assert(b2.bitsSet.equal([333, 666, 999]));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
BitArray b;
|
|
enum wordBits = size_t.sizeof * 8;
|
|
b = BitArray([size_t.max], 0);
|
|
assert(b.bitsSet.empty);
|
|
b = BitArray([size_t.max], 1);
|
|
assert(b.bitsSet.equal([0]));
|
|
b = BitArray([size_t.max], wordBits);
|
|
assert(b.bitsSet.equal(iota(wordBits)));
|
|
b = BitArray([size_t.max, size_t.max], wordBits);
|
|
assert(b.bitsSet.equal(iota(wordBits)));
|
|
b = BitArray([size_t.max, size_t.max], wordBits + 1);
|
|
assert(b.bitsSet.equal(iota(wordBits + 1)));
|
|
b = BitArray([size_t.max, size_t.max], wordBits * 2);
|
|
assert(b.bitsSet.equal(iota(wordBits * 2)));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20241
|
|
@system unittest
|
|
{
|
|
BitArray ba;
|
|
ba.length = 2;
|
|
ba[1] = 1;
|
|
ba.length = 1;
|
|
assert(ba.bitsSet.empty);
|
|
}
|
|
|
|
private void formatBitString(Writer)(auto ref Writer sink) const
|
|
{
|
|
if (!length)
|
|
return;
|
|
|
|
auto leftover = _len % 8;
|
|
foreach (idx; 0 .. leftover)
|
|
{
|
|
put(sink, cast(char)(this[idx] + '0'));
|
|
}
|
|
|
|
if (leftover && _len > 8)
|
|
put(sink, "_");
|
|
|
|
size_t count;
|
|
foreach (idx; leftover .. _len)
|
|
{
|
|
put(sink, cast(char)(this[idx] + '0'));
|
|
if (++count == 8 && idx != _len - 1)
|
|
{
|
|
put(sink, "_");
|
|
count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void formatBitArray(Writer)(auto ref Writer sink) const
|
|
{
|
|
put(sink, "[");
|
|
foreach (idx; 0 .. _len)
|
|
{
|
|
put(sink, cast(char)(this[idx] + '0'));
|
|
if (idx + 1 < _len)
|
|
put(sink, ", ");
|
|
}
|
|
put(sink, "]");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20639
|
|
// Separate @nogc test because public tests use array literals
|
|
// (and workarounds needlessly uglify those examples)
|
|
@system @nogc unittest
|
|
{
|
|
size_t[2] buffer;
|
|
BitArray b = BitArray(buffer[], buffer.sizeof * 8);
|
|
|
|
b[] = true;
|
|
b[0 .. 1] = true;
|
|
b.flip();
|
|
b.flip(1);
|
|
cast(void) b.count();
|
|
}
|
|
}
|
|
|
|
/// Slicing & bitsSet
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
bool[] buf = new bool[64 * 3];
|
|
buf[0 .. 64] = true;
|
|
BitArray b = BitArray(buf);
|
|
assert(b.bitsSet.equal(iota(0, 64)));
|
|
b <<= 64;
|
|
assert(b.bitsSet.equal(iota(64, 128)));
|
|
}
|
|
|
|
/// Concatenation and appending
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto b = BitArray([1, 0]);
|
|
b ~= true;
|
|
assert(b[2] == 1);
|
|
b ~= BitArray([0, 1]);
|
|
auto c = BitArray([1, 0, 1, 0, 1]);
|
|
assert(b == c);
|
|
assert(b.bitsSet.equal([0, 2, 4]));
|
|
}
|
|
|
|
/// Bit flipping
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
|
|
auto b = BitArray([1, 1, 0, 1]);
|
|
b &= BitArray([0, 1, 1, 0]);
|
|
assert(b.bitsSet.equal([1]));
|
|
b.flip;
|
|
assert(b.bitsSet.equal([0, 2, 3]));
|
|
}
|
|
|
|
/// String format of bitarrays
|
|
@system unittest
|
|
{
|
|
import std.format : format;
|
|
auto b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(format("%b", b) == "1_00001111_00001111");
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
BitArray b;
|
|
|
|
b = BitArray([]);
|
|
assert(format("%s", b) == "[]");
|
|
assert(format("%b", b) is null);
|
|
|
|
b = BitArray([1]);
|
|
assert(format("%s", b) == "[1]");
|
|
assert(format("%b", b) == "1");
|
|
|
|
b = BitArray([0, 0, 0, 0]);
|
|
assert(format("%b", b) == "0000");
|
|
|
|
b = BitArray([0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1]");
|
|
assert(format("%b", b) == "00001111");
|
|
|
|
b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]");
|
|
assert(format("%b", b) == "00001111_00001111");
|
|
|
|
b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(format("%b", b) == "1_00001111");
|
|
|
|
b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
|
|
assert(format("%b", b) == "1_00001111_00001111");
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
BitArray a;
|
|
a.length = 5;
|
|
foreach (ref bool b; a)
|
|
{
|
|
assert(b == 0);
|
|
b = 1;
|
|
}
|
|
foreach (bool b; a)
|
|
assert(b == 1);
|
|
}
|
|
|
|
/++
|
|
Swaps the endianness of the given integral value or character.
|
|
+/
|
|
T swapEndian(T)(const T val) @safe pure nothrow @nogc
|
|
if (isIntegral!T || isSomeChar!T || isBoolean!T)
|
|
{
|
|
import core.bitop : bswap, byteswap;
|
|
static if (val.sizeof == 1)
|
|
return val;
|
|
else static if (T.sizeof == 2)
|
|
return cast(T) byteswap(cast(ushort) val);
|
|
else static if (T.sizeof == 4)
|
|
return cast(T) bswap(cast(uint) val);
|
|
else static if (T.sizeof == 8)
|
|
return cast(T) bswap(cast(ulong) val);
|
|
else
|
|
static assert(0, T.stringof ~ " unsupported by swapEndian.");
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(42.swapEndian == 704643072);
|
|
assert(42.swapEndian.swapEndian == 42); // reflexive
|
|
assert(1.swapEndian == 16777216);
|
|
|
|
assert(true.swapEndian == true);
|
|
assert(byte(10).swapEndian == 10);
|
|
assert(char(10).swapEndian == 10);
|
|
|
|
assert(ushort(10).swapEndian == 2560);
|
|
assert(long(10).swapEndian == 720575940379279360);
|
|
assert(ulong(10).swapEndian == 720575940379279360);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
import std.stdio;
|
|
static foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar))
|
|
{{
|
|
scope(failure) writeln("Failed type: ", T.stringof);
|
|
T val;
|
|
const T cval;
|
|
immutable T ival;
|
|
|
|
assert(swapEndian(swapEndian(val)) == val);
|
|
assert(swapEndian(swapEndian(cval)) == cval);
|
|
assert(swapEndian(swapEndian(ival)) == ival);
|
|
assert(swapEndian(swapEndian(T.min)) == T.min);
|
|
assert(swapEndian(swapEndian(T.max)) == T.max);
|
|
|
|
// Check CTFE compiles.
|
|
static assert(swapEndian(swapEndian(T(1))) is T(1));
|
|
|
|
foreach (i; 2 .. 10)
|
|
{
|
|
immutable T maxI = cast(T)(T.max / i);
|
|
immutable T minI = cast(T)(T.min / i);
|
|
|
|
assert(swapEndian(swapEndian(maxI)) == maxI);
|
|
|
|
static if (isSigned!T)
|
|
assert(swapEndian(swapEndian(minI)) == minI);
|
|
}
|
|
|
|
static if (isSigned!T)
|
|
assert(swapEndian(swapEndian(cast(T) 0)) == 0);
|
|
|
|
// used to trigger https://issues.dlang.org/show_bug.cgi?id=6354
|
|
static if (T.sizeof > 1 && isUnsigned!T)
|
|
{
|
|
T left = 0xffU;
|
|
left <<= (T.sizeof - 1) * 8;
|
|
T right = 0xffU;
|
|
|
|
for (size_t i = 1; i < T.sizeof; ++i)
|
|
{
|
|
assert(swapEndian(left) == right);
|
|
assert(swapEndian(right) == left);
|
|
left >>= 8;
|
|
right <<= 8;
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
|
|
|
|
private union EndianSwapper(T)
|
|
if (canSwapEndianness!T)
|
|
{
|
|
T value;
|
|
ubyte[T.sizeof] array;
|
|
|
|
static if (is(immutable FloatingPointTypeOf!(T) == immutable float))
|
|
uint intValue;
|
|
else static if (is(immutable FloatingPointTypeOf!(T) == immutable double))
|
|
ulong intValue;
|
|
|
|
}
|
|
|
|
// Can't use EndianSwapper union during CTFE.
|
|
private auto ctfeRead(T)(const ubyte[T.sizeof] array)
|
|
if (__traits(isIntegral, T))
|
|
{
|
|
Unqual!T result;
|
|
version (LittleEndian)
|
|
foreach_reverse (b; array)
|
|
result = cast() cast(T) ((result << 8) | b);
|
|
else
|
|
foreach (b; array)
|
|
result = cast() cast(T) ((result << 8) | b);
|
|
return cast(T) result;
|
|
}
|
|
|
|
// Can't use EndianSwapper union during CTFE.
|
|
private auto ctfeBytes(T)(const T value)
|
|
if (__traits(isIntegral, T))
|
|
{
|
|
ubyte[T.sizeof] result;
|
|
Unqual!T tmp = value;
|
|
version (LittleEndian)
|
|
{
|
|
foreach (i; 0 .. T.sizeof)
|
|
{
|
|
result[i] = cast(ubyte) tmp;
|
|
tmp = cast() cast(T) (tmp >>> 8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach_reverse (i; 0 .. T.sizeof)
|
|
{
|
|
result[i] = cast(ubyte) tmp;
|
|
tmp = cast()(T) (tmp >>> 8);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/++
|
|
Converts the given value from the native endianness to big endian and
|
|
returns it as a `ubyte[n]` where `n` is the size of the given type.
|
|
|
|
Returning a `ubyte[n]` helps prevent accidentally using a swapped value
|
|
as a regular one (and in the case of floating point values, it's necessary,
|
|
because the FPU will mess up any swapped floating point values. So, you
|
|
can't actually have swapped floating point values as floating point values).
|
|
|
|
`real` is not supported, because its size is implementation-dependent
|
|
and therefore could vary from machine to machine (which could make it
|
|
unusable if you tried to transfer it to another machine).
|
|
+/
|
|
auto nativeToBigEndian(T)(const T val) @safe pure nothrow @nogc
|
|
if (canSwapEndianness!T)
|
|
{
|
|
version (LittleEndian)
|
|
return nativeToEndianImpl!true(val);
|
|
else
|
|
return nativeToEndianImpl!false(val);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int i = 12345;
|
|
ubyte[4] swappedI = nativeToBigEndian(i);
|
|
assert(i == bigEndianToNative!int(swappedI));
|
|
|
|
float f = 123.45f;
|
|
ubyte[4] swappedF = nativeToBigEndian(f);
|
|
assert(f == bigEndianToNative!float(swappedF));
|
|
|
|
const float cf = 123.45f;
|
|
ubyte[4] swappedCF = nativeToBigEndian(cf);
|
|
assert(cf == bigEndianToNative!float(swappedCF));
|
|
|
|
double d = 123.45;
|
|
ubyte[8] swappedD = nativeToBigEndian(d);
|
|
assert(d == bigEndianToNative!double(swappedD));
|
|
|
|
const double cd = 123.45;
|
|
ubyte[8] swappedCD = nativeToBigEndian(cd);
|
|
assert(cd == bigEndianToNative!double(swappedCD));
|
|
}
|
|
|
|
private auto nativeToEndianImpl(bool swap, T)(const T val) @safe pure nothrow @nogc
|
|
if (__traits(isIntegral, T))
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
static if (swap)
|
|
return EndianSwapper!T(swapEndian(val)).array;
|
|
else
|
|
return EndianSwapper!T(val).array;
|
|
}
|
|
else
|
|
{
|
|
// Can't use EndianSwapper in CTFE.
|
|
static if (swap)
|
|
return ctfeBytes(swapEndian(val));
|
|
else
|
|
return ctfeBytes(val);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
import std.stdio;
|
|
static foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong,
|
|
char, wchar, dchar
|
|
/* The trouble here is with floats and doubles being compared against nan
|
|
* using a bit compare. There are two kinds of nans, quiet and signaling.
|
|
* When a nan passes through the x87, it converts signaling to quiet.
|
|
* When a nan passes through the XMM, it does not convert signaling to quiet.
|
|
* float.init is a signaling nan.
|
|
* The binary API sometimes passes the data through the XMM, sometimes through
|
|
* the x87, meaning these will fail the 'is' bit compare under some circumstances.
|
|
* I cannot think of a fix for this that makes consistent sense.
|
|
*/
|
|
/*,float, double*/))
|
|
{{
|
|
scope(failure) writeln("Failed type: ", T.stringof);
|
|
T val;
|
|
const T cval;
|
|
immutable T ival;
|
|
|
|
//is instead of == because of NaN for floating point values.
|
|
assert(bigEndianToNative!T(nativeToBigEndian(val)) is val);
|
|
assert(bigEndianToNative!T(nativeToBigEndian(cval)) is cval);
|
|
assert(bigEndianToNative!T(nativeToBigEndian(ival)) is ival);
|
|
assert(bigEndianToNative!T(nativeToBigEndian(T.min)) == T.min);
|
|
assert(bigEndianToNative!T(nativeToBigEndian(T.max)) == T.max);
|
|
|
|
//Check CTFE compiles.
|
|
static assert(bigEndianToNative!T(nativeToBigEndian(T(1))) is T(1));
|
|
|
|
static if (isSigned!T)
|
|
assert(bigEndianToNative!T(nativeToBigEndian(cast(T) 0)) == 0);
|
|
|
|
static if (!is(T == bool))
|
|
{
|
|
foreach (i; [2, 4, 6, 7, 9, 11])
|
|
{
|
|
immutable T maxI = cast(T)(T.max / i);
|
|
immutable T minI = cast(T)(T.min / i);
|
|
|
|
assert(bigEndianToNative!T(nativeToBigEndian(maxI)) == maxI);
|
|
|
|
static if (T.sizeof > 1)
|
|
assert(nativeToBigEndian(maxI) != nativeToLittleEndian(maxI));
|
|
else
|
|
assert(nativeToBigEndian(maxI) == nativeToLittleEndian(maxI));
|
|
|
|
static if (isSigned!T)
|
|
{
|
|
assert(bigEndianToNative!T(nativeToBigEndian(minI)) == minI);
|
|
|
|
static if (T.sizeof > 1)
|
|
assert(nativeToBigEndian(minI) != nativeToLittleEndian(minI));
|
|
else
|
|
assert(nativeToBigEndian(minI) == nativeToLittleEndian(minI));
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (isUnsigned!T || T.sizeof == 1 || is(T == wchar))
|
|
assert(nativeToBigEndian(T.max) == nativeToLittleEndian(T.max));
|
|
else
|
|
assert(nativeToBigEndian(T.max) != nativeToLittleEndian(T.max));
|
|
|
|
static if (isUnsigned!T || T.sizeof == 1 || isSomeChar!T)
|
|
assert(nativeToBigEndian(T.min) == nativeToLittleEndian(T.min));
|
|
else
|
|
assert(nativeToBigEndian(T.min) != nativeToLittleEndian(T.min));
|
|
}}
|
|
}
|
|
|
|
|
|
/++
|
|
Converts the given value from big endian to the native endianness and
|
|
returns it. The value is given as a `ubyte[n]` where `n` is the size
|
|
of the target type. You must give the target type as a template argument,
|
|
because there are multiple types with the same size and so the type of the
|
|
argument is not enough to determine the return type.
|
|
|
|
Taking a `ubyte[n]` helps prevent accidentally using a swapped value
|
|
as a regular one (and in the case of floating point values, it's necessary,
|
|
because the FPU will mess up any swapped floating point values. So, you
|
|
can't actually have swapped floating point values as floating point values).
|
|
+/
|
|
T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
|
|
if (canSwapEndianness!T && n == T.sizeof)
|
|
{
|
|
version (LittleEndian)
|
|
return endianToNativeImpl!(true, T, n)(val);
|
|
else
|
|
return endianToNativeImpl!(false, T, n)(val);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
ushort i = 12345;
|
|
ubyte[2] swappedI = nativeToBigEndian(i);
|
|
assert(i == bigEndianToNative!ushort(swappedI));
|
|
|
|
dchar c = 'D';
|
|
ubyte[4] swappedC = nativeToBigEndian(c);
|
|
assert(c == bigEndianToNative!dchar(swappedC));
|
|
}
|
|
|
|
/++
|
|
Converts the given value from the native endianness to little endian and
|
|
returns it as a `ubyte[n]` where `n` is the size of the given type.
|
|
|
|
Returning a `ubyte[n]` helps prevent accidentally using a swapped value
|
|
as a regular one (and in the case of floating point values, it's necessary,
|
|
because the FPU will mess up any swapped floating point values. So, you
|
|
can't actually have swapped floating point values as floating point values).
|
|
+/
|
|
auto nativeToLittleEndian(T)(const T val) @safe pure nothrow @nogc
|
|
if (canSwapEndianness!T)
|
|
{
|
|
version (BigEndian)
|
|
return nativeToEndianImpl!true(val);
|
|
else
|
|
return nativeToEndianImpl!false(val);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int i = 12345;
|
|
ubyte[4] swappedI = nativeToLittleEndian(i);
|
|
assert(i == littleEndianToNative!int(swappedI));
|
|
|
|
double d = 123.45;
|
|
ubyte[8] swappedD = nativeToLittleEndian(d);
|
|
assert(d == littleEndianToNative!double(swappedD));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
import std.stdio;
|
|
static foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong,
|
|
char, wchar, dchar/*,
|
|
float, double*/))
|
|
{{
|
|
scope(failure) writeln("Failed type: ", T.stringof);
|
|
T val;
|
|
const T cval;
|
|
immutable T ival;
|
|
|
|
//is instead of == because of NaN for floating point values.
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(val)) is val);
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(cval)) is cval);
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(ival)) is ival);
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(T.min)) == T.min);
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(T.max)) == T.max);
|
|
|
|
//Check CTFE compiles.
|
|
static assert(littleEndianToNative!T(nativeToLittleEndian(T(1))) is T(1));
|
|
|
|
static if (isSigned!T)
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(cast(T) 0)) == 0);
|
|
|
|
static if (!is(T == bool))
|
|
{
|
|
foreach (i; 2 .. 10)
|
|
{
|
|
immutable T maxI = cast(T)(T.max / i);
|
|
immutable T minI = cast(T)(T.min / i);
|
|
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(maxI)) == maxI);
|
|
|
|
static if (isSigned!T)
|
|
assert(littleEndianToNative!T(nativeToLittleEndian(minI)) == minI);
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
|
|
|
|
/++
|
|
Converts the given value from little endian to the native endianness and
|
|
returns it. The value is given as a `ubyte[n]` where `n` is the size
|
|
of the target type. You must give the target type as a template argument,
|
|
because there are multiple types with the same size and so the type of the
|
|
argument is not enough to determine the return type.
|
|
|
|
Taking a `ubyte[n]` helps prevent accidentally using a swapped value
|
|
as a regular one (and in the case of floating point values, it's necessary,
|
|
because the FPU will mess up any swapped floating point values. So, you
|
|
can't actually have swapped floating point values as floating point values).
|
|
|
|
`real` is not supported, because its size is implementation-dependent
|
|
and therefore could vary from machine to machine (which could make it
|
|
unusable if you tried to transfer it to another machine).
|
|
+/
|
|
T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc
|
|
if (canSwapEndianness!T && n == T.sizeof)
|
|
{
|
|
version (BigEndian)
|
|
return endianToNativeImpl!(true, T, n)(val);
|
|
else
|
|
return endianToNativeImpl!(false, T, n)(val);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
ushort i = 12345;
|
|
ubyte[2] swappedI = nativeToLittleEndian(i);
|
|
assert(i == littleEndianToNative!ushort(swappedI));
|
|
|
|
dchar c = 'D';
|
|
ubyte[4] swappedC = nativeToLittleEndian(c);
|
|
assert(c == littleEndianToNative!dchar(swappedC));
|
|
}
|
|
|
|
private T endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @nogc nothrow pure @trusted
|
|
if (__traits(isIntegral, T) && n == T.sizeof)
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
EndianSwapper!T es = { array: val };
|
|
static if (swap)
|
|
return swapEndian(es.value);
|
|
else
|
|
return es.value;
|
|
}
|
|
else
|
|
{
|
|
static if (swap)
|
|
return swapEndian(ctfeRead!T(val));
|
|
else
|
|
return ctfeRead!T(val);
|
|
}
|
|
}
|
|
|
|
private auto nativeToEndianImpl(bool swap, T)(const T val) @trusted pure nothrow @nogc
|
|
if (isFloatOrDouble!T)
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
EndianSwapper!T es = EndianSwapper!T(val);
|
|
static if (swap)
|
|
es.intValue = swapEndian(es.intValue);
|
|
return es.array;
|
|
}
|
|
else
|
|
{
|
|
static if (T.sizeof == 4)
|
|
uint intValue = *cast(const uint*) &val;
|
|
else static if (T.sizeof == 8)
|
|
ulong intValue = *cast(const ulong*) & val;
|
|
static if (swap)
|
|
intValue = swapEndian(intValue);
|
|
return ctfeBytes(intValue);
|
|
}
|
|
}
|
|
|
|
private auto endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc
|
|
if (isFloatOrDouble!T && n == T.sizeof)
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
EndianSwapper!T es = { array: val };
|
|
static if (swap)
|
|
es.intValue = swapEndian(es.intValue);
|
|
return es.value;
|
|
}
|
|
else
|
|
{
|
|
static if (n == 4)
|
|
uint x = ctfeRead!uint(val);
|
|
else static if (n == 8)
|
|
ulong x = ctfeRead!ulong(val);
|
|
static if (swap)
|
|
x = swapEndian(x);
|
|
return *cast(T*) &x;
|
|
}
|
|
}
|
|
|
|
private template isFloatOrDouble(T)
|
|
{
|
|
enum isFloatOrDouble = isFloatingPoint!T &&
|
|
!is(immutable FloatingPointTypeOf!T == immutable real);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
static foreach (T; AliasSeq!(float, double))
|
|
{
|
|
static assert(isFloatOrDouble!(T));
|
|
static assert(isFloatOrDouble!(const T));
|
|
static assert(isFloatOrDouble!(immutable T));
|
|
static assert(isFloatOrDouble!(shared T));
|
|
static assert(isFloatOrDouble!(shared(const T)));
|
|
static assert(isFloatOrDouble!(shared(immutable T)));
|
|
}
|
|
|
|
static assert(!isFloatOrDouble!(real));
|
|
static assert(!isFloatOrDouble!(const real));
|
|
static assert(!isFloatOrDouble!(immutable real));
|
|
static assert(!isFloatOrDouble!(shared real));
|
|
static assert(!isFloatOrDouble!(shared(const real)));
|
|
static assert(!isFloatOrDouble!(shared(immutable real)));
|
|
}
|
|
|
|
private template canSwapEndianness(T)
|
|
{
|
|
enum canSwapEndianness = isIntegral!T ||
|
|
isSomeChar!T ||
|
|
isBoolean!T ||
|
|
isFloatOrDouble!T;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
static foreach (T; AliasSeq!(bool, ubyte, byte, ushort, short, uint, int, ulong,
|
|
long, char, wchar, dchar, float, double))
|
|
{
|
|
static assert(canSwapEndianness!(T));
|
|
static assert(canSwapEndianness!(const T));
|
|
static assert(canSwapEndianness!(immutable T));
|
|
static assert(canSwapEndianness!(shared(T)));
|
|
static assert(canSwapEndianness!(shared(const T)));
|
|
static assert(canSwapEndianness!(shared(immutable T)));
|
|
}
|
|
|
|
//!
|
|
static foreach (T; AliasSeq!(real, string, wstring, dstring))
|
|
{
|
|
static assert(!canSwapEndianness!(T));
|
|
static assert(!canSwapEndianness!(const T));
|
|
static assert(!canSwapEndianness!(immutable T));
|
|
static assert(!canSwapEndianness!(shared(T)));
|
|
static assert(!canSwapEndianness!(shared(const T)));
|
|
static assert(!canSwapEndianness!(shared(immutable T)));
|
|
}
|
|
}
|
|
|
|
/++
|
|
Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to
|
|
`T`. The value returned is converted from the given endianness to the
|
|
native endianness. The range is not consumed.
|
|
|
|
Params:
|
|
T = The integral type to convert the first `T.sizeof` bytes to.
|
|
endianness = The endianness that the bytes are assumed to be in.
|
|
range = The range to read from.
|
|
index = The index to start reading from (instead of starting at the
|
|
front). If index is a pointer, then it is updated to the index
|
|
after the bytes read. The overloads with index are only
|
|
available if `hasSlicing!R` is `true`.
|
|
+/
|
|
|
|
T peek(T, Endian endianness = Endian.bigEndian, R)(R range)
|
|
if (canSwapEndianness!T &&
|
|
isForwardRange!R &&
|
|
is(ElementType!R : const ubyte))
|
|
{
|
|
static if (hasSlicing!R)
|
|
const ubyte[T.sizeof] bytes = range[0 .. T.sizeof];
|
|
else
|
|
{
|
|
ubyte[T.sizeof] bytes;
|
|
//Make sure that range is not consumed, even if it's a class.
|
|
range = range.save;
|
|
|
|
foreach (ref e; bytes)
|
|
{
|
|
e = range.front;
|
|
range.popFront();
|
|
}
|
|
}
|
|
|
|
static if (endianness == Endian.bigEndian)
|
|
return bigEndianToNative!T(bytes);
|
|
else
|
|
return littleEndianToNative!T(bytes);
|
|
}
|
|
|
|
/++ Ditto +/
|
|
T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t index)
|
|
if (canSwapEndianness!T &&
|
|
isForwardRange!R &&
|
|
hasSlicing!R &&
|
|
is(ElementType!R : const ubyte))
|
|
{
|
|
return peek!(T, endianness)(range, &index);
|
|
}
|
|
|
|
/++ Ditto +/
|
|
T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t* index)
|
|
if (canSwapEndianness!T &&
|
|
isForwardRange!R &&
|
|
hasSlicing!R &&
|
|
is(ElementType!R : const ubyte))
|
|
{
|
|
assert(index, "index must not point to null");
|
|
|
|
immutable begin = *index;
|
|
immutable end = begin + T.sizeof;
|
|
const ubyte[T.sizeof] bytes = range[begin .. end];
|
|
*index = end;
|
|
|
|
static if (endianness == Endian.bigEndian)
|
|
return bigEndianToNative!T(bytes);
|
|
else
|
|
return littleEndianToNative!T(bytes);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
|
|
assert(buffer.peek!uint() == 17110537);
|
|
assert(buffer.peek!ushort() == 261);
|
|
assert(buffer.peek!ubyte() == 1);
|
|
|
|
assert(buffer.peek!uint(2) == 369700095);
|
|
assert(buffer.peek!ushort(2) == 5641);
|
|
assert(buffer.peek!ubyte(2) == 22);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!ushort(&index) == 261);
|
|
assert(index == 2);
|
|
|
|
assert(buffer.peek!uint(&index) == 369700095);
|
|
assert(index == 6);
|
|
|
|
assert(buffer.peek!ubyte(&index) == 8);
|
|
assert(index == 7);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.iteration : filter;
|
|
ubyte[] buffer = [1, 5, 22, 9, 44, 255, 7];
|
|
auto range = filter!"true"(buffer);
|
|
assert(range.peek!uint() == 17110537);
|
|
assert(range.peek!ushort() == 261);
|
|
assert(range.peek!ubyte() == 1);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
{
|
|
//bool
|
|
ubyte[] buffer = [0, 1];
|
|
assert(buffer.peek!bool() == false);
|
|
assert(buffer.peek!bool(1) == true);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!bool(&index) == false);
|
|
assert(index == 1);
|
|
|
|
assert(buffer.peek!bool(&index) == true);
|
|
assert(index == 2);
|
|
}
|
|
|
|
{
|
|
//char (8bit)
|
|
ubyte[] buffer = [97, 98, 99, 100];
|
|
assert(buffer.peek!char() == 'a');
|
|
assert(buffer.peek!char(1) == 'b');
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!char(&index) == 'a');
|
|
assert(index == 1);
|
|
|
|
assert(buffer.peek!char(&index) == 'b');
|
|
assert(index == 2);
|
|
}
|
|
|
|
{
|
|
//wchar (16bit - 2x ubyte)
|
|
ubyte[] buffer = [1, 5, 32, 29, 1, 7];
|
|
assert(buffer.peek!wchar() == 'ą');
|
|
assert(buffer.peek!wchar(2) == '”');
|
|
assert(buffer.peek!wchar(4) == 'ć');
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!wchar(&index) == 'ą');
|
|
assert(index == 2);
|
|
|
|
assert(buffer.peek!wchar(&index) == '”');
|
|
assert(index == 4);
|
|
|
|
assert(buffer.peek!wchar(&index) == 'ć');
|
|
assert(index == 6);
|
|
}
|
|
|
|
{
|
|
//dchar (32bit - 4x ubyte)
|
|
ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7];
|
|
assert(buffer.peek!dchar() == 'ą');
|
|
assert(buffer.peek!dchar(4) == '”');
|
|
assert(buffer.peek!dchar(8) == 'ć');
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!dchar(&index) == 'ą');
|
|
assert(index == 4);
|
|
|
|
assert(buffer.peek!dchar(&index) == '”');
|
|
assert(index == 8);
|
|
|
|
assert(buffer.peek!dchar(&index) == 'ć');
|
|
assert(index == 12);
|
|
}
|
|
|
|
{
|
|
//float (32bit - 4x ubyte)
|
|
ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
|
|
assert(buffer.peek!float()== 32.0);
|
|
assert(buffer.peek!float(4) == 25.0f);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!float(&index) == 32.0f);
|
|
assert(index == 4);
|
|
|
|
assert(buffer.peek!float(&index) == 25.0f);
|
|
assert(index == 8);
|
|
}
|
|
|
|
{
|
|
//double (64bit - 8x ubyte)
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
assert(buffer.peek!double() == 32.0);
|
|
assert(buffer.peek!double(8) == 25.0);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!double(&index) == 32.0);
|
|
assert(index == 8);
|
|
|
|
assert(buffer.peek!double(&index) == 25.0);
|
|
assert(index == 16);
|
|
}
|
|
|
|
{
|
|
//enum
|
|
ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30];
|
|
|
|
enum Foo
|
|
{
|
|
one = 10,
|
|
two = 20,
|
|
three = 30
|
|
}
|
|
|
|
assert(buffer.peek!Foo() == Foo.one);
|
|
assert(buffer.peek!Foo(0) == Foo.one);
|
|
assert(buffer.peek!Foo(4) == Foo.two);
|
|
assert(buffer.peek!Foo(8) == Foo.three);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!Foo(&index) == Foo.one);
|
|
assert(index == 4);
|
|
|
|
assert(buffer.peek!Foo(&index) == Foo.two);
|
|
assert(index == 8);
|
|
|
|
assert(buffer.peek!Foo(&index) == Foo.three);
|
|
assert(index == 12);
|
|
}
|
|
|
|
{
|
|
//enum - bool
|
|
ubyte[] buffer = [0, 1];
|
|
|
|
enum Bool: bool
|
|
{
|
|
bfalse = false,
|
|
btrue = true,
|
|
}
|
|
|
|
assert(buffer.peek!Bool() == Bool.bfalse);
|
|
assert(buffer.peek!Bool(0) == Bool.bfalse);
|
|
assert(buffer.peek!Bool(1) == Bool.btrue);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!Bool(&index) == Bool.bfalse);
|
|
assert(index == 1);
|
|
|
|
assert(buffer.peek!Bool(&index) == Bool.btrue);
|
|
assert(index == 2);
|
|
}
|
|
|
|
{
|
|
//enum - float
|
|
ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
|
|
|
|
enum Float: float
|
|
{
|
|
one = 32.0f,
|
|
two = 25.0f
|
|
}
|
|
|
|
assert(buffer.peek!Float() == Float.one);
|
|
assert(buffer.peek!Float(0) == Float.one);
|
|
assert(buffer.peek!Float(4) == Float.two);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!Float(&index) == Float.one);
|
|
assert(index == 4);
|
|
|
|
assert(buffer.peek!Float(&index) == Float.two);
|
|
assert(index == 8);
|
|
}
|
|
|
|
{
|
|
//enum - double
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Double: double
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
assert(buffer.peek!Double() == Double.one);
|
|
assert(buffer.peek!Double(0) == Double.one);
|
|
assert(buffer.peek!Double(8) == Double.two);
|
|
|
|
size_t index = 0;
|
|
assert(buffer.peek!Double(&index) == Double.one);
|
|
assert(index == 8);
|
|
|
|
assert(buffer.peek!Double(&index) == Double.two);
|
|
assert(index == 16);
|
|
}
|
|
|
|
{
|
|
//enum - real
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Real: real
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
static assert(!__traits(compiles, buffer.peek!Real()));
|
|
}
|
|
}
|
|
|
|
/++
|
|
Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to
|
|
`T`. The value returned is converted from the given endianness to the
|
|
native endianness. The `T.sizeof` bytes which are read are consumed from
|
|
the range.
|
|
|
|
Params:
|
|
T = The integral type to convert the first `T.sizeof` bytes to.
|
|
endianness = The endianness that the bytes are assumed to be in.
|
|
range = The range to read from.
|
|
+/
|
|
T read(T, Endian endianness = Endian.bigEndian, R)(ref R range)
|
|
if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte))
|
|
{
|
|
static if (hasSlicing!R && is(typeof(R.init[0 .. 0]) : const(ubyte)[]))
|
|
{
|
|
const ubyte[T.sizeof] bytes = range[0 .. T.sizeof];
|
|
range.popFrontN(T.sizeof);
|
|
}
|
|
else
|
|
{
|
|
ubyte[T.sizeof] bytes;
|
|
|
|
foreach (ref e; bytes)
|
|
{
|
|
e = range.front;
|
|
range.popFront();
|
|
}
|
|
}
|
|
|
|
static if (endianness == Endian.bigEndian)
|
|
return bigEndianToNative!T(bytes);
|
|
else
|
|
return littleEndianToNative!T(bytes);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.range.primitives : empty;
|
|
ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
|
|
assert(buffer.length == 7);
|
|
|
|
assert(buffer.read!ushort() == 261);
|
|
assert(buffer.length == 5);
|
|
|
|
assert(buffer.read!uint() == 369700095);
|
|
assert(buffer.length == 1);
|
|
|
|
assert(buffer.read!ubyte() == 8);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
{
|
|
//bool
|
|
ubyte[] buffer = [0, 1];
|
|
assert(buffer.length == 2);
|
|
|
|
assert(buffer.read!bool() == false);
|
|
assert(buffer.length == 1);
|
|
|
|
assert(buffer.read!bool() == true);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//char (8bit)
|
|
ubyte[] buffer = [97, 98, 99];
|
|
assert(buffer.length == 3);
|
|
|
|
assert(buffer.read!char() == 'a');
|
|
assert(buffer.length == 2);
|
|
|
|
assert(buffer.read!char() == 'b');
|
|
assert(buffer.length == 1);
|
|
|
|
assert(buffer.read!char() == 'c');
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//wchar (16bit - 2x ubyte)
|
|
ubyte[] buffer = [1, 5, 32, 29, 1, 7];
|
|
assert(buffer.length == 6);
|
|
|
|
assert(buffer.read!wchar() == 'ą');
|
|
assert(buffer.length == 4);
|
|
|
|
assert(buffer.read!wchar() == '”');
|
|
assert(buffer.length == 2);
|
|
|
|
assert(buffer.read!wchar() == 'ć');
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//dchar (32bit - 4x ubyte)
|
|
ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7];
|
|
assert(buffer.length == 12);
|
|
|
|
assert(buffer.read!dchar() == 'ą');
|
|
assert(buffer.length == 8);
|
|
|
|
assert(buffer.read!dchar() == '”');
|
|
assert(buffer.length == 4);
|
|
|
|
assert(buffer.read!dchar() == 'ć');
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//float (32bit - 4x ubyte)
|
|
ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
|
|
assert(buffer.length == 8);
|
|
|
|
assert(buffer.read!float()== 32.0);
|
|
assert(buffer.length == 4);
|
|
|
|
assert(buffer.read!float() == 25.0f);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//double (64bit - 8x ubyte)
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
assert(buffer.length == 16);
|
|
|
|
assert(buffer.read!double() == 32.0);
|
|
assert(buffer.length == 8);
|
|
|
|
assert(buffer.read!double() == 25.0);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//enum - uint
|
|
ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30];
|
|
assert(buffer.length == 12);
|
|
|
|
enum Foo
|
|
{
|
|
one = 10,
|
|
two = 20,
|
|
three = 30
|
|
}
|
|
|
|
assert(buffer.read!Foo() == Foo.one);
|
|
assert(buffer.length == 8);
|
|
|
|
assert(buffer.read!Foo() == Foo.two);
|
|
assert(buffer.length == 4);
|
|
|
|
assert(buffer.read!Foo() == Foo.three);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//enum - bool
|
|
ubyte[] buffer = [0, 1];
|
|
assert(buffer.length == 2);
|
|
|
|
enum Bool: bool
|
|
{
|
|
bfalse = false,
|
|
btrue = true,
|
|
}
|
|
|
|
assert(buffer.read!Bool() == Bool.bfalse);
|
|
assert(buffer.length == 1);
|
|
|
|
assert(buffer.read!Bool() == Bool.btrue);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//enum - float
|
|
ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0];
|
|
assert(buffer.length == 8);
|
|
|
|
enum Float: float
|
|
{
|
|
one = 32.0f,
|
|
two = 25.0f
|
|
}
|
|
|
|
assert(buffer.read!Float() == Float.one);
|
|
assert(buffer.length == 4);
|
|
|
|
assert(buffer.read!Float() == Float.two);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//enum - double
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
assert(buffer.length == 16);
|
|
|
|
enum Double: double
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
assert(buffer.read!Double() == Double.one);
|
|
assert(buffer.length == 8);
|
|
|
|
assert(buffer.read!Double() == Double.two);
|
|
assert(buffer.empty);
|
|
}
|
|
|
|
{
|
|
//enum - real
|
|
ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Real: real
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
static assert(!__traits(compiles, buffer.read!Real()));
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17247
|
|
@safe unittest
|
|
{
|
|
struct UbyteRange
|
|
{
|
|
ubyte[] impl;
|
|
@property bool empty() { return impl.empty; }
|
|
@property ubyte front() { return impl.front; }
|
|
void popFront() { impl.popFront(); }
|
|
@property UbyteRange save() { return this; }
|
|
|
|
// N.B. support slicing but do not return ubyte[] slices.
|
|
UbyteRange opSlice(size_t start, size_t end)
|
|
{
|
|
return UbyteRange(impl[start .. end]);
|
|
}
|
|
@property size_t length() { return impl.length; }
|
|
alias opDollar = length;
|
|
}
|
|
static assert(hasSlicing!UbyteRange);
|
|
|
|
auto r = UbyteRange([0x01, 0x00, 0x00, 0x00]);
|
|
int x = r.read!(int, Endian.littleEndian)();
|
|
assert(x == 1);
|
|
}
|
|
|
|
|
|
/++
|
|
Takes an integral value, converts it to the given endianness, and writes it
|
|
to the given range of `ubyte`s as a sequence of `T.sizeof` `ubyte`s
|
|
starting at index. `hasSlicing!R` must be `true`.
|
|
|
|
Params:
|
|
T = The integral type to convert the first `T.sizeof` bytes to.
|
|
endianness = The endianness to _write the bytes in.
|
|
range = The range to _write to.
|
|
value = The value to _write.
|
|
index = The index to start writing to. If index is a pointer, then it
|
|
is updated to the index after the bytes read.
|
|
+/
|
|
void write(T, Endian endianness = Endian.bigEndian, R)(R range, const T value, size_t index)
|
|
if (canSwapEndianness!T &&
|
|
isForwardRange!R &&
|
|
hasSlicing!R &&
|
|
is(ElementType!R : ubyte))
|
|
{
|
|
write!(T, endianness)(range, value, &index);
|
|
}
|
|
|
|
/++ Ditto +/
|
|
void write(T, Endian endianness = Endian.bigEndian, R)(R range, const T value, size_t* index)
|
|
if (canSwapEndianness!T &&
|
|
isForwardRange!R &&
|
|
hasSlicing!R &&
|
|
is(ElementType!R : ubyte))
|
|
{
|
|
assert(index, "index must not point to null");
|
|
|
|
static if (endianness == Endian.bigEndian)
|
|
immutable bytes = nativeToBigEndian!T(value);
|
|
else
|
|
immutable bytes = nativeToLittleEndian!T(value);
|
|
|
|
immutable begin = *index;
|
|
immutable end = begin + T.sizeof;
|
|
*index = end;
|
|
range[begin .. end] = bytes[0 .. T.sizeof];
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
buffer.write!uint(29110231u, 0);
|
|
assert(buffer == [1, 188, 47, 215, 0, 0, 0, 0]);
|
|
|
|
buffer.write!ushort(927, 0);
|
|
assert(buffer == [3, 159, 47, 215, 0, 0, 0, 0]);
|
|
|
|
buffer.write!ubyte(42, 0);
|
|
assert(buffer == [42, 159, 47, 215, 0, 0, 0, 0]);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
buffer.write!uint(142700095u, 2);
|
|
assert(buffer == [0, 0, 8, 129, 110, 63, 0, 0, 0]);
|
|
|
|
buffer.write!ushort(19839, 2);
|
|
assert(buffer == [0, 0, 77, 127, 110, 63, 0, 0, 0]);
|
|
|
|
buffer.write!ubyte(132, 2);
|
|
assert(buffer == [0, 0, 132, 127, 110, 63, 0, 0, 0]);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
size_t index = 0;
|
|
buffer.write!ushort(261, &index);
|
|
assert(buffer == [1, 5, 0, 0, 0, 0, 0, 0]);
|
|
assert(index == 2);
|
|
|
|
buffer.write!uint(369700095u, &index);
|
|
assert(buffer == [1, 5, 22, 9, 44, 255, 0, 0]);
|
|
assert(index == 6);
|
|
|
|
buffer.write!ubyte(8, &index);
|
|
assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]);
|
|
assert(index == 7);
|
|
}
|
|
|
|
/// bool
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0];
|
|
buffer.write!bool(false, 0);
|
|
assert(buffer == [0, 0]);
|
|
|
|
buffer.write!bool(true, 0);
|
|
assert(buffer == [1, 0]);
|
|
|
|
buffer.write!bool(true, 1);
|
|
assert(buffer == [1, 1]);
|
|
|
|
buffer.write!bool(false, 1);
|
|
assert(buffer == [1, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!bool(false, &index);
|
|
assert(buffer == [0, 0]);
|
|
assert(index == 1);
|
|
|
|
buffer.write!bool(true, &index);
|
|
assert(buffer == [0, 1]);
|
|
assert(index == 2);
|
|
}
|
|
|
|
/// char(8-bit)
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0];
|
|
|
|
buffer.write!char('a', 0);
|
|
assert(buffer == [97, 0, 0]);
|
|
|
|
buffer.write!char('b', 1);
|
|
assert(buffer == [97, 98, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!char('a', &index);
|
|
assert(buffer == [97, 98, 0]);
|
|
assert(index == 1);
|
|
|
|
buffer.write!char('b', &index);
|
|
assert(buffer == [97, 98, 0]);
|
|
assert(index == 2);
|
|
|
|
buffer.write!char('c', &index);
|
|
assert(buffer == [97, 98, 99]);
|
|
assert(index == 3);
|
|
}
|
|
|
|
/// wchar (16bit - 2x ubyte)
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0];
|
|
|
|
buffer.write!wchar('ą', 0);
|
|
assert(buffer == [1, 5, 0, 0]);
|
|
|
|
buffer.write!wchar('”', 2);
|
|
assert(buffer == [1, 5, 32, 29]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!wchar('ć', &index);
|
|
assert(buffer == [1, 7, 32, 29]);
|
|
assert(index == 2);
|
|
|
|
buffer.write!wchar('ą', &index);
|
|
assert(buffer == [1, 7, 1, 5]);
|
|
assert(index == 4);
|
|
}
|
|
|
|
/// dchar (32bit - 4x ubyte)
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
buffer.write!dchar('ą', 0);
|
|
assert(buffer == [0, 0, 1, 5, 0, 0, 0, 0]);
|
|
|
|
buffer.write!dchar('”', 4);
|
|
assert(buffer == [0, 0, 1, 5, 0, 0, 32, 29]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!dchar('ć', &index);
|
|
assert(buffer == [0, 0, 1, 7, 0, 0, 32, 29]);
|
|
assert(index == 4);
|
|
|
|
buffer.write!dchar('ą', &index);
|
|
assert(buffer == [0, 0, 1, 7, 0, 0, 1, 5]);
|
|
assert(index == 8);
|
|
}
|
|
|
|
/// float (32bit - 4x ubyte)
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
buffer.write!float(32.0f, 0);
|
|
assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.write!float(25.0f, 4);
|
|
assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!float(25.0f, &index);
|
|
assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]);
|
|
assert(index == 4);
|
|
|
|
buffer.write!float(32.0f, &index);
|
|
assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]);
|
|
assert(index == 8);
|
|
}
|
|
|
|
/// double (64bit - 8x ubyte)
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
buffer.write!double(32.0, 0);
|
|
assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.write!double(25.0, 8);
|
|
assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!double(25.0, &index);
|
|
assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
|
|
assert(index == 8);
|
|
|
|
buffer.write!double(32.0, &index);
|
|
assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
|
|
assert(index == 16);
|
|
}
|
|
|
|
/// enum
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Foo
|
|
{
|
|
one = 10,
|
|
two = 20,
|
|
three = 30
|
|
}
|
|
|
|
buffer.write!Foo(Foo.one, 0);
|
|
assert(buffer == [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.write!Foo(Foo.two, 4);
|
|
assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 0]);
|
|
|
|
buffer.write!Foo(Foo.three, 8);
|
|
assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!Foo(Foo.three, &index);
|
|
assert(buffer == [0, 0, 0, 30, 0, 0, 0, 20, 0, 0, 0, 30]);
|
|
assert(index == 4);
|
|
|
|
buffer.write!Foo(Foo.one, &index);
|
|
assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 30]);
|
|
assert(index == 8);
|
|
|
|
buffer.write!Foo(Foo.two, &index);
|
|
assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 20]);
|
|
assert(index == 12);
|
|
}
|
|
|
|
// enum - bool
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0];
|
|
|
|
enum Bool: bool
|
|
{
|
|
bfalse = false,
|
|
btrue = true,
|
|
}
|
|
|
|
buffer.write!Bool(Bool.btrue, 0);
|
|
assert(buffer == [1, 0]);
|
|
|
|
buffer.write!Bool(Bool.btrue, 1);
|
|
assert(buffer == [1, 1]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!Bool(Bool.bfalse, &index);
|
|
assert(buffer == [0, 1]);
|
|
assert(index == 1);
|
|
|
|
buffer.write!Bool(Bool.bfalse, &index);
|
|
assert(buffer == [0, 0]);
|
|
assert(index == 2);
|
|
}
|
|
|
|
/// enum - float
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Float: float
|
|
{
|
|
one = 32.0f,
|
|
two = 25.0f
|
|
}
|
|
|
|
buffer.write!Float(Float.one, 0);
|
|
assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.write!Float(Float.two, 4);
|
|
assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!Float(Float.two, &index);
|
|
assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]);
|
|
assert(index == 4);
|
|
|
|
buffer.write!Float(Float.one, &index);
|
|
assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]);
|
|
assert(index == 8);
|
|
}
|
|
|
|
/// enum - double
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Double: double
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
buffer.write!Double(Double.one, 0);
|
|
assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.write!Double(Double.two, 8);
|
|
assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
|
|
|
|
size_t index = 0;
|
|
buffer.write!Double(Double.two, &index);
|
|
assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
|
|
assert(index == 8);
|
|
|
|
buffer.write!Double(Double.one, &index);
|
|
assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
|
|
assert(index == 16);
|
|
}
|
|
|
|
/// enum - real
|
|
@system unittest
|
|
{
|
|
ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
enum Real: real
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
static assert(!__traits(compiles, buffer.write!Real(Real.one)));
|
|
}
|
|
|
|
|
|
/++
|
|
Takes an integral value, converts it to the given endianness, and appends
|
|
it to the given range of `ubyte`s (using `put`) as a sequence of
|
|
`T.sizeof` `ubyte`s starting at index. `hasSlicing!R` must be
|
|
`true`.
|
|
|
|
Params:
|
|
T = The integral type to convert the first `T.sizeof` bytes to.
|
|
endianness = The endianness to write the bytes in.
|
|
range = The range to _append to.
|
|
value = The value to _append.
|
|
+/
|
|
void append(T, Endian endianness = Endian.bigEndian, R)(R range, const T value)
|
|
if (canSwapEndianness!T && isOutputRange!(R, ubyte))
|
|
{
|
|
static if (endianness == Endian.bigEndian)
|
|
immutable bytes = nativeToBigEndian!T(value);
|
|
else
|
|
immutable bytes = nativeToLittleEndian!T(value);
|
|
|
|
put(range, bytes[]);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.array;
|
|
auto buffer = appender!(const ubyte[])();
|
|
buffer.append!ushort(261);
|
|
assert(buffer.data == [1, 5]);
|
|
|
|
buffer.append!uint(369700095u);
|
|
assert(buffer.data == [1, 5, 22, 9, 44, 255]);
|
|
|
|
buffer.append!ubyte(8);
|
|
assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]);
|
|
}
|
|
|
|
/// bool
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
buffer.append!bool(true);
|
|
assert(buffer.data == [1]);
|
|
|
|
buffer.append!bool(false);
|
|
assert(buffer.data == [1, 0]);
|
|
}
|
|
|
|
/// char wchar dchar
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
buffer.append!char('a');
|
|
assert(buffer.data == [97]);
|
|
|
|
buffer.append!char('b');
|
|
assert(buffer.data == [97, 98]);
|
|
|
|
buffer.append!wchar('ą');
|
|
assert(buffer.data == [97, 98, 1, 5]);
|
|
|
|
buffer.append!dchar('ą');
|
|
assert(buffer.data == [97, 98, 1, 5, 0, 0, 1, 5]);
|
|
}
|
|
|
|
/// float double
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
buffer.append!float(32.0f);
|
|
assert(buffer.data == [66, 0, 0, 0]);
|
|
|
|
buffer.append!double(32.0);
|
|
assert(buffer.data == [66, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]);
|
|
}
|
|
|
|
/// enum
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
enum Foo
|
|
{
|
|
one = 10,
|
|
two = 20,
|
|
three = 30
|
|
}
|
|
|
|
buffer.append!Foo(Foo.one);
|
|
assert(buffer.data == [0, 0, 0, 10]);
|
|
|
|
buffer.append!Foo(Foo.two);
|
|
assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20]);
|
|
|
|
buffer.append!Foo(Foo.three);
|
|
assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]);
|
|
}
|
|
|
|
/// enum - bool
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
enum Bool: bool
|
|
{
|
|
bfalse = false,
|
|
btrue = true,
|
|
}
|
|
|
|
buffer.append!Bool(Bool.btrue);
|
|
assert(buffer.data == [1]);
|
|
|
|
buffer.append!Bool(Bool.bfalse);
|
|
assert(buffer.data == [1, 0]);
|
|
|
|
buffer.append!Bool(Bool.btrue);
|
|
assert(buffer.data == [1, 0, 1]);
|
|
}
|
|
|
|
/// enum - float
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
enum Float: float
|
|
{
|
|
one = 32.0f,
|
|
two = 25.0f
|
|
}
|
|
|
|
buffer.append!Float(Float.one);
|
|
assert(buffer.data == [66, 0, 0, 0]);
|
|
|
|
buffer.append!Float(Float.two);
|
|
assert(buffer.data == [66, 0, 0, 0, 65, 200, 0, 0]);
|
|
}
|
|
|
|
/// enum - double
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
enum Double: double
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
buffer.append!Double(Double.one);
|
|
assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0]);
|
|
|
|
buffer.append!Double(Double.two);
|
|
assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]);
|
|
}
|
|
|
|
/// enum - real
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
auto buffer = appender!(const ubyte[])();
|
|
|
|
enum Real: real
|
|
{
|
|
one = 32.0,
|
|
two = 25.0
|
|
}
|
|
|
|
static assert(!__traits(compiles, buffer.append!Real(Real.one)));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.array;
|
|
import std.format : format;
|
|
import std.meta : AliasSeq;
|
|
static foreach (endianness; [Endian.bigEndian, Endian.littleEndian])
|
|
{{
|
|
auto toWrite = appender!(ubyte[])();
|
|
alias Types = AliasSeq!(uint, int, long, ulong, short, ubyte, ushort, byte, uint);
|
|
ulong[] values = [42, -11, long.max, 1098911981329L, 16, 255, 19012, 2, 17];
|
|
assert(Types.length == values.length);
|
|
|
|
size_t index = 0;
|
|
size_t length = 0;
|
|
static foreach (T; Types)
|
|
{
|
|
toWrite.append!(T, endianness)(cast(T) values[index++]);
|
|
length += T.sizeof;
|
|
}
|
|
|
|
auto toRead = toWrite.data;
|
|
assert(toRead.length == length);
|
|
|
|
index = 0;
|
|
static foreach (T; Types)
|
|
{
|
|
assert(toRead.peek!(T, endianness)() == values[index], format("Failed Index: %s", index));
|
|
assert(toRead.peek!(T, endianness)(0) == values[index], format("Failed Index: %s", index));
|
|
assert(toRead.length == length,
|
|
format("Failed Index [%s], Actual Length: %s", index, toRead.length));
|
|
assert(toRead.read!(T, endianness)() == values[index], format("Failed Index: %s", index));
|
|
length -= T.sizeof;
|
|
assert(toRead.length == length,
|
|
format("Failed Index [%s], Actual Length: %s", index, toRead.length));
|
|
++index;
|
|
}
|
|
assert(toRead.empty);
|
|
}}
|
|
}
|
|
|
|
/**
|
|
Counts the number of set bits in the binary representation of `value`.
|
|
For signed integers, the sign bit is included in the count.
|
|
*/
|
|
private uint countBitsSet(T)(const T value)
|
|
if (isIntegral!T)
|
|
{
|
|
static if (T.sizeof == 8)
|
|
{
|
|
import core.bitop : popcnt;
|
|
const c = popcnt(cast(ulong) value);
|
|
}
|
|
else static if (T.sizeof == 4)
|
|
{
|
|
import core.bitop : popcnt;
|
|
const c = popcnt(cast(uint) value);
|
|
}
|
|
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
|
|
else static if (T.sizeof == 2)
|
|
{
|
|
uint c = value - ((value >> 1) & 0x5555);
|
|
c = ((c >> 2) & 0x3333) + (c & 0X3333);
|
|
c = ((c >> 4) + c) & 0x0F0F;
|
|
c = ((c >> 8) + c) & 0x00FF;
|
|
}
|
|
else static if (T.sizeof == 1)
|
|
{
|
|
uint c = value - ((value >> 1) & 0x55);
|
|
c = ((c >> 2) & 0x33) + (c & 0X33);
|
|
c = ((c >> 4) + c) & 0x0F;
|
|
}
|
|
else
|
|
{
|
|
static assert(false, "countBitsSet only supports 1, 2, 4, or 8 byte sized integers.");
|
|
}
|
|
return cast(uint) c;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
assert(countBitsSet(1) == 1);
|
|
assert(countBitsSet(0) == 0);
|
|
assert(countBitsSet(int.min) == 1);
|
|
assert(countBitsSet(uint.max) == 32);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta;
|
|
static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
assert(countBitsSet(cast(T) 0) == 0);
|
|
assert(countBitsSet(cast(T) 1) == 1);
|
|
assert(countBitsSet(cast(T) 2) == 1);
|
|
assert(countBitsSet(cast(T) 3) == 2);
|
|
assert(countBitsSet(cast(T) 4) == 1);
|
|
assert(countBitsSet(cast(T) 5) == 2);
|
|
assert(countBitsSet(cast(T) 127) == 7);
|
|
static if (isSigned!T)
|
|
{
|
|
assert(countBitsSet(cast(T)-1) == 8 * T.sizeof);
|
|
assert(countBitsSet(T.min) == 1);
|
|
}
|
|
else
|
|
{
|
|
assert(countBitsSet(T.max) == 8 * T.sizeof);
|
|
}
|
|
// Check CTFE compiles.
|
|
static assert(countBitsSet(cast(T) 1) == 1);
|
|
}
|
|
assert(countBitsSet(1_000_000) == 7);
|
|
foreach (i; 0 .. 63)
|
|
assert(countBitsSet(1UL << i) == 1);
|
|
}
|
|
|
|
private struct BitsSet(T)
|
|
{
|
|
static assert(T.sizeof <= 8, "bitsSet assumes T is no more than 64-bit.");
|
|
|
|
@nogc pure nothrow:
|
|
|
|
this(T value, size_t startIndex = 0)
|
|
{
|
|
_value = value;
|
|
// Further calculation is only valid and needed when the range is non-empty.
|
|
if (!_value)
|
|
return;
|
|
|
|
import core.bitop : bsf;
|
|
immutable trailingZerosCount = bsf(value);
|
|
_value >>>= trailingZerosCount;
|
|
_index = startIndex + trailingZerosCount;
|
|
}
|
|
|
|
@property size_t front() const
|
|
{
|
|
return _index;
|
|
}
|
|
|
|
@property bool empty() const
|
|
{
|
|
return !_value;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(_value, "Cannot call popFront on empty range.");
|
|
|
|
_value >>>= 1;
|
|
// Further calculation is only valid and needed when the range is non-empty.
|
|
if (!_value)
|
|
return;
|
|
|
|
import core.bitop : bsf;
|
|
immutable trailingZerosCount = bsf(_value);
|
|
_value >>>= trailingZerosCount;
|
|
_index += trailingZerosCount + 1;
|
|
}
|
|
|
|
@property BitsSet save() const
|
|
{
|
|
return this;
|
|
}
|
|
|
|
@property size_t length() const
|
|
{
|
|
return countBitsSet(_value);
|
|
}
|
|
|
|
private T _value;
|
|
private size_t _index;
|
|
}
|
|
|
|
/**
|
|
Range that iterates the indices of the set bits in `value`.
|
|
Index 0 corresponds to the least significant bit.
|
|
For signed integers, the highest index corresponds to the sign bit.
|
|
*/
|
|
auto bitsSet(T)(const T value) @nogc pure nothrow
|
|
if (isIntegral!T)
|
|
{
|
|
return BitsSet!T(value);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
assert(bitsSet(1).equal([0]));
|
|
assert(bitsSet(5).equal([0, 2]));
|
|
assert(bitsSet(-1).equal(iota(32)));
|
|
assert(bitsSet(int.min).equal([31]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota;
|
|
|
|
import std.meta;
|
|
static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong))
|
|
{
|
|
assert(bitsSet(cast(T) 0).empty);
|
|
assert(bitsSet(cast(T) 1).equal([0]));
|
|
assert(bitsSet(cast(T) 2).equal([1]));
|
|
assert(bitsSet(cast(T) 3).equal([0, 1]));
|
|
assert(bitsSet(cast(T) 4).equal([2]));
|
|
assert(bitsSet(cast(T) 5).equal([0, 2]));
|
|
assert(bitsSet(cast(T) 127).equal(iota(7)));
|
|
static if (isSigned!T)
|
|
{
|
|
assert(bitsSet(cast(T)-1).equal(iota(8 * T.sizeof)));
|
|
assert(bitsSet(T.min).equal([8 * T.sizeof - 1]));
|
|
}
|
|
else
|
|
{
|
|
assert(bitsSet(T.max).equal(iota(8 * T.sizeof)));
|
|
}
|
|
}
|
|
assert(bitsSet(1_000_000).equal([6, 9, 14, 16, 17, 18, 19]));
|
|
foreach (i; 0 .. 63)
|
|
assert(bitsSet(1UL << i).equal([i]));
|
|
}
|