Skip to content

Commit b6bcf75

Browse files
authored
Merge pull request #4927 from edi33416/bitwise_adapter
[WIP] std.range: implement bitwise adapter over Integral type Ranges
2 parents 428217b + 5faa96d commit b6bcf75

File tree

1 file changed

+382
-0
lines changed

1 file changed

+382
-0
lines changed

std/range/package.d

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9594,6 +9594,388 @@ auto refRange(R)(R* range)
95949594
foo(r);
95959595
}
95969596

9597+
/**
9598+
Bitwise adapter over integral type ranges. Consumes the range elements bit by bit.
9599+
9600+
Params:
9601+
R = an input range to iterate over
9602+
9603+
Returns:
9604+
At minimum, an input range. If `R` was a forward, bidirectional or random
9605+
access range, `Bitwise!R` will be as well.
9606+
*/
9607+
struct Bitwise(R)
9608+
if (isInputRange!R && isIntegral!(ElementType!R))
9609+
{
9610+
private:
9611+
alias ElemType = ElementType!R;
9612+
alias UnsignedElemType = Unsigned!ElemType;
9613+
9614+
R parent;
9615+
enum bitsNum = ElemType.sizeof * 8;
9616+
size_t maskPos = bitsNum;
9617+
9618+
static if (isBidirectionalRange!R)
9619+
{
9620+
size_t backMaskPos = 1;
9621+
}
9622+
9623+
public:
9624+
this()(auto ref R range)
9625+
{
9626+
parent = range;
9627+
}
9628+
9629+
/**
9630+
* Check if the range is empty
9631+
*
9632+
* Returns: a boolean true or false
9633+
*/
9634+
bool empty()
9635+
{
9636+
static if (hasLength!R)
9637+
{
9638+
return length() == 0;
9639+
}
9640+
else static if (isBidirectionalRange!R)
9641+
{
9642+
bool isOverlapping = parent.empty;
9643+
if (!parent.empty)
9644+
{
9645+
/*
9646+
If we have consumed the last element of the range both from
9647+
the front and the back, then the masks positions will overlap
9648+
*/
9649+
R parentCopy = parent.save;
9650+
parentCopy.popFront;
9651+
isOverlapping = parentCopy.empty && (maskPos < backMaskPos);
9652+
}
9653+
return parent.empty || isOverlapping;
9654+
}
9655+
else
9656+
{
9657+
/*
9658+
If we consumed the last element of the range, but not all the
9659+
bits in the last element
9660+
*/
9661+
return parent.empty;
9662+
}
9663+
}
9664+
9665+
bool front()
9666+
{
9667+
assert(!empty());
9668+
return (parent.front & mask(maskPos)) != 0;
9669+
}
9670+
9671+
void popFront()
9672+
{
9673+
--maskPos;
9674+
if (maskPos == 0)
9675+
{
9676+
parent.popFront;
9677+
maskPos = bitsNum;
9678+
}
9679+
}
9680+
9681+
static if (hasLength!R)
9682+
{
9683+
size_t length()
9684+
{
9685+
auto len = parent.length * bitsNum - (bitsNum - maskPos);
9686+
static if (isBidirectionalRange!R)
9687+
{
9688+
len -= (backMaskPos - 1);
9689+
}
9690+
return len;
9691+
}
9692+
9693+
alias opDollar = length;
9694+
}
9695+
9696+
static if (isForwardRange!R)
9697+
{
9698+
typeof(this) save()
9699+
{
9700+
auto result = this;
9701+
result.parent = parent.save;
9702+
return result;
9703+
}
9704+
}
9705+
9706+
static if (isBidirectionalRange!R)
9707+
{
9708+
bool back()
9709+
{
9710+
assert(!empty());
9711+
return (parent.back & mask(backMaskPos)) != 0;
9712+
}
9713+
9714+
void popBack()
9715+
{
9716+
++backMaskPos;
9717+
if (backMaskPos > bitsNum)
9718+
{
9719+
parent.popBack;
9720+
backMaskPos = 1;
9721+
}
9722+
}
9723+
}
9724+
9725+
static if (isRandomAccessRange!R)
9726+
{
9727+
/**
9728+
Return the `n`th bit within the range
9729+
*/
9730+
bool opIndex(size_t n)
9731+
in
9732+
{
9733+
/*
9734+
If it does not have the length property, it means that R is
9735+
an infinite range
9736+
*/
9737+
static if (hasLength!R)
9738+
{
9739+
assert(n < length(), __PRETTY_FUNCTION__ ~ ": Index out of bounds");
9740+
}
9741+
}
9742+
body
9743+
{
9744+
// If n >= maskPos, then the bit sign will be 1, otherwise 0
9745+
immutable sizediff_t sign = (maskPos - n - 1) >> (sizediff_t.sizeof * 8 - 1);
9746+
/*
9747+
By truncating n with maskPos bits we have skipped the remaining
9748+
maskPos bits in parent[0], so we need to add 1 to elemIndex.
9749+
9750+
Because bitsNum is a power of 2, n / bitsNum == n >> bitsNum.bsf
9751+
*/
9752+
import core.bitop : bsf;
9753+
immutable size_t elemIndex = sign * (((n - maskPos) >> bitsNum.bsf) + 1);
9754+
9755+
/*
9756+
Since the indexing is from MSB to LSB, we need to subtract the
9757+
remainder from the number of bits that the element type has.
9758+
9759+
Because bitsNum is a power of 2, n % bitsNum == n & (bitsNum - 1)
9760+
*/
9761+
immutable size_t elemMaskPos = (sign ^ 1) * (maskPos - n)
9762+
+ sign * (bitsNum - ((n - maskPos) & (bitsNum - 1)));
9763+
9764+
return (parent[elemIndex] & mask(elemMaskPos)) != 0;
9765+
}
9766+
9767+
/**
9768+
Assignes `flag` to the `n`th bit within the range
9769+
*/
9770+
void opIndexAssign(bool flag, size_t n)
9771+
in
9772+
{
9773+
static if (hasLength!R)
9774+
{
9775+
assert(n < length(), __PRETTY_FUNCTION__ ~ ": Index out of bounds");
9776+
}
9777+
}
9778+
body
9779+
{
9780+
import core.bitop : bsf;
9781+
9782+
immutable sizediff_t sign = (maskPos - n - 1) >> (sizediff_t.sizeof * 8 - 1);
9783+
immutable size_t elemIndex = sign * (((n - maskPos) >> bitsNum.bsf) + 1);
9784+
immutable size_t elemMaskPos = (sign ^ 1) * (maskPos - n)
9785+
+ sign * (bitsNum - ((n - maskPos) & (bitsNum - 1)));
9786+
9787+
auto elem = parent[elemIndex];
9788+
auto elemMask = mask(elemMaskPos);
9789+
parent[elemIndex] = cast(UnsignedElemType)(flag * (elem | elemMask)
9790+
+ (flag ^ 1) * (elem & ~elemMask));
9791+
}
9792+
9793+
Bitwise!R opSlice()
9794+
{
9795+
return this;
9796+
}
9797+
9798+
Bitwise!R opSlice(size_t start, size_t end)
9799+
in
9800+
{
9801+
assert(start < end, __PRETTY_FUNCTION__ ~ ": Invalid bounds: end <= start");
9802+
}
9803+
body
9804+
{
9805+
import core.bitop : bsf;
9806+
9807+
sizediff_t sign = (maskPos - start - 1) >> (sizediff_t.sizeof * 8 - 1);
9808+
immutable size_t startElemIndex = sign * (((start - maskPos) >> bitsNum.bsf) + 1);
9809+
immutable size_t startElemMaskPos = (sign ^ 1) * (maskPos - start)
9810+
+ sign * (bitsNum - (start & (bitsNum - 1)));
9811+
9812+
immutable size_t sliceLen = end - start - 1;
9813+
sign = (maskPos - sliceLen - 1) >> (sizediff_t.sizeof * 8 - 1);
9814+
immutable size_t endElemIndex = sign * (((sliceLen - maskPos) >> bitsNum.bsf) + 1);
9815+
immutable size_t endElemMaskPos = (sign ^ 1) * (maskPos - sliceLen)
9816+
+ sign * (bitsNum - ((sliceLen - maskPos) & (bitsNum - 1)));
9817+
9818+
typeof(return) result;
9819+
// Get the slice to be returned from the parent
9820+
result.parent = (parent[startElemIndex .. endElemIndex + 1]).save;
9821+
result.maskPos = startElemMaskPos;
9822+
static if (isBidirectionalRange!R)
9823+
{
9824+
result.backMaskPos = endElemMaskPos;
9825+
}
9826+
return result;
9827+
}
9828+
}
9829+
9830+
private:
9831+
auto mask(size_t maskPos)
9832+
{
9833+
return (1UL << (maskPos - 1UL));
9834+
}
9835+
}
9836+
9837+
// Test all range types over all integral types
9838+
///
9839+
@safe unittest
9840+
{
9841+
import std.internal.test.dummyrange;
9842+
9843+
alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint,
9844+
long, ulong);
9845+
foreach (IntegralType; IntegralTypes)
9846+
{
9847+
foreach (T; AllDummyRangesType!(IntegralType[]))
9848+
{
9849+
T a;
9850+
auto bw = Bitwise!T(a);
9851+
9852+
static if (isForwardRange!T)
9853+
{
9854+
auto bw2 = bw.save;
9855+
}
9856+
9857+
static if (isBidirectionalRange!T)
9858+
{
9859+
auto bw3 = bw.save;
9860+
auto bw4 = bw.save;
9861+
}
9862+
9863+
static if (hasLength!T)
9864+
{
9865+
auto bwLength = bw.length;
9866+
assert(bw.length == (IntegralType.sizeof * 8 * a.length));
9867+
static if (isForwardRange!T)
9868+
{
9869+
assert(bw.length == bw2.length);
9870+
}
9871+
}
9872+
9873+
// Make sure front and back are not the mechanisms that modify the range
9874+
long numCalls = 42;
9875+
bool initialFrontValue;
9876+
9877+
if (!bw.empty())
9878+
{
9879+
initialFrontValue = bw.front;
9880+
}
9881+
9882+
while (!bw.empty() && (--numCalls))
9883+
{
9884+
bw.front;
9885+
assert(bw.front == initialFrontValue);
9886+
}
9887+
9888+
/*
9889+
Check that empty works properly and that popFront does not get called
9890+
more times than it should
9891+
*/
9892+
numCalls = 0;
9893+
while (!bw.empty())
9894+
{
9895+
++numCalls;
9896+
9897+
static if (hasLength!T)
9898+
{
9899+
assert(bw.length == bwLength);
9900+
--bwLength;
9901+
}
9902+
9903+
static if (isForwardRange!T)
9904+
{
9905+
assert(bw.front == bw2.front);
9906+
bw2.popFront();
9907+
}
9908+
9909+
static if (isBidirectionalRange!T)
9910+
{
9911+
assert(bw3.front == bw4.front);
9912+
bw3.popBack();
9913+
bw4.popBack();
9914+
}
9915+
bw.popFront();
9916+
}
9917+
9918+
auto rangeLen = numCalls / (IntegralType.sizeof * 8);
9919+
assert(numCalls == (IntegralType.sizeof * 8 * rangeLen));
9920+
assert(bw.empty());
9921+
static if (isForwardRange!T)
9922+
{
9923+
assert(bw2.empty());
9924+
}
9925+
9926+
static if (isBidirectionalRange!T)
9927+
{
9928+
assert(bw3.empty());
9929+
}
9930+
}
9931+
}
9932+
}
9933+
9934+
// Test opIndex and opSlice
9935+
///
9936+
@system unittest
9937+
{
9938+
alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint,
9939+
long, ulong);
9940+
foreach (IntegralType; IntegralTypes)
9941+
{
9942+
size_t bitsNum = IntegralType.sizeof * 8;
9943+
9944+
auto first = cast(IntegralType)(1);
9945+
9946+
// 2 ^ (bitsNum - 1)
9947+
auto second = cast(IntegralType)(cast(IntegralType)(1) << (bitsNum - 2));
9948+
9949+
IntegralType[] a = [first, second];
9950+
auto bw = Bitwise!(IntegralType[])(a);
9951+
9952+
// Check against lsb of a[0]
9953+
assert(bw[bitsNum - 1] == true);
9954+
// Check against msb - 1 of a[1]
9955+
assert(bw[bitsNum + 1] == true);
9956+
9957+
bw.popFront();
9958+
assert(bw[bitsNum] == true);
9959+
9960+
import core.exception : Error;
9961+
import std.exception : assertThrown;
9962+
9963+
// Check out of bounds error
9964+
assertThrown!Error(bw[2 * bitsNum - 1]);
9965+
9966+
bw[2] = true;
9967+
assert(bw[2] == true);
9968+
bw.popFront();
9969+
assert(bw[1] == true);
9970+
9971+
auto bw2 = bw[0 .. $ - 5];
9972+
auto bw3 = bw2[];
9973+
assert(bw2.length == (bw.length - 5));
9974+
assert(bw2.length == bw3.length);
9975+
bw2.popFront();
9976+
assert(bw2.length != bw3.length);
9977+
}
9978+
}
95979979

95989980
/*********************************
95999981
* An OutputRange that discards the data it receives.

0 commit comments

Comments
 (0)