Skip to content

Commit 75cd70b

Browse files
committed
EnumSet implements IteratorAggregate instead of Iterator and return Generator
1 parent 5304281 commit 75cd70b

File tree

4 files changed

+353
-451
lines changed

4 files changed

+353
-451
lines changed

src/Enum.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ final public static function byOrdinal(int $ordinal): self
249249

250250
if (!isset(self::$names[static::class][$ordinal])) {
251251
throw new InvalidArgumentException(\sprintf(
252-
'Invalid ordinal number, must between 0 and %s',
252+
'Invalid ordinal number %s, must between 0 and %s',
253+
$ordinal,
253254
\count(self::$names[static::class]) - 1
254255
));
255256
}

src/EnumSet.php

Lines changed: 73 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
namespace MabeEnum;
66

77
use Countable;
8-
use Iterator;
98
use InvalidArgumentException;
9+
use Iterator;
10+
use IteratorAggregate;
11+
use OutOfBoundsException;
1012

1113
/**
1214
* A set of enumerators of the given enumeration (EnumSet<T>)
@@ -16,7 +18,7 @@
1618
* @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
1719
* @link http://github.com/marc-mabe/php-enum for the canonical source repository
1820
*/
19-
class EnumSet implements Iterator, Countable
21+
class EnumSet implements IteratorAggregate, Countable
2022
{
2123
/**
2224
* The classname of the Enumeration
@@ -25,16 +27,10 @@ class EnumSet implements Iterator, Countable
2527
private $enumeration;
2628

2729
/**
28-
* Ordinal number of current iterator position
30+
* Number of enumerators defined in the enumeration
2931
* @var int
3032
*/
31-
private $ordinal = 0;
32-
33-
/**
34-
* Highest possible ordinal number
35-
* @var int
36-
*/
37-
private $ordinalMax;
33+
private $enumerationCount;
3834

3935
/**
4036
* Integer or binary (little endian) bitset
@@ -49,7 +45,7 @@ class EnumSet implements Iterator, Countable
4945
*
5046
* @var string
5147
*/
52-
private $fnDoRewind = 'doRewindInt';
48+
private $fnDoGetIterator = 'doGetIteratorInt';
5349
private $fnDoCount = 'doCountInt';
5450
private $fnDoGetOrdinals = 'doGetOrdinalsInt';
5551
private $fnDoGetBit = 'doGetBitInt';
@@ -75,18 +71,18 @@ public function __construct(string $enumeration)
7571
));
7672
}
7773

78-
$this->enumeration = $enumeration;
79-
$this->ordinalMax = \count($enumeration::getConstants());
74+
$this->enumeration = $enumeration;
75+
$this->enumerationCount = \count($enumeration::getConstants());
8076

8177
// By default the bitset is initialized as integer bitset
8278
// in case the enumeraton has more enumerators then integer bits
8379
// we will switch this into a binary bitset
84-
if ($this->ordinalMax > \PHP_INT_SIZE * 8) {
80+
if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
8581
// init binary bitset with zeros
86-
$this->bitset = \str_repeat("\0", (int)\ceil($this->ordinalMax / 8));
82+
$this->bitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));
8783

8884
// switch internal binary bitset functions
89-
$this->fnDoRewind = 'doRewindBin';
85+
$this->fnDoGetIterator = 'doGetIteratorBin';
9086
$this->fnDoCount = 'doCountBin';
9187
$this->fnDoGetOrdinals = 'doGetOrdinalsBin';
9288
$this->fnDoGetBit = 'doGetBitBin';
@@ -138,106 +134,79 @@ public function contains($enumerator): bool
138134
return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
139135
}
140136

141-
/* Iterator */
142-
143-
/**
144-
* Get the current enumerator
145-
* @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
146-
*/
147-
public function current(): ?Enum
148-
{
149-
if ($this->valid()) {
150-
return ($this->enumeration)::byOrdinal($this->ordinal);
151-
}
152-
153-
return null;
154-
}
137+
/* IteratorAggregate */
155138

156139
/**
157-
* Get the ordinal number of the current iterator position
158-
* @return int
140+
* Get a new iterator
141+
* @return Iterator
142+
* @uses doGetIteratorInt()
143+
* @uses doGetIteratorBin()
159144
*/
160-
public function key(): int
145+
public function getIterator(): Iterator
161146
{
162-
return $this->ordinal;
163-
}
147+
return $this->{$this->fnDoGetIterator}();
164148

