Last active
September 22, 2018 12:46
-
-
Save Revolucent/beac15070d1f92f82d54 to your computer and use it in GitHub Desktop.
BitwiseOptions implementation for Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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