| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| library; | |
| import 'dart:collection'; | |
| import 'package:meta/meta.dart'; | |
| import 'bitset.dart'; | |
| extension<E extends Enum> on E { | |
| EnumSet<E> get mask { | |
| assert(index < 64); | |
| return EnumSet.fromRawBits(1 << index); | |
| } | |
| } | |
| /// A set of enum values based on a bit mask of the shifted enum indices. The | |
| /// enum indices used must be less than 64. | |
| extension type const EnumSet<E extends Enum>(Bitset mask) { | |
| const EnumSet.fromRawBits(int bits) : this(bits as Bitset); | |
| /// Creates an empty set. | |
| const EnumSet.empty() : this.fromRawBits(0); | |
| /// Creates a set containing all enum values in [E]. | |
| /// [values] is intended to be the `values` property of the enum class. | |
| const EnumSet.allValues(Iterable<E> values) | |
| : this.fromRawBits((1 << values.length) - 1); | |
| /// Creates a singleton set containing [value]. | |
| factory EnumSet.fromValue(E value) => value.mask; | |
| /// Creates a set containing [values]. | |
| factory EnumSet.fromValues(Iterable<E> values) => | |
| values.fold(EnumSet.empty(), (acc, e) => acc.union(e.mask)); | |
| /// Returns a set containing all enum values in [this] as well as [enumValue]. | |
| @useResult | |
| EnumSet<E> add(E enumValue) => union(enumValue.mask); | |
| /// Returns a set containing all enum values in [this] except for [enumValue]. | |
| @useResult | |
| EnumSet<E> remove(E enumValue) => setMinus(enumValue.mask); | |
| /// Returns a set with the bit for [value] enabled or disabled depending on | |
| /// [state]. | |
| @useResult | |
| EnumSet<E> update(E value, bool state) => state ? add(value) : remove(value); | |
| /// Returns a new set containing all values in both this and the [other] set. | |
| @useResult | |
| EnumSet<E> intersection(EnumSet<E> other) => | |
| EnumSet(mask.intersection(other.mask)); | |
| /// Returns a new set containing all values either in this set or in the | |
| /// [other] set. | |
| @useResult | |
| EnumSet<E> union(EnumSet<E> other) => EnumSet(mask.union(other.mask)); | |
| /// Returns a new set containing all values in this set that are not in the | |
| /// [other] set. | |
| @useResult | |
| EnumSet<E> setMinus(EnumSet<E> other) => EnumSet(mask.setMinus(other.mask)); | |
| /// Returns `true` if [enumValue] is in this set. | |
| bool contains(E enumValue) => intersects(enumValue.mask); | |
| /// Returns `true` if this and [other] have any values in common. | |
| bool intersects(EnumSet<E> other) => mask.intersects(other.mask); | |
| /// Returns `true` if this set is empty. | |
| bool get isEmpty => mask.isEmpty; | |
| /// Returns `true` if this set is not empty. | |
| bool get isNotEmpty => mask.isNotEmpty; | |
| /// Returns an [Iterable] of the values is in this set using [values] to | |
| /// convert the stored indices to enum values. | |
| /// | |
| /// The method is typically called with the `values` property of the enum | |
| /// class as argument: | |
| /// | |
| /// EnumSet<EnumClass> set = ... | |
| /// Iterable<EnumClass> iterable = set.iterable(EnumClass.values); | |
| /// | |
| Iterable<E> iterable(List<E> values) => | |
| // We may store extra data in the bits unused by the enum values, but that | |
| // will result in iteration attempting to look up enum values out of bounds. | |
| // We can avoid this by masking off such bits. | |
| _EnumSetIterable(mask.bits & ((1 << values.length) - 1), values); | |
| } | |
| class _EnumSetIterable<E extends Enum> extends IterableBase<E> { | |
| final int _mask; | |
| final List<E> _values; | |
| _EnumSetIterable(this._mask, this._values); | |
| @override | |
| Iterator<E> get iterator => _EnumSetIterator(_mask, _values); | |
| } | |
| class _EnumSetIterator<E extends Enum> implements Iterator<E> { | |
| int _mask; | |
| final List<E> _values; | |
| E? _current; | |
| _EnumSetIterator(this._mask, this._values); | |
| @override | |
| E get current => _current!; | |
| @override | |
| bool moveNext() { | |
| if (_mask == 0) { | |
| _current = null; | |
| return false; | |
| } | |
| final value = _current = _values[_mask.bitLength - 1]; | |
| _mask &= ~value.mask.mask.bits; | |
| return true; | |
| } | |
| } | |
| /// Generalizes some [EnumSet] operations to support offsets. | |
| /// The offset allows multiple enumsets to be compacted into the same shared | |
| /// [int] by using different ranges of bits for different enums. | |
| /// The shared [int] is provided to each operation rather than owned by `this`. | |
| class EnumSetDomain<E extends Enum> { | |
| /// How many low-significance bits to skip over when converting enum values to | |
| /// bits, or equivalently, which bit is used to represent the smallest enum | |
| /// value, where 0 indicates the LSB and 63 indicates the MSB. An offset of 0 | |
| /// is equivalent to using an [EnumSet] directly. | |
| final int offset; | |
| final List<E> values; | |
| late final Bitset singletonMask; | |
| EnumSetDomain( | |
| this.offset, | |
| this.values, { | |
| Iterable<E> singletonBits = const [], | |
| }) { | |
| assert(values.length + offset <= 64); | |
| singletonMask = fromValues(singletonBits); | |
| } | |
| /// A bitset containing all enum values in [E]. | |
| late final Bitset allValues = Bitset(((1 << values.length) - 1) << offset); | |
| /// When composing [EnumSetDomain]s, the [offset] to use for the next domain. | |
| int get nextOffset => offset + values.length; | |
| /// Returns a bitset derived from [values] shifted by the appropriate offset. | |
| Bitset fromEnumSet(EnumSet<E> values) => Bitset(values.mask.bits << offset); | |
| /// Returns a singleton bitset containing [value]. | |
| Bitset fromValue(E value) => fromEnumSet(value.mask); | |
| /// Returns a bitset containing [values]. | |
| Bitset fromValues(Iterable<E> values) => | |
| values.fold(Bitset.empty(), (acc, e) => acc.union(fromValue(e))); | |
| EnumSet<E> toEnumSet(Bitset bits) => | |
| EnumSet.fromRawBits(restrict(bits).bits >> offset); | |
| /// Returns a bitset equal to [bits] with only bits in this domain set. | |
| @useResult | |
| Bitset restrict(Bitset bits) => bits.intersection(allValues); | |
| /// Returns a bitset containing all enum values in [bits] as well as | |
| /// [enumValue]. | |
| @useResult | |
| Bitset add(Bitset bits, E enumValue) => bits.union(fromValue(enumValue)); | |
| /// Returns a bitset containing all enum values in [bits] as well as | |
| /// [enumValues]. | |
| @useResult | |
| Bitset addAll(Bitset bits, Iterable<E> enumValues) => | |
| bits.union(fromEnumSet(EnumSet.fromValues(enumValues))); | |
| /// Returns a bitset containing all enum values in [bits] except for | |
| /// [enumValue]. | |
| @useResult | |
| Bitset remove(Bitset bits, E enumValue) => | |
| bits.setMinus(fromValue(enumValue)); | |
| /// Returns a bitset containing all enum values in [bits] except for enum | |
| /// values in [E]. | |
| @useResult | |
| Bitset clear(Bitset bits) => bits.setMinus(allValues); | |
| /// Returns [bits] with all bits corresponding to [E] values replaced by the | |
| /// bit for [enumValue]. | |
| @useResult | |
| Bitset replace(Bitset bits, E enumValue) => add(clear(bits), enumValue); | |
| /// Returns [bits] with all bits corresponding to [E] values replaced by bits | |
| /// for [enumValues]. | |
| @useResult | |
| Bitset replaceMany(Bitset bits, Iterable<E> enumValues) => | |
| addAll(clear(bits), enumValues); | |
| /// Returns a copy of [bits] with the bit for [value] enabled or disabled | |
| /// depending on [state]. | |
| @useResult | |
| Bitset update(Bitset bits, E value, bool state) => | |
| state ? add(bits, value) : remove(bits, value); | |
| /// Returns `true` if [enumValue] is in [bits]. | |
| bool contains(Bitset bits, E enumValue) => | |
| bits.intersects(fromValue(enumValue)); | |
| /// Returns `true` if [enumValue] is in [bits] and no other [E] is. | |
| bool containsSingle(Bitset bits, E enumValue) => | |
| toEnumSet(bits) == enumValue.mask; | |
| /// Returns `true` if the only [E] values in [bits] are in [enumValues]. | |
| bool containsOnly(Bitset bits, EnumSet<E> enumValues) => | |
| toEnumSet(bits).union(enumValues) == enumValues; | |
| /// Returns `true` if [bits] contains any [E]. | |
| bool isNotEmpty(Bitset bits) => bits.intersects(allValues); | |
| /// Returns `true` if [bits] does not contain any [E]. | |
| bool isEmpty(Bitset bits) => !isNotEmpty(bits); | |
| } | |
| class ComposedEnumSetDomains { | |
| final List<EnumSetDomain> domains; | |
| final int bitWidth; | |
| late final Bitset mask; | |
| late final Bitset notMask; | |
| final Bitset singletonsMask; | |
| ComposedEnumSetDomains(this.domains) | |
| : bitWidth = domains.last.nextOffset, | |
| singletonsMask = domains.fold( | |
| Bitset.empty(), | |
| (mask, domain) => mask.union(domain.singletonMask), | |
| ) { | |
| final maskBits = (1 << bitWidth) - 1; | |
| mask = Bitset(maskBits); | |
| notMask = Bitset(~maskBits); | |
| } | |
| @useResult | |
| Bitset restrict(Bitset bits) => bits.intersection(mask); | |
| } |