165-
/**
166-
* Go to the next valid iterator position.
167-
* If no valid iterator position is found the iterator position will be the last possible + 1.
168-
* @return void
169-
*/
170-
public function next(): void
171-
{
172-
do {
173-
if (++$this->ordinal >= $this->ordinalMax) {
174-
$this->ordinal = $this->ordinalMax;
175-
return;
149+
/*
150+
$ordinal = 0;
151+
$enumerationCount = $this->enumerationCount;
152+
$fnDoGetBit = $this->fnDoGetBit;
153+
for (; $ordinal < $enumerationCount; ++$ordinal) {
154+
if ($this->{$fnDoGetBit}($ordinal)) {
155+
yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
176156
}
177-
} while (!$this->{$this->fnDoGetBit}($this->ordinal));
178-
}
179-
180-
/**
181-
* Go to the first valid iterator position.
182-
* If no valid iterator position was found the iterator position will be 0.
183-
* @return void
184-
* @uses doRewindBin()
185-
* @uses doRewindInt()
186-
*/
187-
public function rewind(): void
188-
{
189-
$this->{$this->fnDoRewind}();
157+
}
158+
*/
190159
}
191160

192161
/**
193-
* Go to the first valid iterator position.
194-
* If no valid iterator position was found the iterator position will be 0.
162+
* Get a new Iterator.
195163
*
196164
* This is the binary bitset implementation.
197165
*
198-
* @return void
199-
* @see rewind()
200-
* @see doRewindInt()
166+
* @return Iterator
167+
* @see getIterator()
168+
* @see goGetIteratorInt()
201169
*/
202-
private function doRewindBin(): void
170+
private function doGetIteratorBin(): Iterator
203171
{
204-
if (\ltrim($this->bitset, "\0") !== '') {
205-
$this->ordinal = -1;
206-
$this->next();
207-
} else {
208-
$this->ordinal = 0;
172+
$bitset = $this->bitset;
173+
$byteLen = \strlen($bitset);
174+
for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
175+
if ($bitset[$bytePos] === "\0") {
176+
// fast skip null byte
177+
continue;
178+
}
179+
180+
$ord = \ord($bitset[$bytePos]);
181+
for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
182+
if ($ord & (1 << $bitPos)) {
183+
$ordinal = $bytePos * 8 + $bitPos;
184+
yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
185+
}
186+
}
209187
}
210188
}
211189

212190
/**
213-
* Go to the first valid iterator position.
214-
* If no valid iterator position was found the iterator position will be 0.
191+
* Get a new Iterator.
215192
*
216-
* This is the binary bitset implementation.
193+
* This is the integer bitset implementation.
217194
*
218-
* @return void
219-
* @see rewind()
220-
* @see doRewindBin()
195+
* @return Iterator
196+
* @see getIterator()
197+
* @see doGetIteratorBin()
221198
*/
222-
private function doRewindInt(): void
199+
private function doGetIteratorInt(): Iterator
223200
{
224-
if ($this->bitset) {
225-
$this->ordinal = -1;
226-
$this->next();
227-
} else {
228-
$this->ordinal = 0;
201+
$count = $this->enumerationCount;
202+
$bitset = $this->bitset;
203+
for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
204+
if ($bitset & (1 << $ordinal)) {
205+
yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
206+
}
229207
}
230208
}
231209

232-
/**
233-
* Test if the iterator is in a valid state
234-
* @return bool
235-
*/
236-
public function valid(): bool
237-
{
238-
return $this->ordinal !== $this->ordinalMax && $this->{$this->fnDoGetBit}($this->ordinal);
239-
}
240-
241210
/* Countable */
242211

243212
/**
@@ -499,12 +468,12 @@ private function doGetOrdinalsBin(): array
499468
*/
500469
private function doGetOrdinalsInt(): array
501470
{
502-
$ordinals = [];
503-
$ordinalMax = $this->ordinalMax;
504-
$bitset = $this->bitset;
505-
for ($ord = 0; $ord < $ordinalMax; ++$ord) {
506-
if ($bitset & (1 << $ord)) {
507-
$ordinals[] = $ord;
471+
$ordinals = [];
472+
$count = $this->enumerationCount;
473+
$bitset = $this->bitset;
474+
for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
475+
if ($bitset & (1 << $ordinal)) {
476+
$ordinals[] = $ordinal;
508477
}
509478
}
510479
return $ordinals;
@@ -590,7 +559,7 @@ private function doGetBinaryBitsetLeBin(): string
590559
private function doGetBinaryBitsetLeInt(): string
591560
{
592561
$bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
593-
return \substr($bin, 0, (int)\ceil($this->ordinalMax / 8));
562+
return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
594563
}
595564

596565
/**
@@ -607,9 +576,6 @@ private function doGetBinaryBitsetLeInt(): string
607576
public function setBinaryBitsetLe(string $bitset): void
608577
{
609578
$this->{$this->fnDoSetBinaryBitsetLe}($bitset);
610-
611-
// reset the iterator position
612-
$this->rewind();
613579
}
614580

615581
/**
@@ -639,7 +605,7 @@ private function doSetBinaryBitsetLeBin(string $bitset): void
639605
}
640606

641607
// truncate out-of-range bits of last byte
642-
$lastByteMaxOrd = $this->ordinalMax % 8;
608+
$lastByteMaxOrd = $this->enumerationCount % 8;
643609
if ($lastByteMaxOrd !== 0) {
644610
$lastByte = $bitset[-1];
645611
$lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
@@ -678,7 +644,7 @@ private function doSetBinaryBitsetLeInt(string $bitset): void
678644
$int |= $ord << (8 * $i);
679645
}
680646

681-
if ($int & (~0 << $this->ordinalMax)) {
647+
if ($int & (~0 << $this->enumerationCount)) {
682648
throw new InvalidArgumentException('out-of-range bits detected');
683649
}
684650

@@ -720,8 +686,8 @@ public function setBinaryBitsetBe(string $bitset): void
720686
*/
721687
public function getBit(int $ordinal): bool
722688
{
723-
if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
724-
throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
689+
if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
690+
throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
725691
}
726692

727693
return $this->{$this->fnDoGetBit}($ordinal);
@@ -771,8 +737,8 @@ private function doGetBitInt(int $ordinal): bool
771737
*/
772738
public function setBit(int $ordinal, bool $bit): void
773739
{
774-
if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
775-
throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
740+
if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
741+
throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
776742
}
777743

778744
if ($bit) {

0 commit comments

Comments
 (0)