55namespace MabeEnum ;
66
77use Countable ;
8- use Iterator ;
98use InvalidArgumentException ;
9+ use Iterator ;
10+ use IteratorAggregate ;
11+ use OutOfBoundsException ;
1012
1113/**
1214 * A set of enumerators of the given enumeration (EnumSet<T>)
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