blob: 4906b5884b01eca55982345ebc8034db0118c5ab [file] [log] [blame]
// 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);
}