From eb796e0ddf49312e01fcf2cab42b93d40c1c61eb Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 16 Sep 2018 18:15:35 +0200 Subject: [PATCH] Add bitmanip.BitFlags --- README.md | 1 + source/tanya/async/event/iocp.d | 4 +- source/tanya/async/event/selector.d | 6 +- source/tanya/async/loop.d | 2 +- source/tanya/bitmanip.d | 359 ++++++++++++++++++++++++++++ 5 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 source/tanya/bitmanip.d diff --git a/README.md b/README.md index 61376bc..c947021 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Tanya consists of the following packages and (top-level) modules: * `algorithm`: Collection of generic algorithms. * `async`: Event loop (epoll, kqueue and IOCP). +* `bitmanip`: Bit manipulation. * `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8 string, Set, Hash table. * `conv`: This module provides functions for converting between different diff --git a/source/tanya/async/event/iocp.d b/source/tanya/async/event/iocp.d index 9d09dcd..acd37a5 100644 --- a/source/tanya/async/event/iocp.d +++ b/source/tanya/async/event/iocp.d @@ -318,7 +318,9 @@ final class IOCPLoop : Loop connection.incoming.insertBack(transport); - reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); + reify(transport, + EventMask(Event.none), + EventMask(Event.read | Event.write)); pendings.insertBack(connection); listener.beginAccept(overlapped); diff --git a/source/tanya/async/event/selector.d b/source/tanya/async/event/selector.d index f8149cf..9e83261 100644 --- a/source/tanya/async/event/selector.d +++ b/source/tanya/async/event/selector.d @@ -140,7 +140,7 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport { closing = true; loop.reify(this, - EventMask(Event.read, Event.write), + EventMask(Event.read | Event.write), EventMask(Event.write)); } @@ -393,7 +393,9 @@ abstract class SelectorLoop : Loop transport.socket = client; } - reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); + reify(transport, + EventMask(Event.none), + EventMask(Event.read | Event.write)); connection.incoming.insertBack(transport); } diff --git a/source/tanya/async/loop.d b/source/tanya/async/loop.d index f5c7dae..b706ff3 100644 --- a/source/tanya/async/loop.d +++ b/source/tanya/async/loop.d @@ -72,9 +72,9 @@ module tanya.async.loop; import core.time; -import std.typecons; import tanya.async.transport; import tanya.async.watcher; +import tanya.bitmanip; import tanya.container.buffer; import tanya.container.list; import tanya.memory; diff --git a/source/tanya/bitmanip.d b/source/tanya/bitmanip.d new file mode 100644 index 0000000..98e7042 --- /dev/null +++ b/source/tanya/bitmanip.d @@ -0,0 +1,359 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bit manipulation. + * + * Copyright: Eugene Wissner 2018. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/bitmanip.d, + * tanya/bitmanip.d) + */ +module tanya.bitmanip; + +import tanya.meta.metafunction; +import tanya.meta.trait; +import tanya.meta.transform; + +/** + * Determines whether $(D_PARAM E) is a $(D_KEYWORD enum), whose members can be + * used as bit flags. + * + * This is the case if all members of $(D_PARAM E) are integral numbers that + * are either 0 or positive integral powers of 2. + * + * Params: + * E = Some $(D_KEYWORD enum). + * + * Returns: $(D_KEYWORD true) if $(D_PARAM E) contains only bit flags, + * $(D_KEYWORD false) otherwise. + */ +template isBitFlagEnum(E) +{ + enum bool isValid(OriginalType!E x) = x == 0 + || (x > 0 && ((x & (x - 1)) == 0)); + static if (isIntegral!E) + { + enum bool isBitFlagEnum = allSatisfy!(isValid, EnumMembers!E); + } + else + { + enum bool isBitFlagEnum = false; + } +} + +/// +@nogc nothrow pure @safe unittest +{ + enum Valid + { + none = 0, + one = 1 << 0, + two = 1 << 1, + } + static assert(isBitFlagEnum!Valid); + + enum Invalid + { + one, + two, + three, + four, + } + static assert(!isBitFlagEnum!Invalid); + + enum Negative + { + one = -1, + two = -2, + } + static assert(!isBitFlagEnum!Negative); +} + +/** + * Validates that $(D_PARAM field) contains only bits from $(D_PARAM E). + * + * Params: + * E = Some $(D_KEYWORD enum). + * field = Bit field. + * + * Returns: $(D_KEYWORD true) if $(D_PARAM field) is valid, $(D_KEYWORD false) + * otherwise. + */ +bool containsBitFlags(E)(E field) +if (isBitFlagEnum!E) +{ + OriginalType!E fillField() + { + typeof(return) full; + static foreach (member; EnumMembers!E) + { + full |= member; + } + return full; + } + enum OriginalType!E full = fillField(); + return (field & ~full) == OriginalType!E.init; +} + +/// +@nogc nothrow pure @safe unittest +{ + enum E + { + one, + two, + three, + } + assert(containsBitFlags(E.one | E.two)); + assert(!containsBitFlags(cast(E) 0x8)); +} + +/** + * Allows to use $(D_KEYWORD enum) values as a set of bit flags. + * + * $(D_PSYMBOL BitFlags) behaves the same as a bit field of type $(D_PARAM E), + * but does additional cheks to ensure that the bit field contains only valid + * values, this is only values from $(D_PARAM E). + * + * Params: + * E = Some $(D_KEYWORD enum). + */ +struct BitFlags(E) +if (isBitFlagEnum!E) +{ + private OriginalType!E field; + + /** + * Constructs $(D_PSYMBOL BitFlags) from $(D_PARAM field). + * + * Params: + * field = Bits to be set. + */ + this(E field) + { + this.field = field; + } + + /** + * Converts $(D_PSYMBOL BitFlags) to a boolean. + * + * It is $(D_KEYWORD true) if any bit is set, $(D_KEYWORD false) otherwise. + * + * Returns: $(D_KEYWORD true) if this $(D_PSYMBOL BitFlags) contains any + * set bits, $(D_KEYWORD false) otherwise. + */ + bool opCast(T : bool)() + { + return this.field != 0; + } + + /** + * Converts to the original type of $(D_PARAM E) ($(D_KEYWORD int) by + * default). + * + * Returns: $(D_KEYWORD this) as $(D_INLINECODE OriginalType!T). + */ + OriginalType!E opCast(T : OriginalType!E)() const + { + return this.field; + } + + /** + * Tests (&), sets (|) or toggles (^) bits. + * + * Params: + * op = Operation. + * that = 0 or more bit flags. + * + * Returns: New $(D_PSYMBOL BitFlags) object. + */ + BitFlags opBinary(string op)(E that) const + if (op == "&" || op == "|" || op == "^") + { + BitFlags result = this; + mixin("return result " ~ op ~ "= that;"); + } + + /// ditto + BitFlags opBinary(string op)(BitFlags that) const + if (op == "&" || op == "|" || op == "^") + { + BitFlags result = this; + mixin("return result " ~ op ~ "= that;"); + } + + /// ditto + BitFlags opBinaryRight(string op)(E that) const + if (op == "&" || op == "|" || op == "^") + { + BitFlags result = this; + mixin("return result " ~ op ~ "= that;"); + } + + /** + * Tests (&), sets (|) or toggles (^) bits. + * + * Params: + * op = Operation. + * that = 0 or more bit flags. + * + * Returns: $(D_KEYWORD this). + */ + ref BitFlags opOpAssign(string op)(E that) + if (op == "&" || op == "|" || op == "^") + { + mixin("this.field " ~ op ~ "= that;"); + return this; + } + + /// ditto + ref BitFlags opOpAssign(string op)(BitFlags that) + if (op == "&" || op == "|" || op == "^") + { + mixin("this.field " ~ op ~ "= that.field;"); + return this; + } + + /** + * Inverts all bit flags. + * + * Returns: New $(D_PSYMBOL BitFlags) object with all bits inverted. + */ + BitFlags opUnary(string op : "~")() const + { + BitFlags result; + result.field = ~this.field; + return result; + } + + /** + * Assigns a bit field. + * + * Params: + * that = Bit field of type $(D_PARAM E). + * + * Returns: $(D_KEYWORD this). + */ + ref BitFlags opAssign(E that) + { + this.field = that; + return this; + } + + /** + * Compares this $(D_PSYMBOL BitFlags) object to another bit field. + * + * Params: + * that = $(D_PSYMBOL BitFlags) object or a bit field of type + * $(D_PARAM E). + * + * Returns: $(D_KEYWORD true) if $(D_KEYWORD this) and $(D_PARAM that) + * contain the same bits ,$(D_KEYWORD false) otherwise. + */ + bool opEquals(E that) const + { + return this.field == that; + } + + /// ditto + bool opEquals(BitFlags that) const + { + return this.field == that.field; + } + + /** + * Generates a hash value of this object. + * + * Returns: Hash value. + */ + size_t toHash() const + { + return cast(size_t) this.field; + } +} + +@nogc nothrow pure @safe unittest +{ + enum E : int + { + one = 1, + } + + // Casts to a boolean + assert(BitFlags!E(E.one)); + assert(!BitFlags!E()); + + // Assigns to and compares with a single value + { + BitFlags!E bitFlags; + bitFlags = E.one; + assert(bitFlags == E.one); + } + // Assigns to and compares with the same type + { + auto bitFlags1 = BitFlags!E(E.one); + BitFlags!E bitFlags2; + bitFlags2 = bitFlags1; + assert(bitFlags1 == bitFlags2); + } + assert((BitFlags!E() | E.one) == BitFlags!E(E.one)); + assert((BitFlags!E() | BitFlags!E(E.one)) == BitFlags!E(E.one)); + + assert(!(BitFlags!E() & BitFlags!E(E.one))); + + assert(!(BitFlags!E(E.one) ^ E.one)); + assert(BitFlags!E() ^ BitFlags!E(E.one)); + + assert(~BitFlags!E()); + + assert(BitFlags!E().toHash() == 0); + assert(BitFlags!E(E.one).toHash() != 0); + + // opBinaryRight is allowed + static assert(is(typeof({ E.one | BitFlags!E(); }))); +} + +/** + * Creates a $(D_PSYMBOL BitFlags) object initialized with $(D_PARAM field). + * + * Params: + * E = Some $(D_KEYWORD enum). + * field = Bits to be set. + */ +BitFlags!E bitFlags(E)(E field) +if (isBitFlagEnum!E) +{ + return BitFlags!E(field); +} + +/// +@nogc nothrow pure @safe unittest +{ + enum E + { + one = 1 << 0, + two = 1 << 1, + three = 1 << 2, + } + // Construct with E.one and E.two set + auto flags = bitFlags(E.one | E.two); + + // Test wheter E.one is set + assert(flags & E.one); + + // Toggle E.one + flags ^= E.one; + assert(!(flags & E.one)); + + // Set E.three + flags |= E.three; + assert(flags & E.three); + + // Clear E.three + flags &= ~E.three; + assert(!(flags & E.three)); +}