Skip to content

Instantly share code, notes, and snippets.

@Revolucent
Last active September 22, 2018 12:46
Show Gist options
  • Save Revolucent/beac15070d1f92f82d54 to your computer and use it in GitHub Desktop.
Save Revolucent/beac15070d1f92f82d54 to your computer and use it in GitHub Desktop.
BitwiseOptions implementation for Swift
//
// BitwiseOptions.swift
//
// Created by Gregory Higley on 11/24/14.
// Copyright (c) 2014 Prosumma LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/*
BitwiseOptions is a simple protocol (BitwiseOptionsType) and class (BitwiseOptions<T>)
that allow bitmask-like operations on native Swift enumerations:
enum Animal: BitwiseOptionsType {
case Chicken
case Cow
case Goat
static let allOptions = [.Chicken, .Cow, .Goat]
}
var animals = Animal.Chicken | Animal.Goat // Automatically creates an instance of BitwiseOptions<Animal>
animals ^= .Chicken // Toggle .Chicken so we only have .Goat
Since Swift enumerations won't work with Objective-C, neither will BitwiseOptions. This
is for native Swift code only.
No actual bitmasking of any kind is going on here. There is no math. These are effectively
operations on sets.
All operations produce an instance of BitwiseOptions<T>. If you wish to allow bitmasks
to be passed to functions, you should use this type:
func putIntoBarn(animals: BitwiseOptions<Animal>)
Then you can say:
putIntoBarn(.Chicken | .Goat)
putIntoBarn(nil)
BitwiseOptions<Animal> implements NilLiteralConvertible, so nil gets you an empty set of options.
This allows us to use a little trick to say we want all options:
putIntoBarn(~nil)
What we are saying here is that we want the complement (~) of the empty set of options, which is the
set of all options.
This brings us to the only real problem with BitwiseOptions: single enumeration values. Because
Swift does not support implicit conversions (except of various kinds of literals), there are
four ways to pass a single enumeration value:
putIntoBarn(BitwiseOptions<Animal>(.Chicken))
putIntoBarn([.Chicken])
putIntoBarn(|.Chicken)
putIntoBarn(nil | .Chicken)
The second one works because BitwiseOptions<T> implements ArrayLiteralConvertible. The third one
works because I have defined | as a prefix operator to handle this situation. The fourth one
works for obvious reasons. I recommend the third or fourth versions, which also work with assignments:
var animal = |.Chicken
var animal = nil | .Chicken
Using BitwiseOptions is pretty easy. Just create an enumeration and make it conform to the
BitwiseOptionsType protocol:
enum Animal: BitwiseOptionsType {
case Chicken
case Cow
case Goat
static let allOptions: [Animal] = [.Chicken, .Cow, .Goat]
}
The only work you have to do is to add allOptions, whose implementation should be obvious.
The rest you get automatically.
I wrote most of this code in a single furious sitting. It may have some bugs. It may omit some things
that are important for bitmasking and would work with this paradigm. (I implemented what I
tended to use.) Some of the algorithms could be more efficient. So let me know what I can
do to improve this, or just copy it and do it yourself. (Though I'd still love some feedback.)
*/
prefix operator | {}
func intersect<S: SequenceType where S.Generator.Element: Equatable>(sequences: [S]) -> [S.Generator.Element] {
var candidates: [S.Generator.Element]!
if sequences.count > 0 {
candidates = Array<S.Generator.Element>(sequences[0])
for sequence in sequences {
candidates = filter(candidates) { contains(sequence, $0) }
}
} else {
candidates = Array<S.Generator.Element>()
}
return candidates
}
func intersect<S: SequenceType where S.Generator.Element: Equatable>(sequences: S...) -> [S.Generator.Element] {
return intersect(sequences)
}
struct BitwiseOptions<T: BitwiseOptionsType>: ArrayLiteralConvertible, NilLiteralConvertible, SequenceType, Equatable {
typealias Generator = GeneratorOf<T>
internal let bits: [T: Bool]
init() {
bits = [T: Bool]()
}
init(_ options: [T]) {
bits = BitwiseOptions.merge(options)
}
init(_ options: T...) {
self.init(options)
}
init(_ bitwiseOptions: BitwiseOptions<T>, _ options: T...) {
self.bits = BitwiseOptions.merge(bitwiseOptions.options, options)
}
init(_ bitwiseOptions: BitwiseOptions<T>...) {
self.bits = BitwiseOptions.merge(bitwiseOptions)
}
init(arrayLiteral elements: T...) {
self.init(elements)
}
init(nilLiteral: ()) {
self.init()
}
var options: [T] {
return bits.keys.array
}
func generate() -> Generator {
return GeneratorOf(bits.keys.generate())
}
internal static func merge<S: SequenceType, K: BitwiseOptionsType where S.Generator.Element == K>(sequences: [S]) -> Dictionary<K, Bool> {
var result = Dictionary<K, Bool>()
for sequence in sequences {
for elem in sequence {
result[elem] = true
}
}
return result
}
internal static func merge<S: SequenceType, K: BitwiseOptionsType where S.Generator.Element == K>(sequence: S...) -> Dictionary<K, Bool> {
return merge(sequence)
}
}
protocol BitwiseOptionsType: Hashable {
class var allOptions: [Self] { get }
func |(lhs: Self, rhs: Self) -> BitwiseOptions<Self>
func |(lhs: BitwiseOptions<Self>, rhs: Self) -> BitwiseOptions<Self>
func |(lhs: Self, rhs: BitwiseOptions<Self>) -> BitwiseOptions<Self>
func |=(inout lhs: BitwiseOptions<Self>, rhs: Self)
func &(lhs: Self, rhs: Self) -> BitwiseOptions<Self>
func &(lhs: BitwiseOptions<Self>, rhs: Self) -> BitwiseOptions<Self>
func &(lhs: Self, rhs: BitwiseOptions<Self>) -> BitwiseOptions<Self>
func &=(inout lhs: BitwiseOptions<Self>, rhs: Self)
func ^(lhs: Self, rhs: Self) -> BitwiseOptions<Self>
func ^(lhs: BitwiseOptions<Self>, rhs: Self) -> BitwiseOptions<Self>
func ^(lhs: Self, BitwiseOptions<Self>) -> BitwiseOptions<Self>
func ^=(inout lhs: BitwiseOptions<Self>, rhs: Self)
func ==(lhs: BitwiseOptions<Self>, rhs: Self) -> Bool
func ==(lhs: Self, rhs: BitwiseOptions<Self>) -> Bool
func !=(lhs: BitwiseOptions<Self>, rhs: Self) -> Bool
func !=(lhs: Self, rhs: BitwiseOptions<Self>) -> Bool
prefix func ~(option: Self) -> BitwiseOptions<Self>
prefix func |(option: Self) -> BitwiseOptions<Self>
}
func |<T: BitwiseOptionsType>(lhs: T, rhs: T) -> BitwiseOptions<T> {
return BitwiseOptions<T>(lhs, rhs)
}
func |<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: T) -> BitwiseOptions<T> {
return BitwiseOptions<T>(lhs, rhs)
}
func |<T: BitwiseOptionsType>(lhs: T, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
return BitwiseOptions<T>(rhs, lhs)
}
func |<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
return BitwiseOptions<T>(lhs, rhs)
}
func |=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: T) {
lhs = lhs | rhs
}
func |=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) {
lhs = lhs | rhs
}
func &<T: BitwiseOptionsType>(lhs: T, rhs: T) -> BitwiseOptions<T> {
return lhs == rhs ? BitwiseOptions<T>(lhs) : BitwiseOptions<T>()
}
func &<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: T) -> BitwiseOptions<T> {
return (lhs.bits[rhs] ?? false) ? BitwiseOptions<T>(rhs) : BitwiseOptions<T>()
}
func &<T: BitwiseOptionsType>(lhs: T, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
return rhs & lhs
}
func &<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
return BitwiseOptions<T>(intersect(lhs.options, rhs.options))
}
func &=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: T) {
lhs = lhs & rhs
}
func &=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) {
lhs = lhs & rhs
}
func ^<T: BitwiseOptionsType>(lhs: T, rhs: T) -> BitwiseOptions<T> {
return BitwiseOptions<T>(lhs) ^ BitwiseOptions<T>(rhs)
}
func ^<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: T) -> BitwiseOptions<T> {
return lhs ^ BitwiseOptions<T>(rhs)
}
func ^<T: BitwiseOptionsType>(lhs: T, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
return BitwiseOptions<T>(lhs) ^ rhs
}
func ^<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) -> BitwiseOptions<T> {
let intersection = intersect(lhs.options, rhs.options)
let lhsOptions = filter(lhs.options) { !contains(intersection, $0) }
let rhsOptions = filter(rhs.options) { !contains(intersection, $0) }
return BitwiseOptions<T>(lhsOptions) | BitwiseOptions<T>(rhsOptions)
}
func ^=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: T) {
lhs = lhs ^ rhs
}
func ^=<T: BitwiseOptionsType>(inout lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) {
lhs = lhs ^ rhs
}
func ==<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: T) -> Bool {
return lhs.bits.count == 1 && (lhs.bits[rhs] ?? false)
}
func ==<T: BitwiseOptionsType>(lhs: T, rhs: BitwiseOptions<T>) -> Bool {
return rhs == lhs
}
func ==<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: BitwiseOptions<T>) -> Bool {
return lhs.bits == rhs.bits
}
func !=<T: BitwiseOptionsType>(lhs: BitwiseOptions<T>, rhs: T) -> Bool {
return !(lhs == rhs)
}
func !=<T: BitwiseOptionsType>(lhs: T, rhs: BitwiseOptions<T>) -> Bool {
return !(lhs == rhs)
}
prefix func ~<T: BitwiseOptionsType>(option: T) -> BitwiseOptions<T> {
return ~BitwiseOptions<T>(option)
}
prefix func ~<T: BitwiseOptionsType>(bitwiseOptions: BitwiseOptions<T>) -> BitwiseOptions<T> {
return BitwiseOptions<T>(filter(T.allOptions, { !contains(bitwiseOptions.options, $0) }))
}
prefix func |<T: BitwiseOptionsType>(option: T) -> BitwiseOptions<T> {
return BitwiseOptions<T>(option)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment