summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--README.md2
-rw-r--r--source/tanya/container/buffer.d1264
-rw-r--r--source/tanya/container/entry.d10
-rw-r--r--source/tanya/container/list.d1209
-rw-r--r--source/tanya/container/package.d4
-rw-r--r--source/tanya/container/queue.d506
-rw-r--r--source/tanya/container/vector.d3128
-rw-r--r--source/tanya/math/mp.d2267
-rw-r--r--source/tanya/math/package.d226
-rw-r--r--source/tanya/math/random.d532
-rw-r--r--source/tanya/memory/allocator.d90
-rw-r--r--source/tanya/memory/mallocator.d185
-rw-r--r--source/tanya/memory/package.d389
-rw-r--r--source/tanya/memory/types.d766
-rw-r--r--source/tanya/network/inet.d356
-rw-r--r--source/tanya/network/package.d17
-rw-r--r--source/tanya/network/socket.d2564
-rw-r--r--source/tanya/network/url.d1024
19 files changed, 7910 insertions, 6631 deletions
diff --git a/.travis.yml b/.travis.yml
index d2c2f90..e307d73 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ os:
language: d
d:
- - dmd-2.073.0
+ - dmd-2.073.2
- dmd-2.072.2
- dmd-2.071.2
- dmd-2.070.2
diff --git a/README.md b/README.md
index ffb554c..33f95d5 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ helper functions).
### Supported compilers
-* dmd 2.073.0
+* dmd 2.073.2
* dmd 2.072.2
* dmd 2.071.2
* dmd 2.070.2
diff --git a/source/tanya/container/buffer.d b/source/tanya/container/buffer.d
index 4d01ad5..9dde930 100644
--- a/source/tanya/container/buffer.d
+++ b/source/tanya/container/buffer.d
@@ -7,7 +7,7 @@
* 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)
- */
+ */
module tanya.container.buffer;
import std.traits;
@@ -15,23 +15,23 @@ import tanya.memory;
version (unittest)
{
- private int fillBuffer(ubyte[] buffer,
- in size_t size,
- int start = 0,
- int end = 10) @nogc pure nothrow
- in
- {
- assert(start < end);
- }
- body
- {
- auto numberRead = end - start;
- for (ubyte i; i < numberRead; ++i)
- {
- buffer[i] = cast(ubyte) (start + i);
- }
- return numberRead;
- }
+ private int fillBuffer(ubyte[] buffer,
+ in size_t size,
+ int start = 0,
+ int end = 10) @nogc pure nothrow
+ in
+ {
+ assert(start < end);
+ }
+ body
+ {
+ auto numberRead = end - start;
+ for (ubyte i; i < numberRead; ++i)
+ {
+ buffer[i] = cast(ubyte) (start + i);
+ }
+ return numberRead;
+ }
}
/**
@@ -45,253 +45,253 @@ version (unittest)
* of the pended asynchronous call.
*
* Params:
- * T = Buffer type.
+ * T = Buffer type.
*/
struct ReadBuffer(T = ubyte)
- if (isScalarType!T)
+ if (isScalarType!T)
{
- /// Internal buffer.
- private T[] buffer_;
-
- /// Filled buffer length.
- private size_t length_;
-
- /// Start of available data.
- private size_t start;
-
- /// Last position returned with $(D_KEYWORD []).
- private size_t ring;
-
- /// Available space.
- private immutable size_t minAvailable = 1024;
-
- /// Size by which the buffer will grow.
- private immutable size_t blockSize = 8192;
-
- invariant
- {
- assert(length_ <= buffer_.length);
- assert(blockSize > 0);
- assert(minAvailable > 0);
- }
-
- /**
- * Creates a new read buffer.
- *
- * Params:
- * size = Initial buffer size and the size by which the buffer
- * will grow.
- * minAvailable = minimal size should be always available to fill.
- * So it will reallocate if $(D_INLINECODE
- * $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
- * allocator = Allocator.
- */
- this(in size_t size,
- in size_t minAvailable = 1024,
- shared Allocator allocator = defaultAllocator) @trusted
- {
- this(allocator);
- this.minAvailable = minAvailable;
- this.blockSize = size;
- buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
- }
-
- /// Ditto.
- this(shared Allocator allocator)
- in
- {
- assert(allocator_ is null);
- }
- body
- {
- allocator_ = allocator;
- }
-
- /**
- * Deallocates the internal buffer.
- */
- ~this() @trusted
- {
- allocator.deallocate(buffer_);
- }
-
- ///
- unittest
- {
- ReadBuffer!ubyte b;
- assert(b.capacity == 0);
- assert(b.length == 0);
- }
-
- /**
- * Returns: The size of the internal buffer.
- */
- @property size_t capacity() const
- {
- return buffer_.length;
- }
-
- /**
- * Returns: Data size.
- */
- @property size_t length() const
- {
- return length_ - start;
- }
-
- /// Ditto.
- alias opDollar = length;
-
- /**
- * Clears the buffer.
- *
- * Returns: $(D_KEYWORD this).
- */
- void clear()
- {
- start = length_ = ring;
- }
-
- /**
- * Returns: Available space.
- */
- @property size_t free() const
- {
- return length > ring ? capacity - length : capacity - ring;
- }
-
- ///
- unittest
- {
- ReadBuffer!ubyte b;
- size_t numberRead;
-
- assert(b.free == 0);
-
- // Fills the buffer with values 0..10
- numberRead = fillBuffer(b[], b.free, 0, 10);
- b += numberRead;
- assert(b.free == b.blockSize - numberRead);
- b.clear();
- assert(b.free == b.blockSize);
- }
-
- /**
- * Appends some data to the buffer.
- *
- * Params:
- * length = Number of the bytes read.
- *
- * Returns: $(D_KEYWORD this).
- */
- ref ReadBuffer opOpAssign(string op)(in size_t length)
- if (op == "+")
- {
- length_ += length;
- ring = start;
- return this;
- }
-
- ///
- unittest
- {
- ReadBuffer!ubyte b;
- size_t numberRead;
- ubyte[] result;
-
- // Fills the buffer with values 0..10
- numberRead = fillBuffer(b[], b.free, 0, 10);
- b += numberRead;
-
- result = b[0 .. $];
- assert(result[0] == 0);
- assert(result[1] == 1);
- assert(result[9] == 9);
- b.clear();
-
- // It shouldn't overwrite, but append another 5 bytes to the buffer
- numberRead = fillBuffer(b[], b.free, 0, 10);
- b += numberRead;
-
- numberRead = fillBuffer(b[], b.free, 20, 25);
- b += numberRead;
-
- result = b[0..$];
- assert(result[0] == 0);
- assert(result[1] == 1);
- assert(result[9] == 9);
- assert(result[10] == 20);
- assert(result[14] == 24);
- }
-
- /**
- * Params:
- * start = Start position.
- * end = End position.
- *
- * Returns: Array between $(D_PARAM start) and $(D_PARAM end).
- */
- T[] opSlice(in size_t start, in size_t end)
- {
- return buffer_[this.start + start .. this.start + end];
- }
-
- /**
- * Returns a free chunk of the buffer.
- *
- * Add ($(D_KEYWORD +=)) the number of the read bytes after using it.
- *
- * Returns: A free chunk of the buffer.
- */
- T[] opIndex()
- {
- if (start > 0)
- {
- auto ret = buffer_[0 .. start];
- ring = 0;
- return ret;
- }
- else
- {
- if (capacity - length < minAvailable)
- {
- void[] buf = buffer_;
- immutable cap = capacity;
- () @trusted {
- allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
- buffer_ = cast(T[]) buf;
- }();
- }
- ring = length_;
- return buffer_[length_ .. $];
- }
- }
-
- ///
- unittest
- {
- ReadBuffer!ubyte b;
- size_t numberRead;
- ubyte[] result;
-
- // Fills the buffer with values 0..10
- numberRead = fillBuffer(b[], b.free, 0, 10);
- b += numberRead;
-
- assert(b.length == 10);
- result = b[0 .. $];
- assert(result[0] == 0);
- assert(result[9] == 9);
- b.clear();
- assert(b.length == 0);
- }
-
- mixin DefaultAllocator;
+ /// Internal buffer.
+ private T[] buffer_;
+
+ /// Filled buffer length.
+ private size_t length_;
+
+ /// Start of available data.
+ private size_t start;
+
+ /// Last position returned with $(D_KEYWORD []).
+ private size_t ring;
+
+ /// Available space.
+ private immutable size_t minAvailable = 1024;
+
+ /// Size by which the buffer will grow.
+ private immutable size_t blockSize = 8192;
+
+ invariant
+ {
+ assert(length_ <= buffer_.length);
+ assert(blockSize > 0);
+ assert(minAvailable > 0);
+ }
+
+ /**
+ * Creates a new read buffer.
+ *
+ * Params:
+ * size = Initial buffer size and the size by which the buffer
+ * will grow.
+ * minAvailable = minimal size should be always available to fill.
+ * So it will reallocate if $(D_INLINECODE
+ * $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
+ * allocator = Allocator.
+ */
+ this(in size_t size,
+ in size_t minAvailable = 1024,
+ shared Allocator allocator = defaultAllocator) @trusted
+ {
+ this(allocator);
+ this.minAvailable = minAvailable;
+ this.blockSize = size;
+ buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator)
+ in
+ {
+ assert(allocator_ is null);
+ }
+ body
+ {
+ allocator_ = allocator;
+ }
+
+ /**
+ * Deallocates the internal buffer.
+ */
+ ~this() @trusted
+ {
+ allocator.deallocate(buffer_);
+ }
+
+ ///
+ unittest
+ {
+ ReadBuffer!ubyte b;
+ assert(b.capacity == 0);
+ assert(b.length == 0);
+ }
+
+ /**
+ * Returns: The size of the internal buffer.
+ */
+ @property size_t capacity() const
+ {
+ return buffer_.length;
+ }
+
+ /**
+ * Returns: Data size.
+ */
+ @property size_t length() const
+ {
+ return length_ - start;
+ }
+
+ /// Ditto.
+ alias opDollar = length;
+
+ /**
+ * Clears the buffer.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ void clear()
+ {
+ start = length_ = ring;
+ }
+
+ /**
+ * Returns: Available space.
+ */
+ @property size_t free() const
+ {
+ return length > ring ? capacity - length : capacity - ring;
+ }
+
+ ///
+ unittest
+ {
+ ReadBuffer!ubyte b;
+ size_t numberRead;
+
+ assert(b.free == 0);
+
+ // Fills the buffer with values 0..10
+ numberRead = fillBuffer(b[], b.free, 0, 10);
+ b += numberRead;
+ assert(b.free == b.blockSize - numberRead);
+ b.clear();
+ assert(b.free == b.blockSize);
+ }
+
+ /**
+ * Appends some data to the buffer.
+ *
+ * Params:
+ * length = Number of the bytes read.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref ReadBuffer opOpAssign(string op)(in size_t length)
+ if (op == "+")
+ {
+ length_ += length;
+ ring = start;
+ return this;
+ }
+
+ ///
+ unittest
+ {
+ ReadBuffer!ubyte b;
+ size_t numberRead;
+ ubyte[] result;
+
+ // Fills the buffer with values 0..10
+ numberRead = fillBuffer(b[], b.free, 0, 10);
+ b += numberRead;
+
+ result = b[0 .. $];
+ assert(result[0] == 0);
+ assert(result[1] == 1);
+ assert(result[9] == 9);
+ b.clear();
+
+ // It shouldn't overwrite, but append another 5 bytes to the buffer
+ numberRead = fillBuffer(b[], b.free, 0, 10);
+ b += numberRead;
+
+ numberRead = fillBuffer(b[], b.free, 20, 25);
+ b += numberRead;
+
+ result = b[0..$];
+ assert(result[0] == 0);
+ assert(result[1] == 1);
+ assert(result[9] == 9);
+ assert(result[10] == 20);
+ assert(result[14] == 24);
+ }
+
+ /**
+ * Params:
+ * start = Start position.
+ * end = End position.
+ *
+ * Returns: Array between $(D_PARAM start) and $(D_PARAM end).
+ */
+ T[] opSlice(in size_t start, in size_t end)
+ {
+ return buffer_[this.start + start .. this.start + end];
+ }
+
+ /**
+ * Returns a free chunk of the buffer.
+ *
+ * Add ($(D_KEYWORD +=)) the number of the read bytes after using it.
+ *
+ * Returns: A free chunk of the buffer.
+ */
+ T[] opIndex()
+ {
+ if (start > 0)
+ {
+ auto ret = buffer_[0 .. start];
+ ring = 0;
+ return ret;
+ }
+ else
+ {
+ if (capacity - length < minAvailable)
+ {
+ void[] buf = buffer_;
+ immutable cap = capacity;
+ () @trusted {
+ allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
+ buffer_ = cast(T[]) buf;
+ }();
+ }
+ ring = length_;
+ return buffer_[length_ .. $];
+ }
+ }
+
+ ///
+ unittest
+ {
+ ReadBuffer!ubyte b;
+ size_t numberRead;
+ ubyte[] result;
+
+ // Fills the buffer with values 0..10
+ numberRead = fillBuffer(b[], b.free, 0, 10);
+ b += numberRead;
+
+ assert(b.length == 10);
+ result = b[0 .. $];
+ assert(result[0] == 0);
+ assert(result[9] == 9);
+ b.clear();
+ assert(b.length == 0);
+ }
+
+ mixin DefaultAllocator;
}
private unittest
{
- static assert(is(ReadBuffer!int));
+ static assert(is(ReadBuffer!int));
}
/**
@@ -304,385 +304,385 @@ private unittest
* reading, because it may allocate and move elements.
*
* Params:
- * T = Buffer type.
+ * T = Buffer type.
*/
struct WriteBuffer(T = ubyte)
- if (isScalarType!T)
+ if (isScalarType!T)
{
- /// Internal buffer.
- private T[] buffer_;
-
- /// Buffer start position.
- private size_t start;
-
- /// Buffer ring area size. After this position begins buffer overflow area.
- private size_t ring;
-
- /// Size by which the buffer will grow.
- private immutable size_t blockSize;
-
- /// The position of the free area in the buffer.
- private size_t position;
-
- invariant
- {
- assert(blockSize > 0);
- // Position can refer to an element outside the buffer if the buffer is full.
- assert(position <= buffer_.length);
- }
-
- /**
- * Params:
- * size = Initial buffer size and the size by which the buffer will
- * grow.
- * allocator = Allocator.
- *
- * Precondition: $(D_INLINECODE size > 0 && allocator !is null)
- */
- this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
- in
- {
- assert(size > 0);
- assert(allocator !is null);
- }
- body
- {
- blockSize = size;
- ring = size - 1;
- allocator_ = allocator;
- buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
- }
-
- @disable this();
-
- /**
- * Deallocates the internal buffer.
- */
- ~this()
- {
- allocator.deallocate(buffer_);
- }
-
- /**
- * Returns: The size of the internal buffer.
- */
- @property size_t capacity() const
- {
- return buffer_.length;
- }
-
- /**
- * Note that $(D_PSYMBOL length) doesn't return the real length of the data,
- * but only the array length that will be returned with $(D_PSYMBOL opIndex)
- * next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
- * until $(D_PSYMBOL length) returns 0.
- *
- * Returns: Data size.
- */
- @property size_t length() const
- {
- if (position > ring || position < start) // Buffer overflowed
- {
- return ring - start + 1;
- }
- else
- {
- return position - start;
- }
- }
-
- /// Ditto.
- alias opDollar = length;
-
- ///
- unittest
- {
- auto b = WriteBuffer!ubyte(4);
- ubyte[3] buf = [48, 23, 255];
-
- b ~= buf;
- assert(b.length == 3);
- b += 2;
- assert(b.length == 1);
-
- b ~= buf;
- assert(b.length == 2);
- b += 2;
- assert(b.length == 2);
-
- b ~= buf;
- assert(b.length == 5);
- b += b.length;
- assert(b.length == 0);
- }
-
- /**
- * Returns: Available space.
- */
- @property size_t free() const
- {
- return capacity - length;
- }
-
- /**
- * Appends data to the buffer.
- *
- * Params:
- * buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
- */
- ref WriteBuffer opOpAssign(string op)(in T[] buffer)
- if (op == "~")
- {
- size_t end, start;
-
- if (position >= this.start && position <= ring)
- {
- auto afterRing = ring + 1;
-
- end = position + buffer.length;
- if (end > afterRing)
- {
- end = afterRing;
- }
- start = end - position;
- buffer_[position .. end] = buffer[0 .. start];
- if (end == afterRing)
- {
- position = this.start == 0 ? afterRing : 0;
- }
- else
- {
- position = end;
- }
- }
-
- // Check if we have some free space at the beginning
- if (start < buffer.length && position < this.start)
- {
- end = position + buffer.length - start;
- if (end > this.start)
- {
- end = this.start;
- }
- auto areaEnd = end - position + start;
- buffer_[position .. end] = buffer[start .. areaEnd];
- position = end == this.start ? ring + 1 : end - position;
- start = areaEnd;
- }
-
- // And if we still haven't found any place, save the rest in the overflow area
- if (start < buffer.length)
- {
- end = position + buffer.length - start;
- if (end > capacity)
- {
- auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
- () @trusted {
- void[] buf = buffer_;
- allocator.reallocate(buf, newSize);
- buffer_ = cast(T[]) buf;
- }();
- }
- buffer_[position .. end] = buffer[start .. $];
- position = end;
- if (this.start == 0)
- {
- ring = capacity - 1;
- }
- }
-
- return this;
- }
-
- ///
- unittest
- {
- auto b = WriteBuffer!ubyte(4);
- ubyte[3] buf = [48, 23, 255];
-
- b ~= buf;
- assert(b.capacity == 4);
- assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
-
- b += 2;
- b ~= buf;
- assert(b.capacity == 4);
- assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
- && b.buffer_[2] == 255 && b.buffer_[3] == 48);
-
- b += 2;
- b ~= buf;
- assert(b.capacity == 8);
- assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
- && b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
- }
-
- ///
- unittest
- {
- auto b = WriteBuffer!ubyte(2);
- ubyte[3] buf = [48, 23, 255];
-
- b ~= buf;
- assert(b.start == 0);
- assert(b.capacity == 4);
- assert(b.ring == 3);
- assert(b.position == 3);
- }
-
- /**
- * Sets how many bytes were written. It will shrink the buffer
- * appropriately. Always call it after $(D_PSYMBOL opIndex).
- *
- * Params:
- * length = Length of the written data.
- *
- * Returns: $(D_KEYWORD this).
- */
- ref WriteBuffer opOpAssign(string op)(in size_t length)
- if (op == "+")
- in
- {
- assert(length <= this.length);
- }
- body
- {
- auto afterRing = ring + 1;
- auto oldStart = start;
-
- if (length <= 0)
- {
- return this;
- }
- else if (position <= afterRing)
- {
- start += length;
- if (start > 0 && position == afterRing)
- {
- position = oldStart;
- }
- }
- else
- {
- auto overflow = position - afterRing;
-
- if (overflow > length)
- {
- immutable afterLength = afterRing + length;
- buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
- buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
- position -= length;
- }
- else if (overflow == length)
- {
- buffer_[start .. start + overflow] = buffer_[afterRing .. position];
- position -= overflow;
- }
- else
- {
- buffer_[start .. start + overflow] = buffer_[afterRing .. position];
- position = overflow;
- }
- start += length;
-
- if (start == position)
- {
- if (position != afterRing)
- {
- position = 0;
- }
- start = 0;
- ring = capacity - 1;
- }
- }
- if (start > ring)
- {
- start = 0;
- }
- return this;
- }
-
- ///
- unittest
- {
- auto b = WriteBuffer!ubyte(6);
- ubyte[6] buf = [23, 23, 255, 128, 127, 9];
-
- b ~= buf;
- assert(b.length == 6);
- b += 2;
- assert(b.length == 4);
- b += 4;
- assert(b.length == 0);
- }
-
- /**
- * Returns a chunk with data.
- *
- * After calling it, set $(D_KEYWORD +=) to the length could be
- * written.
- *
- * $(D_PSYMBOL opIndex) may return only part of the data. You may need
- * to call it and set $(D_KEYWORD +=) several times until
- * $(D_PSYMBOL length) is 0. If all the data can be written,
- * maximally 3 calls are required.
- *
- * Returns: A chunk of data buffer.
- */
- T[] opSlice(in size_t start, in size_t end)
- {
- immutable internStart = this.start + start;
-
- if (position > ring || position < start) // Buffer overflowed
- {
- return buffer_[this.start .. ring + 1 - length + end];
- }
- else
- {
- return buffer_[this.start .. this.start + end];
- }
- }
-
- ///
- unittest
- {
- auto b = WriteBuffer!ubyte(6);
- ubyte[6] buf = [23, 23, 255, 128, 127, 9];
-
- b ~= buf;
- assert(b[0 .. $] == buf[0 .. 6]);
- b += 2;
-
- assert(b[0 .. $] == buf[2 .. 6]);
-
- b ~= buf;
- assert(b[0 .. $] == buf[2 .. 6]);
- b += b.length;
-
- assert(b[0 .. $] == buf[0 .. 6]);
- b += b.length;
- }
-
- /**
- * After calling it, set $(D_KEYWORD +=) to the length could be
- * written.
- *
- * $(D_PSYMBOL opIndex) may return only part of the data. You may need
- * to call it and set $(D_KEYWORD +=) several times until
- * $(D_PSYMBOL length) is 0. If all the data can be written,
- * maximally 3 calls are required.
- *
- * Returns: A chunk of data buffer.
- */
- T[] opIndex()
- {
- return opSlice(0, length);
- }
-
- mixin DefaultAllocator;
+ /// Internal buffer.
+ private T[] buffer_;
+
+ /// Buffer start position.
+ private size_t start;
+
+ /// Buffer ring area size. After this position begins buffer overflow area.
+ private size_t ring;
+
+ /// Size by which the buffer will grow.
+ private immutable size_t blockSize;
+
+ /// The position of the free area in the buffer.
+ private size_t position;
+
+ invariant
+ {
+ assert(blockSize > 0);
+ // Position can refer to an element outside the buffer if the buffer is full.
+ assert(position <= buffer_.length);
+ }
+
+ /**
+ * Params:
+ * size = Initial buffer size and the size by which the buffer will
+ * grow.
+ * allocator = Allocator.
+ *
+ * Precondition: $(D_INLINECODE size > 0 && allocator !is null)
+ */
+ this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
+ in
+ {
+ assert(size > 0);
+ assert(allocator !is null);
+ }
+ body
+ {
+ blockSize = size;
+ ring = size - 1;
+ allocator_ = allocator;
+ buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
+ }
+
+ @disable this();
+
+ /**
+ * Deallocates the internal buffer.
+ */
+ ~this()
+ {
+ allocator.deallocate(buffer_);
+ }
+
+ /**
+ * Returns: The size of the internal buffer.
+ */
+ @property size_t capacity() const
+ {
+ return buffer_.length;
+ }
+
+ /**
+ * Note that $(D_PSYMBOL length) doesn't return the real length of the data,
+ * but only the array length that will be returned with $(D_PSYMBOL opIndex)
+ * next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
+ * until $(D_PSYMBOL length) returns 0.
+ *
+ * Returns: Data size.
+ */
+ @property size_t length() const
+ {
+ if (position > ring || position < start) // Buffer overflowed
+ {
+ return ring - start + 1;
+ }
+ else
+ {
+ return position - start;
+ }
+ }
+
+ /// Ditto.
+ alias opDollar = length;
+
+ ///
+ unittest
+ {
+ auto b = WriteBuffer!ubyte(4);
+ ubyte[3] buf = [48, 23, 255];
+
+ b ~= buf;
+ assert(b.length == 3);
+ b += 2;
+ assert(b.length == 1);
+
+ b ~= buf;
+ assert(b.length == 2);
+ b += 2;
+ assert(b.length == 2);
+
+ b ~= buf;
+ assert(b.length == 5);
+ b += b.length;
+ assert(b.length == 0);
+ }
+
+ /**
+ * Returns: Available space.
+ */
+ @property size_t free() const
+ {
+ return capacity - length;
+ }
+
+ /**
+ * Appends data to the buffer.
+ *
+ * Params:
+ * buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
+ */
+ ref WriteBuffer opOpAssign(string op)(in T[] buffer)
+ if (op == "~")
+ {
+ size_t end, start;
+
+ if (position >= this.start && position <= ring)
+ {
+ auto afterRing = ring + 1;
+
+ end = position + buffer.length;
+ if (end > afterRing)
+ {
+ end = afterRing;
+ }
+ start = end - position;
+ buffer_[position .. end] = buffer[0 .. start];
+ if (end == afterRing)
+ {
+ position = this.start == 0 ? afterRing : 0;
+ }
+ else
+ {
+ position = end;
+ }
+ }
+
+ // Check if we have some free space at the beginning
+ if (start < buffer.length && position < this.start)
+ {
+ end = position + buffer.length - start;
+ if (end > this.start)
+ {
+ end = this.start;
+ }
+ auto areaEnd = end - position + start;
+ buffer_[position .. end] = buffer[start .. areaEnd];
+ position = end == this.start ? ring + 1 : end - position;
+ start = areaEnd;
+ }
+
+ // And if we still haven't found any place, save the rest in the overflow area
+ if (start < buffer.length)
+ {
+ end = position + buffer.length - start;
+ if (end > capacity)
+ {
+ auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
+ () @trusted {
+ void[] buf = buffer_;
+ allocator.reallocate(buf, newSize);
+ buffer_ = cast(T[]) buf;
+ }();
+ }
+ buffer_[position .. end] = buffer[start .. $];
+ position = end;
+ if (this.start == 0)
+ {
+ ring = capacity - 1;
+ }
+ }
+
+ return this;
+ }
+
+ ///
+ unittest
+ {
+ auto b = WriteBuffer!ubyte(4);
+ ubyte[3] buf = [48, 23, 255];
+
+ b ~= buf;
+ assert(b.capacity == 4);
+ assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
+
+ b += 2;
+ b ~= buf;
+ assert(b.capacity == 4);
+ assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
+ && b.buffer_[2] == 255 && b.buffer_[3] == 48);
+
+ b += 2;
+ b ~= buf;
+ assert(b.capacity == 8);
+ assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
+ && b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
+ }
+
+ ///
+ unittest
+ {
+ auto b = WriteBuffer!ubyte(2);
+ ubyte[3] buf = [48, 23, 255];
+
+ b ~= buf;
+ assert(b.start == 0);
+ assert(b.capacity == 4);
+ assert(b.ring == 3);
+ assert(b.position == 3);
+ }
+
+ /**
+ * Sets how many bytes were written. It will shrink the buffer
+ * appropriately. Always call it after $(D_PSYMBOL opIndex).
+ *
+ * Params:
+ * length = Length of the written data.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref WriteBuffer opOpAssign(string op)(in size_t length)
+ if (op == "+")
+ in
+ {
+ assert(length <= this.length);
+ }
+ body
+ {
+ auto afterRing = ring + 1;
+ auto oldStart = start;
+
+ if (length <= 0)
+ {
+ return this;
+ }
+ else if (position <= afterRing)
+ {
+ start += length;
+ if (start > 0 && position == afterRing)
+ {
+ position = oldStart;
+ }
+ }
+ else
+ {
+ auto overflow = position - afterRing;
+
+ if (overflow > length)
+ {
+ immutable afterLength = afterRing + length;
+ buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
+ buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
+ position -= length;
+ }
+ else if (overflow == length)
+ {
+ buffer_[start .. start + overflow] = buffer_[afterRing .. position];
+ position -= overflow;
+ }
+ else
+ {
+ buffer_[start .. start + overflow] = buffer_[afterRing .. position];
+ position = overflow;
+ }
+ start += length;
+
+ if (start == position)
+ {
+ if (position != afterRing)
+ {
+ position = 0;
+ }
+ start = 0;
+ ring = capacity - 1;
+ }
+ }
+ if (start > ring)
+ {
+ start = 0;
+ }
+ return this;
+ }
+
+ ///
+ unittest
+ {
+ auto b = WriteBuffer!ubyte(6);
+ ubyte[6] buf = [23, 23, 255, 128, 127, 9];
+
+ b ~= buf;
+ assert(b.length == 6);
+ b += 2;
+ assert(b.length == 4);
+ b += 4;
+ assert(b.length == 0);
+ }
+
+ /**
+ * Returns a chunk with data.
+ *
+ * After calling it, set $(D_KEYWORD +=) to the length could be
+ * written.
+ *
+ * $(D_PSYMBOL opIndex) may return only part of the data. You may need
+ * to call it and set $(D_KEYWORD +=) several times until
+ * $(D_PSYMBOL length) is 0. If all the data can be written,
+ * maximally 3 calls are required.
+ *
+ * Returns: A chunk of data buffer.
+ */
+ T[] opSlice(in size_t start, in size_t end)
+ {
+ immutable internStart = this.start + start;
+
+ if (position > ring || position < start) // Buffer overflowed
+ {
+ return buffer_[this.start .. ring + 1 - length + end];
+ }
+ else
+ {
+ return buffer_[this.start .. this.start + end];
+ }
+ }
+
+ ///
+ unittest
+ {
+ auto b = WriteBuffer!ubyte(6);
+ ubyte[6] buf = [23, 23, 255, 128, 127, 9];
+
+ b ~= buf;
+ assert(b[0 .. $] == buf[0 .. 6]);
+ b += 2;
+
+ assert(b[0 .. $] == buf[2 .. 6]);
+
+ b ~= buf;
+ assert(b[0 .. $] == buf[2 .. 6]);
+ b += b.length;
+
+ assert(b[0 .. $] == buf[0 .. 6]);
+ b += b.length;
+ }
+
+ /**
+ * After calling it, set $(D_KEYWORD +=) to the length could be
+ * written.
+ *
+ * $(D_PSYMBOL opIndex) may return only part of the data. You may need
+ * to call it and set $(D_KEYWORD +=) several times until
+ * $(D_PSYMBOL length) is 0. If all the data can be written,
+ * maximally 3 calls are required.
+ *
+ * Returns: A chunk of data buffer.
+ */
+ T[] opIndex()
+ {
+ return opSlice(0, length);
+ }
+
+ mixin DefaultAllocator;
}
private unittest
{
- static assert(is(typeof(WriteBuffer!int(5))));
+ static assert(is(typeof(WriteBuffer!int(5))));
}
diff --git a/source/tanya/container/entry.d b/source/tanya/container/entry.d
index 1194f6b..cc74204 100644
--- a/source/tanya/container/entry.d
+++ b/source/tanya/container/entry.d
@@ -9,14 +9,14 @@
* 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)
- */
+ */
module tanya.container.entry;
package struct SEntry(T)
{
- /// Item content.
- T content;
+ /// Item content.
+ T content;
- /// Next item.
- SEntry* next;
+ /// Next item.
+ SEntry* next;
}
diff --git a/source/tanya/container/list.d b/source/tanya/container/list.d
index 6405baa..adbd4fd 100644
--- a/source/tanya/container/list.d
+++ b/source/tanya/container/list.d
@@ -3,414 +3,861 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
+ * Linked list.
+ *
* Copyright: Eugene Wissner 2016-2017.
* 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)
- */
+ */
module tanya.container.list;
import std.algorithm.comparison;
+import std.algorithm.mutation;
import std.algorithm.searching;
+import std.range.primitives;
import std.traits;
import tanya.container.entry;
import tanya.memory;
-private struct Range(E)
- if (__traits(isSame, TemplateOf!E, SEntry))
+/**
+ * Forward range for the $(D_PSYMBOL SList).
+ *
+ * Params:
+ * E = Element type.
+ */
+struct SRange(E)
{
- private alias T = typeof(E.content);
-
- private E* head;
-
- private this(E* head)
- {
- this.head = head;
- }
-
- @property Range save()
- {
- return this;
- }
-
- @property size_t length() const
- {
- return count(opIndex());
- }
-
- @property bool empty() const
- {
- return head is null;
- }
-
- @property ref inout(T) front() inout
- in
- {
- assert(!empty);
- }
- body
- {
- return head.content;
- }
-
- void popFront()
- in
- {
- assert(!empty);
- }
- body
- {
- head = head.next;
- }
-
- Range opIndex()
- {
- return typeof(return)(head);
- }
-
- Range!(const E) opIndex() const
- {
- return typeof(return)(head);
- }
+ private alias EntryPointer = CopyConstness!(E, SEntry!(Unqual!E)*);
+
+ private EntryPointer* head;
+
+ invariant
+ {
+ assert(head !is null);
+ }
+
+ private this(ref EntryPointer head) @trusted
+ {
+ this.head = &head;
+ }
+
+ @disable this();
+
+ @property SRange save()
+ {
+ return this;
+ }
+
+ @property size_t length() const
+ {
+ return count(opIndex());
+ }
+
+ @property bool empty() const
+ {
+ return *head is null;
+ }
+
+ @property ref inout(E) front() inout
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return (*head).content;
+ }
+
+ void popFront() @trusted
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ head = &(*head).next;
+ }
+
+ SRange opIndex()
+ {
+ return typeof(return)(*head);
+ }
+
+ SRange!(const E) opIndex() const
+ {
+ return typeof(return)(*head);
+ }
}
/**
* Singly-linked list.
*
* Params:
- * T = Content type.
+ * T = Content type.
*/
struct SList(T)
{
- private alias Entry = SEntry!T;
-
- // 0th element of the list.
- private Entry* head;
-
- /**
- * Removes all elements from the list.
- */
- ~this()
- {
- clear();
- }
-
- /**
- * Removes all contents from the list.
- */
- void clear()
- {
- while (!empty)
- {
- removeFront();
- }
- }
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(8);
- l.insertFront(5);
- l.clear();
- assert(l.empty);
- }
-
- /**
- * Returns: First element.
- */
- @property ref inout(T) front() inout
- in
- {
- assert(!empty);
- }
- body
- {
- return head.content;
- }
-
- /**
- * Inserts a new element at the beginning.
- *
- * Params:
- * x = New element.
- */
- void insertFront(ref T x)
- {
- auto temp = allocator.make!Entry;
-
- temp.content = x;
- temp.next = head;
- head = temp;
- }
-
- /// Ditto.
- void insertFront(T x)
- {
- insertFront(x);
- }
-
- /// Ditto.
- alias insert = insertFront;
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(8);
- assert(l.front == 8);
- l.insertFront(9);
- assert(l.front == 9);
- }
-
- /**
- * Returns: How many elements are in the list.
- */
- @property size_t length() const
- {
- return count(opIndex());
- }
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(8);
- l.insertFront(9);
- assert(l.length == 2);
- l.removeFront();
- assert(l.length == 1);
- l.removeFront();
- assert(l.length == 0);
- }
-
- /**
- * Comparison for equality.
- *
- * Params:
- * that = The list to compare with.
- *
- * Returns: $(D_KEYWORD true) if the lists are equal, $(D_KEYWORD false)
- * otherwise.
- */
- bool opEquals()(auto ref typeof(this) that) @trusted
- {
- return equal(opIndex(), that[]);
- }
-
- /// Ditto.
- bool opEquals()(in auto ref typeof(this) that) const @trusted
- {
- return equal(opIndex(), that[]);
- }
-
- ///
- unittest
- {
- SList!int l1, l2;
-
- l1.insertFront(8);
- l1.insertFront(9);
- l2.insertFront(8);
- l2.insertFront(10);
- assert(l1 != l2);
-
- l1.removeFront();
- assert(l1 != l2);
-
- l2.removeFront();
- assert(l1 == l2);
-
- l1.removeFront();
- assert(l1 != l2);
-
- l2.removeFront();
- assert(l1 == l2);
- }
-
- /**
- * Returns: $(D_KEYWORD true) if the list is empty.
- */
- @property bool empty() const
- {
- return head is null;
- }
-
- /**
- * Returns the first element and moves to the next one.
- *
- * Returns: The first element.
- *
- * Precondition: $(D_INLINECODE !empty)
- */
- void removeFront()
- in
- {
- assert(!empty);
- }
- body
- {
- auto n = head.next;
-
- allocator.dispose(head);
- head = n;
- }
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(8);
- l.insertFront(9);
- assert(l.front == 9);
- l.removeFront();
- assert(l.front == 8);
- l.removeFront();
- assert(l.empty);
- }
-
- /**
- * Removes $(D_PARAM howMany) elements from the list.
- *
- * Unlike $(D_PSYMBOL removeFront()), this method doesn't fail, if it could not
- * remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
- * greater than the list length, all elements are removed.
- *
- * Params:
- * howMany = How many elements should be removed.
- *
- * Returns: The number of elements removed.
- */
- size_t removeFront(in size_t howMany)
- out (removed)
- {
- assert(removed <= howMany);
- }
- body
- {
- size_t i;
- for (; i < howMany && !empty; ++i)
- {
- removeFront();
- }
- return i;
- }
-
- /// Ditto.
- alias remove = removeFront;
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(8);
- l.insertFront(5);
- l.insertFront(4);
- assert(l.removeFront(0) == 0);
- assert(l.removeFront(2) == 2);
- assert(l.removeFront(3) == 1);
- assert(l.removeFront(3) == 0);
- }
-
- /**
- * $(D_KEYWORD foreach) iteration.
- *
- * Params:
- * dg = $(D_KEYWORD foreach) body.
- *
- * Returns: The value returned from $(D_PARAM dg).
- */
- int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
- {
- int result;
- size_t i;
-
- for (auto pos = head; pos; pos = pos.next, ++i)
- {
- result = dg(i, pos.content);
-
- if (result != 0)
- {
- return result;
- }
- }
- return result;
- }
-
- /// Ditto.
- int opApply(scope int delegate(ref T) @nogc dg)
- {
- int result;
-
- for (auto pos = head; pos; pos = pos.next)
- {
- result = dg(pos.content);
-
- if (result != 0)
- {
- return result;
- }
- }
- return result;
- }
-
- ///
- unittest
- {
- SList!int l;
-
- l.insertFront(5);
- l.insertFront(4);
- l.insertFront(9);
- foreach (i, e; l)
- {
- assert(i != 0 || e == 9);
- assert(i != 1 || e == 4);
- assert(i != 2 || e == 5);
- }
- }
-
- Range!Entry opIndex()
- {
- return typeof(return)(head);
- }
-
- Range!(const Entry) opIndex() const
- {
- return typeof(return)(head);
- }
-
- mixin DefaultAllocator;
+ private alias Entry = SEntry!T;
+
+ // 0th element of the list.
+ private Entry* head;
+
+ /**
+ * Creates a new $(D_PSYMBOL SList) with the elements from a static array.
+ *
+ * Params:
+ * R = Static array size.
+ * init = Values to initialize the list with.
+ * allocator = Allocator.
+ */
+ this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator)
+ {
+ this(allocator);
+ insertFront(init[]);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l = SList!int([5, 8, 15]);
+ assert(l.front == 5);
+ }
+
+ /**
+ * Creates a new $(D_PSYMBOL SList) with the elements from an input range.
+ *
+ * Params:
+ * R = Type of the initial range.
+ * init = Values to initialize the list with.
+ * allocator = Allocator.
+ */
+ this(R)(R init, shared Allocator allocator = defaultAllocator)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ this(allocator);
+ insertFront(init);
+ }
+
+ /**
+ * Creates a new $(D_PSYMBOL SList).
+ *
+ * Params:
+ * len = Initial length of the list.
+ * init = Initial value to fill the list with.
+ * allocator = Allocator.
+ */
+ this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted
+ {
+ this(allocator);
+
+ Entry* next;
+ foreach (i; 0 .. len)
+ {
+ if (next is null)
+ {
+ next = allocator.make!Entry(init);
+ head = next;
+ }
+ else
+ {
+ next.next = allocator.make!Entry(init);
+ next = next.next;
+ }
+ }
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l = SList!int(2, 3);
+ assert(l.length == 2);
+ assert(l.front == 3);
+ }
+
+ /// Ditto.
+ this(const size_t len, shared Allocator allocator = defaultAllocator)
+ {
+ this(len, T.init, allocator);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l = SList!int(2);
+ assert(l.length == 2);
+ assert(l.front == 0);
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator)
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ this.allocator_ = allocator;
+ }
+
+ /**
+ * Initializes this list from another one.
+ *
+ * If $(D_PARAM init) is passed by value, it won't be copied, but moved.
+ * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator),
+ * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s
+ * storage, otherwise, the storage will be allocated with
+ * $(D_PARAM allocator) and all elements will be moved;
+ * $(D_PARAM init) will be destroyed at the end.
+ *
+ * If $(D_PARAM init) is passed by reference, it will be copied.
+ *
+ * Params:
+ * init = Source list.
+ * allocator = Allocator.
+ */
+ this(ref SList init, shared Allocator allocator = defaultAllocator)
+ {
+ this(init[], allocator);
+ }
+
+ /// Ditto.
+ this(SList init, shared Allocator allocator = defaultAllocator) @trusted
+ {
+ this(allocator);
+ if (allocator is init.allocator)
+ {
+ head = init.head;
+ init.head = null;
+ }
+ else
+ {
+ Entry* next;
+ for (auto current = init.head; current !is null; current = current.next)
+ {
+ if (head is null)
+ {
+ head = allocator.make!Entry(move(current.content));
+ next = head;
+ }
+ else
+ {
+ next.next = allocator.make!Entry(move(current.content));
+ next = next.next;
+ }
+ }
+ }
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 1, 234]);
+ auto l2 = SList!int(l1);
+ assert(l1 == l2);
+ }
+
+ /**
+ * Removes all elements from the list.
+ */
+ ~this()
+ {
+ clear();
+ }
+
+ /**
+ * Copies the list.
+ */
+ this(this)
+ {
+ auto list = typeof(this)(this[], this.allocator);
+ this.head = list.head;
+ list.head = null;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 1, 234]);
+ auto l2 = l1;
+ assert(l1 == l2);
+ }
+
+ /**
+ * Removes all contents from the list.
+ */
+ void clear()
+ {
+ while (!empty)
+ {
+ removeFront();
+ }
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l = SList!int([8, 5]);
+
+ assert(!l.empty);
+ l.clear();
+ assert(l.empty);
+ }
+
+ /**
+ * Returns: First element.
+ */
+ @property ref inout(T) front() inout
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return head.content;
+ }
+
+ private size_t moveEntry(R)(ref Entry* head, ref R el) @trusted
+ if (isImplicitlyConvertible!(R, T))
+ {
+ auto temp = cast(Entry*) allocator.allocate(Entry.sizeof);
+
+ el.moveEmplace(temp.content);
+ temp.next = head;
+
+ head = temp;
+ return 1;
+ }
+
+ /**
+ * Inserts a new element at the beginning.
+ *
+ * Params:
+ * R = Type of the inserted value(s).
+ * el = New element(s).
+ *
+ * Returns: The number of elements inserted.
+ */
+ size_t insertFront(R)(R el)
+ if (isImplicitlyConvertible!(R, T))
+ {
+ return moveEntry(head, el);
+ }
+
+ /// Ditto.
+ size_t insertFront(R)(ref R el) @trusted
+ if (isImplicitlyConvertible!(R, T))
+ {
+ head = allocator.make!Entry(el, head);
+ return 1;
+ }
+
+ /// Ditto.
+ size_t insertFront(R)(R el) @trusted
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ size_t retLength;
+ Entry* next, newHead;
+
+ if (!el.empty)
+ {
+ next = allocator.make!Entry(el.front);
+ newHead = next;
+ el.popFront();
+ retLength = 1;
+ }
+ foreach (ref e; el)
+ {
+ next.next = allocator.make!Entry(e);
+ next = next.next;
+ ++retLength;
+ }
+ if (newHead !is null)
+ {
+ next.next = head;
+ head = newHead;
+ }
+ return retLength;
+ }
+
+ /// Ditto.
+ size_t insertFront(size_t R)(T[R] el)
+ {
+ return insertFront!(T[])(el[]);
+ }
+
+ /// Ditto.
+ alias insert = insertFront;
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l1;
+
+ assert(l1.insertFront(8) == 1);
+ assert(l1.front == 8);
+ assert(l1.insertFront(9) == 1);
+ assert(l1.front == 9);
+
+ SList!int l2;
+ assert(l2.insertFront([25, 30, 15]) == 3);
+ assert(l2.front == 25);
+
+ l2.insertFront(l1[]);
+ assert(l2.length == 5);
+ assert(l2.front == 9);
+ }
+
+ version (assert)
+ {
+ private bool checkRangeBelonging(ref SRange!T r) const
+ {
+ const(Entry*)* pos;
+ for (pos = &head; pos != r.head && *pos !is null; pos = &(*pos).next)
+ {
+ }
+ return pos == r.head;
+ }
+ }
+
+ /**
+ * Inserts new elements before $(D_PARAM r).
+ *
+ * Params:
+ * R = Type of the inserted value(s).
+ * r = Range extracted from this list.
+ * el = New element(s).
+ *
+ * Returns: The number of elements inserted.
+ *
+ * Precondition: $(D_PARAM r) is extracted from this list.
+ */
+ size_t insertBefore(R)(SRange!T r, R el)
+ if (isImplicitlyConvertible!(R, T))
+ in
+ {
+ assert(checkRangeBelonging(r));
+ }
+ body
+ {
+ return moveEntry(*r.head, el);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([234, 5, 1]);
+ auto l2 = SList!int([5, 1]);
+ l2.insertBefore(l2[], 234);
+ assert(l1 == l2);
+ }
+
+ /// Ditto.
+ size_t insertBefore(R)(SRange!T r, R el)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ in
+ {
+ assert(checkRangeBelonging(r));
+ }
+ body
+ {
+ size_t inserted;
+ foreach (e; el)
+ {
+ inserted += insertBefore(r, e);
+ r.popFront();
+ }
+ return inserted;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 234, 30, 1]);
+ auto l2 = SList!int([5, 1]);
+ auto l3 = SList!int([234, 30]);
+ auto r = l2[];
+ r.popFront();
+ l2.insertBefore(r, l3[]);
+ assert(l1 == l2);
+ }
+
+ /// Ditto.
+ size_t insertBefore(SRange!T r, ref T el) @trusted
+ in
+ {
+ assert(checkRangeBelonging(r));
+ }
+ body
+ {
+ *r.head = allocator.make!Entry(el, *r.head);
+ return 1;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([234, 5, 1]);
+ auto l2 = SList!int([5, 1]);
+ int var = 234;
+ l2.insertBefore(l2[], var);
+ assert(l1 == l2);
+ }
+
+ /**
+ * Inserts elements from a static array before $(D_PARAM r).
+ *
+ * Params:
+ * R = Static array size.
+ * r = Range extracted from this list.
+ * el = New elements.
+ *
+ * Returns: The number of elements inserted.
+ *
+ * Precondition: $(D_PARAM r) is extracted from this list.
+ */
+ size_t insertBefore(size_t R)(SRange!T r, T[R] el)
+ {
+ return insertFront!(T[])(el[]);
+ }
+
+ /**
+ * Returns: How many elements are in the list.
+ */
+ @property size_t length() const
+ {
+ return count(this[]);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l;
+
+ l.insertFront(8);
+ l.insertFront(9);
+ assert(l.length == 2);
+ l.removeFront();
+ assert(l.length == 1);
+ l.removeFront();
+ assert(l.length == 0);
+ }
+
+ /**
+ * Comparison for equality.
+ *
+ * Params:
+ * that = The list to compare with.
+ *
+ * Returns: $(D_KEYWORD true) if the lists are equal, $(D_KEYWORD false)
+ * otherwise.
+ */
+ bool opEquals()(auto ref typeof(this) that) inout
+ {
+ return equal(this[], that[]);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l1, l2;
+
+ l1.insertFront(8);
+ l1.insertFront(9);
+ l2.insertFront(8);
+ l2.insertFront(10);
+ assert(l1 != l2);
+
+ l1.removeFront();
+ assert(l1 != l2);
+
+ l2.removeFront();
+ assert(l1 == l2);
+
+ l1.removeFront();
+ assert(l1 != l2);
+
+ l2.removeFront();
+ assert(l1 == l2);
+ }
+
+ /**
+ * Returns: $(D_KEYWORD true) if the list is empty.
+ */
+ @property bool empty() const
+ {
+ return head is null;
+ }
+
+ /**
+ * Returns the first element and moves to the next one.
+ *
+ * Returns: The first element.
+ *
+ * Precondition: $(D_INLINECODE !empty)
+ */
+ void removeFront()
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ auto n = this.head.next;
+
+ this.allocator.dispose(this.head);
+ this.head = n;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l;
+
+ l.insertFront(8);
+ l.insertFront(9);
+ assert(l.front == 9);
+ l.removeFront();
+ assert(l.front == 8);
+ l.removeFront();
+ assert(l.empty);
+ }
+
+ /**
+ * Removes $(D_PARAM howMany) elements from the list.
+ *
+ * Unlike $(D_PSYMBOL removeFront()), this method doesn't fail, if it could not
+ * remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
+ * greater than the list length, all elements are removed.
+ *
+ * Params:
+ * howMany = How many elements should be removed.
+ *
+ * Returns: The number of elements removed.
+ */
+ size_t removeFront(const size_t howMany)
+ out (removed)
+ {
+ assert(removed <= howMany);
+ }
+ body
+ {
+ size_t i;
+ for (; i < howMany && !empty; ++i)
+ {
+ removeFront();
+ }
+ return i;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ SList!int l = SList!int([8, 5, 4]);
+
+ assert(l.removeFront(0) == 0);
+ assert(l.removeFront(2) == 2);
+ assert(l.removeFront(3) == 1);
+ assert(l.removeFront(3) == 0);
+ }
+
+ /**
+ * Removes $(D_PARAM r) from the list.
+ *
+ * Params:
+ * r = The range to remove.
+ *
+ * Returns: An empty range.
+ *
+ * Precondition: $(D_PARAM r) is extracted from this list.
+ */
+ SRange!T remove(SRange!T r)
+ in
+ {
+ assert(checkRangeBelonging(r));
+ }
+ body
+ {
+ typeof(this) outOfScopeList;
+ outOfScopeList.head = *r.head;
+ *r.head = null;
+
+ return r;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 234, 30, 1]);
+ auto l2 = SList!int([5]);
+ auto r = l1[];
+
+ r.popFront();
+
+ assert(l1.remove(r).empty);
+ assert(l1 == l2);
+ }
+
+ /**
+ * Returns: Range that iterates over all elements of the container, in
+ * forward order.
+ */
+ SRange!T opIndex()
+ {
+ return typeof(return)(head);
+ }
+
+ /// Ditto.
+ SRange!(const T) opIndex() const
+ {
+ return typeof(return)(head);
+ }
+
+ /**
+ * Assigns another list.
+ *
+ * If $(D_PARAM that) is passed by value, it won't be copied, but moved.
+ * This list will take the ownership over $(D_PARAM that)'s storage and
+ * the allocator.
+ *
+ * If $(D_PARAM that) is passed by reference, it will be copied.
+ *
+ * Params:
+ * R = Content type.
+ * that = The value should be assigned.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(R)(const ref R that)
+ if (is(Unqual!R == SList))
+ {
+ return this = that[];
+ }
+
+ /// Ditto.
+ ref typeof(this) opAssign(R)(const ref R that)
+ if (is(Unqual!R == SList))
+ {
+ swap(this.head, that.head);
+ swap(this.allocator_, that.allocator_);
+ }
+
+ /**
+ * Assigns an input range.
+ *
+ * Params:
+ * R = Type of the initial range.
+ * that = Values to initialize the list with.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(R)(R that) @trusted
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ Entry** next = &head;
+
+ foreach (ref e; that)
+ {
+ if (*next is null)
+ {
+ *next = allocator.make!Entry(e);
+ }
+ else
+ {
+ (*next).content = e;
+ }
+ next = &(*next).next;
+ }
+ remove(SRange!T(*next));
+
+ return this;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 4, 9]);
+ auto l2 = SList!int([9, 4]);
+ l1 = l2[];
+ assert(l1 == l2);
+ }
+
+ /**
+ * Assigns a static array.
+ *
+ * Params:
+ * R = Static array size.
+ * that = Values to initialize the vector with.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(size_t R)(T[R] that)
+ {
+ return opAssign!(T[])(that[]);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto l1 = SList!int([5, 4, 9]);
+ auto l2 = SList!int([9, 4]);
+ l1 = [9, 4];
+ assert(l1 == l2);
+ }
+
+
+ mixin DefaultAllocator;
}
///
-unittest
+@nogc unittest
+{
+ SList!int l;
+ size_t i;
+
+ l.insertFront(5);
+ l.insertFront(4);
+ l.insertFront(9);
+ foreach (e; l)
+ {
+ assert(i != 0 || e == 9);
+ assert(i != 1 || e == 4);
+ assert(i != 2 || e == 5);
+ ++i;
+ }
+ assert(i == 3);
+}
+
+@safe @nogc private unittest
{
- SList!int l;
- size_t i;
-
- l.insertFront(5);
- l.insertFront(4);
- l.insertFront(9);
- foreach (e; l)
- {
- assert(i != 0 || e == 9);
- assert(i != 1 || e == 4);
- assert(i != 2 || e == 5);
- ++i;
- }
- assert(i == 3);
+ interface Stuff
+ {
+ }
+ static assert(is(SList!Stuff));
}
-unittest
+// foreach called using opIndex().
+private @nogc @safe unittest
{
- interface Stuff
- {
- }
- static assert(is(SList!Stuff));
+ SList!int l;
+ size_t i;
+
+ l.insertFront(5);
+ l.insertFront(4);
+ l.insertFront(9);
+ foreach (e; l)
+ {
+ assert(i != 0 || e == 9);
+ assert(i != 1 || e == 4);
+ assert(i != 2 || e == 5);
+ ++i;
+ }
}
diff --git a/source/tanya/container/package.d b/source/tanya/container/package.d
index db8f025..8012ffe 100644
--- a/source/tanya/container/package.d
+++ b/source/tanya/container/package.d
@@ -3,11 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
+ * Abstract data types whose instances are collections of other objects.
+ *
* Copyright: Eugene Wissner 2016-2017.
* 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)
- */
+ */
module tanya.container;
public import tanya.container.buffer;
diff --git a/source/tanya/container/queue.d b/source/tanya/container/queue.d
index 6388013..48cc483 100644
--- a/source/tanya/container/queue.d
+++ b/source/tanya/container/queue.d
@@ -3,11 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
+ * FIFO queue.
+ *
* Copyright: Eugene Wissner 2016-2017.
* 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)
- */
+ */
module tanya.container.queue;
import core.exception;
@@ -20,267 +22,267 @@ import tanya.memory;
* FIFO queue.
*
* Params:
- * T = Content type.
+ * T = Content type.
*/
struct Queue(T)
{
- /**
- * Removes all elements from the queue.
- */
- ~this()
- {
- while (!empty)
- {
- dequeue();
- }
- }
-
- /**
- * Returns how many elements are in the queue. It iterates through the queue
- * to count the elements.
- *
- * Returns: How many elements are in the queue.
- */
- size_t length() const
- {
- size_t len;
- for (const(SEntry!T)* i = first; i !is null; i = i.next)
- {
- ++len;
- }
- return len;
- }
-
- ///
- unittest
- {
- Queue!int q;
-
- assert(q.length == 0);
- q.enqueue(5);
- assert(q.length == 1);
- q.enqueue(4);
- assert(q.length == 2);
- q.enqueue(9);
- assert(q.length == 3);
-
- q.dequeue();
- assert(q.length == 2);
- q.dequeue();
- assert(q.length == 1);
- q.dequeue();
- assert(q.length == 0);
- }
-
- private void enqueueEntry(ref SEntry!T* entry)
- {
- if (empty)
- {
- first = rear = entry;
- }
- else
- {
- rear.next = entry;
- rear = rear.next;
- }
- }
-
- private SEntry!T* allocateEntry()
- {
- auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
- if (temp is null)
- {
- onOutOfMemoryError();
- }
- return temp;
- }
-
- /**
- * Inserts a new element.
- *
- * Params:
- * x = New element.
- */
- void enqueue(ref T x)
- {
- auto temp = allocateEntry();
-
- *temp = SEntry!T.init;
- temp.content = x;
-
- enqueueEntry(temp);
- }
-
- /// Ditto.
- void enqueue(T x)
- {
- auto temp = allocateEntry();
-
- moveEmplace(x, (*temp).content);
- (*temp).next = null;
-
- enqueueEntry(temp);
- }
-
- ///
- unittest
- {
- Queue!int q;
-
- assert(q.empty);
- q.enqueue(8);
- q.enqueue(9);
- assert(q.dequeue() == 8);
- assert(q.dequeue() == 9);
- }
-
- /**
- * Returns: $(D_KEYWORD true) if the queue is empty.
- */
- @property bool empty() const
- {
- return first is null;
- }
-
- ///
- unittest
- {
- Queue!int q;
- int value = 7;
-
- assert(q.empty);
- q.enqueue(value);
- assert(!q.empty);
- }
-
- /**
- * Move the position to the next element.
- *
- * Returns: Dequeued element.
- */
- T dequeue()
- in
- {
- assert(!empty);
- }
- body
- {
- auto n = first.next;
- T ret = move(first.content);
-
- allocator.dispose(first);
- first = n;
- return ret;
- }
-
- ///
- unittest
- {
- Queue!int q;
-
- q.enqueue(8);
- q.enqueue(9);
- assert(q.dequeue() == 8);
- assert(q.dequeue() == 9);
- }
-
- /**
- * $(D_KEYWORD foreach) iteration. The elements will be automatically
- * dequeued.
- *
- * Params:
- * dg = $(D_KEYWORD foreach) body.
- *
- * Returns: The value returned from $(D_PARAM dg).
- */
- int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
- {
- int result;
-
- for (size_t i = 0; !empty; ++i)
- {
- auto e = dequeue();
- if ((result = dg(i, e)) != 0)
- {
- return result;
- }
- }
- return result;
- }
-
- /// Ditto.
- int opApply(scope int delegate(ref T) @nogc dg)
- {
- int result;
-
- while (!empty)
- {
- auto e = dequeue();
- if ((result = dg(e)) != 0)
- {
- return result;
- }
- }
- return result;
- }
-
- ///
- unittest
- {
- Queue!int q;
-
- size_t j;
- q.enqueue(5);
- q.enqueue(4);
- q.enqueue(9);
- foreach (i, e; q)
- {
- assert(i != 2 || e == 9);
- assert(i != 1 || e == 4);
- assert(i != 0 || e == 5);
- ++j;
- }
- assert(j == 3);
- assert(q.empty);
-
- j = 0;
- q.enqueue(5);
- q.enqueue(4);
- q.enqueue(9);
- foreach (e; q)
- {
- assert(j != 2 || e == 9);
- assert(j != 1 || e == 4);
- assert(j != 0 || e == 5);
- ++j;
- }
- assert(j == 3);
- assert(q.empty);
- }
-
- private SEntry!T* first;
- private SEntry!T* rear;
-
- mixin DefaultAllocator;
+ /**
+ * Removes all elements from the queue.
+ */
+ ~this()
+ {
+ while (!empty)
+ {
+ dequeue();
+ }
+ }
+
+ /**
+ * Returns how many elements are in the queue. It iterates through the queue
+ * to count the elements.
+ *
+ * Returns: How many elements are in the queue.
+ */
+ size_t length() const
+ {
+ size_t len;
+ for (const(SEntry!T)* i = first; i !is null; i = i.next)
+ {
+ ++len;
+ }
+ return len;
+ }
+
+ ///
+ unittest
+ {
+ Queue!int q;
+
+ assert(q.length == 0);
+ q.enqueue(5);
+ assert(q.length == 1);
+ q.enqueue(4);
+ assert(q.length == 2);
+ q.enqueue(9);
+ assert(q.length == 3);
+
+ q.dequeue();
+ assert(q.length == 2);
+ q.dequeue();
+ assert(q.length == 1);
+ q.dequeue();
+ assert(q.length == 0);
+ }
+
+ private void enqueueEntry(ref SEntry!T* entry)
+ {
+ if (empty)
+ {
+ first = rear = entry;
+ }
+ else
+ {
+ rear.next = entry;
+ rear = rear.next;
+ }
+ }
+
+ private SEntry!T* allocateEntry()
+ {
+ auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
+ if (temp is null)
+ {
+ onOutOfMemoryError();
+ }
+ return temp;
+ }
+
+ /**
+ * Inserts a new element.
+ *
+ * Params:
+ * x = New element.
+ */
+ void enqueue(ref T x)
+ {
+ auto temp = allocateEntry();
+
+ *temp = SEntry!T.init;
+ temp.content = x;
+
+ enqueueEntry(temp);
+ }
+
+ /// Ditto.
+ void enqueue(T x)
+ {
+ auto temp = allocateEntry();
+
+ moveEmplace(x, (*temp).content);
+ (*temp).next = null;
+
+ enqueueEntry(temp);
+ }
+
+ ///
+ unittest
+ {
+ Queue!int q;
+
+ assert(q.empty);
+ q.enqueue(8);
+ q.enqueue(9);
+ assert(q.dequeue() == 8);
+ assert(q.dequeue() == 9);
+ }
+
+ /**
+ * Returns: $(D_KEYWORD true) if the queue is empty.
+ */
+ @property bool empty() const
+ {
+ return first is null;
+ }
+
+ ///
+ unittest
+ {
+ Queue!int q;
+ int value = 7;
+
+ assert(q.empty);
+ q.enqueue(value);
+ assert(!q.empty);
+ }
+
+ /**
+ * Move the position to the next element.
+ *
+ * Returns: Dequeued element.
+ */
+ T dequeue()
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ auto n = first.next;
+ T ret = move(first.content);
+
+ allocator.dispose(first);
+ first = n;
+ return ret;
+ }
+
+ ///
+ unittest
+ {
+ Queue!int q;
+
+ q.enqueue(8);
+ q.enqueue(9);
+ assert(q.dequeue() == 8);
+ assert(q.dequeue() == 9);
+ }
+
+ /**
+ * $(D_KEYWORD foreach) iteration. The elements will be automatically
+ * dequeued.
+ *
+ * Params:
+ * dg = $(D_KEYWORD foreach) body.
+ *
+ * Returns: The value returned from $(D_PARAM dg).
+ */
+ int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
+ {
+ int result;
+
+ for (size_t i = 0; !empty; ++i)
+ {
+ auto e = dequeue();
+ if ((result = dg(i, e)) != 0)
+ {
+ return result;
+ }
+ }
+ return result;
+ }
+
+ /// Ditto.
+ int opApply(scope int delegate(ref T) @nogc dg)
+ {
+ int result;
+
+ while (!empty)
+ {
+ auto e = dequeue();
+ if ((result = dg(e)) != 0)
+ {
+ return result;
+ }
+ }
+ return result;
+ }
+
+ ///
+ unittest
+ {
+ Queue!int q;
+
+ size_t j;
+ q.enqueue(5);
+ q.enqueue(4);
+ q.enqueue(9);
+ foreach (i, e; q)
+ {
+ assert(i != 2 || e == 9);
+ assert(i != 1 || e == 4);
+ assert(i != 0 || e == 5);
+ ++j;
+ }
+ assert(j == 3);
+ assert(q.empty);
+
+ j = 0;
+ q.enqueue(5);
+ q.enqueue(4);
+ q.enqueue(9);
+ foreach (e; q)
+ {
+ assert(j != 2 || e == 9);
+ assert(j != 1 || e == 4);
+ assert(j != 0 || e == 5);
+ ++j;
+ }
+ assert(j == 3);
+ assert(q.empty);
+ }
+
+ private SEntry!T* first;
+ private SEntry!T* rear;
+
+ mixin DefaultAllocator;
}
///
unittest
{
- Queue!int q;
+ Queue!int q;
- q.enqueue(5);
- assert(!q.empty);
+ q.enqueue(5);
+ assert(!q.empty);
- q.enqueue(4);
- q.enqueue(9);
+ q.enqueue(4);
+ q.enqueue(9);
- assert(q.dequeue() == 5);
+ assert(q.dequeue() == 5);
- foreach (i, ref e; q)
- {
- assert(i != 0 || e == 4);
- assert(i != 1 || e == 9);
- }
- assert(q.empty);
+ foreach (i, ref e; q)
+ {
+ assert(i != 0 || e == 4);
+ assert(i != 1 || e == 9);
+ }
+ assert(q.empty);
}
diff --git a/source/tanya/container/vector.d b/source/tanya/container/vector.d
index b38c0a4..7f56bf0 100644
--- a/source/tanya/container/vector.d
+++ b/source/tanya/container/vector.d
@@ -3,11 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
+ * Single-dimensioned array.
+ *
* Copyright: Eugene Wissner 2016-2017.
* 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)
- */
+ */
module tanya.container.vector;
import core.checkedint;
@@ -20,1578 +22,1634 @@ import std.meta;
import std.traits;
import tanya.memory;
-// Defines the container's primary range.
-private struct Range(E)
+/**
+ * Random-access range for the $(D_PSYMBOL Vector).
+ *
+ * Params:
+ * E = Element type.
+ */
+struct Range(E)
{
- private E* begin, end;
-
- invariant
- {
- assert(begin <= end);
- }
-
- private this(E* begin, E* end)
- {
- this.begin = begin;
- this.end = end;
- }
-
- @property Range save()
- {
- return this;
- }
-
- @property bool empty() const
- {
- return begin == end;
- }
-
- @property size_t length() const
- {
- return end - begin;
- }
-
- alias opDollar = length;
-
- @property ref inout(E) front() inout
- in
- {
- assert(!empty);
- }
- body
- {
- return *begin;
- }
-
- @property ref inout(E) back() inout @trusted
- in
- {
- assert(!empty);
- }
- body
- {
- return *(end - 1);
- }
-
- void popFront() @trusted
- in
- {
- assert(!empty);
- }
- body
- {
- ++begin;
- }
-
- void popBack() @trusted
- in
- {
- assert(!empty);
- }
- body
- {
- --end;
- }
-
- ref inout(E) opIndex(in size_t i) inout @trusted
- in
- {
- assert(i < length);
- }
- body
- {
- return *(begin + i);
- }
-
- Range opIndex()
- {
- return typeof(return)(begin, end);
- }
-
- Range!(const E) opIndex() const
- {
- return typeof(return)(begin, end);
- }
-
- Range opSlice(in size_t i, in size_t j) @trusted
- in
- {
- assert(i <= j);
- assert(j <= length);
- }
- body
- {
- return typeof(return)(begin + i, begin + j);
- }
-
- Range!(const E) opSlice(in size_t i, in size_t j) const @trusted
- in
- {
- assert(i <= j);
- assert(j <= length);
- }
- body
- {
- return typeof(return)(begin + i, begin + j);
- }
-
- inout(E[]) get() inout @trusted
- {
- return begin[0 .. length];
- }
+ private E* begin, end;
+ private alias ContainerType = CopyConstness!(E, Vector!(Unqual!E));
+ private ContainerType* vector;
+
+ invariant
+ {
+ assert(this.begin <= this.end);
+ assert(this.vector !is null);
+ assert(this.begin >= this.vector.data);
+ assert(this.end <= this.vector.data + this.vector.length);
+ }
+
+ private this(ref ContainerType vector, E* begin, E* end) @trusted
+ in
+ {
+ assert(begin <= end);
+ assert(begin >= vector.data);
+ assert(end <= vector.data + vector.length);
+ }
+ body
+ {
+ this.vector = &vector;
+ this.begin = begin;
+ this.end = end;
+ }
+
+ @disable this();
+
+ @property Range save()
+ {
+ return this;
+ }
+
+ @property bool empty() const
+ {
+ return this.begin == this.end;
+ }
+
+ @property size_t length() const
+ {
+ return this.end - this.begin;
+ }
+
+ alias opDollar = length;
+
+ @property ref inout(E) front() inout
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return *this.begin;
+ }
+
+ @property ref inout(E) back() inout @trusted
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return *(this.end - 1);
+ }
+
+ void popFront() @trusted
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ ++this.begin;
+ }
+
+ void popBack() @trusted
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ --this.end;
+ }
+
+ ref inout(E) opIndex(const size_t i) inout @trusted
+ in
+ {
+ assert(i < length);
+ }
+ body
+ {
+ return *(this.begin + i);
+ }
+
+ Range opIndex()
+ {
+ return typeof(return)(*this.vector, this.begin, this.end);
+ }
+
+ Range!(const E) opIndex() const
+ {
+ return typeof(return)(*this.vector, this.begin, this.end);
+ }
+
+ Range opSlice(const size_t i, const size_t j) @trusted
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ }
+ body
+ {
+ return typeof(return)(*this.vector, this.begin + i, this.begin + j);
+ }
+
+ Range!(const E) opSlice(const size_t i, const size_t j) const @trusted
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ }
+ body
+ {
+ return typeof(return)(*this.vector, this.begin + i, this.begin + j);
+ }
+
+ inout(E[]) get() inout @trusted
+ {
+ return this.begin[0 .. length];
+ }
}
/**
* One dimensional array.
*
* Params:
- * T = Content type.
+ * T = Content type.
*/
struct Vector(T)
{
- private size_t length_;
- private T* vector;
- private size_t capacity_;
-
- invariant
- {
- assert(length_ <= capacity_);
- assert(capacity_ == 0 || vector !is null);
- }
-
- /**
- * Creates a new $(D_PSYMBOL Vector) with the elements from another input
- * range or a static array $(D_PARAM init).
- *
- * Params:
- * R = Type of the initial range or size of the static array.
- * init = Values to initialize the array with.
- * to generate a list.
- * allocator = Allocator.
- */
- this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator)
- {
- this(allocator);
- insertBack!(T[])(init[]);
- }
-
- /// Ditto.
- this(R)(R init, shared Allocator allocator = defaultAllocator)
- if (!isInfinite!R
- && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- {
- this(allocator);
- insertBack(init);
- }
-
- /**
- * Initializes this vector from another one.
- *
- * If $(D_PARAM init) is passed by value, it won't be copied, but moved
- * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator),
- * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s
- * storage, otherwise, the storage will be allocated with
- * $(D_PARAM allocator) and all elements will be moved;
- * $(D_PARAM init) will be destroyed at the end.
- *
- * If $(D_PARAM init) is passed by reference, it will be copied.
- *
- * Params:
- * R = Vector type.
- * init = Source vector.
- * allocator = Allocator.
- */
- this(R)(ref R init, shared Allocator allocator = defaultAllocator)
- if (is(Unqual!R == Vector))
- {
- this(allocator);
- insertBack(init[]);
- }
-
- /// Ditto.
- this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted
- if (is(R == Vector))
- {
- this(allocator);
- if (allocator is init.allocator)
- {
- // Just steal all references and the allocator.
- vector = init.vector;
- length_ = init.length_;
- capacity_ = init.capacity_;
-
- // Reset the source vector, so it can't destroy the moved storage.
- init.length_ = init.capacity_ = 0;
- init.vector = null;
- }
- else
- {
- // Move each element.
- reserve(init.length);
- moveEmplaceAll(init.vector[0 .. init.length_], vector[0 .. init.length_]);
- length_ = init.length;
- // Destructor of init should destroy it here.
- }
- }
-
- ///
- unittest
- {
- auto v1 = Vector!int([1, 2, 3]);
- auto v2 = Vector!int(v1);
- assert(v1.vector !is v2.vector);
- assert(v1 == v2);
-
- auto v3 = Vector!int(Vector!int([1, 2, 3]));
- assert(v1 == v3);
- assert(v3.length == 3);
- assert(v3.capacity == 3);
- }
-
- unittest // const constructor tests
- {
- auto v1 = const Vector!int([1, 2, 3]);
- auto v2 = Vector!int(v1);
- assert(v1.vector !is v2.vector);
- assert(v1 == v2);
-
- auto v3 = const Vector!int(Vector!int([1, 2, 3]));
- assert(v1 == v3);
- assert(v3.length == 3);
- assert(v3.capacity == 3);
- }
-
- /**
- * Creates a new $(D_PSYMBOL Vector).
- *
- * Params:
- * len = Initial length of the vector.
- * allocator = Allocator.
- */
- this(in size_t len, shared Allocator allocator = defaultAllocator)
- {
- this(allocator);
- length = len;
- }
-
- /**
- * Creates a new $(D_PSYMBOL Vector).
- *
- * Params:
- * len = Initial length of the vector.
- * init = Initial value to fill the vector with.
- * allocator = Allocator.
- */
- this(in size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted
- {
- this(allocator);
- reserve(len);
- uninitializedFill(vector[0 .. len], init);
- length_ = len;
- }
-
- /// Ditto.
- this(shared Allocator allocator)
- in
- {
- assert(allocator !is null);
- }
- body
- {
- allocator_ = allocator;
- }
-
- ///
- unittest
- {
- auto v = Vector!int([3, 8, 2]);
-
- assert(v.capacity == 3);
- assert(v.length == 3);
- assert(v[0] == 3 && v[1] == 8 && v[2] == 2);
- }
-
- ///
- unittest
- {
- auto v = Vector!int(3, 5);
-
- assert(v.capacity == 3);
- assert(v.length == 3);
- assert(v[0] == 5 && v[1] == 5 && v[2] == 5);
- }
-
- @safe unittest
- {
- auto v1 = Vector!int(defaultAllocator);
- }
-
- /**
- * Destroys this $(D_PSYMBOL Vector).
- */
- ~this() @trusted
- {
- clear();
- allocator.deallocate(vector[0 .. capacity_]);
- }
-
- /**
- * Copies the vector.
- */
- this(this)
- {
- auto buf = opIndex();
- length_ = capacity_ = 0;
- vector = null;
- insertBack(buf);
- }
-
- /**
- * Removes all elements.
- */
- void clear()
- {
- length = 0;
- }
-
- ///
- unittest
- {
- auto v = Vector!int([18, 20, 15]);
- v.clear();
- assert(v.length == 0);
- assert(v.capacity == 3);
- }
-
- /**
- * Returns: How many elements the vector can contain without reallocating.
- */
- @property size_t capacity() const
- {
- return capacity_;
- }
-
- /**
- * Returns: Vector length.
- */
- @property size_t length() const
- {
- return length_;
- }
-
- /// Ditto.
- size_t opDollar() const
- {
- return length;
- }
-
- /**
- * Expands/shrinks the vector.
- *
- * Params:
- * len = New length.
- */
- @property void length(in size_t len) @trusted
- {
- if (len == length)
- {
- return;
- }
- else if (len > length)
- {
- reserve(len);
- initializeAll(vector[length_ .. len]);
- }
- else
- {
- static if (hasElaborateDestructor!T)
- {
- const T* end = vector + length_ - 1;
- for (T* e = vector + len; e != end; ++e)
- {
- destroy(*e);
- }
- }
- }
- length_ = len;
- }
-
- ///
- unittest
- {
- Vector!int v;
-
- v.length = 5;
- assert(v.length == 5);
- assert(v.capacity == 5);
-
- v.length = 7;
- assert(v.length == 7);
- assert(v.capacity == 7);
-
- assert(v[$ - 1] == 0);
- v[$ - 1] = 3;
- assert(v[$ - 1] == 3);
-
- v.length = 0;
- assert(v.length == 0);
- assert(v.capacity == 7);
- }
-
- /**
- * Reserves space for $(D_PARAM size) elements.
- *
- * Params:
- * size = Desired size.
- */
- void reserve(in size_t size) @trusted
- {
- if (capacity_ >= size)
- {
- return;
- }
- bool overflow;
- immutable byteSize = mulu(size, T.sizeof, overflow);
- assert(!overflow);
-
- void[] buf = vector[0 .. capacity_];
- if (!allocator.reallocateInPlace(buf, byteSize))
- {
- buf = allocator.allocate(byteSize);
- if (buf is null)
- {
- onOutOfMemoryErrorNoGC();
- }
- scope (failure)
- {
- allocator.deallocate(buf);
- }
- const T* end = vector + length_;
- for (T* src = vector, dest = cast(T*) buf; src != end; ++src, ++dest)
- {
- moveEmplace(*src, *dest);
- static if (hasElaborateDestructor!T)
- {
- destroy(*src);
- }
- }
- allocator.deallocate(vector[0 .. capacity_]);
- vector = cast(T*) buf;
- }
- capacity_ = size;
- }
-
- ///
- unittest
- {
- Vector!int v;
- assert(v.capacity == 0);
- assert(v.length == 0);
-
- v.reserve(3);
- assert(v.capacity == 3);
- assert(v.length == 0);
- }
-
- /**
- * Requests the vector to reduce its capacity to fit the $(D_PARAM size).
- *
- * The request is non-binding. The vector won't become smaller than the
- * $(D_PARAM length).
- *
- * Params:
- * size = Desired size.
- */
- void shrink(in size_t size) @trusted
- {
- if (capacity_ <= size)
- {
- return;
- }
- immutable n = max(length, size);
- void[] buf = vector[0 .. capacity_];
- if (allocator.reallocateInPlace(buf, n * T.sizeof))
- {
- capacity_ = n;
- }
- }
-
- ///
- unittest
- {
- Vector!int v;
- assert(v.capacity == 0);
- assert(v.length == 0);
-
- v.reserve(5);
- v.insertBack(1);
- v.insertBack(3);
- assert(v.capacity == 5);
- assert(v.length == 2);
-
- v.shrink(4);
- assert(v.capacity == 4);
- assert(v.length == 2);
-
- v.shrink(1);
- assert(v.capacity == 2);
- assert(v.length == 2);
- }
-
- /**
- * Returns: $(D_KEYWORD true) if the vector is empty.
- */
- @property bool empty() const
- {
- return length == 0;
- }
-
- /**
- * Removes the value at the back of the vector.
- *
- * Returns: The number of elements removed
- *
- * Precondition: $(D_INLINECODE !empty)
- */
- void removeBack()
- in
- {
- assert(!empty);
- }
- body
- {
- length = length - 1;
- }
-
- /**
- * Removes $(D_PARAM howMany) elements from the vector.
- *
- * This method doesn't fail if it could not remove $(D_PARAM howMany)
- * elements. Instead, if $(D_PARAM howMany) is greater than the vector
- * length, all elements are removed.
- *
- * Params:
- * howMany = How many elements should be removed.
- *
- * Returns: The number of elements removed
- */
- size_t removeBack(in size_t howMany)
- out (removed)
- {
- assert(removed <= howMany);
- }
- body
- {
- immutable toRemove = min(howMany, length);
-
- length = length - toRemove;
-
- return toRemove;
- }
-
- ///
- unittest
- {
- auto v = Vector!int([5, 18, 17]);
-
- assert(v.removeBack(0) == 0);
- assert(v.removeBack(2) == 2);
- assert(v.removeBack(3) == 1);
- assert(v.removeBack(3) == 0);
- }
-
- /**
- * Remove all elements beloning to $(D_PARAM r).
- *
- * Params:
- * r = Range originally obtained from this vector.
- *
- * Returns: Elements in $(D_PARAM r) after removing.
- *
- * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this).
- */
- Range!T remove(Range!T r) @trusted
- in
- {
- assert(r.begin >= vector);
- assert(r.end <= vector + length_);
- }
- body
- {
- auto end = vector + length_;
- moveAll(Range!T(r.end, end), Range!T(r.begin, end));
- length = length - r.length;
- return r;
- }
-
- ///
- @safe @nogc unittest
- {
- auto v = Vector!int([5, 18, 17, 2, 4, 6, 1]);
-
- assert(v.remove(v[1 .. 3]).length == 2);
- assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1);
- assert(v.length == 5);
-
- assert(v.remove(v[4 .. 4]).length == 0);
- assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1);
- assert(v.length == 5);
-
- assert(v.remove(v[4 .. 5]).length == 1);
- assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6);
- assert(v.length == 4);
-
- assert(v.remove(v[]).length == 4);
- assert(v.empty);
-
- assert(v.remove(v[]).length == 0);
- assert(v.empty);
- }
-
- private void moveBack(R)(ref R el) @trusted
- if (isImplicitlyConvertible!(R, T))
- {
- reserve(length + 1);
- moveEmplace(el, *(vector + length_));
- ++length_;
- }
-
- /**
- * Inserts the $(D_PARAM el) into the vector.
- *
- * Params:
- * R = Type of the inserted value(s) (single value, range or static array).
- * el = Value(s) should be inserted.
- *
- * Returns: The number of elements inserted.
- */
- size_t insertBack(R)(auto ref R el) @trusted
- if (isImplicitlyConvertible!(R, T))
- {
- static if (__traits(isRef, el))
- {
- reserve(length + 1);
- emplace(vector + length_, el);
- ++length_;
- }
- else
- {
- moveBack(el);
- }
- return 1;
- }
-
- /// Ditto.
- size_t insertBack(R)(R el)
- if (!isInfinite!R
- && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- {
- static if (hasLength!R)
- {
- reserve(length + el.length);
- }
- size_t retLength;
- foreach (e; el)
- {
- retLength += insertBack(e);
- }
- return retLength;
- }
-
- /// Ditto.
- size_t insertBack(size_t R)(T[R] el)
- {
- return insertBack!(T[])(el[]);
- }
-
- /// Ditto.
- alias insert = insertBack;
-
- ///
- unittest
- {
- struct TestRange
- {
- int counter = 6;
-
- int front()
- {
- return counter;
- }
-
- void popFront()
- {
- counter -= 2;
- }
-
- bool empty()
- {
- return counter == 0;
- }
- }
-
- Vector!int v1;
-
- assert(v1.insertBack(5) == 1);
- assert(v1.length == 1);
- assert(v1.capacity == 1);
- assert(v1.back == 5);
-
- assert(v1.insertBack(TestRange()) == 3);
- assert(v1.length == 4);
- assert(v1.capacity == 4);
- assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2);
-
- assert(v1.insertBack([34, 234]) == 2);
- assert(v1.length == 6);
- assert(v1.capacity == 6);
- assert(v1[4] == 34 && v1[5] == 234);
- }
-
- /**
- * Inserts $(D_PARAM el) before or after $(D_PARAM r).
- *
- * Params:
- * R = Type of the inserted value(s) (single value, range or static array).
- * r = Range originally obtained from this vector.
- * el = Value(s) should be inserted.
- *
- * Returns: The number of elements inserted.
- */
- size_t insertAfter(R)(Range!T r, R el)
- if (!isInfinite!R
- && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- in
- {
- assert(r.begin >= vector);
- assert(r.end <= vector + length_);
- }
- body
- {
- immutable oldLen = length;
- immutable offset = r.end - vector;
- immutable inserted = insertBack(el);
- bringToFront(vector[offset .. oldLen], vector[oldLen .. length]);
- return inserted;
- }
-
- /// Ditto.
- size_t insertAfter(size_t R)(Range!T r, T[R] el)
- {
- return insertAfter!(T[])(r, el[]);
- }
-
- /// Ditto.
- size_t insertAfter(R)(Range!T r, auto ref R el)
- if (isImplicitlyConvertible!(R, T))
- in
- {
- assert(r.begin >= vector);
- assert(r.end <= vector + length_);
- }
- body
- {
- immutable oldLen = length;
- immutable offset = r.end - vector;
-
- static if (__traits(isRef, el))
- {
- insertBack(el);
- }
- else
- {
- moveBack(el);
- }
- bringToFront(vector[offset .. oldLen], vector[oldLen .. length_]);
-
- return 1;
- }
-
- /// Ditto.
- size_t insertBefore(R)(Range!T r, R el)
- if (!isInfinite!R
- && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- in
- {
- assert(r.begin >= vector);
- assert(r.end <= vector + length_);
- }
- body
- {
- return insertAfter(Range!T(vector, r.begin), el);
- }
-
- /// Ditto.
- size_t insertBefore(size_t R)(Range!T r, T[R] el)
- {
- return insertBefore!(T[])(r, el[]);
- }
-
- /// Ditto.
- size_t insertBefore(R)(Range!T r, auto ref R el)
- if (isImplicitlyConvertible!(R, T))
- in
- {
- assert(r.begin >= vector);
- assert(r.end <= vector + length_);
- }
- body
- {
- immutable oldLen = length;
- immutable offset = r.begin - vector;
-
- static if (__traits(isRef, el))
- {
- insertBack(el);
- }
- else
- {
- moveBack(el);
- }
- bringToFront(vector[offset .. oldLen], vector[oldLen .. length_]);
-
- return 1;
- }
-
- ///
- unittest
- {
- Vector!int v1;
- v1.insertAfter(v1[], [2, 8]);
- assert(v1[0] == 2);
- assert(v1[1] == 8);
- assert(v1.length == 2);
-
- v1.insertAfter(v1[], [1, 2]);
- assert(v1[0] == 2);
- assert(v1[1] == 8);
- assert(v1[2] == 1);
- assert(v1[3] == 2);
- assert(v1.length == 4);
-
- v1.insertAfter(v1[0 .. 0], [1, 2]);
- assert(v1[0] == 1);
- assert(v1[1] == 2);
- assert(v1[2] == 2);
- assert(v1[3] == 8);
- assert(v1[4] == 1);
- assert(v1[5] == 2);
- assert(v1.length == 6);
-
- v1.insertAfter(v1[0 .. 4], 9);
- assert(v1[0] == 1);
- assert(v1[1] == 2);
- assert(v1[2] == 2);
- assert(v1[3] == 8);
- assert(v1[4] == 9);
- assert(v1[5] == 1);
- assert(v1[6] == 2);
- assert(v1.length == 7);
- }
-
- ///
- unittest
- {
- Vector!int v1;
- v1.insertBefore(v1[], [2, 8]);
- assert(v1[0] == 2);
- assert(v1[1] == 8);
- assert(v1.length == 2);
-
- v1.insertBefore(v1[], [1, 2]);
- assert(v1[0] == 1);
- assert(v1[1] == 2);
- assert(v1[2] == 2);
- assert(v1[3] == 8);
- assert(v1.length == 4);
-
- v1.insertBefore(v1[0 .. 1], [1, 2]);
- assert(v1[0] == 1);
- assert(v1[1] == 2);
- assert(v1[2] == 1);
- assert(v1[3] == 2);
- assert(v1[4] == 2);
- assert(v1[5] == 8);
- assert(v1.length == 6);
-
- v1.insertBefore(v1[2 .. $], 9);
- assert(v1[0] == 1);
- assert(v1[1] == 2);
- assert(v1[2] == 9);
- assert(v1[3] == 1);
- assert(v1[4] == 2);
- assert(v1[5] == 2);
- assert(v1[6] == 8);
- assert(v1.length == 7);
- }
-
- /**
- * Assigns a value to the element with the index $(D_PARAM pos).
- *
- * Params:
- * value = Value.
- * pos = Position.
- *
- * Returns: Assigned value.
- *
- * Precondition: $(D_INLINECODE length > pos)
- */
- ref T opIndexAssign(ref T value, in size_t pos)
- {
- return opIndex(pos) = value;
- }
-
- @safe unittest
- {
- Vector!int a = Vector!int(1);
- a[0] = 5;
- assert(a[0] == 5);
- }
-
- /// Ditto.
- T opIndexAssign(T value, in size_t pos)
- {
- return opIndexAssign(value, pos);
- }
-
- /// Ditto.
- Range!T opIndexAssign(T value)
- {
- return opSliceAssign(value, 0, length);
- }
-
- /// Ditto.
- Range!T opIndexAssign(ref T value)
- {
- return opSliceAssign(value, 0, length);
- }
-
- /**
- * Assigns a range or a static array.
- *
- * Params:
- * R = Range type or static array length.
- * value = Value.
- *
- * Returns: Assigned value.
- *
- * Precondition: $(D_INLINECODE length == value.length)
- */
- Range!T opIndexAssign(R)(R value)
- if (!isInfinite!R && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- {
- return opSliceAssign!R(value, 0, length);
- }
-
- /// Ditto.
- Range!T opIndexAssign(size_t R)(T[R] value)
- {
- return opSliceAssign!R(value, 0, length);
- }
-
- ///
- @nogc unittest
- {
- auto v1 = Vector!int([12, 1, 7]);
-
- v1[] = 3;
- assert(v1[0] == 3);
- assert(v1[1] == 3);
- assert(v1[2] == 3);
-
- v1[] = [7, 1, 12];
- assert(v1[0] == 7);
- assert(v1[1] == 1);
- assert(v1[2] == 12);
- }
-
- /**
- * Params:
- * pos = Index.
- *
- * Returns: The value at a specified index.
- *
- * Precondition: $(D_INLINECODE length > pos)
- */
- ref inout(T) opIndex(in size_t pos) inout @trusted
- in
- {
- assert(length > pos);
- }
- body
- {
- return *(vector + pos);
- }
-
- /**
- * Returns: Random access range that iterates over elements of the vector, in
- * forward order.
- */
- Range!T opIndex() @trusted
- {
- return typeof(return)(vector, vector + length_);
- }
-
- /// Ditto.
- Range!(const T) opIndex() const @trusted
- {
- return typeof(return)(vector, vector + length_);
- }
-
- ///
- unittest
- {
- const v1 = Vector!int([6, 123, 34, 5]);
-
- assert(v1[0] == 6);
- assert(v1[1] == 123);
- assert(v1[2] == 34);
- assert(v1[3] == 5);
- static assert(is(typeof(v1[0]) == const(int)));
- static assert(is(typeof(v1[])));
- }
-
- /**
- * Comparison for equality.
- *
- * Params:
- * that = The vector to compare with.
- *
- * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false)
- * otherwise.
- */
- bool opEquals()(auto ref typeof(this) that) @trusted
- {
- return equal(vector[0 .. length_], that.vector[0 .. that.length_]);
- }
-
- /// Ditto.
- bool opEquals()(in auto ref typeof(this) that) const @trusted
- {
- return equal(vector[0 .. length_], that.vector[0 .. that.length_]);
- }
-
- /// Ditto.
- bool opEquals(Range!T that)
- {
- return equal(opIndex(), that);
- }
-
- /**
- * Comparison for equality.
- *
- * Params:
- * R = Right hand side type.
- * that = Right hand side vector range.
- *
- * Returns: $(D_KEYWORD true) if the vector and the range are equal,
- * $(D_KEYWORD false) otherwise.
- */
- bool opEquals(R)(Range!R that) const
- if (is(Unqual!R == T))
- {
- return equal(opIndex(), that);
- }
-
- ///
- unittest
- {
- Vector!int v1, v2;
- assert(v1 == v2);
-
- v1.length = 1;
- v2.length = 2;
- assert(v1 != v2);
-
- v1.length = 2;
- v1[0] = v2[0] = 2;
- v1[1] = 3;
- v2[1] = 4;
- assert(v1 != v2);
-
- v2[1] = 3;
- assert(v1 == v2);
- }
-
- /**
- * $(D_KEYWORD foreach) iteration.
- *
- * Params:
- * dg = $(D_KEYWORD foreach) body.
- *
- * Returns: The value returned from $(D_PARAM dg).
- */
- int opApply(scope int delegate(ref T) @nogc dg)
- {
- T* end = vector + length_ - 1;
- for (T* begin = vector; begin != end; ++begin)
- {
- int result = dg(*begin);
- if (result != 0)
- {
- return result;
- }
- }
- return 0;
- }
-
- /// Ditto.
- int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
- {
- for (size_t i = 0; i < length; ++i)
- {
- assert(i < length);
- int result = dg(i, *(vector + i));
-
- if (result != 0)
- {
- return result;
- }
- }
- return 0;
- }
-
- /// Ditto.
- int opApplyReverse(scope int delegate(ref T) dg)
- {
- for (T* end = vector + length - 1; vector != end; --end)
- {
- int result = dg(*end);
- if (result != 0)
- {
- return result;
- }
- }
- return 0;
- }
-
- /// Ditto.
- int opApplyReverse(scope int delegate(ref size_t i, ref T) dg)
- {
- if (length > 0)
- {
- size_t i = length;
- do
- {
- --i;
- assert(i < length);
- int result = dg(i, *(vector + i));
-
- if (result != 0)
- {
- return result;
- }
- }
- while (i > 0);
- }
- return 0;
- }
-
- ///
- unittest
- {
- auto v = Vector!int([5, 15, 8]);
-
- size_t i;
- foreach (j, ref e; v)
- {
- i = j;
- }
- assert(i == 2);
-
- foreach (j, e; v)
- {
- assert(j != 0 || e == 5);
- assert(j != 1 || e == 15);
- assert(j != 2 || e == 8);
- }
- }
-
- ///
- unittest
- {
- auto v = Vector!int([5, 15, 8]);
- size_t i;
-
- foreach_reverse (j, ref e; v)
- {
- i = j;
- }
- assert(i == 0);
-
- foreach_reverse (j, e; v)
- {
- assert(j != 2 || e == 8);
- assert(j != 1 || e == 15);
- assert(j != 0 || e == 5);
- }
- }
-
- /**
- * Returns: The first element.
- *
- * Precondition: $(D_INLINECODE !empty)
- */
- @property ref inout(T) front() inout
- in
- {
- assert(!empty);
- }
- body
- {
- return *vector;
- }
-
- ///
- @safe unittest
- {
- auto v = Vector!int([5]);
-
- assert(v.front == 5);
-
- v.length = 2;
- v[1] = 15;
- assert(v.front == 5);
- }
-
- /**
- * Returns: The last element.
- *
- * Precondition: $(D_INLINECODE !empty)
- */
- @property ref inout(T) back() inout @trusted
- in
- {
- assert(!empty);
- }
- body
- {
- return *(vector + length_ - 1);
- }
-
- ///
- unittest
- {
- auto v = Vector!int([5]);
-
- assert(v.back == 5);
-
- v.length = 2;
- v[1] = 15;
- assert(v.back == 15);
- }
-
- /**
- * Params:
- * i = Slice start.
- * j = Slice end.
- *
- * Returns: A range that iterates over elements of the container from
- * index $(D_PARAM i) up to (excluding) index $(D_PARAM j).
- *
- * Precondition: $(D_INLINECODE i <= j && j <= length)
- */
- Range!T opSlice(in size_t i, in size_t j) @trusted
- in
- {
- assert(i <= j);
- assert(j <= length);
- }
- body
- {
- return typeof(return)(vector + i, vector + j);
- }
-
- /// Ditto.
- Range!(const T) opSlice(in size_t i, in size_t j) const @trusted
- in
- {
- assert(i <= j);
- assert(j <= length);
- }
- body
- {
- return typeof(return)(vector + i, vector + j);
- }
-
- ///
- unittest
- {
- Vector!int v;
- auto r = v[];
- assert(r.length == 0);
- assert(r.empty);
- }
-
- ///
- unittest
- {
- auto v = Vector!int([1, 2, 3]);
- auto r = v[];
-
- assert(r.front == 1);
- assert(r.back == 3);
-
- r.popFront();
- assert(r.front == 2);
-
- r.popBack();
- assert(r.back == 2);
-
- assert(r.length == 1);
- }
-
- ///
- unittest
- {
- auto v = Vector!int([1, 2, 3, 4]);
- auto r = v[1 .. 4];
- assert(r.length == 3);
- assert(r[0] == 2);
- assert(r[1] == 3);
- assert(r[2] == 4);
-
- r = v[0 .. 0];
- assert(r.length == 0);
-
- r = v[4 .. 4];
- assert(r.length == 0);
- }
-
- /**
- * Slicing assignment.
- *
- * Params:
- * R = Type of the assigned slice or length of the static array should be
- * assigned.
- * value = New value (single value, input range or static array).
- * i = Slice start.
- * j = Slice end.
- *
- * Returns: Slice with the assigned part of the vector.
- *
- * Precondition: $(D_INLINECODE i <= j && j <= length
- * && value.length == j - i)
- */
- Range!T opSliceAssign(R)(R value, in size_t i, in size_t j) @trusted
- if (!isInfinite!R
- && isInputRange!R
- && isImplicitlyConvertible!(ElementType!R, T))
- in
- {
- assert(i <= j);
- assert(j <= length);
- assert(j - i == walkLength(value));
- }
- body
- {
- copy(value, vector[i .. j]);
- return opSlice(i, j);
- }
-
- /// Ditto.
- Range!T opSliceAssign(size_t R)(T[R] value, in size_t i, in size_t j)
- {
- return opSliceAssign!(T[])(value[], i, j);
- }
-
- /// Ditto.
- Range!T opSliceAssign(ref T value, in size_t i, in size_t j) @trusted
- in
- {
- assert(i <= j);
- assert(j <= length);
- }
- body
- {
- fill(vector[i .. j], value);
- return opSlice(i, j);
- }
-
- /// Ditto.
- Range!T opSliceAssign(T value, in size_t i, in size_t j)
- {
- return opSliceAssign(value, i, j);
- }
-
- ///
- @nogc @safe unittest
- {
- auto v1 = Vector!int([3, 3, 3]);
- auto v2 = Vector!int([1, 2]);
-
- v1[0 .. 2] = 286;
- assert(v1[0] == 286);
- assert(v1[1] == 286);
- assert(v1[2] == 3);
-
- v2[0 .. $] = v1[1 .. 3];
- assert(v2[0] == 286);
- assert(v2[1] == 3);
-
- v1[0 .. 2] = [5, 8];
- assert(v1[0] == 5);
- assert(v1[1] == 8);
- assert(v1[2] == 3);
- }
-
- /**
- * Returns an array used internally by the vector to store its owned elements.
- * The length of the returned array may differ from the size of the allocated
- * memory for the vector: the array contains only initialized elements, but
- * not the reserved memory.
- *
- * Returns: The array with elements of this vector.
- */
- inout(T[]) get() inout @trusted
- {
- return vector[0 .. length];
- }
-
- ///
- unittest
- {
- auto v = Vector!int([1, 2, 4]);
- auto data = v.get();
-
- assert(data[0] == 1);
- assert(data[1] == 2);
- assert(data[2] == 4);
- assert(data.length == 3);
-
- data = v[1 .. 2].get();
- assert(data[0] == 2);
- assert(data.length == 1);
- }
-
- mixin DefaultAllocator;
+ private size_t length_;
+ private T* data;
+ private size_t capacity_;
+
+ invariant
+ {
+ assert(this.length_ <= this.capacity_);
+ assert(this.capacity_ == 0 || this.data !is null);
+ }
+
+ /**
+ * Creates a new $(D_PSYMBOL Vector) with the elements from a static array.
+ *
+ * Params:
+ * R = Static array size.
+ * init = Values to initialize the vector with.
+ * allocator = Allocator.
+ */
+ this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator)
+ {
+ this(allocator);
+ insertBack!(T[])(init[]);
+ }
+
+ /**
+ * Creates a new $(D_PSYMBOL Vector) with the elements from an input range.
+ *
+ * Params:
+ * R = Type of the initial range.
+ * init = Values to initialize the vector with.
+ * allocator = Allocator.
+ */
+ this(R)(R init, shared Allocator allocator = defaultAllocator)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ this(allocator);
+ insertBack(init);
+ }
+
+ /**
+ * Initializes this vector from another one.
+ *
+ * If $(D_PARAM init) is passed by value, it won't be copied, but moved.
+ * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator),
+ * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s
+ * storage, otherwise, the storage will be allocated with
+ * $(D_PARAM allocator) and all elements will be moved;
+ * $(D_PARAM init) will be destroyed at the end.
+ *
+ * If $(D_PARAM init) is passed by reference, it will be copied.
+ *
+ * Params:
+ * R = Vector type.
+ * init = Source vector.
+ * allocator = Allocator.
+ */
+ this(R)(const ref R init, shared Allocator allocator = defaultAllocator)
+ if (is(Unqual!R == Vector))
+ {
+ this(allocator);
+ insertBack(init[]);
+ }
+
+ /// Ditto.
+ this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted
+ if (is(R == Vector))
+ {
+ this(allocator);
+ if (allocator is init.allocator)
+ {
+ // Just steal all references and the allocator.
+ this.data = init.data;
+ this.length_ = init.length_;
+ this.capacity_ = init.capacity_;
+
+ // Reset the source vector, so it can't destroy the moved storage.
+ init.length_ = init.capacity_ = 0;
+ init.data = null;
+ }
+ else
+ {
+ // Move each element.
+ reserve(init.length_);
+ moveEmplaceAll(init.data[0 .. init.length_], this.data[0 .. init.length_]);
+ this.length_ = init.length_;
+ // Destructor of init should destroy it here.
+ }
+ }
+
+ ///
+ @trusted @nogc unittest
+ {
+ auto v1 = Vector!int([1, 2, 3]);
+ auto v2 = Vector!int(v1);
+ assert(v1 == v2);
+
+ auto v3 = Vector!int(Vector!int([1, 2, 3]));
+ assert(v1 == v3);
+ assert(v3.length == 3);
+ assert(v3.capacity == 3);
+ }
+
+ private @trusted @nogc unittest // const constructor tests
+ {
+ auto v1 = const Vector!int([1, 2, 3]);
+ auto v2 = Vector!int(v1);
+ assert(v1.data !is v2.data);
+ assert(v1 == v2);
+
+ auto v3 = const Vector!int(Vector!int([1, 2, 3]));
+ assert(v1 == v3);
+ assert(v3.length == 3);
+ assert(v3.capacity == 3);
+ }
+
+ /**
+ * Creates a new $(D_PSYMBOL Vector).
+ *
+ * Params:
+ * len = Initial length of the vector.
+ * init = Initial value to fill the vector with.
+ * allocator = Allocator.
+ */
+ this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted
+ {
+ this(allocator);
+ reserve(len);
+ uninitializedFill(this.data[0 .. len], init);
+ length_ = len;
+ }
+
+ /// Ditto.
+ this(const size_t len, shared Allocator allocator = defaultAllocator)
+ {
+ this(allocator);
+ length = len;
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator)
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ allocator_ = allocator;
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([3, 8, 2]);
+
+ assert(v.capacity == 3);
+ assert(v.length == 3);
+ assert(v[0] == 3 && v[1] == 8 && v[2] == 2);
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int(3, 5);
+
+ assert(v.capacity == 3);
+ assert(v.length == 3);
+ assert(v[0] == 5 && v[1] == 5 && v[2] == 5);
+ }
+
+ @safe unittest
+ {
+ auto v1 = Vector!int(defaultAllocator);
+ }
+
+ /**
+ * Destroys this $(D_PSYMBOL Vector).
+ */
+ ~this() @trusted
+ {
+ clear();
+ allocator.deallocate(this.data[0 .. capacity]);
+ }
+
+ /**
+ * Copies the vector.
+ */
+ this(this)
+ {
+ auto buf = this.data[0 .. this.length_];
+ this.length_ = capacity_ = 0;
+ this.data = null;
+ insertBack(buf);
+ }
+
+ /**
+ * Removes all elements.
+ */
+ void clear()
+ {
+ length = 0;
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([18, 20, 15]);
+ v.clear();
+ assert(v.length == 0);
+ assert(v.capacity == 3);
+ }
+
+ /**
+ * Returns: How many elements the vector can contain without reallocating.
+ */
+ @property size_t capacity() const
+ {
+ return capacity_;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto v = Vector!int(4);
+ assert(v.capacity == 4);
+ }
+
+ /**
+ * Returns: Vector length.
+ */
+ @property size_t length() const
+ {
+ return length_;
+ }
+
+ /// Ditto.
+ size_t opDollar() const
+ {
+ return length;
+ }
+
+ /**
+ * Expands/shrinks the vector.
+ *
+ * Params:
+ * len = New length.
+ */
+ @property void length(const size_t len) @trusted
+ {
+ if (len == length)
+ {
+ return;
+ }
+ else if (len > length)
+ {
+ reserve(len);
+ initializeAll(this.data[length_ .. len]);
+ }
+ else
+ {
+ static if (hasElaborateDestructor!T)
+ {
+ const T* end = this.data + length_ - 1;
+ for (T* e = this.data + len; e != end; ++e)
+ {
+ destroy(*e);
+ }
+ }
+ }
+ length_ = len;
+ }
+
+ ///
+ unittest
+ {
+ Vector!int v;
+
+ v.length = 5;
+ assert(v.length == 5);
+ assert(v.capacity == 5);
+
+ v.length = 7;
+ assert(v.length == 7);
+ assert(v.capacity == 7);
+
+ assert(v[$ - 1] == 0);
+ v[$ - 1] = 3;
+ assert(v[$ - 1] == 3);
+
+ v.length = 0;
+ assert(v.length == 0);
+ assert(v.capacity == 7);
+ }
+
+ /**
+ * Reserves space for $(D_PARAM size) elements.
+ *
+ * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the
+ * function call does not cause a reallocation and the vector capacity is not
+ * affected.
+ *
+ * Params:
+ * size = Desired size.
+ */
+ void reserve(const size_t size) @trusted
+ {
+ if (capacity_ >= size)
+ {
+ return;
+ }
+ bool overflow;
+ immutable byteSize = mulu(size, T.sizeof, overflow);
+ assert(!overflow);
+
+ void[] buf = this.data[0 .. this.capacity_];
+ if (!allocator.reallocateInPlace(buf, byteSize))
+ {
+ buf = allocator.allocate(byteSize);
+ if (buf is null)
+ {
+ onOutOfMemoryErrorNoGC();
+ }
+ scope (failure)
+ {
+ allocator.deallocate(buf);
+ }
+ const T* end = this.data + this.length_;
+ for (T* src = this.data, dest = cast(T*) buf; src != end; ++src, ++dest)
+ {
+ moveEmplace(*src, *dest);
+ static if (hasElaborateDestructor!T)
+ {
+ destroy(*src);
+ }
+ }
+ allocator.deallocate(this.data[0 .. this.capacity_]);
+ this.data = cast(T*) buf;
+ }
+ this.capacity_ = size;
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ Vector!int v;
+ assert(v.capacity == 0);
+ assert(v.length == 0);
+
+ v.reserve(3);
+ assert(v.capacity == 3);
+ assert(v.length == 0);
+ }
+
+ /**
+ * Requests the vector to reduce its capacity to fit the $(D_PARAM size).
+ *
+ * The request is non-binding. The vector won't become smaller than the
+ * $(D_PARAM length).
+ *
+ * Params:
+ * size = Desired size.
+ */
+ void shrink(const size_t size) @trusted
+ {
+ if (capacity <= size)
+ {
+ return;
+ }
+ immutable n = max(length, size);
+ void[] buf = this.data[0 .. this.capacity_];
+ if (allocator.reallocateInPlace(buf, n * T.sizeof))
+ {
+ this.capacity_ = n;
+ }
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ Vector!int v;
+ assert(v.capacity == 0);
+ assert(v.length == 0);
+
+ v.reserve(5);
+ v.insertBack(1);
+ v.insertBack(3);
+ assert(v.capacity == 5);
+ assert(v.length == 2);
+ }
+
+ /**
+ * Returns: $(D_KEYWORD true) if the vector is empty.
+ */
+ @property bool empty() const
+ {
+ return length == 0;
+ }
+
+ /**
+ * Removes the value at the back of the vector.
+ *
+ * Returns: The number of elements removed
+ *
+ * Precondition: $(D_INLINECODE !empty).
+ */
+ void removeBack()
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ length = length - 1;
+ }
+
+ /**
+ * Removes $(D_PARAM howMany) elements from the vector.
+ *
+ * This method doesn't fail if it could not remove $(D_PARAM howMany)
+ * elements. Instead, if $(D_PARAM howMany) is greater than the vector
+ * length, all elements are removed.
+ *
+ * Params:
+ * howMany = How many elements should be removed.
+ *
+ * Returns: The number of elements removed
+ */
+ size_t removeBack(const size_t howMany)
+ out (removed)
+ {
+ assert(removed <= howMany);
+ }
+ body
+ {
+ immutable toRemove = min(howMany, length);
+
+ length = length - toRemove;
+
+ return toRemove;
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([5, 18, 17]);
+
+ assert(v.removeBack(0) == 0);
+ assert(v.removeBack(2) == 2);
+ assert(v.removeBack(3) == 1);
+ assert(v.removeBack(3) == 0);
+ }
+
+ /**
+ * Remove all elements beloning to $(D_PARAM r).
+ *
+ * Params:
+ * r = Range originally obtained from this vector.
+ *
+ * Returns: A range spanning the remaining elements in the array that
+ * initially were right after $(D_PARAM r).
+ *
+ * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this).
+ */
+ Range!T remove(Range!T r) @trusted
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ auto end = this.data + this.length;
+ moveAll(Range!T(this, r.end, end), Range!T(this, r.begin, end));
+ length = length - r.length;
+ return Range!T(this, r.begin, this.data + length);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto v = Vector!int([5, 18, 17, 2, 4, 6, 1]);
+
+ assert(v.remove(v[1 .. 3]).length == 4);
+ assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1);
+ assert(v.length == 5);
+
+ assert(v.remove(v[4 .. 4]).length == 1);
+ assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1);
+ assert(v.length == 5);
+
+ assert(v.remove(v[4 .. 5]).length == 0);
+ assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6);
+ assert(v.length == 4);
+
+ assert(v.remove(v[]).length == 0);
+
+ }
+
+ private void moveBack(R)(ref R el) @trusted
+ if (isImplicitlyConvertible!(R, T))
+ {
+ reserve(this.length + 1);
+ moveEmplace(el, *(this.data + this.length_));
+ ++this.length_;
+ }
+
+ /**
+ * Inserts the $(D_PARAM el) into the vector.
+ *
+ * Params:
+ * R = Type of the inserted value(s) (single value, range or static array).
+ * el = Value(s) should be inserted.
+ *
+ * Returns: The number of elements inserted.
+ */
+ size_t insertBack(R)(R el)
+ if (isImplicitlyConvertible!(R, T))
+ {
+ moveBack(el);
+ return 1;
+ }
+
+ /// Ditto.
+ size_t insertBack(R)(ref R el) @trusted
+ if (isImplicitlyConvertible!(R, T))
+ {
+ reserve(this.length_ + 1);
+ emplace(this.data + this.length_, el);
+ ++this.length_;
+ return 1;
+ }
+
+ /// Ditto.
+ size_t insertBack(R)(R el)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ static if (hasLength!R)
+ {
+ reserve(length + el.length);
+ }
+ size_t retLength;
+ foreach (e; el)
+ {
+ retLength += insertBack(e);
+ }
+ return retLength;
+ }
+
+ /// Ditto.
+ size_t insertBack(size_t R)(T[R] el)
+ {
+ return insertBack!(T[])(el[]);
+ }
+
+ /// Ditto.
+ alias insert = insertBack;
+
+ ///
+ unittest
+ {
+ struct TestRange
+ {
+ int counter = 6;
+
+ int front()
+ {
+ return counter;
+ }
+
+ void popFront()
+ {
+ counter -= 2;
+ }
+
+ bool empty()
+ {
+ return counter == 0;
+ }
+ }
+
+ Vector!int v1;
+
+ assert(v1.insertBack(5) == 1);
+ assert(v1.length == 1);
+ assert(v1.capacity == 1);
+ assert(v1.back == 5);
+
+ assert(v1.insertBack(TestRange()) == 3);
+ assert(v1.length == 4);
+ assert(v1.capacity == 4);
+ assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2);
+
+ assert(v1.insertBack([34, 234]) == 2);
+ assert(v1.length == 6);
+ assert(v1.capacity == 6);
+ assert(v1[4] == 34 && v1[5] == 234);
+ }
+
+ /**
+ * Inserts $(D_PARAM el) before or after $(D_PARAM r).
+ *
+ * Params:
+ * R = Type of the inserted value(s) (single value, range or static array).
+ * r = Range originally obtained from this vector.
+ * el = Value(s) should be inserted.
+ *
+ * Returns: The number of elements inserted.
+ *
+ * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this).
+ */
+ size_t insertAfter(R)(Range!T r, R el)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ immutable oldLen = length;
+ immutable offset = r.end - this.data;
+ immutable inserted = insertBack(el);
+ bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]);
+ return inserted;
+ }
+
+ /// Ditto.
+ size_t insertAfter(size_t R)(Range!T r, T[R] el)
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ return insertAfter!(T[])(r, el[]);
+ }
+
+ /// Ditto.
+ size_t insertAfter(R)(Range!T r, auto ref R el)
+ if (isImplicitlyConvertible!(R, T))
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ immutable oldLen = length;
+ immutable offset = r.end - this.data;
+
+ static if (__traits(isRef, el))
+ {
+ insertBack(el);
+ }
+ else
+ {
+ moveBack(el);
+ }
+ bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]);
+
+ return 1;
+ }
+
+ /// Ditto.
+ size_t insertBefore(R)(Range!T r, R el)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ return insertAfter(Range!T(this, this.data, r.begin), el);
+ }
+
+ /// Ditto.
+ size_t insertBefore(size_t R)(Range!T r, T[R] el)
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ return insertBefore!(T[])(r, el[]);
+ }
+
+ /// Ditto.
+ size_t insertBefore(R)(Range!T r, auto ref R el)
+ if (isImplicitlyConvertible!(R, T))
+ in
+ {
+ assert(r.vector is &this);
+ assert(r.begin >= this.data);
+ assert(r.end <= this.data + length);
+ }
+ body
+ {
+ immutable oldLen = length;
+ immutable offset = r.begin - this.data;
+
+ static if (__traits(isRef, el))
+ {
+ insertBack(el);
+ }
+ else
+ {
+ moveBack(el);
+ }
+ bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]);
+
+ return 1;
+ }
+
+ ///
+ unittest
+ {
+ Vector!int v1;
+ v1.insertAfter(v1[], [2, 8]);
+ assert(v1[0] == 2);
+ assert(v1[1] == 8);
+ assert(v1.length == 2);
+
+ v1.insertAfter(v1[], [1, 2]);
+ assert(v1[0] == 2);
+ assert(v1[1] == 8);
+ assert(v1[2] == 1);
+ assert(v1[3] == 2);
+ assert(v1.length == 4);
+
+ v1.insertAfter(v1[0 .. 0], [1, 2]);
+ assert(v1[0] == 1);
+ assert(v1[1] == 2);
+ assert(v1[2] == 2);
+ assert(v1[3] == 8);
+ assert(v1[4] == 1);
+ assert(v1[5] == 2);
+ assert(v1.length == 6);
+
+ v1.insertAfter(v1[0 .. 4], 9);
+ assert(v1[0] == 1);
+ assert(v1[1] == 2);
+ assert(v1[2] == 2);
+ assert(v1[3] == 8);
+ assert(v1[4] == 9);
+ assert(v1[5] == 1);
+ assert(v1[6] == 2);
+ assert(v1.length == 7);
+ }
+
+ ///
+ unittest
+ {
+ Vector!int v1;
+ v1.insertBefore(v1[], [2, 8]);
+ assert(v1[0] == 2);
+ assert(v1[1] == 8);
+ assert(v1.length == 2);
+
+ v1.insertBefore(v1[], [1, 2]);
+ assert(v1[0] == 1);
+ assert(v1[1] == 2);
+ assert(v1[2] == 2);
+ assert(v1[3] == 8);
+ assert(v1.length == 4);
+
+ v1.insertBefore(v1[0 .. 1], [1, 2]);
+ assert(v1[0] == 1);
+ assert(v1[1] == 2);
+ assert(v1[2] == 1);
+ assert(v1[3] == 2);
+ assert(v1[4] == 2);
+ assert(v1[5] == 8);
+ assert(v1.length == 6);
+
+ v1.insertBefore(v1[2 .. $], 9);
+ assert(v1[0] == 1);
+ assert(v1[1] == 2);
+ assert(v1[2] == 9);
+ assert(v1[3] == 1);
+ assert(v1[4] == 2);
+ assert(v1[5] == 2);
+ assert(v1[6] == 8);
+ assert(v1.length == 7);
+ }
+
+ /**
+ * Assigns a value to the element with the index $(D_PARAM pos).
+ *
+ * Params:
+ * value = Value.
+ * pos = Position.
+ *
+ * Returns: Assigned value.
+ *
+ * Precondition: $(D_INLINECODE length > pos).
+ */
+ ref T opIndexAssign(ref T value, const size_t pos)
+ {
+ return opIndex(pos) = value;
+ }
+
+ @safe unittest
+ {
+ Vector!int a = Vector!int(1);
+ a[0] = 5;
+ assert(a[0] == 5);
+ }
+
+ /// Ditto.
+ T opIndexAssign(T value, const size_t pos)
+ {
+ return opIndexAssign(value, pos);
+ }
+
+ /// Ditto.
+ Range!T opIndexAssign(T value)
+ {
+ return opSliceAssign(value, 0, length);
+ }
+
+ /// Ditto.
+ Range!T opIndexAssign(ref T value)
+ {
+ return opSliceAssign(value, 0, length);
+ }
+
+ /**
+ * Assigns a range or a static array.
+ *
+ * Params:
+ * R = Range type or static array length.
+ * value = Value.
+ *
+ * Returns: Assigned value.
+ *
+ * Precondition: $(D_INLINECODE length == value.length).
+ */
+ Range!T opIndexAssign(R)(R value)
+ if (!isInfinite!R && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ return opSliceAssign!R(value, 0, length);
+ }
+
+ /// Ditto.
+ Range!T opIndexAssign(size_t R)(T[R] value)
+ {
+ return opSliceAssign!R(value, 0, length);
+ }
+
+ ///
+ @nogc unittest
+ {
+ auto v1 = Vector!int([12, 1, 7]);
+
+ v1[] = 3;
+ assert(v1[0] == 3);
+ assert(v1[1] == 3);
+ assert(v1[2] == 3);
+
+ v1[] = [7, 1, 12];
+ assert(v1[0] == 7);
+ assert(v1[1] == 1);
+ assert(v1[2] == 12);
+ }
+
+ /**
+ * Params:
+ * pos = Index.
+ *
+ * Returns: The value at a specified index.
+ *
+ * Precondition: $(D_INLINECODE length > pos).
+ */
+ ref inout(T) opIndex(const size_t pos) inout @trusted
+ in
+ {
+ assert(length > pos);
+ }
+ body
+ {
+ return *(this.data + pos);
+ }
+
+ /**
+ * Returns: Random access range that iterates over elements of the vector, in
+ * forward order.
+ */
+ Range!T opIndex() @trusted
+ {
+ return typeof(return)(this, this.data, this.data + length);
+ }
+
+ /// Ditto.
+ Range!(const T) opIndex() const @trusted
+ {
+ return typeof(return)(this, this.data, this.data + length);
+ }
+
+ ///
+ unittest
+ {
+ const v1 = Vector!int([6, 123, 34, 5]);
+
+ assert(v1[0] == 6);
+ assert(v1[1] == 123);
+ assert(v1[2] == 34);
+ assert(v1[3] == 5);
+ static assert(is(typeof(v1[0]) == const(int)));
+ static assert(is(typeof(v1[])));
+ }
+
+ /**
+ * Comparison for equality.
+ *
+ * Params:
+ * that = The vector to compare with.
+ *
+ * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false)
+ * otherwise.
+ */
+ bool opEquals()(auto ref typeof(this) that) @trusted
+ {
+ return equal(this.data[0 .. length], that.data[0 .. that.length]);
+ }
+
+ /// Ditto.
+ bool opEquals()(const auto ref typeof(this) that) const @trusted
+ {
+ return equal(this.data[0 .. length], that.data[0 .. that.length]);
+ }
+
+ /// Ditto.
+ bool opEquals(Range!T that)
+ {
+ return equal(opIndex(), that);
+ }
+
+ /**
+ * Comparison for equality.
+ *
+ * Params:
+ * R = Right hand side type.
+ * that = Right hand side vector range.
+ *
+ * Returns: $(D_KEYWORD true) if the vector and the range are equal,
+ * $(D_KEYWORD false) otherwise.
+ */
+ bool opEquals(R)(Range!R that) const
+ if (is(Unqual!R == T))
+ {
+ return equal(opIndex(), that);
+ }
+
+ ///
+ unittest
+ {
+ Vector!int v1, v2;
+ assert(v1 == v2);
+
+ v1.length = 1;
+ v2.length = 2;
+ assert(v1 != v2);
+
+ v1.length = 2;
+ v1[0] = v2[0] = 2;
+ v1[1] = 3;
+ v2[1] = 4;
+ assert(v1 != v2);
+
+ v2[1] = 3;
+ assert(v1 == v2);
+ }
+
+ /**
+ * Returns: The first element.
+ *
+ * Precondition: $(D_INLINECODE !empty).
+ */
+ @property ref inout(T) front() inout
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return *this.data;
+ }
+
+ ///
+ @safe unittest
+ {
+ auto v = Vector!int([5]);
+
+ assert(v.front == 5);
+
+ v.length = 2;
+ v[1] = 15;
+ assert(v.front == 5);
+ }
+
+ /**
+ * Returns: The last element.
+ *
+ * Precondition: $(D_INLINECODE !empty).
+ */
+ @property ref inout(T) back() inout @trusted
+ in
+ {
+ assert(!empty);
+ }
+ body
+ {
+ return *(this.data + length - 1);
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([5]);
+
+ assert(v.back == 5);
+
+ v.length = 2;
+ v[1] = 15;
+ assert(v.back == 15);
+ }
+
+ /**
+ * Params:
+ * i = Slice start.
+ * j = Slice end.
+ *
+ * Returns: A range that iterates over elements of the container from
+ * index $(D_PARAM i) up to (excluding) index $(D_PARAM j).
+ *
+ * Precondition: $(D_INLINECODE i <= j && j <= length).
+ */
+ Range!T opSlice(const size_t i, const size_t j) @trusted
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ }
+ body
+ {
+ return typeof(return)(this, this.data + i, this.data + j);
+ }
+
+ /// Ditto.
+ Range!(const T) opSlice(const size_t i, const size_t j) const @trusted
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ }
+ body
+ {
+ return typeof(return)(this, this.data + i, this.data + j);
+ }
+
+ ///
+ unittest
+ {
+ Vector!int v;
+ auto r = v[];
+ assert(r.length == 0);
+ assert(r.empty);
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([1, 2, 3]);
+ auto r = v[];
+
+ assert(r.front == 1);
+ assert(r.back == 3);
+
+ r.popFront();
+ assert(r.front == 2);
+
+ r.popBack();
+ assert(r.back == 2);
+
+ assert(r.length == 1);
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([1, 2, 3, 4]);
+ auto r = v[1 .. 4];
+ assert(r.length == 3);
+ assert(r[0] == 2);
+ assert(r[1] == 3);
+ assert(r[2] == 4);
+
+ r = v[0 .. 0];
+ assert(r.length == 0);
+
+ r = v[4 .. 4];
+ assert(r.length == 0);
+ }
+
+ /**
+ * Slicing assignment.
+ *
+ * Params:
+ * R = Type of the assigned slice or length of the static array should be
+ * assigned.
+ * value = New value (single value, input range or static array).
+ * i = Slice start.
+ * j = Slice end.
+ *
+ * Returns: Slice with the assigned part of the vector.
+ *
+ * Precondition: $(D_INLINECODE i <= j && j <= length
+ * && value.length == j - i)
+ */
+ Range!T opSliceAssign(R)(R value, const size_t i, const size_t j) @trusted
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ assert(j - i == walkLength(value));
+ }
+ body
+ {
+ copy(value, this.data[i .. j]);
+ return opSlice(i, j);
+ }
+
+ /// Ditto.
+ Range!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j)
+ {
+ return opSliceAssign!(T[])(value[], i, j);
+ }
+
+ /// Ditto.
+ Range!T opSliceAssign(ref T value, const size_t i, const size_t j) @trusted
+ in
+ {
+ assert(i <= j);
+ assert(j <= length);
+ }
+ body
+ {
+ fill(this.data[i .. j], value);
+ return opSlice(i, j);
+ }
+
+ /// Ditto.
+ Range!T opSliceAssign(T value, const size_t i, const size_t j)
+ {
+ return opSliceAssign(value, i, j);
+ }
+
+ ///
+ @nogc @safe unittest
+ {
+ auto v1 = Vector!int([3, 3, 3]);
+ auto v2 = Vector!int([1, 2]);
+
+ v1[0 .. 2] = 286;
+ assert(v1[0] == 286);
+ assert(v1[1] == 286);
+ assert(v1[2] == 3);
+
+ v2[0 .. $] = v1[1 .. 3];
+ assert(v2[0] == 286);
+ assert(v2[1] == 3);
+
+ v1[0 .. 2] = [5, 8];
+ assert(v1[0] == 5);
+ assert(v1[1] == 8);
+ assert(v1[2] == 3);
+ }
+
+ /**
+ * Returns an array used internally by the vector to store its owned elements.
+ * The length of the returned array may differ from the size of the allocated
+ * memory for the vector: the array contains only initialized elements, but
+ * not the reserved memory.
+ *
+ * Returns: The array with elements of this vector.
+ */
+ inout(T[]) get() inout @trusted
+ {
+ return this.data[0 .. length];
+ }
+
+ ///
+ unittest
+ {
+ auto v = Vector!int([1, 2, 4]);
+ auto data = v.get();
+
+ assert(data[0] == 1);
+ assert(data[1] == 2);
+ assert(data[2] == 4);
+ assert(data.length == 3);
+
+ data = v[1 .. 2].get();
+ assert(data[0] == 2);
+ assert(data.length == 1);
+ }
+
+ /**
+ * Assigns another vector.
+ *
+ * If $(D_PARAM that) is passed by value, it won't be copied, but moved.
+ * This vector will take the ownership over $(D_PARAM that)'s storage and
+ * the allocator.
+ *
+ * If $(D_PARAM that) is passed by reference, it will be copied.
+ *
+ * Params:
+ * R = Content type.
+ * that = The value should be assigned.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(R)(const ref R that)
+ if (is(Unqual!R == Vector))
+ {
+ return this = that[];
+ }
+
+ /// Ditto.
+ ref typeof(this) opAssign(R)(R that) @trusted
+ if (is(R == Vector))
+ {
+ swap(this.data, that.data);
+ swap(this.length_, that.length_);
+ swap(this.capacity_, that.capacity_);
+ swap(this.allocator_, that.allocator_);
+ return this;
+ }
+
+ /**
+ * Assigns a range to the vector.
+ *
+ * Params:
+ * R = Content type.
+ * that = The value should be assigned.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(R)(R that)
+ if (!isInfinite!R
+ && isInputRange!R
+ && isImplicitlyConvertible!(ElementType!R, T))
+ {
+ length = 0;
+ insertBack(that);
+ return this;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto v1 = const Vector!int([5, 15, 8]);
+ Vector!int v2;
+ v2 = v1;
+ assert(v1 == v2);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto v1 = const Vector!int([5, 15, 8]);
+ Vector!int v2;
+ v2 = v1[0 .. 2];
+ assert(equal(v1[0 .. 2], v2[]));
+ }
+
+ // Move assignment.
+ private @safe @nogc unittest
+ {
+ Vector!int v1;
+ v1 = Vector!int([5, 15, 8]);
+ }
+
+ /**
+ * Assigns a static array.
+ *
+ * Params:
+ * R = Static array size.
+ * that = Values to initialize the vector with.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(size_t R)(T[R] that)
+ {
+ return opAssign!(T[])(that[]);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto v1 = Vector!int([5, 15, 8]);
+ Vector!int v2;
+
+ v2 = [5, 15, 8];
+ assert(v1 == v2);
+ }
+
+ mixin DefaultAllocator;
}
///
unittest
{
- auto v = Vector!int([5, 15, 8]);
+ auto v = Vector!int([5, 15, 8]);
- assert(v.front == 5);
- assert(v[1] == 15);
- assert(v.back == 8);
+ assert(v.front == 5);
+ assert(v[1] == 15);
+ assert(v.back == 8);
- auto r = v[];
- r[0] = 7;
- assert(r.front == 7);
- assert(r.front == v.front);
+ auto r = v[];
+ r[0] = 7;
+ assert(r.front == 7);
+ assert(r.front == v.front);
}
@nogc unittest
{
- const v1 = Vector!int();
- const Vector!int v2;
- const v3 = Vector!int([1, 5, 8]);
- static assert(is(PointerTarget!(typeof(v3.vector)) == const(int)));
+ const v1 = Vector!int();
+ const Vector!int v2;
+ const v3 = Vector!int([1, 5, 8]);
+ static assert(is(PointerTarget!(typeof(v3.data)) == const(int)));
}
@nogc unittest
{
- // Test that const vectors return usable ranges.
- auto v = const Vector!int([1, 2, 4]);
- auto r1 = v[];
-
- assert(r1.back == 4);
- r1.popBack();
- assert(r1.back == 2);
- r1.popBack();
- assert(r1.back == 1);
- r1.popBack();
- assert(r1.length == 0);
-
- static assert(!is(typeof(r1[0] = 5)));
- static assert(!is(typeof(v[0] = 5)));
-
- const r2 = r1[];
- static assert(is(typeof(r2[])));
+ // Test that const vectors return usable ranges.
+ auto v = const Vector!int([1, 2, 4]);
+ auto r1 = v[];
+
+ assert(r1.back == 4);
+ r1.popBack();
+ assert(r1.back == 2);
+ r1.popBack();
+ assert(r1.back == 1);
+ r1.popBack();
+ assert(r1.length == 0);
+
+ static assert(!is(typeof(r1[0] = 5)));
+ static assert(!is(typeof(v[0] = 5)));
+
+ const r2 = r1[];
+ static assert(is(typeof(r2[])));
}
@nogc unittest
{
- Vector!int v1;
- const Vector!int v2;
-
- auto r1 = v1[];
- auto r2 = v1[];
-
- assert(r1.length == 0);
- assert(r2.empty);
- assert(r1 == r2);
-
- v1.insertBack([1, 2, 4]);
- assert(v1[] == v1);
- assert(v2[] == v2);
- assert(v2[] != v1);
- assert(v1[] != v2);
- assert(v1[].equal(v1[]));
- assert(v2[].equal(v2[]));
- assert(!v1[].equal(v2[]));
+ Vector!int v1;
+ const Vector!int v2;
+
+ auto r1 = v1[];
+ auto r2 = v1[];
+
+ assert(r1.length == 0);
+ assert(r2.empty);
+ assert(r1 == r2);
+
+ v1.insertBack([1, 2, 4]);
+ assert(v1[] == v1);
+ assert(v2[] == v2);
+ assert(v2[] != v1);
+ assert(v1[] != v2);
+ assert(v1[].equal(v1[]));
+ assert(v2[].equal(v2[]));
+ assert(!v1[].equal(v2[]));
}
@nogc unittest
{
- struct MutableEqualsStruct
- {
- int opEquals(typeof(this) that) @nogc
- {
- return true;
- }
- }
- struct ConstEqualsStruct
- {
- int opEquals(in typeof(this) that) const @nogc
- {
- return true;
- }
- }
- auto v1 = Vector!ConstEqualsStruct();
- auto v2 = Vector!ConstEqualsStruct();
- assert(v1 == v2);
- assert(v1[] == v2);
- assert(v1 == v2[]);
- assert(v1[].equal(v2[]));
-
- auto v3 = const Vector!ConstEqualsStruct();
- auto v4 = const Vector!ConstEqualsStruct();
- assert(v3 == v4);
- assert(v3[] == v4);
- assert(v3 == v4[]);
- assert(v3[].equal(v4[]));
-
- auto v7 = Vector!MutableEqualsStruct(1, MutableEqualsStruct());
- auto v8 = Vector!MutableEqualsStruct(1, MutableEqualsStruct());
- assert(v7 == v8);
- assert(v7[] == v8);
- assert(v7 == v8[]);
- assert(v7[].equal(v8[]));
+ struct MutableEqualsStruct
+ {
+ int opEquals(typeof(this) that) @nogc
+ {
+ return true;
+ }
+ }
+ struct ConstEqualsStruct
+ {
+ int opEquals(const typeof(this) that) const @nogc
+ {
+ return true;
+ }
+ }
+ auto v1 = Vector!ConstEqualsStruct();
+ auto v2 = Vector!ConstEqualsStruct();
+ assert(v1 == v2);
+ assert(v1[] == v2);
+ assert(v1 == v2[]);
+ assert(v1[].equal(v2[]));
+
+ auto v3 = const Vector!ConstEqualsStruct();
+ auto v4 = const Vector!ConstEqualsStruct();
+ assert(v3 == v4);
+ assert(v3[] == v4);
+ assert(v3 == v4[]);
+ assert(v3[].equal(v4[]));
+
+ auto v7 = Vector!MutableEqualsStruct(1, MutableEqualsStruct());
+ auto v8 = Vector!MutableEqualsStruct(1, MutableEqualsStruct());
+ assert(v7 == v8);
+ assert(v7[] == v8);
+ assert(v7 == v8[]);
+ assert(v7[].equal(v8[]));
}
@nogc unittest
{
- struct SWithDtor
- {
- ~this() @nogc
- {
- }
- }
- auto v = Vector!SWithDtor(); // Destructor can destroy empty vectors.
+ struct SWithDtor
+ {
+ ~this() @nogc
+ {
+ }
+ }
+ auto v = Vector!SWithDtor(); // Destructor can destroy empty vectors.
}
-unittest
+private unittest
+{
+ class A
+ {
+ }
+ A a1, a2;
+ auto v1 = Vector!A([a1, a2]);
+}
+
+private @safe @nogc unittest
{
- class A
- {
- }
- A a1, a2;
- auto v1 = Vector!A([a1, a2]);
+ auto v = Vector!int([5, 15, 8]);
+ {
+ size_t i;
+
+ foreach (e; v)
+ {
+ assert(i != 0 || e == 5);
+ assert(i != 1 || e == 15);
+ assert(i != 2 || e == 8);
+ ++i;
+ }
+ assert(i == 3);
+ }
+ {
+ size_t i = 3;
+
+ foreach_reverse (e; v)
+ {
+ --i;
+ assert(i != 2 || e == 8);
+ assert(i != 1 || e == 15);
+ assert(i != 0 || e == 5);
+ }
+ assert(i == 0);
+ }
}
diff --git a/source/tanya/math/mp.d b/source/tanya/math/mp.d
index d36ec18..57a8d5c 100644
--- a/source/tanya/math/mp.d
+++ b/source/tanya/math/mp.d
@@ -3,1058 +3,1243 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * Arbitrary precision arithmetic.
+ *
+ * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
- * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
- */
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ */
module tanya.math.mp;
-import std.algorithm.iteration;
-import std.algorithm.searching;
-import std.algorithm.mutation;
-import std.math;
+import core.exception;
+import std.algorithm;
import std.range;
import std.traits;
+import tanya.math;
import tanya.memory;
/**
+ * Algebraic sign.
+ */
+enum Sign : bool
+{
+ /// The value is positive or `0`.
+ positive = false,
+
+ /// The value is negative.
+ negative = true,
+}
+
+/**
* Mutliple precision integer.
*/
struct Integer
{
- package ubyte[] rep;
- private bool sign;
-
- /**
- * Creates a multiple precision integer.
- *
- * Params:
- * T = Value type.
- * value = Initial value.
- * allocator = Allocator.
- *
- * Precondition: $(D_INLINECODE allocator !is null)
- */
- this(T)(in auto ref T value, shared Allocator allocator = defaultAllocator)
- nothrow @safe @nogc
- if (isIntegral!T || is(T == Integer))
- {
- this(allocator);
- static if (isIntegral!T)
- {
- assignInt(value);
- }
- else if (value.length > 0)
- {
- allocator.resize!(ubyte, false)(rep, value.length);
- rep[] = value.rep[];
- sign = value.sign;
- }
- }
-
- /// Ditto.
- this(shared Allocator allocator) pure nothrow @safe @nogc
- in
- {
- assert(allocator !is null);
- }
- body
- {
- allocator_ = allocator;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(79);
- assert(h1.length == 1);
- assert(h1.rep[0] == 79);
- assert(!h1.sign);
-
- auto h2 = Integer(-2);
- assert(h2.length == 1);
- assert(h2.rep[0] == 2);
- assert(h2.sign);
- }
-
- @disable this(this);
-
- /**
- * Destroys the internal representation.
- */
- ~this() nothrow @safe @nogc
- {
- allocator.dispose(rep);
- }
-
- /*
- * Figures out the minimum amount of space this value will take
- * up in bytes and resizes the internal storage. Sets the sign.
- */
- private void assignInt(T)(in ref T value)
- nothrow @safe @nogc
- in
- {
- static assert(isIntegral!T);
- }
- body
- {
- ubyte size = ulong.sizeof;
- ulong mask;
-
- static if (isSigned!T)
- {
- sign = value < 0 ? true : false;
- immutable absolute = value.abs;
- }
- else
- {
- sign = false;
- alias absolute = value;
- }
- for (mask = 0xff00000000000000; mask >= 0xff; mask >>= 8)
- {
- if (absolute & mask)
- {
- break;
- }
- --size;
- }
- allocator.resize!(ubyte, false)(rep, size);
-
- /* Work backward through the int, masking off each byte (up to the
- first 0 byte) and copy it into the internal representation in
- big-endian format. */
- mask = 0xff;
- ushort shift;
- for (auto i = rep.length; i; --i, mask <<= 8, shift += 8)
- {
- rep[i - 1] = cast(ubyte) ((absolute & mask) >> shift);
- }
- }
-
- /**
- * Assigns a new value.
- *
- * Params:
- * T = Value type.
- * value = Value.
- *
- * Returns: $(D_KEYWORD this).
- */
- ref Integer opAssign(T)(in auto ref T value) nothrow @safe @nogc
- if (isIntegral!T || is(T == Integer))
- {
- static if (isIntegral!T)
- {
- assignInt(value);
- }
- else
- {
- allocator.resize!(ubyte, false)(rep, value.length);
- rep[0 .. $] = value.rep[0 .. $];
- sign = value.sign;
- }
- return this;
- }
-
- private @nogc unittest
- {
- auto h = Integer(1019);
- assert(h.length == 2);
- assert(h.rep[0] == 0b00000011 && h.rep[1] == 0b11111011);
-
- h = 3337;
- assert(h.length == 2);
- assert(h.rep[0] == 0b00001101 && h.rep[1] == 0b00001001);
-
- h = 688;
- assert(h.length == 2);
- assert(h.rep[0] == 0b00000010 && h.rep[1] == 0b10110000);
-
- h = 0;
- assert(h.length == 0);
- }
-
- /**
- * Returns: Integer size.
- */
- @property size_t length() const pure nothrow @safe @nogc
- {
- return rep.length;
- }
-
- /**
- * Params:
- * h = The second integer.
- *
- * Returns: Whether the two integers are equal.
- */
- bool opEquals(in Integer h) const nothrow @safe @nogc
- {
- return rep == h.rep;
- }
-
- /// Ditto.
- bool opEquals(in ref Integer h) const nothrow @safe @nogc
- {
- return rep == h.rep;
- }
-
- ///
- unittest
- {
- auto h1 = Integer(1019);
-
- assert(h1 == Integer(1019));
- assert(h1 != Integer(109));
- }
-
- /**
- * Params:
- * h = The second integer.
- *
- * Returns: A positive number if $(D_INLINECODE this > h), a negative
- * number if $(D_INLINECODE this > h), `0` otherwise.
- */
- int opCmp(in ref Integer h) const nothrow @safe @nogc
- {
- if (length > h.length)
- {
- return 1;
- }
- if (length < h.length)
- {
- return -1;
- }
- // Otherwise, keep searching through the representational integers
- // until one is bigger than another - once we've found one, it's
- // safe to stop, since the lower order bytes can't affect the
- // comparison
- for (size_t i, j; i < length && j < h.length; ++i, ++j)
- {
- if (rep[i] < h.rep[j])
- {
- return -1;
- }
- else if (rep[i] > h.rep[j])
- {
- return 1;
- }
- }
- // if we got all the way to the end without a comparison, the
- // two are equal
- return 0;
- }
-
- /// Ditto.
- int opCmp(in Integer h) const nothrow @safe @nogc
- {
- return opCmp(h);
- }
-
- ///
- unittest
- {
- auto h1 = Integer(1019);
- auto h2 = Integer(1019);
- assert(h1 == h2);
-
- h2 = 3337;
- assert(h1 < h2);
-
- h2 = 688;
- assert(h1 > h2);
- }
-
- private void add(in ref ubyte[] h) nothrow @safe @nogc
- {
- uint sum;
- uint carry = 0;
- ubyte[] tmp;
-
- if (h.length > length)
- {
- allocator.resize!(ubyte, false)(tmp, h.length);
- tmp[0 .. h.length] = 0;
- tmp[h.length - length .. $] = rep[0 .. length];
- swap(rep, tmp);
- }
-
- auto i = length;
- auto j = h.length;
-
- do
- {
- --i;
- if (j)
- {
- --j;
- sum = rep[i] + h[j] + carry;
- }
- else
- {
- sum = rep[i] + carry;
- }
- carry = sum > 0xff;
- rep[i] = cast(ubyte) sum;
- }
- while (i);
-
- if (carry)
- {
- // Still overflowed; allocate more space
- allocator.resize!(ubyte, false)(tmp, length + 1);
- tmp[1 .. $] = rep[0 .. length];
- tmp[0] = 0x01;
- swap(rep, tmp);
- }
- allocator.dispose(tmp);
- }
-
- private void subtract(in ref ubyte[] h) nothrow @trusted @nogc
- {
- auto i = rep.length;
- auto j = h.length;
- uint borrow = 0;
-
- do
- {
- int difference;
- --i;
-
- if (j)
- {
- --j;
- difference = rep[i] - h[j] - borrow;
- }
- else
- {
- difference = rep[i] - borrow;
- }
- borrow = difference < 0;
- rep[i] = cast(ubyte) difference;
- }
- while (i);
-
- if (borrow && i && rep[i - 1])
- {
- --rep[i - 1];
- }
- // Go through the representation array and see how many of the
- // left-most bytes are unused. Remove them and resize the array.
- immutable offset = rep.countUntil!((const ref a) => a != 0);
- if (offset > 0)
- {
- ubyte[] tmp = cast(ubyte[]) allocator.allocate(rep.length - offset);
- tmp[0 .. $] = rep[offset .. $];
- allocator.deallocate(rep);
- rep = tmp;
- }
- else if (offset == -1)
- {
- allocator.dispose(rep);
- }
- }
-
- /**
- * Assignment operators with another $(D_PSYMBOL Integer).
- *
- * Params:
- * op = Operation.
- * h = The second integer.
- *
- * Returns: $(D_KEYWORD this).
- */
- ref Integer opOpAssign(string op)(in auto ref Integer h) nothrow @safe @nogc
- if ((op == "+") || (op == "-"))
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- static if (op == "+")
- {
- if (h.sign == sign)
- {
- add(h.rep);
- }
- else
- {
- if (this >= h)
- {
- subtract(h.rep);
- }
- else
- {
- auto tmp = Integer(h);
- tmp.subtract(rep);
- swap(rep, tmp.rep);
- sign = length == 0 ? false : h.sign;
- }
- }
- }
- else
- {
- if (h.sign == sign)
- {
- if (this >= h)
- {
- subtract(h.rep);
- }
- else
- {
- auto tmp = Integer(h);
- tmp.subtract(rep);
- swap(rep, tmp.rep);
- sign = length == 0 ? false : !sign;
- }
- }
- else
- {
- add(h.rep);
- }
- }
- return this;
- }
-
- private @nogc unittest
- {
- {
- auto h1 = Integer(1019);
- auto h2 = Integer(3337);
- h1 += h2;
- assert(h1.length == 2);
- assert(h1.rep[0] == 0x11 && h1.rep[1] == 0x04);
- }
- {
- auto h1 = Integer(4356);
- auto h2 = Integer(2_147_483_647);
- ubyte[4] expected = [0x80, 0x00, 0x11, 0x03];
- h1 += h2;
- assert(h1.rep == expected);
- }
- {
- auto h1 = Integer(2147488003L);
- auto h2 = Integer(2_147_483_647);
- ubyte[5] expected = [0x01, 0x00, 0x00, 0x11, 0x02];
- h1 += h2;
- assert(h1.rep == expected);
- }
- {
- auto h1 = Integer(3);
- auto h2 = Integer(4);
- h1 -= h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 0x01);
- assert(h1.sign);
- }
- }
-
- private @nogc unittest
- {
- {
- auto h1 = Integer(8589934590L);
- auto h2 = Integer(2147483647);
- ubyte[5] expected = [0x01, 0x7f, 0xff, 0xff, 0xff];
-
- h1 -= h2;
- assert(h1.rep == expected);
- }
- {
- auto h1 = Integer(6442450943);
- auto h2 = Integer(4294967294);
- ubyte[4] expected = [0x80, 0x00, 0x00, 0x01];
- h1 -= h2;
- assert(h1.rep == expected);
- }
- {
- auto h1 = Integer(2147483649);
- auto h2 = Integer(h1);
- h1 -= h2;
- assert(h1.length == 0);
- }
- }
-
- /// Ditto.
- ref Integer opOpAssign(string op)(in auto ref Integer h) nothrow @safe @nogc
- if (op == "*")
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- auto i = h.rep.length;
- if (length == 0)
- {
- return this;
- }
- else if (i == 0)
- {
- opAssign(0);
- return this;
- }
- auto temp = Integer(this, allocator);
- immutable sign = sign == h.sign ? false : true;
-
- opAssign(0);
- do
- {
- --i;
- for (ubyte mask = 0x01; mask; mask <<= 1)
- {
- if (mask & h.rep[i])
- {
- opOpAssign!"+"(temp);
- }
- temp <<= 1;
- }
- }
- while (i);
- this.sign = sign;
-
- return this;
- }
-
- ///
- unittest
- {
- auto h1 = Integer(123);
- auto h2 = Integer(456);
- h1 *= h2;
- assert(cast(long) h1 == 56088);
- }
-
- private unittest
- {
- assert((Integer(1) * Integer()).length == 0);
- }
-
- /// Ditto.
- ref Integer opOpAssign(string op)(in auto ref Integer h) nothrow @safe @nogc
- if ((op == "/") || (op == "%"))
- in
- {
- assert(h.length > 0, "Division by zero.");
- }
- body
- {
- auto divisor = Integer(h, allocator);
- size_t bitSize;
-
- // First, left-shift divisor until it's >= than the dividend
- for (; opCmp(divisor) > 0; ++bitSize)
- {
- divisor <<= 1;
- }
- static if (op == "/")
- {
- ubyte[] quotient;
- allocator.resize!(ubyte, false)(quotient, bitSize / 8 + 1);
- }
-
- // "bitPosition" keeps track of which bit, of the quotient,
- // is being set or cleared on the current operation.
- auto bitPosition = 8 - (bitSize % 8) - 1;
- do
- {
- if (opCmp(divisor) >= 0)
- {
- opOpAssign!"-"(divisor);
- static if (op == "/")
- {
- quotient[bitPosition / 8] |= (0x80 >> (bitPosition % 8));
- }
- }
- if (bitSize)
- {
- divisor >>= 1;
- }
- else
- {
- break;
- }
- ++bitPosition, --bitSize;
- }
- while (true);
-
- static if (op == "/")
- {
- allocator.dispose(rep);
- rep = quotient;
- sign = sign == h.sign ? false : true;
- }
- return this;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(18);
- auto h2 = Integer(4);
- h1 %= h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 0x02);
-
- h1 = 8;
- h1 %= h2;
- assert(h1.length == 0);
-
- h1 = 7;
- h1 %= h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 0x03);
-
- h1 = 56088;
- h2 = 456;
- h1 /= h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 0x7b);
- }
-
- /// Ditto.
- ref Integer opOpAssign(string op)(in auto ref Integer exp) nothrow @safe @nogc
- if (op == "^^")
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- auto i = exp.rep.length;
- auto tmp1 = Integer(this, allocator);
- auto tmp2 = Integer(allocator);
-
- opAssign(1);
-
- do
- {
- --i;
- for (ubyte mask = 0x01; mask; mask <<= 1)
- {
- if (exp.rep[i] & mask)
- {
- opOpAssign!"*"(tmp1);
- }
- // Square tmp1
- tmp2 = tmp1;
- tmp1 *= tmp2;
- }
- }
- while (i);
-
- return this;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(2);
- auto h2 = Integer(4);
-
- h1 ^^= h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 0x10);
-
- h1 = Integer(2342);
- h1 ^^= h2;
- ubyte[6] expected = [0x1b, 0x5c, 0xab, 0x9c, 0x31, 0x10];
- assert(h1.rep == expected);
- }
-
- /**
- * Unary operators.
- *
- * Params:
- * op = Operation.
- *
- * Returns: New $(D_PSYMBOL Integer).
- */
- Integer opUnary(string op)() nothrow @safe @nogc
- if ((op == "+") || (op == "-") || (op == "~"))
- {
- auto h = Integer(this, allocator);
- static if (op == "-")
- {
- h.sign = !h.sign;
- }
- else static if (op == "~")
- {
- h.rep.each!((ref a) => a = ~a);
- }
- return h;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(79);
- Integer h2;
-
- h2 = +h1;
- assert(h2.length == 1);
- assert(h2.rep[0] == 79);
- assert(!h2.sign);
- assert(h2 !is h1);
-
- h2 = -h1;
- assert(h2.length == 1);
- assert(h2.rep[0] == 79);
- assert(h2.sign);
-
- h1 = -h2;
- assert(h2.length == 1);
- assert(h2.rep[0] == 79);
- assert(h2.sign);
- assert(h2 !is h1);
-
- h1 = -h2;
- assert(h1.length == 1);
- assert(h1.rep[0] == 79);
- assert(!h1.sign);
-
- h2 = ~h1;
- assert(h2.rep[0] == ~cast(ubyte) 79);
- }
-
- private void decrement() nothrow @safe @nogc
- {
- immutable size = rep.retro.countUntil!((const ref a) => a != 0);
- if (rep[0] == 1)
- {
- allocator.resize!(ubyte, false)(rep, rep.length - 1);
- rep[0 .. $] = typeof(rep[0]).max;
- }
- else
- {
- --rep[$ - size - 1];
- rep[$ - size .. $] = typeof(rep[0]).max;
- }
- }
-
- private void increment() nothrow @safe @nogc
- {
- auto size = rep.retro.countUntil!((const ref a) => a != typeof(rep[0]).max);
- if (size == -1)
- {
- size = length;
- allocator.resize!(ubyte, false)(rep, rep.length + 1);
- rep[0] = 1;
- }
- else
- {
- ++rep[$ - size - 1];
- }
- rep[$ - size .. $] = 0;
- }
-
- /**
- * Increment/decrement.
- *
- * Params:
- * op = Operation.
- *
- * Returns: $(D_KEYWORD this).
- */
- ref Integer opUnary(string op)() nothrow @safe @nogc
- if ((op == "++") || (op == "--"))
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- static if (op == "++")
- {
- if (sign)
- {
- decrement();
- if (length == 0)
- {
- sign = false;
- }
- }
- else
- {
- increment();
- }
- }
- else if (sign)
- {
- increment();
- }
- else
- {
- decrement();
- }
- return this;
- }
-
- private @nogc unittest
- {
- Integer h;
-
- ++h;
- assert(h.length == 1);
- assert(h.rep[0] == 0x01);
-
- --h;
- assert(h.length == 0);
-
- h = 511;
- ++h;
- assert(h.length == 2);
- assert(h.rep[0] == 0x02 && h.rep[1] == 0x00);
-
- --h;
- assert(h.length == 2);
- assert(h.rep[0] == 0x01 && h.rep[1] == 0xff);
-
- h = 79;
- ++h;
- assert(h.length == 1);
- assert(h.rep[0] == 0x50);
-
- --h;
- assert(h.length == 1);
- assert(h.rep[0] == 0x4f);
-
- h = 65535;
- ++h;
- assert(h.length == 3);
- assert(h.rep[0] == 0x01 && h.rep[1] == 0x00 && h.rep[2] == 0x00);
-
- --h;
- assert(h.length == 2);
- assert(h.rep[0] == 0xff && h.rep[1] == 0xff);
-
- h = -2;
- ++h;
- assert(h.length == 1);
- assert(h.rep[0] == 0x01);
- }
-
- /**
- * Casting.
- *
- * Params:
- * T = Target type.
- *
- * Returns: $(D_KEYWORD false) if the $(D_PSYMBOL Integer) is 0,
- * $(D_KEYWORD true) otherwise.
- */
- T opCast(T : bool)() const pure nothrow @safe @nogc
- {
- return length == 0 ? false : true;
- }
-
- /// Ditto.
- T opCast(T)() const pure nothrow @safe @nogc
- if (isIntegral!T && isSigned!T)
- {
- ulong ret;
- for (size_t i = length, j; i > 0 && j <= T.sizeof * 4; --i, j += 8)
- {
- ret |= cast(T) (rep[i - 1]) << j;
- }
- return sign ? -ret : ret;
- }
-
- /// Ditto.
- T opCast(T)() const pure nothrow @safe @nogc
- if (isIntegral!T && isUnsigned!T)
- {
- ulong ret;
- for (size_t i = length, j; i > 0 && j <= T.sizeof * 8; --i, j += 8)
- {
- ret |= cast(T) (rep[i - 1]) << j;
- }
- return ret;
- }
-
- ///
- unittest
- {
- auto h = Integer(79);
- assert(cast(long) h == 79);
-
- h = -79;
- assert(cast(long) h == -79);
-
- h = 4294967295;
- assert(cast(long) h == 4294967295);
-
- h = -4294967295;
- assert(cast(long) h == -4294967295);
- }
-
- /**
- * Shift operations.
- *
- * Params:
- * op = Left or right shift.
- * n = Number of bits to shift by.
- *
- * Returns: An $(D_PSYMBOL Integer) shifted by $(D_PARAM n) bits.
- */
- ref Integer opOpAssign(string op)(in auto ref size_t n) nothrow @safe @nogc
- if (op == ">>")
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- immutable step = n / 8;
-
- if (step >= rep.length)
- {
- allocator.resize!(ubyte, false)(rep, 0);
- return this;
- }
-
- size_t i, j;
- ubyte carry;
- immutable bit = n % 8;
- immutable delta = 8 - bit;
-
- carry = cast(ubyte) (rep[0] << delta);
- rep[0] = (rep[0] >> bit);
- if (rep[0])
- {
- ++j;
- }
- for (i = 1; i < rep.length; ++i)
- {
- immutable oldCarry = carry;
- carry = cast(ubyte) (rep[i] << delta);
- rep[j] = (rep[i] >> bit | oldCarry);
- ++j;
- }
- allocator.resize!(ubyte, false)(rep, rep.length - n / 8 - (i == j ? 0 : 1));
-
- return this;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(4294967294);
- h1 >>= 10;
- assert(h1.length == 3);
- assert(h1.rep[0] == 0x3f && h1.rep[1] == 0xff && h1.rep[2] == 0xff);
-
- h1 = 27336704;
- h1 >>= 1;
- assert(h1.length == 3);
- assert(h1.rep[0] == 0xd0 && h1.rep[1] == 0x90 && h1.rep[2] == 0x00);
-
- h1 = 4294967294;
- h1 >>= 20;
- assert(h1.length == 2);
- assert(h1.rep[0] == 0x0f && h1.rep[1] == 0xff);
-
- h1 >>= 0;
- assert(h1.length == 2);
- assert(h1.rep[0] == 0x0f && h1.rep[1] == 0xff);
-
- h1 >>= 20;
- assert(h1.length == 0);
-
- h1 >>= 2;
- assert(h1.length == 0);
-
- h1 = 1431655765;
- h1 >>= 16;
- assert(h1.length == 2);
- assert(h1.rep[0] == 0x55 && h1.rep[1] == 0x55);
-
- h1 >>= 16;
- assert(h1.length == 0);
- }
-
- /// Ditto.
- ref Integer opOpAssign(string op)(in auto ref size_t n) nothrow @safe @nogc
- if (op == "<<")
- out
- {
- assert(rep.length || !sign, "0 should be positive.");
- assert(!rep.length || rep[0]);
- }
- body
- {
- ubyte carry;
- auto i = rep.length;
- size_t j;
- immutable bit = n % 8;
- immutable delta = 8 - bit;
-
- if (cast(ubyte) (rep[0] >> delta))
- {
- allocator.resize!(ubyte, false)(rep, i + n / 8 + 1);
- j = i + 1;
- }
- else
- {
- allocator.resize!(ubyte, false)(rep, i + n / 8);
- j = i;
- }
- do
- {
- --i, --j;
- immutable oldCarry = carry;
- carry = rep[i] >> delta;
- rep[j] = cast(ubyte) ((rep[i] << bit) | oldCarry);
- }
- while (i);
- if (carry)
- {
- rep[0] = carry;
- }
- return this;
- }
-
- private @nogc unittest
- {
- auto h1 = Integer(4294967295);
- ubyte[5] expected = [0x01, 0xff, 0xff, 0xff, 0xfe];
- h1 <<= 1;
- assert(h1.rep == expected);
- }
-
- /// Ditto.
- Integer opBinary(string op)(in auto ref size_t n) nothrow @safe @nogc
- if (op == "<<" || op == ">>" || op == "+" || op == "-" || op == "/"
- || op == "*" || op == "^^" || op == "%")
- {
- auto ret = Integer(this, allocator);
- mixin("ret " ~ op ~ "= n;");
- return ret;
- }
-
- ///
- unittest
- {
- auto h1 = Integer(425);
- auto h2 = h1 << 1;
- assert(cast(long) h2 == 850);
-
- h2 = h1 >> 1;
- assert(cast(long) h2 == 212);
- }
-
- /// Ditto.
- Integer opBinary(string op)(in auto ref Integer h) nothrow @safe @nogc
- if (op == "+" || op == "-" || op == "/"
- || op == "*" || op == "^^" || op == "%")
- {
- auto ret = Integer(this, allocator);
- mixin("ret " ~ op ~ "= h;");
- return ret;
- }
-
- mixin DefaultAllocator;
+ private size_t size;
+ package ubyte* rep;
+ package Sign sign;
+
+ pure nothrow @safe @nogc invariant
+ {
+ assert(this.size > 0 || !this.sign, "0 should be positive.");
+ }
+
+ /**
+ * Creates a multiple precision integer.
+ *
+ * Params:
+ * T = Value type.
+ * value = Initial value.
+ * allocator = Allocator.
+ *
+ * Precondition: $(D_INLINECODE allocator !is null)
+ */
+ this(T)(const T value, shared Allocator allocator = defaultAllocator)
+ if (isIntegral!T)
+ {
+ this(allocator);
+ assign(value);
+ }
+
+ /// Ditto.
+ this(const ref Integer value, shared Allocator allocator = defaultAllocator)
+ nothrow @safe @nogc
+ {
+ this(allocator);
+ assign(value);
+ }
+
+ /// Ditto.
+ this(Integer value, shared Allocator allocator = defaultAllocator)
+ nothrow @safe @nogc
+ {
+ this(allocator);
+ if (allocator is value.allocator)
+ {
+ this.rep = value.rep;
+ this.size = value.size;
+ this.sign = value.sign;
+ value.rep = null;
+ value.size = 0;
+ value.sign = Sign.positive;
+ }
+ else
+ {
+ assign(value);
+ }
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator) pure nothrow @safe @nogc
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ this.allocator_ = allocator;
+ }
+
+ private @nogc unittest
+ {
+ {
+ auto integer = Integer(79);
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 79);
+ assert(!integer.sign);
+ }
+ {
+ auto integer = Integer(-2);
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 2);
+ assert(integer.sign);
+ }
+ }
+
+ /**
+ * Constructs the integer from a sign-magnitude $(D_KEYWORD ubyte) range.
+ *
+ * Params:
+ * R = Range type.
+ * sign = Sign.
+ * value = Range.
+ * allocator = Allocator.
+ *
+ * Precondition: $(D_INLINECODE allocator !is null)
+ */
+ this(R)(const Sign sign, R value, shared Allocator allocator = defaultAllocator)
+ if (isInputRange!R && hasLength!R && is(Unqual!(ElementType!R) == ubyte))
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ this(allocator);
+ while (!value.empty && value.front == 0)
+ {
+ value.popFront();
+ }
+ this.rep = allocator.resize(this.rep[0 .. this.size], value.length).ptr;
+ this.size = value.length;
+ this.sign = sign;
+ value.copy(this.rep[0 .. this.size]);
+ }
+
+ /**
+ * Copies the integer.
+ */
+ this(this) nothrow @trusted @nogc
+ {
+ auto tmp = allocator.resize!ubyte(null, this.size);
+ this.rep[0 .. this.size].copy(tmp);
+ this.rep = tmp.ptr;
+ }
+
+ /**
+ * Destroys the integer.
+ */
+ ~this() nothrow @trusted @nogc
+ {
+ allocator.deallocate(this.rep[0 .. this.size]);
+ }
+
+ /*
+ * Figures out the minimum amount of space this value will take
+ * up in bytes and resizes the internal storage. Sets the sign.
+ */
+ private void assign(T)(const ref T value) @trusted
+ if (isIntegral!T)
+ {
+ ubyte size = ulong.sizeof;
+ ulong mask;
+
+ this.sign = Sign.positive; // Reset the sign.
+ static if (isSigned!T)
+ {
+ const absolute = value.abs;
+ }
+ else
+ {
+ alias absolute = value;
+ }
+ for (mask = 0xff00000000000000; mask >= 0xff; mask >>= 8)
+ {
+ if (absolute & mask)
+ {
+ break;
+ }
+ --size;
+ }
+
+ this.rep = allocator.resize(this.rep[0 .. this.size], size).ptr;
+ this.size = size;
+ static if (isSigned!T)
+ {
+ this.sign = value < 0 ? Sign.negative : Sign.positive;
+ }
+
+ /* Work backward through the int, masking off each byte (up to the
+ first 0 byte) and copy it into the internal representation in
+ big-endian format. */
+ mask = 0xff;
+ ubyte shift;
+ for (auto i = this.size; i; --i, mask <<= 8, shift += 8)
+ {
+ this.rep[i - 1] = cast(ubyte) ((absolute & mask) >> shift);
+ }
+ }
+
+ private void assign(const ref Integer value) nothrow @trusted @nogc
+ {
+ this.rep = allocator.resize(this.rep[0 .. this.size], value.size).ptr;
+ this.size = value.size;
+ this.sign = value.sign;
+ value.rep[0 .. value.size].copy(this.rep[0 .. this.size]);
+ }
+
+ /**
+ * Returns: Integer size.
+ */
+ @property size_t length() const pure nothrow @safe @nogc
+ {
+ return this.size;
+ }
+
+ /**
+ * Assigns a new value.
+ *
+ * Params:
+ * T = Value type.
+ * value = Value.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref Integer opAssign(T)(const T value)
+ if (isIntegral!T)
+ {
+ assign(value);
+ return this;
+ }
+
+ /// Ditto.
+ ref Integer opAssign(const ref Integer value) nothrow @safe @nogc
+ {
+ assign(value);
+ return this;
+ }
+
+ /// Ditto.
+ ref Integer opAssign(Integer value) nothrow @safe @nogc
+ {
+ swap(this.rep, value.rep);
+ swap(this.sign, value.sign);
+ swap(this.size, value.size);
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto integer = Integer(1019);
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0b00000011 && integer.rep[1] == 0b11111011);
+
+ integer = 3337;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0b00001101 && integer.rep[1] == 0b00001001);
+
+ integer = 688;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0b00000010 && integer.rep[1] == 0b10110000);
+
+ integer = 0;
+ assert(integer.length == 0);
+ }
+
+ /*
+ * Extend the space for this by 1 byte and set the LSB to 1.
+ */
+ private void expand() nothrow @trusted @nogc
+ {
+ rep = allocator.resize(this.rep[0 .. this.size], this.size + 1).ptr;
+ ++this.size;
+ auto target = this.rep[1 .. this.size].retro;
+ this.rep[0 .. this.size - 1].retro.copy(target);
+ this.rep[0] = 0x01;
+ }
+
+ /*
+ * Go through this and see how many of the left-most bytes are unused.
+ * Remove them and resize this appropriately.
+ */
+ private void contract() nothrow @trusted @nogc
+ {
+ const i = this.rep[0 .. this.size].countUntil!((const ref a) => a != 0);
+
+ if (i > 0)
+ {
+ this.rep[i .. this.size].copy(this.rep[0 .. this.size - i]);
+ this.rep = allocator.resize(this.rep[0 .. this.size], this.size - i).ptr;
+ this.size -= i;
+ }
+ else if (i == -1)
+ {
+ this.sign = Sign.positive;
+ this.rep = allocator.resize(this.rep[0 .. this.size], 0).ptr;
+ this.size = 0;
+ }
+ }
+
+ private void add(const ref Integer summand) nothrow @trusted @nogc
+ {
+ if (summand.length > this.length)
+ {
+ const delta = summand.size - this.size;
+ auto tmp = this.rep[0 .. this.size];
+
+ this.rep = allocator.resize!ubyte(null, summand.size).ptr;
+ tmp.copy(this.rep[delta .. summand.size]);
+ this.rep[0 .. delta].initializeAll();
+
+ allocator.deallocate(tmp[0 .. this.size]);
+ this.size = summand.size;
+ }
+
+ bool carry;
+ size_t i = this.size;
+ size_t j = summand.size;
+ do
+ {
+ uint sum;
+ --i;
+ if (j)
+ {
+ --j;
+ sum = this.rep[i] + summand.rep[j] + carry;
+ }
+ else
+ {
+ sum = this.rep[i] + carry;
+ }
+
+ carry = sum > 0xff;
+ this.rep[i] = cast(ubyte) sum;
+ }
+ while (i);
+
+ if (carry)
+ {
+ // Still overflowed; allocate more space
+ expand();
+ }
+ }
+
+ private void subtract(const ref Integer subtrahend) nothrow @trusted @nogc
+ {
+ size_t i = this.size;
+ size_t j = subtrahend.size;
+ bool borrow;
+
+ while (i)
+ {
+ int difference;
+ --i;
+
+ if (j)
+ {
+ --j;
+ difference = this.rep[i] - subtrahend.rep[j] - borrow;
+ }
+ else
+ {
+ difference = this.rep[i] - borrow;
+ }
+ borrow = difference < 0;
+ this.rep[i] = cast(ubyte) difference;
+ }
+
+ if (borrow && i > 0)
+ {
+ if (this.rep[i - 1]) // Don't borrow i
+ {
+ --this.rep[i - 1];
+ }
+ }
+ contract();
+ }
+
+ private int compare(const ref Integer that) const nothrow @trusted @nogc
+ {
+ if (this.length > that.length)
+ {
+ return 1;
+ }
+ else if (this.length < that.length)
+ {
+ return -1;
+ }
+ return this.rep[0 .. this.size].cmp(that.rep[0 .. that.size]);
+ }
+
+ /**
+ * Comparison.
+ *
+ * Params:
+ * that = The second integer.
+ *
+ * Returns: A positive number if $(D_INLINECODE this > that), a negative
+ * number if $(D_INLINECODE this < that), `0` otherwise.
+ */
+ int opCmp(I : Integer)(const auto ref I that) const
+ {
+ if (this.sign != that.sign)
+ {
+ return this.sign ? -1 : 1;
+ }
+ return compare(that);
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto integer1 = Integer(1019);
+ auto integer2 = Integer(1019);
+ assert(integer1 == integer2);
+
+ integer2 = 3337;
+ assert(integer1 < integer2);
+
+ integer2 = 688;
+ assert(integer1 > integer2);
+
+ integer2 = -3337;
+ assert(integer1 > integer2);
+ }
+
+ /// Ditto.
+ int opCmp(I)(const auto ref I that) const
+ if (isIntegral!I)
+ {
+ if (that < 0 && !this.sign)
+ {
+ return 1;
+ }
+ else if (that > 0 && this.sign)
+ {
+ return -1;
+ }
+ else if (this.length > I.sizeof)
+ {
+ return this.sign ? -1 : 1;
+ }
+
+ const diff = (cast(I) this) - that;
+ if (diff > 0)
+ {
+ return 1;
+ }
+ else if (diff < 0)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto integer = Integer(1019);
+
+ assert(integer == 1019);
+ assert(integer < 3337);
+ assert(integer > 688);
+ assert(integer > -3337);
+ }
+
+ /**
+ * Params:
+ * that = The second integer.
+ *
+ * Returns: Whether the two integers are equal.
+ */
+ bool opEquals(I)(const auto ref I that) const
+ if (is(I : Integer) || isIntegral!I)
+ {
+ return opCmp!I(that) == 0;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto integer = Integer(1019);
+
+ assert(integer == Integer(1019));
+ assert(integer != Integer(109));
+ }
+
+ /**
+ * Assignment operators with another $(D_PSYMBOL Integer).
+ *
+ * Params:
+ * op = Operation.
+ * operand = The second operand.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref Integer opOpAssign(string op : "+")(const auto ref Integer operand)
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ if (this.sign == operand.sign)
+ {
+ add(operand);
+ }
+ else
+ {
+ if (this >= operand)
+ {
+ subtract(operand);
+ }
+ else
+ {
+ // Swap the operands.
+ auto tmp = Integer(this, this.allocator);
+ this = Integer(operand, this.allocator);
+ subtract(tmp);
+ this.sign = operand.sign;
+ }
+
+ }
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ {
+ auto h1 = Integer(1019);
+ auto h2 = Integer(3337);
+ h1 += h2;
+ assert(h1.length == 2);
+ assert(h1.rep[0] == 0x11 && h1.rep[1] == 0x04);
+ }
+ {
+ auto h1 = Integer(4356);
+ auto h2 = Integer(2_147_483_647);
+ ubyte[4] expected = [0x80, 0x00, 0x11, 0x03];
+ h1 += h2;
+ assert(h1.rep[0 .. h1.size] == expected);
+ }
+ {
+ auto h1 = Integer(2147488003L);
+ auto h2 = Integer(2_147_483_647);
+ ubyte[5] expected = [0x01, 0x00, 0x00, 0x11, 0x02];
+ h1 += h2;
+ assert(h1.rep[0 .. h1.size] == expected);
+ }
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op : "-")(const auto ref Integer operand)
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ if (operand.sign == this.sign)
+ {
+ if (this >= operand)
+ {
+ subtract(operand);
+ }
+ else
+ {
+ // Swap the operands.
+ auto tmp = Integer(this, this.allocator);
+ this = Integer(operand, this.allocator);
+ subtract(tmp);
+
+ if (operand.sign || this.size == 0)
+ {
+ this.sign = Sign.positive;
+ }
+ else
+ {
+ this.sign = Sign.negative;
+ }
+ }
+ }
+ else
+ {
+ add(operand);
+ }
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ {
+ auto h1 = Integer(3);
+ auto h2 = Integer(4);
+ h1 -= h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 0x01);
+ assert(h1.sign);
+ }
+ {
+ auto h1 = Integer(8589934590L);
+ auto h2 = Integer(2147483647);
+ ubyte[5] expected = [0x01, 0x7f, 0xff, 0xff, 0xff];
+
+ h1 -= h2;
+ assert(h1.rep[0 .. h1.size] == expected);
+ }
+ {
+ auto h1 = Integer(6442450943);
+ auto h2 = Integer(4294967294);
+ ubyte[4] expected = [0x80, 0x00, 0x00, 0x01];
+ h1 -= h2;
+ assert(h1.rep[0 .. h1.size] == expected);
+ }
+ {
+ auto h1 = Integer(2147483649);
+ auto h2 = Integer(h1);
+ h1 -= h2;
+ assert(h1.length == 0);
+ }
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op : "*")(const auto ref Integer operand) @trusted
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ size_t i = operand.size;
+ if (this.length == 0)
+ {
+ return this;
+ }
+ else if (i == 0)
+ {
+ this = 0;
+ return this;
+ }
+ auto temp = Integer(this, this.allocator);
+ auto sign = this.sign != operand.sign;
+
+ this = 0;
+ do
+ {
+ --i;
+ for (ubyte mask = 0x01; mask; mask <<= 1)
+ {
+ if (mask & operand.rep[i])
+ {
+ add(temp);
+ }
+ temp <<= 1;
+ }
+ }
+ while (i);
+
+ this.sign = sign ? Sign.negative : Sign.positive;
+
+ return this;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto h1 = Integer(123);
+ auto h2 = Integer(456);
+ h1 *= h2;
+ assert(h1 == 56088);
+ }
+
+ private @nogc unittest
+ {
+ assert((Integer(1) * Integer()).length == 0);
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op : "^^")(const auto ref Integer operand)
+ @trusted
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ size_t i = operand.size;
+
+ auto tmp1 = Integer(this, this.allocator);
+ this = 1;
+
+ do
+ {
+ --i;
+ for (ubyte mask = 0x01; mask; mask <<= 1)
+ {
+ if (operand.rep[i] & mask)
+ {
+ this *= tmp1;
+ }
+ // Square tmp1
+ auto tmp2 = tmp1;
+ tmp1 *= tmp2;
+ }
+ }
+ while (i);
+
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto h1 = Integer(2);
+ auto h2 = Integer(4);
+
+ h1 ^^= h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 0x10);
+
+ h1 = Integer(2342);
+ h1 ^^= h2;
+ ubyte[6] expected = [0x1b, 0x5c, 0xab, 0x9c, 0x31, 0x10];
+ assert(h1.rep[0 .. h1.size] == expected);
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op)(const auto ref Integer operand) @trusted
+ if ((op == "%") || (op == "/"))
+ in
+ {
+ assert(operand.length > 0, "Division by zero.");
+ }
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ auto divisor = Integer(operand, this.allocator);
+ size_t bitSize;
+
+ // First, left-shift divisor until it's >= than the dividend
+ while (compare(divisor) > 0)
+ {
+ divisor <<= 1;
+ ++bitSize;
+ }
+ static if (op == "/")
+ {
+ auto quotient = allocator.resize!ubyte(null, bitSize / 8 + 1);
+ quotient.initializeAll();
+ }
+
+ // bitPosition keeps track of which bit, of the quotient,
+ // is being set or cleared on the current operation.
+ auto bitPosition = 8 - (bitSize % 8) - 1;
+ do
+ {
+ if (compare(divisor) >= 0)
+ {
+ subtract(divisor); // dividend -= divisor
+ static if (op == "/")
+ {
+ quotient[bitPosition / 8] |= 0x80 >> (bitPosition % 8);
+ }
+ }
+ if (bitSize != 0)
+ {
+ divisor >>= 1;
+ }
+ ++bitPosition;
+ }
+ while (bitSize--);
+
+ static if (op == "/")
+ {
+ allocator.resize(this.rep[0 .. this.size], 0);
+ this.rep = quotient.ptr;
+ this.size = quotient.length;
+ this.sign = this.sign == divisor.sign ? Sign.positive : Sign.negative;
+ contract();
+ }
+
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto h1 = Integer(18);
+ auto h2 = Integer(4);
+ h1 %= h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 0x02);
+
+ h1 = 8;
+ h1 %= h2;
+ assert(h1.length == 0);
+
+ h1 = 7;
+ h1 %= h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 0x03);
+
+ h1 = 56088;
+ h2 = 456;
+ h1 /= h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 0x7b);
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op : ">>")(const size_t operand) @trusted
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ const step = operand / 8;
+
+ if (step >= this.length)
+ {
+ this.rep = allocator.resize(this.rep[0 .. this.size], 0).ptr;
+ this.size = 0;
+ return this;
+ }
+
+ size_t i, j;
+ ubyte carry;
+ const bit = operand % 8;
+ const delta = 8 - bit;
+
+ carry = cast(ubyte) (this.rep[0] << delta);
+ this.rep[0] = this.rep[0] >> bit;
+ if (rep[0])
+ {
+ ++j;
+ }
+ for (i = 1; i < this.length; ++i)
+ {
+ immutable oldCarry = carry;
+ carry = cast(ubyte) (this.rep[i] << delta);
+ this.rep[j] = this.rep[i] >> bit | oldCarry;
+ ++j;
+ }
+ const newSize = this.length - operand / 8 - (i == j ? 0 : 1);
+ this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr;
+ this.size = newSize;
+
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto integer = Integer(4294967294);
+ integer >>= 10;
+ assert(integer.length == 3);
+ assert(integer.rep[0] == 0x3f && integer.rep[1] == 0xff && integer.rep[2] == 0xff);
+
+ integer = 27336704;
+ integer >>= 1;
+ assert(integer.length == 3);
+ assert(integer.rep[0] == 0xd0 && integer.rep[1] == 0x90 && integer.rep[2] == 0x00);
+
+ integer = 4294967294;
+ integer >>= 20;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0x0f && integer.rep[1] == 0xff);
+
+ integer >>= 0;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0x0f && integer.rep[1] == 0xff);
+
+ integer >>= 20;
+ assert(integer.length == 0);
+
+ integer >>= 2;
+ assert(integer.length == 0);
+
+ integer = 1431655765;
+ integer >>= 16;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0x55 && integer.rep[1] == 0x55);
+
+ integer >>= 16;
+ assert(integer.length == 0);
+ }
+
+ /// Ditto.
+ ref Integer opOpAssign(string op : "<<")(const size_t operand) @trusted
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ ubyte carry;
+ auto i = this.length;
+ size_t j;
+ const bit = operand % 8;
+ const delta = 8 - bit;
+
+ if (cast(ubyte) (this.rep[0] >> delta))
+ {
+ const newSize = i + operand / 8 + 1;
+ this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr;
+ this.size = newSize;
+ j = i + 1;
+ }
+ else
+ {
+ const newSize = i + operand / 8;
+ this.rep = allocator.resize(this.rep[0 .. this.size], newSize).ptr;
+ j = i;
+ }
+ do
+ {
+ --i, --j;
+ const oldCarry = carry;
+ carry = rep[i] >> delta;
+ rep[j] = cast(ubyte) ((this.rep[i] << bit) | oldCarry);
+ }
+ while (i);
+ if (carry)
+ {
+ rep[0] = carry;
+ }
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto integer = Integer(4294967295);
+ ubyte[5] expected = [0x01, 0xff, 0xff, 0xff, 0xfe];
+ integer <<= 1;
+ assert(integer.rep[0 .. integer.size] == expected);
+ }
+
+ private void decrement() nothrow @trusted @nogc
+ in
+ {
+ assert(this.length > 0);
+ }
+ body
+ {
+ auto p = this.rep + this.size;
+ while (p != this.rep)
+ {
+ --p, --*p;
+ if (*p != ubyte.max)
+ {
+ break;
+ }
+ }
+ contract();
+ }
+
+ private void increment() nothrow @trusted @nogc
+ {
+ auto p = this.rep + this.size;
+ while (p != this.rep)
+ {
+ --p, ++*p;
+ if (*p > 0)
+ {
+ return;
+ }
+ }
+ if (p == this.rep)
+ {
+ expand();
+ }
+ }
+
+ /**
+ * Unary operators.
+ *
+ * Params:
+ * op = Operation.
+ *
+ * Returns: New $(D_PSYMBOL Integer).
+ */
+ Integer opUnary(string op : "~")() const
+ {
+ auto ret = Integer(this, this.allocator);
+ ret.rep[0 .. ret.size].each!((ref a) => a = ~a);
+ return ret;
+ }
+
+ /// Ditto.
+ Integer opUnary(string op : "-")() const
+ {
+ auto ret = Integer(this, this.allocator);
+ ret.sign = ret.sign ? Sign.positive : Sign.negative;
+ return ret;
+ }
+
+ /**
+ * Unary operators.
+ *
+ * Params:
+ * op = Operation.
+ *
+ * Returns: $(D_KEYWORD this).
+ */
+ ref inout(Integer) opUnary(string op : "+")() inout
+ {
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ auto h1 = Integer(79);
+ Integer h2;
+
+ h2 = +h1;
+ assert(h2.length == 1);
+ assert(h2.rep[0] == 79);
+ assert(!h2.sign);
+ assert(h2 !is h1);
+
+ h2 = -h1;
+ assert(h2.length == 1);
+ assert(h2.rep[0] == 79);
+ assert(h2.sign);
+
+ h1 = -h2;
+ assert(h2.length == 1);
+ assert(h2.rep[0] == 79);
+ assert(h2.sign);
+ assert(h2 !is h1);
+
+ h1 = -h2;
+ assert(h1.length == 1);
+ assert(h1.rep[0] == 79);
+ assert(!h1.sign);
+
+ h2 = ~h1;
+ assert(h2.rep[0] == ~cast(ubyte) 79);
+ }
+
+ /// Ditto.
+ ref Integer opUnary(string op : "++")()
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ if (this.sign)
+ {
+ decrement();
+ if (this.length == 0)
+ {
+ this.sign = Sign.positive;
+ }
+ }
+ else
+ {
+ increment();
+ }
+ return this;
+ }
+
+ /// Ditto.
+ ref Integer opUnary(string op : "--")()
+ out
+ {
+ assert(this.size == 0 || this.rep[0] > 0);
+ }
+ body
+ {
+ if (this.sign)
+ {
+ increment();
+ }
+ else
+ {
+ decrement();
+ }
+ return this;
+ }
+
+ private @nogc unittest
+ {
+ Integer integer;
+
+ ++integer;
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 0x01);
+
+ --integer;
+ assert(integer.length == 0);
+
+ integer = 511;
+ ++integer;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0x02 && integer.rep[1] == 0x00);
+
+ --integer;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0x01 && integer.rep[1] == 0xff);
+
+ integer = 79;
+ ++integer;
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 0x50);
+
+ --integer;
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 0x4f);
+
+ integer = 65535;
+ ++integer;
+ assert(integer.length == 3);
+ assert(integer.rep[0] == 0x01 && integer.rep[1] == 0x00 && integer.rep[2] == 0x00);
+
+ --integer;
+ assert(integer.length == 2);
+ assert(integer.rep[0] == 0xff && integer.rep[1] == 0xff);
+
+ integer = -2;
+ ++integer;
+ assert(integer.length == 1);
+ assert(integer.rep[0] == 0x01);
+ }
+
+ /**
+ * Implements binary operators.
+ *
+ * Params:
+ * op = Operation.
+ * operand = The second operand.
+ */
+ Integer opBinary(string op)(const auto ref Integer operand) const
+ if (op == "+" || op == "-" || op == "*" || op == "^^")
+ {
+ auto ret = Integer(this, this.allocator);
+ mixin("ret " ~ op ~ "= operand;");
+ return ret;
+ }
+
+ /// Ditto.
+ Integer opBinary(string op)(const auto ref Integer operand) const
+ if (op == "/" || op == "%")
+ in
+ {
+ assert(operand.length > 0, "Division by zero.");
+ }
+ body
+ {
+ auto ret = Integer(this, this.allocator);
+ mixin("ret " ~ op ~ "= operand;");
+ return ret;
+ }
+
+ /// Ditto.
+ Integer opBinary(string op)(const auto ref size_t operand) const
+ if (op == "<<" || op == ">>")
+ {
+ auto ret = Integer(this, this.allocator);
+ mixin("ret " ~ op ~ "= operand;");
+ return ret;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto integer1 = Integer(425);
+ auto integer2 = integer1 << 1;
+ assert(integer2 == 850);
+
+ integer2 = integer1 >> 1;
+ assert(integer2 == 212);
+ }
+
+ /**
+ * Casting.
+ *
+ * Params:
+ * T = Target type.
+ *
+ * Returns: $(D_KEYWORD false) if the $(D_PSYMBOL Integer) is 0,
+ * $(D_KEYWORD true) otherwise.
+ */
+ T opCast(T : bool)() const
+ {
+ return this.length > 0;
+ }
+
+ /// Ditto.
+ T opCast(T)() const
+ if (isIntegral!T && isSigned!T)
+ {
+ return this.sign ? -(cast(Unsigned!T) this) : cast(Unsigned!T) this;
+ }
+
+ /// Ditto.
+ T opCast(T)() const @trusted
+ if (isIntegral!T && isUnsigned!T)
+ {
+ if (this.length == 0)
+ {
+ return 0;
+ }
+ T ret;
+ const(ubyte)* src = this.rep + this.size - 1;
+ for (ubyte shift; src >= this.rep && shift <= T.sizeof * 8; --src, shift += 8)
+ {
+ ret |= cast(T) (*src) << shift;
+ }
+ return ret;
+ }
+
+ ///
+ @safe @nogc unittest
+ {
+ auto integer = Integer(79);
+ assert(cast(long) integer == 79);
+
+ integer = -79;
+ assert(cast(long) integer == -79);
+
+ integer = 4294967295;
+ assert(cast(long) integer == 4294967295);
+
+ integer = -4294967295;
+ assert(cast(long) integer == -4294967295);
+ }
+
+ mixin DefaultAllocator;
}
diff --git a/source/tanya/math/package.d b/source/tanya/math/package.d
index 8449c34..994d5c7 100644
--- a/source/tanya/math/package.d
+++ b/source/tanya/math/package.d
@@ -3,11 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
- * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
- */
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ */
module tanya.math;
import std.traits;
@@ -16,7 +16,7 @@ public import tanya.math.random;
version (unittest)
{
- import std.algorithm.iteration;
+ import std.algorithm.iteration;
}
/**
@@ -26,12 +26,12 @@ version (unittest)
* is used to allocate the result.
*
* Params:
- * I = Base type.
- * G = Exponent type.
- * H = Divisor type:
- * x = Base.
- * y = Exponent.
- * z = Divisor.
+ * I = Base type.
+ * G = Exponent type.
+ * H = Divisor type:
+ * x = Base.
+ * y = Exponent.
+ * z = Divisor.
*
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
* by $(D_PARAM z).
@@ -39,134 +39,162 @@ version (unittest)
* Precondition: $(D_INLINECODE z > 0)
*/
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
- if (isIntegral!I && isIntegral!G && isIntegral!H)
+ if (isIntegral!I && isIntegral!G && isIntegral!H)
in
{
- assert(z > 0, "Division by zero.");
+ assert(z > 0, "Division by zero.");
}
body
{
- G mask = G.max / 2 + 1;
- H result;
-
- if (y == 0)
- {
- return 1 % z;
- }
- else if (y == 1)
- {
- return x % z;
- }
- do
- {
- immutable bit = y & mask;
- if (!result && bit)
- {
- result = x;
- continue;
- }
-
- result *= result;
- if (bit)
- {
- result *= x;
- }
- result %= z;
- }
- while (mask >>= 1);
-
- return result;
+ G mask = G.max / 2 + 1;
+ H result;
+
+ if (y == 0)
+ {
+ return 1 % z;
+ }
+ else if (y == 1)
+ {
+ return x % z;
+ }
+ do
+ {
+ immutable bit = y & mask;
+ if (!result && bit)
+ {
+ result = x;
+ continue;
+ }
+
+ result *= result;
+ if (bit)
+ {
+ result *= x;
+ }
+ result %= z;
+ }
+ while (mask >>= 1);
+
+ return result;
}
/// Ditto.
-I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
- if (is(I == Integer))
+I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
+ if (is(I == Integer))
in
{
- assert(z.length > 0, "Division by zero.");
+ assert(z.length > 0, "Division by zero.");
}
body
{
- size_t i = y.length;
- auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
- Integer result = Integer(x.allocator);
-
- if (x.length == 0 && i != 0)
- {
- i = 0;
- }
- else
- {
- result = 1;
- }
- while (i)
- {
- --i;
- for (ubyte mask = 0x01; mask; mask <<= 1)
- {
- if (y.rep[i] & mask)
- {
- result *= tmp1;
- result %= z;
- }
- tmp2 = tmp1;
- tmp1 *= tmp2;
- tmp1 %= z;
- }
- }
- return result;
+ size_t i = y.length;
+ auto tmp1 = Integer(x, x.allocator);
+ auto result = Integer(x.allocator);
+
+ if (x.length == 0 && i != 0)
+ {
+ i = 0;
+ }
+ else
+ {
+ result = 1;
+ }
+ while (i)
+ {
+ --i;
+ for (ubyte mask = 0x01; mask; mask <<= 1)
+ {
+ if (y.rep[i] & mask)
+ {
+ result *= tmp1;
+ result %= z;
+ }
+ auto tmp2 = tmp1;
+ tmp1 *= tmp2;
+ tmp1 %= z;
+ }
+ }
+ return result;
}
///
pure nothrow @safe @nogc unittest
{
- assert(pow(3, 5, 7) == 5);
- assert(pow(2, 2, 1) == 0);
- assert(pow(3, 3, 3) == 0);
- assert(pow(7, 4, 2) == 1);
- assert(pow(53, 0, 2) == 1);
- assert(pow(53, 1, 3) == 2);
- assert(pow(53, 2, 5) == 4);
- assert(pow(0, 0, 5) == 1);
- assert(pow(0, 5, 5) == 0);
+ assert(pow(3, 5, 7) == 5);
+ assert(pow(2, 2, 1) == 0);
+ assert(pow(3, 3, 3) == 0);
+ assert(pow(7, 4, 2) == 1);
+ assert(pow(53, 0, 2) == 1);
+ assert(pow(53, 1, 3) == 2);
+ assert(pow(53, 2, 5) == 4);
+ assert(pow(0, 0, 5) == 1);
+ assert(pow(0, 5, 5) == 0);
}
///
unittest
{
- assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
- assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
- assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
- assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
- assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
- assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
- assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
- assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
- assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0);
+ assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
+ assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
+ assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
+ assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
+ assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
+ assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
+ assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
+ assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
+ assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
}
/**
* Checks if $(D_PARAM x) is a prime.
*
* Params:
- * x = The number should be checked.
+ * x = The number should be checked.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise.
*/
bool isPseudoprime(ulong x) nothrow pure @safe @nogc
{
- return pow(2, x - 1, x) == 1;
+ return pow(2, x - 1, x) == 1;
}
///
unittest
{
- uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
- 74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
- 74827, 9973, 104729, 15485867, 49979693, 104395303,
- 593441861, 104729, 15485867, 49979693, 104395303,
- 593441861, 899809363, 982451653];
+ uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
+ 74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
+ 74827, 9973, 104729, 15485867, 49979693, 104395303,
+ 593441861, 104729, 15485867, 49979693, 104395303,
+ 593441861, 899809363, 982451653];
+
+ known.each!((ref x) => assert(isPseudoprime(x)));
+}
+
+/**
+ * Params:
+ * I = Value type.
+ * x = Value.
+ *
+ * Returns: The absolute value of a number.
+ */
+I abs(I : Integer)(const auto ref I x)
+{
+ auto result = Integer(x, x.allocator);
+ result.sign = Sign.positive;
+ return result;
+}
+
+/// Ditto.
+I abs(I : Integer)(I x)
+{
+ x.sign = Sign.positive;
+ return x;
+}
- known.each!((ref x) => assert(isPseudoprime(x)));
+/// Ditto.
+I abs(I)(const I x)
+ if (isIntegral!I)
+{
+ return x >= 0 ? x : -x;
}
diff --git a/source/tanya/math/random.d b/source/tanya/math/random.d
index e58d209..76295f7 100644
--- a/source/tanya/math/random.d
+++ b/source/tanya/math/random.d
@@ -8,8 +8,8 @@
* Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
- * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
- */
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
+ */
module tanya.math.random;
import std.digest.sha;
@@ -27,20 +27,20 @@ enum maxGather = 128;
*/
class EntropyException : Exception
{
- /**
- * Params:
- * msg = Message to output.
- * file = The file where the exception occurred.
- * line = The line number where the exception occurred.
- * next = The previous exception in the chain of exceptions, if any.
- */
- this(string msg,
- string file = __FILE__,
- size_t line = __LINE__,
- Throwable next = null) pure @safe nothrow const @nogc
- {
- super(msg, file, line, next);
- }
+ /**
+ * Params:
+ * msg = Message to output.
+ * file = The file where the exception occurred.
+ * line = The line number where the exception occurred.
+ * next = The previous exception in the chain of exceptions, if any.
+ */
+ this(string msg,
+ string file = __FILE__,
+ size_t line = __LINE__,
+ Throwable next = null) pure @safe nothrow const @nogc
+ {
+ super(msg, file, line, next);
+ }
}
/**
@@ -48,103 +48,103 @@ class EntropyException : Exception
*/
abstract class EntropySource
{
- /// Amount of already generated entropy.
- protected ushort size_;
-
- /**
- * Returns: Minimum bytes required from the entropy source.
- */
- @property immutable(ubyte) threshold() const @safe pure nothrow;
-
- /**
- * Returns: Whether this entropy source is strong.
- */
- @property immutable(bool) strong() const @safe pure nothrow;
-
- /**
- * Returns: Amount of already generated entropy.
- */
- @property ushort size() const @safe pure nothrow
- {
- return size_;
- }
-
- /**
- * Params:
- * size = Amount of already generated entropy. Cannot be smaller than the
- * already set value.
- */
- @property void size(ushort size) @safe pure nothrow
- {
- size_ = size;
- }
-
- /**
- * Poll the entropy source.
- *
- * Params:
- * output = Buffer to save the generate random sequence (the method will
- * to fill the buffer).
- *
- * Returns: Number of bytes that were copied to the $(D_PARAM output)
- * or $(D_PSYMBOL Nullable!ubyte.init) on error.
- */
- Nullable!ubyte poll(out ubyte[maxGather] output);
+ /// Amount of already generated entropy.
+ protected ushort size_;
+
+ /**
+ * Returns: Minimum bytes required from the entropy source.
+ */
+ @property immutable(ubyte) threshold() const @safe pure nothrow;
+
+ /**
+ * Returns: Whether this entropy source is strong.
+ */
+ @property immutable(bool) strong() const @safe pure nothrow;
+
+ /**
+ * Returns: Amount of already generated entropy.
+ */
+ @property ushort size() const @safe pure nothrow
+ {
+ return size_;
+ }
+
+ /**
+ * Params:
+ * size = Amount of already generated entropy. Cannot be smaller than the
+ * already set value.
+ */
+ @property void size(ushort size) @safe pure nothrow
+ {
+ size_ = size;
+ }
+
+ /**
+ * Poll the entropy source.
+ *
+ * Params:
+ * output = Buffer to save the generate random sequence (the method will
+ * to fill the buffer).
+ *
+ * Returns: Number of bytes that were copied to the $(D_PARAM output)
+ * or $(D_PSYMBOL Nullable!ubyte.init) on error.
+ */
+ Nullable!ubyte poll(out ubyte[maxGather] output);
}
version (linux)
{
- extern (C) long syscall(long number, ...) nothrow;
-
- /**
- * Uses getrandom system call.
- */
- class PlatformEntropySource : EntropySource
- {
- /**
- * Returns: Minimum bytes required from the entropy source.
- */
- override @property immutable(ubyte) threshold() const @safe pure nothrow
- {
- return 32;
- }
-
- /**
- * Returns: Whether this entropy source is strong.
- */
- override @property immutable(bool) strong() const @safe pure nothrow
- {
- return true;
- }
-
- /**
- * Poll the entropy source.
- *
- * Params:
- * output = Buffer to save the generate random sequence (the method will
- * to fill the buffer).
- *
- * Returns: Number of bytes that were copied to the $(D_PARAM output)
- * or $(D_PSYMBOL Nullable!ubyte.init) on error.
- */
- override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
- out (length)
- {
- assert(length <= maxGather);
- }
- body
- {
- // int getrandom(void *buf, size_t buflen, unsigned int flags);
- auto length = syscall(318, output.ptr, output.length, 0);
- Nullable!ubyte ret;
-
- if (length >= 0)
- {
- ret = cast(ubyte) length;
- }
- return ret;
- }
- }
+ extern (C) long syscall(long number, ...) nothrow;
+
+ /**
+ * Uses getrandom system call.
+ */
+ class PlatformEntropySource : EntropySource
+ {
+ /**
+ * Returns: Minimum bytes required from the entropy source.
+ */
+ override @property immutable(ubyte) threshold() const @safe pure nothrow
+ {
+ return 32;
+ }
+
+ /**
+ * Returns: Whether this entropy source is strong.
+ */
+ override @property immutable(bool) strong() const @safe pure nothrow
+ {
+ return true;
+ }
+
+ /**
+ * Poll the entropy source.
+ *
+ * Params:
+ * output = Buffer to save the generate random sequence (the method will
+ * to fill the buffer).
+ *
+ * Returns: Number of bytes that were copied to the $(D_PARAM output)
+ * or $(D_PSYMBOL Nullable!ubyte.init) on error.
+ */
+ override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
+ out (length)
+ {
+ assert(length <= maxGather);
+ }
+ body
+ {
+ // int getrandom(void *buf, size_t buflen, unsigned int flags);
+ auto length = syscall(318, output.ptr, output.length, 0);
+ Nullable!ubyte ret;
+
+ if (length >= 0)
+ {
+ ret = cast(ubyte) length;
+ }
+ return ret;
+ }
+ }
}
/**
@@ -156,165 +156,165 @@ version (linux)
*
* output = entropy.random;
*
- * defaultAllocator.finalize(entropy);
+ * defaultAllocator.dispose(entropy);
* ---
*/
class Entropy
{
- /// Entropy sources.
- protected EntropySource[] sources;
-
- private ubyte sourceCount_;
-
- private shared Allocator allocator;
-
- /// Entropy accumulator.
- protected SHA!(maxGather * 8, 512) accumulator;
-
- /**
- * Params:
- * maxSources = Maximum amount of entropy sources can be set.
- * allocator = Allocator to allocate entropy sources available on the
- * system.
- */
- this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
- in
- {
- assert(maxSources > 0 && maxSources <= ubyte.max);
- assert(allocator !is null);
- }
- body
- {
- allocator.resizeArray(sources, maxSources);
-
- version (linux)
- {
- this ~= allocator.make!PlatformEntropySource;
- }
- }
-
- /**
- * Returns: Amount of the registered entropy sources.
- */
- @property ubyte sourceCount() const @safe pure nothrow
- {
- return sourceCount_;
- }
-
- /**
- * Add an entropy source.
- *
- * Params:
- * source = Entropy source.
- *
- * Returns: $(D_PSYMBOL this).
- *
- * See_Also:
- * $(D_PSYMBOL EntropySource)
- */
- Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
- if (Op == "~")
- in
- {
- assert(sourceCount_ <= sources.length);
- }
- body
- {
- sources[sourceCount_++] = source;
- return this;
- }
-
- /**
- * Returns: Generated random sequence.
- *
- * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
- * registered or it failed.
- */
- @property ubyte[blockSize] random()
- in
- {
- assert(sourceCount_ > 0, "No entropy sources defined.");
- }
- body
- {
- bool haveStrong;
- ushort done;
- ubyte[blockSize] output;
-
- do
- {
- ubyte[maxGather] buffer;
-
- // Run through our entropy sources
- for (ubyte i; i < sourceCount; ++i)
- {
- auto outputLength = sources[i].poll(buffer);
-
- if (!outputLength.isNull)
- {
- if (outputLength > 0)
- {
- update(i, buffer, outputLength);
- sources[i].size = cast(ushort) (sources[i].size + outputLength);
- }
- if (sources[i].size < sources[i].threshold)
- {
- continue;
- }
- else if (sources[i].strong)
- {
- haveStrong = true;
- }
- }
- done = 257;
- }
- }
- while (++done < 256);
-
- if (!haveStrong)
- {
- throw allocator.make!EntropyException("No strong entropy source defined.");
- }
-
- output = accumulator.finish();
-
- // Reset accumulator and counters and recycle existing entropy
- accumulator.start();
-
- // Perform second SHA-512 on entropy
- output = sha512Of(output);
-
- for (ubyte i = 0; i < sourceCount; ++i)
- {
- sources[i].size = 0;
- }
- return output;
- }
-
- /**
- * Update entropy accumulator.
- *
- * Params:
- * sourceId = Entropy source index in $(D_PSYMBOL sources).
- * data = Data got from the entropy source.
- * length = Length of the received data.
- */
- protected void update(in ubyte sourceId,
- ref ubyte[maxGather] data,
- ubyte length) @safe pure nothrow
- {
- ubyte[2] header;
-
- if (length > blockSize)
- {
- data[0..64] = sha512Of(data);
- length = blockSize;
- }
-
- header[0] = sourceId;
- header[1] = length;
-
- accumulator.put(header);
- accumulator.put(data[0..length]);
- }
+ /// Entropy sources.
+ protected EntropySource[] sources;
+
+ private ubyte sourceCount_;
+
+ private shared Allocator allocator;
+
+ /// Entropy accumulator.
+ protected SHA!(maxGather * 8, 512) accumulator;
+
+ /**
+ * Params:
+ * maxSources = Maximum amount of entropy sources can be set.
+ * allocator = Allocator to allocate entropy sources available on the
+ * system.
+ */
+ this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
+ in
+ {
+ assert(maxSources > 0 && maxSources <= ubyte.max);
+ assert(allocator !is null);
+ }
+ body
+ {
+ allocator.resize(sources, maxSources);
+
+ version (linux)
+ {
+ this ~= allocator.make!PlatformEntropySource;
+ }
+ }
+
+ /**
+ * Returns: Amount of the registered entropy sources.
+ */
+ @property ubyte sourceCount() const @safe pure nothrow
+ {
+ return sourceCount_;
+ }
+
+ /**
+ * Add an entropy source.
+ *
+ * Params:
+ * source = Entropy source.
+ *
+ * Returns: $(D_PSYMBOL this).
+ *
+ * See_Also:
+ * $(D_PSYMBOL EntropySource)
+ */
+ Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
+ if (Op == "~")
+ in
+ {
+ assert(sourceCount_ <= sources.length);
+ }
+ body
+ {
+ sources[sourceCount_++] = source;
+ return this;
+ }
+
+ /**
+ * Returns: Generated random sequence.
+ *
+ * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
+ * registered or it failed.
+ */
+ @property ubyte[blockSize] random()
+ in
+ {
+ assert(sourceCount_ > 0, "No entropy sources defined.");
+ }
+ body
+ {
+ bool haveStrong;
+ ushort done;
+ ubyte[blockSize] output;
+
+ do
+ {
+ ubyte[maxGather] buffer;
+
+ // Run through our entropy sources
+ for (ubyte i; i < sourceCount; ++i)
+ {
+ auto outputLength = sources[i].poll(buffer);
+
+ if (!outputLength.isNull)
+ {
+ if (outputLength > 0)
+ {
+ update(i, buffer, outputLength);
+ sources[i].size = cast(ushort) (sources[i].size + outputLength);
+ }
+ if (sources[i].size < sources[i].threshold)
+ {
+ continue;
+ }
+ else if (sources[i].strong)
+ {
+ haveStrong = true;
+ }
+ }
+ done = 257;
+ }
+ }
+ while (++done < 256);
+
+ if (!haveStrong)
+ {
+ throw allocator.make!EntropyException("No strong entropy source defined.");
+ }
+
+ output = accumulator.finish();
+
+ // Reset accumulator and counters and recycle existing entropy
+ accumulator.start();
+
+ // Perform second SHA-512 on entropy
+ output = sha512Of(output);
+
+ for (ubyte i = 0; i < sourceCount; ++i)
+ {
+ sources[i].size = 0;
+ }
+ return output;
+ }
+
+ /**
+ * Update entropy accumulator.
+ *
+ * Params:
+ * sourceId = Entropy source index in $(D_PSYMBOL sources).
+ * data = Data got from the entropy source.
+ * length = Length of the received data.
+ */
+ protected void update(in ubyte sourceId,
+ ref ubyte[maxGather] data,
+ ubyte length) @safe pure nothrow
+ {
+ ubyte[2] header;
+
+ if (length > blockSize)
+ {
+ data[0..64] = sha512Of(data);
+ length = blockSize;
+ }
+
+ header[0] = sourceId;
+ header[1] = length;
+
+ accumulator.put(header);
+ accumulator.put(data[0..length]);
+ }
}
diff --git a/source/tanya/memory/allocator.d b/source/tanya/memory/allocator.d
index c942568..560d9a1 100644
--- a/source/tanya/memory/allocator.d
+++ b/source/tanya/memory/allocator.d
@@ -15,53 +15,53 @@ module tanya.memory.allocator;
*/
interface Allocator
{
- /**
- * Returns: Alignment offered.
- */
- @property uint alignment() const shared pure nothrow @safe @nogc;
+ /**
+ * Returns: Alignment offered.
+ */
+ @property uint alignment() const shared pure nothrow @safe @nogc;
- /**
- * Allocates $(D_PARAM size) bytes of memory.
- *
- * Params:
- * size = Amount of memory to allocate.
- *
- * Returns: Pointer to the new allocated memory.
- */
- void[] allocate(in size_t size) shared nothrow @nogc;
+ /**
+ * Allocates $(D_PARAM size) bytes of memory.
+ *
+ * Params:
+ * size = Amount of memory to allocate.
+ *
+ * Returns: Pointer to the new allocated memory.
+ */
+ void[] allocate(in size_t size) shared nothrow @nogc;
- /**
- * Deallocates a memory block.
- *
- * Params:
- * p = A pointer to the memory block to be freed.
- *
- * Returns: Whether the deallocation was successful.
- */
- bool deallocate(void[] p) shared nothrow @nogc;
+ /**
+ * Deallocates a memory block.
+ *
+ * Params:
+ * p = A pointer to the memory block to be freed.
+ *
+ * Returns: Whether the deallocation was successful.
+ */
+ bool deallocate(void[] p) shared nothrow @nogc;
- /**
- * Increases or decreases the size of a memory block.
- *
- * Params:
- * p = A pointer to the memory block.
- * size = Size of the reallocated block.
- *
- * Returns: Pointer to the allocated memory.
- */
- bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
+ /**
+ * Increases or decreases the size of a memory block.
+ *
+ * Params:
+ * p = A pointer to the memory block.
+ * size = Size of the reallocated block.
+ *
+ * Returns: Pointer to the allocated memory.
+ */
+ bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
- /**
- * Reallocates a memory block in place if possible or returns
- * $(D_KEYWORD false). This function cannot be used to allocate or
- * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
- * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
- *
- * Params:
- * p = A pointer to the memory block.
- * size = Size of the reallocated block.
- *
- * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
- */
- bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
+ /**
+ * Reallocates a memory block in place if possible or returns
+ * $(D_KEYWORD false). This function cannot be used to allocate or
+ * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
+ * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
+ *
+ * Params:
+ * p = A pointer to the memory block.
+ * size = Size of the reallocated block.
+ *
+ * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
+ */
+ bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
}
diff --git a/source/tanya/memory/mallocator.d b/source/tanya/memory/mallocator.d
new file mode 100644
index 0000000..0e4ac7f
--- /dev/null
+++ b/source/tanya/memory/mallocator.d
@@ -0,0 +1,185 @@
+/* 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/. */
+
+/**
+ * Copyright: Eugene Wissner 2017.
+ * 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)
+ */
+module tanya.memory.mallocator;
+
+import core.stdc.stdlib;
+import std.algorithm.comparison;
+import tanya.memory.allocator;
+
+/**
+ * Wrapper for malloc/realloc/free from the C standard library.
+ */
+final class Mallocator : Allocator
+{
+ /**
+ * Allocates $(D_PARAM size) bytes of memory.
+ *
+ * Params:
+ * size = Amount of memory to allocate.
+ *
+ * Returns: The pointer to the new allocated memory.
+ */
+ void[] allocate(in size_t size) shared nothrow @nogc
+ {
+ if (!size)
+ {
+ return null;
+ }
+ auto p = malloc(size + psize);
+
+ return p is null ? null : p[psize .. psize + size];
+ }
+
+ ///
+ @nogc nothrow unittest
+ {
+ auto p = Mallocator.instance.allocate(20);
+
+ assert(p.length == 20);
+
+ Mallocator.instance.deallocate(p);
+ }
+
+ /**
+ * Deallocates a memory block.
+ *
+ * Params:
+ * p = A pointer to the memory block to be freed.
+ *
+ * Returns: Whether the deallocation was successful.
+ */
+ bool deallocate(void[] p) shared nothrow @nogc
+ {
+ if (p !is null)
+ {
+ free(p.ptr - psize);
+ }
+ return true;
+ }
+
+ ///
+ @nogc nothrow unittest
+ {
+ void[] p;
+ assert(Mallocator.instance.deallocate(p));
+
+ p = Mallocator.instance.allocate(10);
+ assert(Mallocator.instance.deallocate(p));
+ }
+
+ /**
+ * Reallocating in place isn't supported.
+ *
+ * Params:
+ * p = A pointer to the memory block.
+ * size = Size of the reallocated block.
+ *
+ * Returns: $(D_KEYWORD false).
+ */
+ bool reallocateInPlace(ref void[] p, const size_t size) shared nothrow @nogc
+ {
+ return false;
+ }
+
+ /**
+ * Increases or decreases the size of a memory block.
+ *
+ * Params:
+ * p = A pointer to the memory block.
+ * size = Size of the reallocated block.
+ *
+ * Returns: Whether the reallocation was successful.
+ */
+ bool reallocate(ref void[] p, const size_t size) shared nothrow @nogc
+ {
+ if (size == 0)
+ {
+ if (deallocate(p))
+ {
+ p = null;
+ return true;
+ }
+ }
+ else if (p is null)
+ {
+ p = allocate(size);
+ return p is null ? false : true;
+ }
+ else
+ {
+ auto r = realloc(p.ptr - psize, size + psize);
+
+ if (r !is null)
+ {
+ p = r[psize .. psize + size];
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ @nogc nothrow unittest
+ {
+ void[] p;
+
+ assert(Mallocator.instance.reallocate(p, 20));
+ assert(p.length == 20);
+
+ assert(Mallocator.instance.reallocate(p, 30));
+ assert(p.length == 30);
+
+ assert(Mallocator.instance.reallocate(p, 10));
+ assert(p.length == 10);
+
+ assert(Mallocator.instance.reallocate(p, 0));
+ assert(p is null);
+ }
+
+ /**
+ * Returns: The alignment offered.
+ */
+ @property uint alignment() shared const pure nothrow @safe @nogc
+ {
+ return cast(uint) max(double.alignof, real.alignof);
+ }
+
+ /**
+ * Static allocator instance and initializer.
+ *
+ * Returns: The global $(D_PSYMBOL Allocator) instance.
+ */
+ static @property ref shared(Mallocator) instance() @nogc nothrow
+ {
+ if (instance_ is null)
+ {
+ immutable size = __traits(classInstanceSize, Mallocator) + psize;
+ void* p = malloc(size);
+
+ if (p !is null)
+ {
+ p[psize .. size] = typeid(Mallocator).initializer[];
+ instance_ = cast(shared Mallocator) p[psize .. size].ptr;
+ }
+ }
+ return instance_;
+ }
+
+ ///
+ @nogc nothrow unittest
+ {
+ assert(instance is instance);
+ }
+
+ private enum psize = 8;
+
+ private shared static Mallocator instance_;
+}
diff --git a/source/tanya/memory/package.d b/source/tanya/memory/package.d
index 9ecb45b..5e05943 100644
--- a/source/tanya/memory/package.d
+++ b/source/tanya/memory/package.d
@@ -11,7 +11,7 @@
module tanya.memory;
import core.exception;
-public import std.experimental.allocator : make, makeArray;
+public import std.experimental.allocator : make;
import std.traits;
public import tanya.memory.allocator;
@@ -23,59 +23,61 @@ public import tanya.memory.allocator;
*/
mixin template DefaultAllocator()
{
- /// Allocator.
- protected shared Allocator allocator_;
+ /// Allocator.
+ protected shared Allocator allocator_;
- /**
- * Params:
- * allocator = The allocator should be used.
- */
- this(shared Allocator allocator)
- in
- {
- assert(allocator !is null);
- }
- body
- {
- this.allocator_ = allocator;
- }
+ /**
+ * Params:
+ * allocator = The allocator should be used.
+ *
+ * Precondition: $(D_INLINECODE allocator_ !is null)
+ */
+ this(shared Allocator allocator)
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ this.allocator_ = allocator;
+ }
- /**
- * This property checks if the allocator was set in the constructor
- * and sets it to the default one, if not.
- *
- * Returns: Used allocator.
- *
- * Postcondition: $(D_INLINECODE allocator_ !is null)
- */
- protected @property shared(Allocator) allocator() nothrow @safe @nogc
- out (allocator)
- {
- assert(allocator !is null);
- }
- body
- {
- if (allocator_ is null)
- {
- allocator_ = defaultAllocator;
- }
- return allocator_;
- }
+ /**
+ * This property checks if the allocator was set in the constructor
+ * and sets it to the default one, if not.
+ *
+ * Returns: Used allocator.
+ *
+ * Postcondition: $(D_INLINECODE allocator !is null)
+ */
+ protected @property shared(Allocator) allocator() nothrow @safe @nogc
+ out (allocator)
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ if (allocator_ is null)
+ {
+ allocator_ = defaultAllocator;
+ }
+ return allocator_;
+ }
- /// Ditto.
- @property shared(Allocator) allocator() const nothrow @trusted @nogc
- out (allocator)
- {
- assert(allocator !is null);
- }
- body
- {
- if (allocator_ is null)
- {
- return defaultAllocator;
- }
- return cast(shared Allocator) allocator_;
- }
+ /// Ditto.
+ @property shared(Allocator) allocator() const nothrow @trusted @nogc
+ out (allocator)
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ if (allocator_ is null)
+ {
+ return defaultAllocator;
+ }
+ return cast(shared Allocator) allocator_;
+ }
}
// From druntime
@@ -85,28 +87,28 @@ shared Allocator allocator;
shared static this() nothrow @trusted @nogc
{
- import tanya.memory.mmappool;
- allocator = MmapPool.instance;
+ import tanya.memory.mallocator;
+ allocator = Mallocator.instance;
}
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
out (allocator)
{
- assert(allocator !is null);
+ assert(allocator !is null);
}
body
{
- return allocator;
+ return allocator;
}
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
in
{
- assert(allocator !is null);
+ assert(allocator !is null);
}
body
{
- .allocator = allocator;
+ .allocator = allocator;
}
/**
@@ -114,101 +116,90 @@ body
* object of type $(D_PARAM T).
*
* Params:
- * T = Object type.
+ * T = Object type.
*/
template stateSize(T)
{
- static if (is(T == class) || is(T == interface))
- {
- enum stateSize = __traits(classInstanceSize, T);
- }
- else
- {
- enum stateSize = T.sizeof;
- }
+ static if (is(T == class) || is(T == interface))
+ {
+ enum stateSize = __traits(classInstanceSize, T);
+ }
+ else
+ {
+ enum stateSize = T.sizeof;
+ }
}
/**
* Params:
- * size = Raw size.
- * alignment = Alignment.
+ * size = Raw size.
+ * alignment = Alignment.
*
* Returns: Aligned size.
*/
-size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
+size_t alignedSize(const size_t size, const size_t alignment = 8)
+pure nothrow @safe @nogc
{
- return (size - 1) / alignment * alignment + alignment;
+ return (size - 1) / alignment * alignment + alignment;
}
/**
* Internal function used to create, resize or destroy a dynamic array. It
- * throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
- * allocated part of the array is initialized only if $(D_PARAM Init)
- * is set. This function can be trusted only in the data structures that
- * can ensure that the array is allocated/rellocated/deallocated with the
- * same allocator.
+ * may throw $(D_PSYMBOL OutOfMemoryError). The new
+ * allocated part of the array isn't initialized. This function can be trusted
+ * only in the data structures that can ensure that the array is
+ * allocated/rellocated/deallocated with the same allocator.
*
* Params:
- * T = Element type of the array being created.
- * Init = If should be initialized.
- * Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
- * allocator = The allocator used for getting memory.
- * array = A reference to the array being changed.
- * length = New array length.
+ * T = Element type of the array being created.
+ * allocator = The allocator used for getting memory.
+ * array = A reference to the array being changed.
+ * length = New array length.
*
- * Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
- * not be reallocated. In the latter
+ * Returns: $(D_PARAM array).
*/
-package(tanya) bool resize(T,
- bool Init = true,
- bool Throws = true)
- (shared Allocator allocator,
- ref T[] array,
- in size_t length) @trusted
+package(tanya) T[] resize(T)(shared Allocator allocator,
+ auto ref T[] array,
+ const size_t length) @trusted
{
- void[] buf = array;
- static if (Init)
- {
- immutable oldLength = array.length;
- }
- if (!allocator.reallocate(buf, length * T.sizeof))
- {
- static if (Throws)
- {
- onOutOfMemoryError;
- }
- return false;
- }
- // Casting from void[] is unsafe, but we know we cast to the original type.
- array = cast(T[]) buf;
+ if (length == 0)
+ {
+ if (allocator.deallocate(array))
+ {
+ return null;
+ }
+ else
+ {
+ onOutOfMemoryErrorNoGC();
+ }
+ }
- static if (Init)
- {
- if (oldLength < length)
- {
- array[oldLength .. $] = T.init;
- }
- }
- return true;
+ void[] buf = array;
+ if (!allocator.reallocate(buf, length * T.sizeof))
+ {
+ onOutOfMemoryErrorNoGC();
+ }
+ // Casting from void[] is unsafe, but we know we cast to the original type.
+ array = cast(T[]) buf;
+
+ return array;
}
-package(tanya) alias resizeArray = resize;
-///
-unittest
+private unittest
{
- int[] p;
+ int[] p;
- defaultAllocator.resizeArray(p, 20);
- assert(p.length == 20);
+ p = defaultAllocator.resize(p, 20);
+ assert(p.length == 20);
- defaultAllocator.resizeArray(p, 30);
- assert(p.length == 30);
+ p = defaultAllocator.resize(p, 30);
+ assert(p.length == 30);
- defaultAllocator.resizeArray(p, 10);
- assert(p.length == 10);
+ p = defaultAllocator.resize(p, 10);
+ assert(p.length == 10);
- defaultAllocator.resizeArray(p, 0);
- assert(p is null);
+ p = defaultAllocator.resize(p, 0);
+ assert(p is null);
}
/**
@@ -217,101 +208,101 @@ unittest
* allocator.
*
* Params:
- * T = Type of $(D_PARAM p).
- * allocator = Allocator the $(D_PARAM p) was allocated with.
- * p = Object or array to be destroyed.
+ * T = Type of $(D_PARAM p).
+ * allocator = Allocator the $(D_PARAM p) was allocated with.
+ * p = Object or array to be destroyed.
*/
void dispose(T)(shared Allocator allocator, auto ref T* p)
{
- static if (hasElaborateDestructor!T)
- {
- destroy(*p);
- }
- () @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
- p = null;
+ static if (hasElaborateDestructor!T)
+ {
+ destroy(*p);
+ }
+ () @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
+ p = null;
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T p)
- if (is(T == class) || is(T == interface))
+ if (is(T == class) || is(T == interface))
{
- if (p is null)
- {
- return;
- }
- static if (is(T == interface))
- {
- version(Windows)
- {
- import core.sys.windows.unknwn : IUnknown;
- static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
- ~ __PRETTY_FUNCTION__);
- }
- auto ob = cast(Object) p;
- }
- else
- {
- alias ob = p;
- }
- auto ptr = cast(void *) ob;
+ if (p is null)
+ {
+ return;
+ }
+ static if (is(T == interface))
+ {
+ version(Windows)
+ {
+ import core.sys.windows.unknwn : IUnknown;
+ static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
+ ~ __PRETTY_FUNCTION__);
+ }
+ auto ob = cast(Object) p;
+ }
+ else
+ {
+ alias ob = p;
+ }
+ auto ptr = cast(void *) ob;
- auto support = ptr[0 .. typeid(ob).initializer.length];
- scope (success)
- {
- () @trusted { allocator.deallocate(support); }();
- p = null;
- }
+ auto support = ptr[0 .. typeid(ob).initializer.length];
+ scope (success)
+ {
+ () @trusted { allocator.deallocate(support); }();
+ p = null;
+ }
- auto ppv = cast(void**) ptr;
- if (!*ppv)
- {
- return;
- }
- auto pc = cast(ClassInfo*) *ppv;
- scope (exit)
- {
- *ppv = null;
- }
+ auto ppv = cast(void**) ptr;
+ if (!*ppv)
+ {
+ return;
+ }
+ auto pc = cast(ClassInfo*) *ppv;
+ scope (exit)
+ {
+ *ppv = null;
+ }
- auto c = *pc;
- do
- {
- // Assume the destructor is @nogc. Leave it nothrow since the destructor
- // shouldn't throw and if it does, it is an error anyway.
- if (c.destructor)
- {
- (cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
- }
- }
- while ((c = c.base) !is null);
+ auto c = *pc;
+ do
+ {
+ // Assume the destructor is @nogc. Leave it nothrow since the destructor
+ // shouldn't throw and if it does, it is an error anyway.
+ if (c.destructor)
+ {
+ (cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
+ }
+ }
+ while ((c = c.base) !is null);
- if (ppv[1]) // if monitor is not null
- {
- _d_monitordelete(cast(Object) ptr, true);
- }
+ if (ppv[1]) // if monitor is not null
+ {
+ _d_monitordelete(cast(Object) ptr, true);
+ }
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T[] p)
{
- static if (hasElaborateDestructor!(typeof(p[0])))
- {
- import std.algorithm.iteration;
- p.each!(e => destroy(e));
- }
- () @trusted { allocator.deallocate(p); }();
- p = null;
+ static if (hasElaborateDestructor!(typeof(p[0])))
+ {
+ import std.algorithm.iteration;
+ p.each!(e => destroy(e));
+ }
+ () @trusted { allocator.deallocate(p); }();
+ p = null;
}
unittest
{
- struct S
- {
- ~this()
- {
- }
- }
- auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
+ struct S
+ {
+ ~this()
+ {
+ }
+ }
+ auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
- defaultAllocator.dispose(p);
+ defaultAllocator.dispose(p);
}
diff --git a/source/tanya/memory/types.d b/source/tanya/memory/types.d
index be9c8cd..1207d73 100644
--- a/source/tanya/memory/types.d
+++ b/source/tanya/memory/types.d
@@ -3,10 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
- * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.memory.types;
@@ -23,345 +23,345 @@ import tanya.memory;
* when the reference count goes down to zero, frees the underlying store.
*
* Params:
- * T = Type of the reference-counted value.
+ * T = Type of the reference-counted value.
*/
struct RefCounted(T)
{
- static if (is(T == class) || is(T == interface))
- {
- private alias Payload = T;
- }
- else
- {
- private alias Payload = T*;
- }
-
- private class Storage
- {
- private Payload payload;
- private size_t counter = 1;
-
- private final size_t opUnary(string op)() pure nothrow @safe @nogc
- if (op == "--" || op == "++")
- in
- {
- assert(counter > 0);
- }
- body
- {
- mixin("return " ~ op ~ "counter;");
- }
-
- private final int opCmp(size_t counter) const pure nothrow @safe @nogc
- {
- if (this.counter > counter)
- {
- return 1;
- }
- else if (this.counter < counter)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
-
- private final int opEquals(size_t counter) const pure nothrow @safe @nogc
- {
- return this.counter == counter;
- }
- }
-
- private final class RefCountedStorage : Storage
- {
- private shared Allocator allocator;
-
- this(shared Allocator allocator) pure nothrow @safe @nogc
- in
- {
- assert(allocator !is null);
- }
- body
- {
- this.allocator = allocator;
- }
-
- ~this() nothrow @nogc
- {
- allocator.dispose(payload);
- }
- }
-
- private Storage storage;
-
- invariant
- {
- assert(storage is null || allocator_ !is null);
- }
-
- /**
- * Takes ownership over $(D_PARAM value), setting the counter to 1.
- * $(D_PARAM value) may be a pointer, an object or a dynamic array.
- *
- * Params:
- * value = Value whose ownership is taken over.
- * allocator = Allocator used to destroy the $(D_PARAM value) and to
- * allocate/deallocate internal storage.
- *
- * Precondition: $(D_INLINECODE allocator !is null)
- */
- this(Payload value, shared Allocator allocator = defaultAllocator)
- {
- this(allocator);
- storage = allocator.make!RefCountedStorage(allocator);
- move(value, storage.payload);
- }
-
- /// Ditto.
- this(shared Allocator allocator)
- in
- {
- assert(allocator !is null);
- }
- body
- {
- this.allocator_ = allocator;
- }
-
- /**
- * Increases the reference counter by one.
- */
- this(this)
- {
- if (count != 0)
- {
- ++storage;
- }
- }
-
- /**
- * Decreases the reference counter by one.
- *
- * If the counter reaches 0, destroys the owned object.
- */
- ~this()
- {
- if (storage !is null && !(storage.counter && --storage))
- {
- allocator_.dispose(storage);
- }
- }
-
- /**
- * Takes ownership over $(D_PARAM rhs). Initializes this
- * $(D_PSYMBOL RefCounted) if needed.
- *
- * If it is the last reference of the previously owned object,
- * it will be destroyed.
- *
- * To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
- *
- * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
- * be used. If you need a different allocator, create a new
- * $(D_PSYMBOL RefCounted) and assign it.
- *
- * Params:
- * rhs = $(D_KEYWORD this).
- */
- ref typeof(this) opAssign(Payload rhs)
- {
- if (storage is null)
- {
- storage = allocator.make!RefCountedStorage(allocator);
- }
- else if (storage > 1)
- {
- --storage;
- storage = allocator.make!RefCountedStorage(allocator);
- }
- else if (cast(RefCountedStorage) storage is null)
- {
- // Created with refCounted. Always destroyed togethter with the pointer.
- assert(storage.counter != 0);
- allocator.dispose(storage);
- storage = allocator.make!RefCountedStorage(allocator);
- }
- else
- {
- allocator.dispose(storage.payload);
- }
- move(rhs, storage.payload);
- return this;
- }
-
- /// Ditto.
- ref typeof(this) opAssign(typeof(null))
- {
- if (storage is null)
- {
- return this;
- }
- else if (storage > 1)
- {
- --storage;
- storage = null;
- }
- else if (cast(RefCountedStorage) storage is null)
- {
- // Created with refCounted. Always destroyed togethter with the pointer.
- assert(storage.counter != 0);
- allocator.dispose(storage);
- return this;
- }
- else
- {
- allocator.dispose(storage.payload);
- }
- return this;
- }
-
- /// Ditto.
- ref typeof(this) opAssign(typeof(this) rhs)
- {
- swap(allocator_, rhs.allocator_);
- swap(storage, rhs.storage);
- return this;
- }
-
- /**
- * Returns: Reference to the owned object.
- */
- inout(Payload) get() inout pure nothrow @safe @nogc
- in
- {
- assert(count > 0, "Attempted to access an uninitialized reference.");
- }
- body
- {
- return storage.payload;
- }
-
- static if (isPointer!Payload)
- {
- /**
- * Params:
- * op = Operation.
- *
- * Dereferences the pointer. It is defined only for pointers, not for
- * reference types like classes, that can be accessed directly.
- *
- * Returns: Reference to the pointed value.
- */
- ref T opUnary(string op)()
- if (op == "*")
- {
- return *storage.payload;
- }
- }
-
- /**
- * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
- * storage.
- */
- @property bool isInitialized() const
- {
- return storage !is null;
- }
-
- /**
- * Returns: The number of $(D_PSYMBOL RefCounted) instances that share
- * ownership over the same pointer (including $(D_KEYWORD this)).
- * If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
- */
- @property size_t count() const
- {
- return storage is null ? 0 : storage.counter;
- }
-
- mixin DefaultAllocator;
- alias get this;
+ static if (is(T == class) || is(T == interface))
+ {
+ private alias Payload = T;
+ }
+ else
+ {
+ private alias Payload = T*;
+ }
+
+ private class Storage
+ {
+ private Payload payload;
+ private size_t counter = 1;
+
+ private final size_t opUnary(string op)() pure nothrow @safe @nogc
+ if (op == "--" || op == "++")
+ in
+ {
+ assert(counter > 0);
+ }
+ body
+ {
+ mixin("return " ~ op ~ "counter;");
+ }
+
+ private final int opCmp(size_t counter) const pure nothrow @safe @nogc
+ {
+ if (this.counter > counter)
+ {
+ return 1;
+ }
+ else if (this.counter < counter)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ private final int opEquals(size_t counter) const pure nothrow @safe @nogc
+ {
+ return this.counter == counter;
+ }
+ }
+
+ private final class RefCountedStorage : Storage
+ {
+ private shared Allocator allocator;
+
+ this(shared Allocator allocator) pure nothrow @safe @nogc
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ this.allocator = allocator;
+ }
+
+ ~this() nothrow @nogc
+ {
+ allocator.dispose(payload);
+ }
+ }
+
+ private Storage storage;
+
+ invariant
+ {
+ assert(storage is null || allocator_ !is null);
+ }
+
+ /**
+ * Takes ownership over $(D_PARAM value), setting the counter to 1.
+ * $(D_PARAM value) may be a pointer, an object or a dynamic array.
+ *
+ * Params:
+ * value = Value whose ownership is taken over.
+ * allocator = Allocator used to destroy the $(D_PARAM value) and to
+ * allocate/deallocate internal storage.
+ *
+ * Precondition: $(D_INLINECODE allocator !is null)
+ */
+ this(Payload value, shared Allocator allocator = defaultAllocator)
+ {
+ this(allocator);
+ storage = allocator.make!RefCountedStorage(allocator);
+ move(value, storage.payload);
+ }
+
+ /// Ditto.
+ this(shared Allocator allocator)
+ in
+ {
+ assert(allocator !is null);
+ }
+ body
+ {
+ this.allocator_ = allocator;
+ }
+
+ /**
+ * Increases the reference counter by one.
+ */
+ this(this)
+ {
+ if (count != 0)
+ {
+ ++storage;
+ }
+ }
+
+ /**
+ * Decreases the reference counter by one.
+ *
+ * If the counter reaches 0, destroys the owned object.
+ */
+ ~this()
+ {
+ if (storage !is null && !(storage.counter && --storage))
+ {
+ allocator_.dispose(storage);
+ }
+ }
+
+ /**
+ * Takes ownership over $(D_PARAM rhs). Initializes this
+ * $(D_PSYMBOL RefCounted) if needed.
+ *
+ * If it is the last reference of the previously owned object,
+ * it will be destroyed.
+ *
+ * To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
+ *
+ * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
+ * be used. If you need a different allocator, create a new
+ * $(D_PSYMBOL RefCounted) and assign it.
+ *
+ * Params:
+ * rhs = $(D_KEYWORD this).
+ */
+ ref typeof(this) opAssign(Payload rhs)
+ {
+ if (storage is null)
+ {
+ storage = allocator.make!RefCountedStorage(allocator);
+ }
+ else if (storage > 1)
+ {
+ --storage;
+ storage = allocator.make!RefCountedStorage(allocator);
+ }
+ else if (cast(RefCountedStorage) storage is null)
+ {
+ // Created with refCounted. Always destroyed togethter with the pointer.
+ assert(storage.counter != 0);
+ allocator.dispose(storage);
+ storage = allocator.make!RefCountedStorage(allocator);
+ }
+ else
+ {
+ allocator.dispose(storage.payload);
+ }
+ move(rhs, storage.payload);
+ return this;
+ }
+
+ /// Ditto.
+ ref typeof(this) opAssign(typeof(null))
+ {
+ if (storage is null)
+ {
+ return this;
+ }
+ else if (storage > 1)
+ {
+ --storage;
+ storage = null;
+ }
+ else if (cast(RefCountedStorage) storage is null)
+ {
+ // Created with refCounted. Always destroyed togethter with the pointer.
+ assert(storage.counter != 0);
+ allocator.dispose(storage);
+ return this;
+ }
+ else
+ {
+ allocator.dispose(storage.payload);
+ }
+ return this;
+ }
+
+ /// Ditto.
+ ref typeof(this) opAssign(typeof(this) rhs)
+ {
+ swap(allocator_, rhs.allocator_);
+ swap(storage, rhs.storage);
+ return this;
+ }
+
+ /**
+ * Returns: Reference to the owned object.
+ */
+ inout(Payload) get() inout pure nothrow @safe @nogc
+ in
+ {
+ assert(count > 0, "Attempted to access an uninitialized reference.");
+ }
+ body
+ {
+ return storage.payload;
+ }
+
+ static if (isPointer!Payload)
+ {
+ /**
+ * Params:
+ * op = Operation.
+ *
+ * Dereferences the pointer. It is defined only for pointers, not for
+ * reference types like classes, that can be accessed directly.
+ *
+ * Returns: Reference to the pointed value.
+ */
+ ref T opUnary(string op)()
+ if (op == "*")
+ {
+ return *storage.payload;
+ }
+ }
+
+ /**
+ * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
+ * storage.
+ */
+ @property bool isInitialized() const
+ {
+ return storage !is null;
+ }
+
+ /**
+ * Returns: The number of $(D_PSYMBOL RefCounted) instances that share
+ * ownership over the same pointer (including $(D_KEYWORD this)).
+ * If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
+ */
+ @property size_t count() const
+ {
+ return storage is null ? 0 : storage.counter;
+ }
+
+ mixin DefaultAllocator;
+ alias get this;
}
///
unittest
{
- auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
- auto val = rc.get;
+ auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
+ auto val = rc.get;
- *val = 8;
- assert(*rc.storage.payload == 8);
+ *val = 8;
+ assert(*rc.storage.payload == 8);
- val = null;
- assert(rc.storage.payload !is null);
- assert(*rc.storage.payload == 8);
+ val = null;
+ assert(rc.storage.payload !is null);
+ assert(*rc.storage.payload == 8);
- *rc = 9;
- assert(*rc.storage.payload == 9);
+ *rc = 9;
+ assert(*rc.storage.payload == 9);
}
version (unittest)
{
- private class A
- {
- uint *destroyed;
-
- this(ref uint destroyed) @nogc
- {
- this.destroyed = &destroyed;
- }
-
- ~this() @nogc
- {
- ++(*destroyed);
- }
- }
-
- private struct B
- {
- int prop;
- @disable this();
- this(int param1) @nogc
- {
- prop = param1;
- }
- }
+ private class A
+ {
+ uint *destroyed;
+
+ this(ref uint destroyed) @nogc
+ {
+ this.destroyed = &destroyed;
+ }
+
+ ~this() @nogc
+ {
+ ++(*destroyed);
+ }
+ }
+
+ private struct B
+ {
+ int prop;
+ @disable this();
+ this(int param1) @nogc
+ {
+ prop = param1;
+ }
+ }
}
private unittest
{
- uint destroyed;
- auto a = defaultAllocator.make!A(destroyed);
-
- assert(destroyed == 0);
- {
- auto rc = RefCounted!A(a, defaultAllocator);
- assert(rc.count == 1);
-
- void func(RefCounted!A rc)
- {
- assert(rc.count == 2);
- }
- func(rc);
-
- assert(rc.count == 1);
- }
- assert(destroyed == 1);
-
- RefCounted!int rc;
- assert(rc.count == 0);
- rc = defaultAllocator.make!int(8);
- assert(rc.count == 1);
+ uint destroyed;
+ auto a = defaultAllocator.make!A(destroyed);
+
+ assert(destroyed == 0);
+ {
+ auto rc = RefCounted!A(a, defaultAllocator);
+ assert(rc.count == 1);
+
+ void func(RefCounted!A rc)
+ {
+ assert(rc.count == 2);
+ }
+ func(rc);
+
+ assert(rc.count == 1);
+ }
+ assert(destroyed == 1);
+
+ RefCounted!int rc;
+ assert(rc.count == 0);
+ rc = defaultAllocator.make!int(8);
+ assert(rc.count == 1);
}
private unittest
{
- static assert(is(typeof(RefCounted!int.storage.payload) == int*));
- static assert(is(typeof(RefCounted!A.storage.payload) == A));
+ static assert(is(typeof(RefCounted!int.storage.payload) == int*));
+ static assert(is(typeof(RefCounted!A.storage.payload) == A));
- static assert(is(RefCounted!B));
- static assert(is(RefCounted!A));
+ static assert(is(RefCounted!B));
+ static assert(is(RefCounted!A));
}
/**
@@ -374,85 +374,85 @@ private unittest
* object).
*
* Params:
- * T = Type of the constructed object.
- * A = Types of the arguments to the constructor of $(D_PARAM T).
- * allocator = Allocator.
- * args = Constructor arguments of $(D_PARAM T).
+ * T = Type of the constructed object.
+ * A = Types of the arguments to the constructor of $(D_PARAM T).
+ * allocator = Allocator.
+ * args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*/
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
- if (!is(T == interface) && !isAbstractClass!T
+ if (!is(T == interface) && !isAbstractClass!T
&& !isArray!T && !isAssociativeArray!T)
{
- auto rc = typeof(return)(allocator);
-
- immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
- immutable size = alignedSize(stateSize!T + storageSize);
-
- auto mem = (() @trusted => allocator.allocate(size))();
- if (mem is null)
- {
- onOutOfMemoryError();
- }
- scope (failure)
- {
- () @trusted { allocator.deallocate(mem); }();
- }
- rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
-
- static if (is(T == class))
- {
- rc.storage.payload = emplace!T(mem[storageSize .. $], args);
- }
- else
- {
- auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
- rc.storage.payload = emplace!T(ptr, args);
- }
- return rc;
+ auto rc = typeof(return)(allocator);
+
+ immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
+ immutable size = alignedSize(stateSize!T + storageSize);
+
+ auto mem = (() @trusted => allocator.allocate(size))();
+ if (mem is null)
+ {
+ onOutOfMemoryError();
+ }
+ scope (failure)
+ {
+ () @trusted { allocator.deallocate(mem); }();
+ }
+ rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
+
+ static if (is(T == class))
+ {
+ rc.storage.payload = emplace!T(mem[storageSize .. $], args);
+ }
+ else
+ {
+ auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
+ rc.storage.payload = emplace!T(ptr, args);
+ }
+ return rc;
}
///
unittest
{
- auto rc = defaultAllocator.refCounted!int(5);
- assert(rc.count == 1);
-
- void func(RefCounted!int param)
- {
- if (param.count == 2)
- {
- func(param);
- }
- else
- {
- assert(param.count == 3);
- }
- }
- func(rc);
-
- assert(rc.count == 1);
+ auto rc = defaultAllocator.refCounted!int(5);
+ assert(rc.count == 1);
+
+ void func(RefCounted!int param)
+ {
+ if (param.count == 2)
+ {
+ func(param);
+ }
+ else
+ {
+ assert(param.count == 3);
+ }
+ }
+ func(rc);
+
+ assert(rc.count == 1);
}
private @nogc unittest
{
- struct E
- {
- }
- auto b = defaultAllocator.refCounted!B(15);
- static assert(is(typeof(b.storage.payload) == B*));
- static assert(is(typeof(b.prop) == int));
- static assert(!is(typeof(defaultAllocator.refCounted!B())));
-
- static assert(is(typeof(defaultAllocator.refCounted!E())));
- static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
- {
- auto rc = defaultAllocator.refCounted!B(3);
- assert(rc.get.prop == 3);
- }
- {
- auto rc = defaultAllocator.refCounted!E();
- assert(rc.count);
- }
+ struct E
+ {
+ }
+ auto b = defaultAllocator.refCounted!B(15);
+ static assert(is(typeof(b.storage.payload) == B*));
+ static assert(is(typeof(b.prop) == int));
+ static assert(!is(typeof(defaultAllocator.refCounted!B())));
+
+ static assert(is(typeof(defaultAllocator.refCounted!E())));
+ static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
+ {
+ auto rc = defaultAllocator.refCounted!B(3);
+ assert(rc.get.prop == 3);
+ }
+ {
+ auto rc = defaultAllocator.refCounted!E();
+ assert(rc.count);
+ }
}
diff --git a/source/tanya/network/inet.d b/source/tanya/network/inet.d
new file mode 100644
index 0000000..9b0f28a
--- /dev/null
+++ b/source/tanya/network/inet.d
@@ -0,0 +1,356 @@
+/* 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/. */
+
+/**
+ * Internet utilities.
+ *
+ * Copyright: Eugene Wissner 2016-2017.
+ * 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)
+ */
+module tanya.network.inet;
+
+import std.math;
+import std.range.primitives;
+import std.traits;
+
+version (unittest)
+{
+ version (Windows)
+ {
+ import core.sys.windows.winsock2;
+ version = PlattformUnittest;
+ }
+ else version (Posix)
+ {
+ import core.sys.posix.arpa.inet;
+ version = PlattformUnittest;
+ }
+}
+
+/**
+ * Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
+ *
+ * The range is bidirectional. The byte order is always big-endian.
+ *
+ * It can accept any unsigned integral type but the value should fit
+ * in $(D_PARAM L) bytes.
+ *
+ * Params:
+ * L = Desired range length.
+ */
+struct NetworkOrder(uint L)
+ if (L > ubyte.sizeof && L <= ulong.sizeof)
+{
+ static if (L > uint.sizeof)
+ {
+ private alias StorageType = ulong;
+ }
+ else static if (L > ushort.sizeof)
+ {
+ private alias StorageType = uint;
+ }
+ else static if (L > ubyte.sizeof)
+ {
+ private alias StorageType = ushort;
+ }
+ else
+ {
+ private alias StorageType = ubyte;
+ }
+
+ private StorageType value;
+ private size_t size = L;
+
+ const pure nothrow @safe @nogc invariant
+ {
+ assert(this.size <= L);
+ }
+
+ /**
+ * Constructs a new range.
+ *
+ * $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
+ * larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
+ * an assertion failure will be caused.
+ *
+ * Params:
+ * T = Value type.
+ * value = The value should be represented by this range.
+ *
+ * Precondition: $(D_INLINECODE value <= 2 ^^ (length * 8) - 1).
+ */
+ this(T)(const T value)
+ if (isUnsigned!T)
+ in
+ {
+ assert(value <= pow(2, L * 8) - 1);
+ }
+ body
+ {
+ this.value = value & StorageType.max;
+ }
+
+ /**
+ * Returns: LSB.
+ *
+ * Precondition: $(D_INLINECODE length > 0).
+ */
+ @property ubyte back() const
+ in
+ {
+ assert(this.length > 0);
+ }
+ body
+ {
+ return this.value & 0xff;
+ }
+
+ /**
+ * Returns: MSB.
+ *
+ * Precondition: $(D_INLINECODE length > 0).
+ */
+ @property ubyte front() const
+ in
+ {
+ assert(this.length > 0);
+ }
+ body
+ {
+ return (this.value >> ((this.length - 1) * 8)) & 0xff;
+ }
+
+ /**
+ * Eliminates the LSB.
+ *
+ * Precondition: $(D_INLINECODE length > 0).
+ */
+ void popBack()
+ in
+ {
+ assert(this.length > 0);
+ }
+ body
+ {
+ this.value >>= 8;
+ --this.size;
+ }
+
+ /**
+ * Eliminates the MSB.
+ *
+ * Precondition: $(D_INLINECODE length > 0).
+ */
+ void popFront()
+ in
+ {
+ assert(this.length > 0);
+ }
+ body
+ {
+ this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
+ --this.size;
+ }
+
+ /**
+ * Returns: Copy of this range.
+ */
+ typeof(this) save() const
+ {
+ return this;
+ }
+
+ /**
+ * Returns: Whether the range is empty.
+ */
+ @property bool empty() const
+ {
+ return this.length == 0;
+ }
+
+ /**
+ * Returns: Byte length.
+ */
+ @property size_t length() const
+ {
+ return this.size;
+ }
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+ auto networkOrder = NetworkOrder!3(0xae34e2u);
+ assert(!networkOrder.empty);
+ assert(networkOrder.front == 0xae);
+
+ networkOrder.popFront();
+ assert(networkOrder.length == 2);
+ assert(networkOrder.front == 0x34);
+ assert(networkOrder.back == 0xe2);
+
+ networkOrder.popBack();
+ assert(networkOrder.length == 1);
+ assert(networkOrder.front == 0x34);
+ assert(networkOrder.front == 0x34);
+
+ networkOrder.popFront();
+ assert(networkOrder.empty);
+}
+
+// Static.
+private unittest
+{
+ static assert(isBidirectionalRange!(NetworkOrder!4));
+ static assert(isBidirectionalRange!(NetworkOrder!8));
+ static assert(!is(NetworkOrder!9));
+ static assert(!is(NetworkOrder!1));
+}
+
+// Tests against the system's htonl, htons.
+version (PlattformUnittest)
+{
+ private unittest
+ {
+ for (uint counter; counter <= 8 * uint.sizeof; ++counter)
+ {
+ const value = pow(2, counter) - 1;
+ const inNetworkOrder = htonl(value);
+ const p = cast(ubyte*) &inNetworkOrder;
+ auto networkOrder = NetworkOrder!4(value);
+
+ assert(networkOrder.length == 4);
+ assert(!networkOrder.empty);
+ assert(networkOrder.front == *p);
+ assert(networkOrder.back == *(p + 3));
+
+ networkOrder.popBack();
+ assert(networkOrder.length == 3);
+ assert(networkOrder.front == *p);
+ assert(networkOrder.back == *(p + 2));
+
+ networkOrder.popFront();
+ assert(networkOrder.length == 2);
+ assert(networkOrder.front == *(p + 1));
+ assert(networkOrder.back == *(p + 2));
+
+ networkOrder.popFront();
+ assert(networkOrder.length == 1);
+ assert(networkOrder.front == *(p + 2));
+ assert(networkOrder.back == *(p + 2));
+
+ networkOrder.popBack();
+ assert(networkOrder.length == 0);
+ assert(networkOrder.empty);
+ }
+
+ for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
+ {
+ const value = cast(ushort) (pow(2, counter) - 1);
+ const inNetworkOrder = htons(value);
+ const p = cast(ubyte*) &inNetworkOrder;
+
+ auto networkOrder = NetworkOrder!2(value);
+
+ assert(networkOrder.length == 2);
+ assert(!networkOrder.empty);
+ assert(networkOrder.front == *p);
+ assert(networkOrder.back == *(p + 1));
+
+ networkOrder.popBack();
+ assert(networkOrder.length == 1);
+ assert(networkOrder.front == *p);
+ assert(networkOrder.back == *p);
+
+ networkOrder.popBack();
+ assert(networkOrder.length == 0);
+ assert(networkOrder.empty);
+
+ networkOrder = NetworkOrder!2(value);
+
+ networkOrder.popFront();
+ assert(networkOrder.length == 1);
+ assert(networkOrder.front == *(p + 1));
+ assert(networkOrder.back == *(p + 1));
+
+ networkOrder.popFront();
+ assert(networkOrder.length == 0);
+ assert(networkOrder.empty);
+ }
+ }
+}
+
+/**
+ * Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
+ * $(D_PARAM T).
+ *
+ * The byte order of $(D_PARAM r) is assumed to be big-endian. The length
+ * cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
+ * failure will be caused.
+ *
+ * Params:
+ * T = Desired return type.
+ * R = Range type.
+ * range = Input range.
+ *
+ * Returns: Integral representation of $(D_PARAM range) with the host byte
+ * order.
+ */
+T toHostOrder(T = size_t, R)(R range)
+ if (isInputRange!R
+ && !isInfinite!R
+ && is(Unqual!(ElementType!R) == ubyte)
+ && isUnsigned!T)
+{
+ T ret;
+ ushort pos = T.sizeof * 8;
+
+ for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
+ {
+ }
+ for (; !range.empty; range.popFront())
+ {
+ assert(pos != 0);
+ pos -= 8;
+ ret |= (cast(T) range.front) << pos;
+ }
+
+ return ret >> pos;
+}
+
+///
+pure nothrow @safe @nogc unittest
+{
+ const value = 0xae34e2u;
+ auto networkOrder = NetworkOrder!4(value);
+ assert(networkOrder.toHostOrder() == value);
+}
+
+// Tests against the system's htonl, htons.
+version (PlattformUnittest)
+{
+ private unittest
+ {
+ for (uint counter; counter <= 8 * uint.sizeof; ++counter)
+ {
+ const value = pow(2, counter) - 1;
+ const inNetworkOrder = htonl(value);
+ const p = cast(ubyte*) &inNetworkOrder;
+ auto networkOrder = NetworkOrder!4(value);
+
+ assert(p[0 .. uint.sizeof].toHostOrder() == value);
+ }
+ for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
+ {
+ const value = cast(ushort) (pow(2, counter) - 1);
+ const inNetworkOrder = htons(value);
+ const p = cast(ubyte*) &inNetworkOrder;
+ auto networkOrder = NetworkOrder!2(value);
+
+ assert(p[0 .. ushort.sizeof].toHostOrder() == value);
+ }
+ }
+}
diff --git a/source/tanya/network/package.d b/source/tanya/network/package.d
new file mode 100644
index 0000000..14a6752
--- /dev/null
+++ b/source/tanya/network/package.d
@@ -0,0 +1,17 @@
+/* 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/. */
+
+/**
+ * Network programming.
+ *
+ * Copyright: Eugene Wissner 2016-2017.
+ * 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)
+ */
+module tanya.network;
+
+public import tanya.network.inet;
+public import tanya.network.socket;
+public import tanya.network.url;
diff --git a/source/tanya/network/socket.d b/source/tanya/network/socket.d
index 17bcfa4..5dbbe41 100644
--- a/source/tanya/network/socket.d
+++ b/source/tanya/network/socket.d
@@ -3,7 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * Socket programming.
+ *
+ * Copyright: Eugene Wissner 2016-2017.
* 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)
@@ -22,532 +24,532 @@ import std.typecons;
version (Posix)
{
- import core.stdc.errno;
- import core.sys.posix.fcntl;
- import core.sys.posix.netdb;
- import core.sys.posix.netinet.in_;
- import core.sys.posix.sys.socket;
- import core.sys.posix.sys.time;
- import core.sys.posix.unistd;
-
- private enum SOCKET_ERROR = -1;
+ import core.stdc.errno;
+ import core.sys.posix.fcntl;
+ import core.sys.posix.netdb;
+ import core.sys.posix.netinet.in_;
+ import core.sys.posix.sys.socket;
+ import core.sys.posix.sys.time;
+ import core.sys.posix.unistd;
+
+ private enum SOCKET_ERROR = -1;
}
else version (Windows)
{
- import tanya.async.iocp;
- import core.sys.windows.basetyps;
- import core.sys.windows.mswsock;
- import core.sys.windows.winbase;
- import core.sys.windows.windef;
- import core.sys.windows.winsock2;
-
- enum : uint
- {
- IOC_UNIX = 0x00000000,
- IOC_WS2 = 0x08000000,
- IOC_PROTOCOL = 0x10000000,
- IOC_VOID = 0x20000000, /// No parameters.
- IOC_OUT = 0x40000000, /// Copy parameters back.
- IOC_IN = 0x80000000, /// Copy parameters into.
- IOC_VENDOR = 0x18000000,
- IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.
- }
-
- template _WSAIO(int x, int y)
- {
- enum _WSAIO = IOC_VOID | x | y;
- }
- template _WSAIOR(int x, int y)
- {
- enum _WSAIOR = IOC_OUT | x | y;
- }
- template _WSAIOW(int x, int y)
- {
- enum _WSAIOW = IOC_IN | x | y;
- }
- template _WSAIORW(int x, int y)
- {
- enum _WSAIORW = IOC_INOUT | x | y;
- }
-
- alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1);
- alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2);
- alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3);
- alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4);
- alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5);
- alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);
- alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7);
- alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8);
- alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9);
- alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10);
- alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11);
- alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12);
- alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13);
- alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20);
- alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21);
- alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22);
- alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23);
- alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24);
- alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25);
-
- private alias GROUP = uint;
-
- enum
- {
- WSA_FLAG_OVERLAPPED = 0x01,
- MAX_PROTOCOL_CHAIN = 7,
- WSAPROTOCOL_LEN = 255,
- }
-
- struct WSAPROTOCOLCHAIN
- {
- int ChainLen;
- DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;
- }
- alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*;
-
- struct WSAPROTOCOL_INFO
- {
- DWORD dwServiceFlags1;
- DWORD dwServiceFlags2;
- DWORD dwServiceFlags3;
- DWORD dwServiceFlags4;
- DWORD dwProviderFlags;
- GUID ProviderId;
- DWORD dwCatalogEntryId;
- WSAPROTOCOLCHAIN ProtocolChain;
- int iVersion;
- int iAddressFamily;
- int iMaxSockAddr;
- int iMinSockAddr;
- int iSocketType;
- int iProtocol;
- int iProtocolMaxOffset;
- int iNetworkByteOrder;
- int iSecurityScheme;
- DWORD dwMessageSize;
- DWORD dwProviderReserved;
- TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;
- }
- alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*;
-
- extern (Windows) @nogc nothrow
- {
- private SOCKET WSASocketW(int af,
- int type,
- int protocol,
- LPWSAPROTOCOL_INFO lpProtocolInfo,
- GROUP g,
- DWORD dwFlags);
- int WSARecv(SOCKET s,
- LPWSABUF lpBuffers,
- DWORD dwBufferCount,
- LPDWORD lpNumberOfBytesRecvd,
- LPDWORD lpFlags,
- LPOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- int WSASend(SOCKET s,
- LPWSABUF lpBuffers,
- DWORD dwBufferCount,
- LPDWORD lpNumberOfBytesRecvd,
- DWORD lpFlags,
- LPOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- int WSAIoctl(SOCKET s,
- uint dwIoControlCode,
- void* lpvInBuffer,
- uint cbInBuffer,
- void* lpvOutBuffer,
- uint cbOutBuffer,
- uint* lpcbBytesReturned,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- alias LPFN_ACCEPTEX = BOOL function(SOCKET,
- SOCKET,
- PVOID,
- DWORD,
- DWORD,
- DWORD,
- LPDWORD,
- LPOVERLAPPED);
- }
- alias WSASocket = WSASocketW;
-
- alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID,
- DWORD,
- DWORD,
- DWORD,
- SOCKADDR**,
- LPINT,
- SOCKADDR**,
- LPINT);
- const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
-
- struct WSABUF {
- ULONG len;
- CHAR* buf;
- }
- alias WSABUF* LPWSABUF;
-
- struct WSAOVERLAPPED {
- ULONG_PTR Internal;
- ULONG_PTR InternalHigh;
- union {
- struct {
- DWORD Offset;
- DWORD OffsetHigh;
- }
- PVOID Pointer;
- }
- HANDLE hEvent;
- }
- alias LPWSAOVERLAPPED = WSAOVERLAPPED*;
-
- enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B;
-
- enum OverlappedSocketEvent
- {
- accept = 1,
- read = 2,
- write = 3,
- }
-
- class SocketState : State
- {
- private WSABUF buffer;
- }
-
- /**
- * Socket returned if a connection has been established.
- */
- class OverlappedConnectedSocket : ConnectedSocket
- {
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket handle.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- {
- super(handle, af);
- }
-
- /**
- * Begins to asynchronously receive data from a connected socket.
- *
- * Params:
- * buffer = Storage location for the received data.
- * flags = Flags.
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- bool beginReceive(ubyte[] buffer,
- SocketState overlapped,
- Flags flags = Flags(Flag.none)) @nogc @trusted
- {
- auto receiveFlags = cast(DWORD) flags;
-
- overlapped.handle = cast(HANDLE) handle_;
- overlapped.event = OverlappedSocketEvent.read;
- overlapped.buffer.len = cast(ULONG) buffer.length;
- overlapped.buffer.buf = cast(char*) buffer.ptr;
-
- auto result = WSARecv(handle_,
- &overlapped.buffer,
- 1u,
- NULL,
- &receiveFlags,
- &overlapped.overlapped,
- NULL);
-
- if (result == SOCKET_ERROR && !wouldHaveBlocked)
- {
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return result == 0;
- }
-
- /**
- * Ends a pending asynchronous read.
- *
- * Params
- * overlapped = Unique operation identifier.
- *
- * Returns: Number of bytes received.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- int endReceive(SocketState overlapped) @nogc @trusted
- out (count)
- {
- assert(count >= 0);
- }
- body
- {
- DWORD lpNumber;
- BOOL result = GetOverlappedResult(overlapped.handle,
- &overlapped.overlapped,
- &lpNumber,
- FALSE);
- if (result == FALSE && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- if (lpNumber == 0)
- {
- disconnected_ = true;
- }
- return lpNumber;
- }
-
- /**
- * Sends data asynchronously to a connected socket.
- *
- * Params:
- * buffer = Data to be sent.
- * flags = Flags.
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to send.
- */
- bool beginSend(ubyte[] buffer,
- SocketState overlapped,
- Flags flags = Flags(Flag.none)) @nogc @trusted
- {
- overlapped.handle = cast(HANDLE) handle_;
- overlapped.event = OverlappedSocketEvent.write;
- overlapped.buffer.len = cast(ULONG) buffer.length;
- overlapped.buffer.buf = cast(char*) buffer.ptr;
-
- auto result = WSASend(handle_,
- &overlapped.buffer,
- 1u,
- NULL,
- cast(DWORD) flags,
- &overlapped.overlapped,
- NULL);
-
- if (result == SOCKET_ERROR && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to send");
- }
- return result == 0;
- }
-
- /**
- * Ends a pending asynchronous send.
- *
- * Params
- * overlapped = Unique operation identifier.
- *
- * Returns: Number of bytes sent.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- int endSend(SocketState overlapped) @nogc @trusted
- out (count)
- {
- assert(count >= 0);
- }
- body
- {
- DWORD lpNumber;
- BOOL result = GetOverlappedResult(overlapped.handle,
- &overlapped.overlapped,
- &lpNumber,
- FALSE);
- if (result == FALSE && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return lpNumber;
- }
- }
-
- class OverlappedStreamSocket : StreamSocket
- {
- /// Accept extension function pointer.
- package LPFN_ACCEPTEX acceptExtension;
-
- /**
- * Create a socket.
- *
- * Params:
- * af = Address family.
- *
- * Throws: $(D_PSYMBOL SocketException) on errors.
- */
- this(AddressFamily af) @nogc @trusted
- {
- super(af);
- scope (failure)
- {
- this.close();
- }
- blocking = false;
-
- GUID guidAcceptEx = WSAID_ACCEPTEX;
- DWORD dwBytes;
-
- auto result = WSAIoctl(handle_,
- SIO_GET_EXTENSION_FUNCTION_POINTER,
- &guidAcceptEx,
- guidAcceptEx.sizeof,
- &acceptExtension,
- acceptExtension.sizeof,
- &dwBytes,
- NULL,
- NULL);
- if (!result == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to retrieve an accept extension function pointer");
- }
- }
-
- /**
- * Begins an asynchronous operation to accept an incoming connection attempt.
- *
- * Params:
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) on accept errors.
- */
- bool beginAccept(SocketState overlapped) @nogc @trusted
- {
- auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
- if (socket == socket_t.init)
- {
- throw defaultAllocator.make!SocketException("Unable to create socket");
- }
- scope (failure)
- {
- closesocket(socket);
- }
- DWORD dwBytes;
- overlapped.handle = cast(HANDLE) socket;
- overlapped.event = OverlappedSocketEvent.accept;
-
- immutable len = (sockaddr_in.sizeof + 16) * 2;
- overlapped.buffer.len = len;
- overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
-
- // We don't want to get any data now, but only start to accept the connections
- BOOL result = acceptExtension(handle_,
- socket,
- overlapped.buffer.buf,
- 0u,
- sockaddr_in.sizeof + 16,
- sockaddr_in.sizeof + 16,
- &dwBytes,
- &overlapped.overlapped);
- if (result == FALSE && !wouldHaveBlocked)
- {
- throw defaultAllocator.make!SocketException("Unable to accept socket connection");
- }
- return result == TRUE;
- }
-
- /**
- * Asynchronously accepts an incoming connection attempt and creates a
- * new socket to handle remote host communication.
- *
- * Params:
- * overlapped = Unique operation identifier.
- *
- * Returns: Connected socket.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to accept.
- */
- OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
- {
- scope (exit)
- {
- defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
- }
- auto socket = make!OverlappedConnectedSocket(defaultAllocator,
- cast(socket_t) overlapped.handle,
- addressFamily);
- scope (failure)
- {
- defaultAllocator.dispose(socket);
- }
- socket.setOption(SocketOptionLevel.SOCKET,
- cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
- cast(size_t) handle);
- return socket;
- }
- }
+ import tanya.async.iocp;
+ import core.sys.windows.basetyps;
+ import core.sys.windows.mswsock;
+ import core.sys.windows.winbase;
+ import core.sys.windows.windef;
+ import core.sys.windows.winsock2;
+
+ enum : uint
+ {
+ IOC_UNIX = 0x00000000,
+ IOC_WS2 = 0x08000000,
+ IOC_PROTOCOL = 0x10000000,
+ IOC_VOID = 0x20000000, /// No parameters.
+ IOC_OUT = 0x40000000, /// Copy parameters back.
+ IOC_IN = 0x80000000, /// Copy parameters into.
+ IOC_VENDOR = 0x18000000,
+ IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.
+ }
+
+ template _WSAIO(int x, int y)
+ {
+ enum _WSAIO = IOC_VOID | x | y;
+ }
+ template _WSAIOR(int x, int y)
+ {
+ enum _WSAIOR = IOC_OUT | x | y;
+ }
+ template _WSAIOW(int x, int y)
+ {
+ enum _WSAIOW = IOC_IN | x | y;
+ }
+ template _WSAIORW(int x, int y)
+ {
+ enum _WSAIORW = IOC_INOUT | x | y;
+ }
+
+ alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1);
+ alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2);
+ alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3);
+ alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4);
+ alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5);
+ alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);
+ alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7);
+ alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8);
+ alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9);
+ alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10);
+ alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11);
+ alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12);
+ alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13);
+ alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20);
+ alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21);
+ alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22);
+ alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23);
+ alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24);
+ alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25);
+
+ private alias GROUP = uint;
+
+ enum
+ {
+ WSA_FLAG_OVERLAPPED = 0x01,
+ MAX_PROTOCOL_CHAIN = 7,
+ WSAPROTOCOL_LEN = 255,
+ }
+
+ struct WSAPROTOCOLCHAIN
+ {
+ int ChainLen;
+ DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;
+ }
+ alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*;
+
+ struct WSAPROTOCOL_INFO
+ {
+ DWORD dwServiceFlags1;
+ DWORD dwServiceFlags2;
+ DWORD dwServiceFlags3;
+ DWORD dwServiceFlags4;
+ DWORD dwProviderFlags;
+ GUID ProviderId;
+ DWORD dwCatalogEntryId;
+ WSAPROTOCOLCHAIN ProtocolChain;
+ int iVersion;
+ int iAddressFamily;
+ int iMaxSockAddr;
+ int iMinSockAddr;
+ int iSocketType;
+ int iProtocol;
+ int iProtocolMaxOffset;
+ int iNetworkByteOrder;
+ int iSecurityScheme;
+ DWORD dwMessageSize;
+ DWORD dwProviderReserved;
+ TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;
+ }
+ alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*;
+
+ extern (Windows) @nogc nothrow
+ {
+ private SOCKET WSASocketW(int af,
+ int type,
+ int protocol,
+ LPWSAPROTOCOL_INFO lpProtocolInfo,
+ GROUP g,
+ DWORD dwFlags);
+ int WSARecv(SOCKET s,
+ LPWSABUF lpBuffers,
+ DWORD dwBufferCount,
+ LPDWORD lpNumberOfBytesRecvd,
+ LPDWORD lpFlags,
+ LPOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ int WSASend(SOCKET s,
+ LPWSABUF lpBuffers,
+ DWORD dwBufferCount,
+ LPDWORD lpNumberOfBytesRecvd,
+ DWORD lpFlags,
+ LPOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ int WSAIoctl(SOCKET s,
+ uint dwIoControlCode,
+ void* lpvInBuffer,
+ uint cbInBuffer,
+ void* lpvOutBuffer,
+ uint cbOutBuffer,
+ uint* lpcbBytesReturned,
+ LPWSAOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ alias LPFN_ACCEPTEX = BOOL function(SOCKET,
+ SOCKET,
+ PVOID,
+ DWORD,
+ DWORD,
+ DWORD,
+ LPDWORD,
+ LPOVERLAPPED);
+ }
+ alias WSASocket = WSASocketW;
+
+ alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID,
+ DWORD,
+ DWORD,
+ DWORD,
+ SOCKADDR**,
+ LPINT,
+ SOCKADDR**,
+ LPINT);
+ const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
+
+ struct WSABUF {
+ ULONG len;
+ CHAR* buf;
+ }
+ alias WSABUF* LPWSABUF;
+
+ struct WSAOVERLAPPED {
+ ULONG_PTR Internal;
+ ULONG_PTR InternalHigh;
+ union {
+ struct {
+ DWORD Offset;
+ DWORD OffsetHigh;
+ }
+ PVOID Pointer;
+ }
+ HANDLE hEvent;
+ }
+ alias LPWSAOVERLAPPED = WSAOVERLAPPED*;
+
+ enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B;
+
+ enum OverlappedSocketEvent
+ {
+ accept = 1,
+ read = 2,
+ write = 3,
+ }
+
+ class SocketState : State
+ {
+ private WSABUF buffer;
+ }
+
+ /**
+ * Socket returned if a connection has been established.
+ */
+ class OverlappedConnectedSocket : ConnectedSocket
+ {
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket handle.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ {
+ super(handle, af);
+ }
+
+ /**
+ * Begins to asynchronously receive data from a connected socket.
+ *
+ * Params:
+ * buffer = Storage location for the received data.
+ * flags = Flags.
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ bool beginReceive(ubyte[] buffer,
+ SocketState overlapped,
+ Flags flags = Flags(Flag.none)) @nogc @trusted
+ {
+ auto receiveFlags = cast(DWORD) flags;
+
+ overlapped.handle = cast(HANDLE) handle_;
+ overlapped.event = OverlappedSocketEvent.read;
+ overlapped.buffer.len = cast(ULONG) buffer.length;
+ overlapped.buffer.buf = cast(char*) buffer.ptr;
+
+ auto result = WSARecv(handle_,
+ &overlapped.buffer,
+ 1u,
+ NULL,
+ &receiveFlags,
+ &overlapped.overlapped,
+ NULL);
+
+ if (result == SOCKET_ERROR && !wouldHaveBlocked)
+ {
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return result == 0;
+ }
+
+ /**
+ * Ends a pending asynchronous read.
+ *
+ * Params
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Number of bytes received.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ int endReceive(SocketState overlapped) @nogc @trusted
+ out (count)
+ {
+ assert(count >= 0);
+ }
+ body
+ {
+ DWORD lpNumber;
+ BOOL result = GetOverlappedResult(overlapped.handle,
+ &overlapped.overlapped,
+ &lpNumber,
+ FALSE);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ if (lpNumber == 0)
+ {
+ disconnected_ = true;
+ }
+ return lpNumber;
+ }
+
+ /**
+ * Sends data asynchronously to a connected socket.
+ *
+ * Params:
+ * buffer = Data to be sent.
+ * flags = Flags.
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to send.
+ */
+ bool beginSend(ubyte[] buffer,
+ SocketState overlapped,
+ Flags flags = Flags(Flag.none)) @nogc @trusted
+ {
+ overlapped.handle = cast(HANDLE) handle_;
+ overlapped.event = OverlappedSocketEvent.write;
+ overlapped.buffer.len = cast(ULONG) buffer.length;
+ overlapped.buffer.buf = cast(char*) buffer.ptr;
+
+ auto result = WSASend(handle_,
+ &overlapped.buffer,
+ 1u,
+ NULL,
+ cast(DWORD) flags,
+ &overlapped.overlapped,
+ NULL);
+
+ if (result == SOCKET_ERROR && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to send");
+ }
+ return result == 0;
+ }
+
+ /**
+ * Ends a pending asynchronous send.
+ *
+ * Params
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Number of bytes sent.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ int endSend(SocketState overlapped) @nogc @trusted
+ out (count)
+ {
+ assert(count >= 0);
+ }
+ body
+ {
+ DWORD lpNumber;
+ BOOL result = GetOverlappedResult(overlapped.handle,
+ &overlapped.overlapped,
+ &lpNumber,
+ FALSE);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return lpNumber;
+ }
+ }
+
+ class OverlappedStreamSocket : StreamSocket
+ {
+ /// Accept extension function pointer.
+ package LPFN_ACCEPTEX acceptExtension;
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * af = Address family.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on errors.
+ */
+ this(AddressFamily af) @nogc @trusted
+ {
+ super(af);
+ scope (failure)
+ {
+ this.close();
+ }
+ blocking = false;
+
+ GUID guidAcceptEx = WSAID_ACCEPTEX;
+ DWORD dwBytes;
+
+ auto result = WSAIoctl(handle_,
+ SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidAcceptEx,
+ guidAcceptEx.sizeof,
+ &acceptExtension,
+ acceptExtension.sizeof,
+ &dwBytes,
+ NULL,
+ NULL);
+ if (!result == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to retrieve an accept extension function pointer");
+ }
+ }
+
+ /**
+ * Begins an asynchronous operation to accept an incoming connection attempt.
+ *
+ * Params:
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on accept errors.
+ */
+ bool beginAccept(SocketState overlapped) @nogc @trusted
+ {
+ auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
+ if (socket == socket_t.init)
+ {
+ throw defaultAllocator.make!SocketException("Unable to create socket");
+ }
+ scope (failure)
+ {
+ closesocket(socket);
+ }
+ DWORD dwBytes;
+ overlapped.handle = cast(HANDLE) socket;
+ overlapped.event = OverlappedSocketEvent.accept;
+
+ immutable len = (sockaddr_in.sizeof + 16) * 2;
+ overlapped.buffer.len = len;
+ overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
+
+ // We don't want to get any data now, but only start to accept the connections
+ BOOL result = acceptExtension(handle_,
+ socket,
+ overlapped.buffer.buf,
+ 0u,
+ sockaddr_in.sizeof + 16,
+ sockaddr_in.sizeof + 16,
+ &dwBytes,
+ &overlapped.overlapped);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ throw defaultAllocator.make!SocketException("Unable to accept socket connection");
+ }
+ return result == TRUE;
+ }
+
+ /**
+ * Asynchronously accepts an incoming connection attempt and creates a
+ * new socket to handle remote host communication.
+ *
+ * Params:
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Connected socket.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to accept.
+ */
+ OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
+ {
+ scope (exit)
+ {
+ defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
+ }
+ auto socket = make!OverlappedConnectedSocket(defaultAllocator,
+ cast(socket_t) overlapped.handle,
+ addressFamily);
+ scope (failure)
+ {
+ defaultAllocator.dispose(socket);
+ }
+ socket.setOption(SocketOptionLevel.SOCKET,
+ cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
+ cast(size_t) handle);
+ return socket;
+ }
+ }
}
version (linux)
{
- enum SOCK_NONBLOCK = O_NONBLOCK;
- extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow;
+ enum SOCK_NONBLOCK = O_NONBLOCK;
+ extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow;
}
else version (OSX)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (iOS)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (FreeBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (OpenBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (DragonFlyBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
version (MacBSD)
{
- enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
+ enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
}
private immutable
{
- typeof(&getaddrinfo) getaddrinfoPointer;
- typeof(&freeaddrinfo) freeaddrinfoPointer;
+ typeof(&getaddrinfo) getaddrinfoPointer;
+ typeof(&freeaddrinfo) freeaddrinfoPointer;
}
shared static this()
{
- version (Windows)
- {
- auto ws2Lib = GetModuleHandle("ws2_32.dll");
-
- getaddrinfoPointer = cast(typeof(getaddrinfoPointer))
- GetProcAddress(ws2Lib, "getaddrinfo");
- freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))
- GetProcAddress(ws2Lib, "freeaddrinfo");
- }
- else version (Posix)
- {
- getaddrinfoPointer = &getaddrinfo;
- freeaddrinfoPointer = &freeaddrinfo;
- }
+ version (Windows)
+ {
+ auto ws2Lib = GetModuleHandle("ws2_32.dll");
+
+ getaddrinfoPointer = cast(typeof(getaddrinfoPointer))
+ GetProcAddress(ws2Lib, "getaddrinfo");
+ freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))
+ GetProcAddress(ws2Lib, "freeaddrinfo");
+ }
+ else version (Posix)
+ {
+ getaddrinfoPointer = &getaddrinfo;
+ freeaddrinfoPointer = &freeaddrinfo;
+ }
}
/**
@@ -555,32 +557,32 @@ shared static this()
*/
enum SocketError : int
{
- /// Unknown error
- unknown = 0,
- /// Firewall rules forbid connection.
- accessDenied = EPERM,
- /// A socket operation was attempted on a non-socket.
- notSocket = EBADF,
- /// The network is not available.
- networkDown = ECONNABORTED,
- /// An invalid pointer address was detected by the underlying socket provider.
- fault = EFAULT,
- /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.
- invalidArgument = EINVAL,
- /// The limit on the number of open sockets has been reached.
- tooManyOpenSockets = ENFILE,
- /// No free buffer space is available for a Socket operation.
- noBufferSpaceAvailable = ENOBUFS,
- /// The address family is not supported by the protocol family.
- operationNotSupported = EOPNOTSUPP,
- /// The protocol is not implemented or has not been configured.
- protocolNotSupported = EPROTONOSUPPORT,
- /// Protocol error.
- protocolError = EPROTOTYPE,
- /// The connection attempt timed out, or the connected host has failed to respond.
- timedOut = ETIMEDOUT,
- /// The support for the specified socket type does not exist in this address family.
- socketNotSupported = ESOCKTNOSUPPORT,
+ /// Unknown error
+ unknown = 0,
+ /// Firewall rules forbid connection.
+ accessDenied = EPERM,
+ /// A socket operation was attempted on a non-socket.
+ notSocket = EBADF,
+ /// The network is not available.
+ networkDown = ECONNABORTED,
+ /// An invalid pointer address was detected by the underlying socket provider.
+ fault = EFAULT,
+ /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.
+ invalidArgument = EINVAL,
+ /// The limit on the number of open sockets has been reached.
+ tooManyOpenSockets = ENFILE,
+ /// No free buffer space is available for a Socket operation.
+ noBufferSpaceAvailable = ENOBUFS,
+ /// The address family is not supported by the protocol family.
+ operationNotSupported = EOPNOTSUPP,
+ /// The protocol is not implemented or has not been configured.
+ protocolNotSupported = EPROTONOSUPPORT,
+ /// Protocol error.
+ protocolError = EPROTOTYPE,
+ /// The connection attempt timed out, or the connected host has failed to respond.
+ timedOut = ETIMEDOUT,
+ /// The support for the specified socket type does not exist in this address family.
+ socketNotSupported = ESOCKTNOSUPPORT,
}
/**
@@ -590,53 +592,53 @@ enum SocketError : int
*/
class SocketException : Exception
{
- immutable SocketError error = SocketError.unknown;
-
- /**
- * Params:
- * msg = The message for the exception.
- * file = The file where the exception occurred.
- * line = The line number where the exception occurred.
- * next = The previous exception in the chain of exceptions, if any.
- */
- this(string msg,
- string file = __FILE__,
- size_t line = __LINE__,
- Throwable next = null) @nogc @safe nothrow
- {
- super(msg, file, line, next);
-
- foreach (member; EnumMembers!SocketError)
- {
- if (member == lastError)
- {
- error = member;
- return;
- }
- }
- if (lastError == ENOMEM)
- {
- error = SocketError.noBufferSpaceAvailable;
- }
- else if (lastError == EMFILE)
- {
- error = SocketError.tooManyOpenSockets;
- }
- else version (linux)
- {
- if (lastError == ENOSR)
- {
- error = SocketError.networkDown;
- }
- }
- else version (Posix)
- {
- if (lastError == EPROTO)
- {
- error = SocketError.networkDown;
- }
- }
- }
+ immutable SocketError error = SocketError.unknown;
+
+ /**
+ * Params:
+ * msg = The message for the exception.
+ * file = The file where the exception occurred.
+ * line = The line number where the exception occurred.
+ * next = The previous exception in the chain of exceptions, if any.
+ */
+ this(string msg,
+ string file = __FILE__,
+ size_t line = __LINE__,
+ Throwable next = null) @nogc @safe nothrow
+ {
+ super(msg, file, line, next);
+
+ foreach (member; EnumMembers!SocketError)
+ {
+ if (member == lastError)
+ {
+ error = member;
+ return;
+ }
+ }
+ if (lastError == ENOMEM)
+ {
+ error = SocketError.noBufferSpaceAvailable;
+ }
+ else if (lastError == EMFILE)
+ {
+ error = SocketError.tooManyOpenSockets;
+ }
+ else version (linux)
+ {
+ if (lastError == ENOSR)
+ {
+ error = SocketError.networkDown;
+ }
+ }
+ else version (Posix)
+ {
+ if (lastError == EPROTO)
+ {
+ error = SocketError.networkDown;
+ }
+ }
+ }
}
/**
@@ -645,353 +647,353 @@ class SocketException : Exception
*/
abstract class Socket
{
- version (Posix)
- {
- /**
- * How a socket is shutdown.
- */
- enum Shutdown : int
- {
- receive = SHUT_RD, /// Socket receives are disallowed
- send = SHUT_WR, /// Socket sends are disallowed
- both = SHUT_RDWR, /// Both receive and send
- }
- }
- else version (Windows)
- {
- /// Property to get or set whether the socket is blocking or nonblocking.
- private bool blocking_ = true;
-
- /**
- * How a socket is shutdown.
- */
- enum Shutdown : int
- {
- receive = SD_RECEIVE, /// Socket receives are disallowed.
- send = SD_SEND, /// Socket sends are disallowed.
- both = SD_BOTH, /// Both receive and send.
- }
-
- // The WinSock timeouts seem to be effectively skewed by a constant
- // offset of about half a second (in milliseconds).
- private enum WINSOCK_TIMEOUT_SKEW = 500;
- }
-
- /// Socket handle.
- protected socket_t handle_;
-
- /// Address family.
- protected AddressFamily family;
-
- private @property void handle(socket_t handle) @nogc
- in
- {
- assert(handle != socket_t.init);
- assert(handle_ == socket_t.init, "Socket handle cannot be changed");
- }
- body
- {
- handle_ = handle;
-
- // Set the option to disable SIGPIPE on send() if the platform
- // has it (e.g. on OS X).
- static if (is(typeof(SO_NOSIGPIPE)))
- {
- setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);
- }
- }
-
- @property inout(socket_t) handle() inout const pure nothrow @safe @nogc
- {
- return handle_;
- }
-
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- in
- {
- assert(handle != socket_t.init);
- }
- body
- {
- scope (failure)
- {
- this.close();
- }
- this.handle = handle;
- family = af;
- }
-
- /**
- * Closes the socket and calls the destructor on itself.
- */
- ~this() nothrow @trusted @nogc
- {
- this.close();
- }
-
- /**
- * Get a socket option.
- *
- * Params:
- * level = Protocol level at that the option exists.
- * option = Option.
- * result = Buffer to save the result.
- *
- * Returns: The number of bytes written to $(D_PARAM result).
- *
- * Throws: $(D_PSYMBOL SocketException) on error.
- */
- protected int getOption(SocketOptionLevel level,
- SocketOption option,
- void[] result) const @trusted @nogc
- {
- auto length = cast(socklen_t) result.length;
- if (getsockopt(handle_,
- cast(int) level,
- cast(int) option,
- result.ptr,
- &length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to get socket option");
- }
- return length;
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
- out size_t result) const @trusted @nogc
- {
- return getOption(level, option, (&result)[0..1]);
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
+ version (Posix)
+ {
+ /**
+ * How a socket is shutdown.
+ */
+ enum Shutdown : int
+ {
+ receive = SHUT_RD, /// Socket receives are disallowed
+ send = SHUT_WR, /// Socket sends are disallowed
+ both = SHUT_RDWR, /// Both receive and send
+ }
+ }
+ else version (Windows)
+ {
+ /// Property to get or set whether the socket is blocking or nonblocking.
+ private bool blocking_ = true;
+
+ /**
+ * How a socket is shutdown.
+ */
+ enum Shutdown : int
+ {
+ receive = SD_RECEIVE, /// Socket receives are disallowed.
+ send = SD_SEND, /// Socket sends are disallowed.
+ both = SD_BOTH, /// Both receive and send.
+ }
+
+ // The WinSock timeouts seem to be effectively skewed by a constant
+ // offset of about half a second (in milliseconds).
+ private enum WINSOCK_TIMEOUT_SKEW = 500;
+ }
+
+ /// Socket handle.
+ protected socket_t handle_;
+
+ /// Address family.
+ protected AddressFamily family;
+
+ private @property void handle(socket_t handle) @nogc
+ in
+ {
+ assert(handle != socket_t.init);
+ assert(handle_ == socket_t.init, "Socket handle cannot be changed");
+ }
+ body
+ {
+ handle_ = handle;
+
+ // Set the option to disable SIGPIPE on send() if the platform
+ // has it (e.g. on OS X).
+ static if (is(typeof(SO_NOSIGPIPE)))
+ {
+ setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);
+ }
+ }
+
+ @property inout(socket_t) handle() inout const pure nothrow @safe @nogc
+ {
+ return handle_;
+ }
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ in
+ {
+ assert(handle != socket_t.init);
+ }
+ body
+ {
+ scope (failure)
+ {
+ this.close();
+ }
+ this.handle = handle;
+ family = af;
+ }
+
+ /**
+ * Closes the socket and calls the destructor on itself.
+ */
+ ~this() nothrow @trusted @nogc
+ {
+ this.close();
+ }
+
+ /**
+ * Get a socket option.
+ *
+ * Params:
+ * level = Protocol level at that the option exists.
+ * option = Option.
+ * result = Buffer to save the result.
+ *
+ * Returns: The number of bytes written to $(D_PARAM result).
+ *
+ * Throws: $(D_PSYMBOL SocketException) on error.
+ */
+ protected int getOption(SocketOptionLevel level,
+ SocketOption option,
+ void[] result) const @trusted @nogc
+ {
+ auto length = cast(socklen_t) result.length;
+ if (getsockopt(handle_,
+ cast(int) level,
+ cast(int) option,
+ result.ptr,
+ &length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to get socket option");
+ }
+ return length;
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
+ out size_t result) const @trusted @nogc
+ {
+ return getOption(level, option, (&result)[0..1]);
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
out Linger result) const @trusted @nogc
- {
- return getOption(level, option, (&result.clinger)[0..1]);
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
- out Duration result) const @trusted @nogc
- {
- // WinSock returns the timeout values as a milliseconds DWORD,
- // while Linux and BSD return a timeval struct.
- version (Posix)
- {
- timeval tv;
- auto ret = getOption(level, option, (&tv)[0..1]);
- result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
- }
- else version (Windows)
- {
- int msecs;
- auto ret = getOption(level, option, (&msecs)[0 .. 1]);
- if (option == SocketOption.RCVTIMEO)
- {
- msecs += WINSOCK_TIMEOUT_SKEW;
- }
- result = dur!"msecs"(msecs);
- }
- return ret;
- }
-
- /**
- * Set a socket option.
- *
- * Params:
- * level = Protocol level at that the option exists.
- * option = Option.
- * value = Option value.
- *
- * Throws: $(D_PSYMBOL SocketException) on error.
- */
- protected void setOption(SocketOptionLevel level,
- SocketOption option,
- void[] value) const @trusted @nogc
- {
- if (setsockopt(handle_,
- cast(int)level,
- cast(int)option,
- value.ptr,
- cast(uint) value.length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to set socket option");
- }
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, size_t value)
- const @trusted @nogc
- {
- setOption(level, option, (&value)[0..1]);
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, Linger value)
- const @trusted @nogc
- {
- setOption(level, option, (&value.clinger)[0..1]);
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, Duration value)
- const @trusted @nogc
- {
- version (Posix)
- {
- timeval tv;
- value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
- setOption(level, option, (&tv)[0..1]);
- }
- else version (Windows)
- {
- auto msecs = cast(int) value.total!"msecs";
- if (msecs > 0 && option == SocketOption.RCVTIMEO)
- {
- msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);
- }
- setOption(level, option, msecs);
- }
- }
-
- /**
- * Returns: Socket's blocking flag.
- */
- @property inout(bool) blocking() inout const nothrow @nogc
- {
- version (Posix)
- {
- return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK);
- }
- else version (Windows)
- {
- return blocking_;
- }
- }
-
- /**
- * Params:
- * yes = Socket's blocking flag.
- */
- @property void blocking(bool yes) @nogc
- {
- version (Posix)
- {
- int fl = fcntl(handle_, F_GETFL, 0);
-
- if (fl != SOCKET_ERROR)
- {
- fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
- fl = fcntl(handle_, F_SETFL, fl);
- }
- if (fl == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to set socket blocking");
- }
- }
- else version (Windows)
- {
- uint num = !yes;
- if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to set socket blocking");
- }
- blocking_ = yes;
- }
- }
-
- /**
- * Returns: The socket's address family.
- */
- @property AddressFamily addressFamily() const @nogc @safe pure nothrow
- {
- return family;
- }
-
- /**
- * Returns: $(D_KEYWORD true) if this is a valid, alive socket.
- */
- @property bool isAlive() @trusted const nothrow @nogc
- {
- int type;
- socklen_t typesize = cast(socklen_t) type.sizeof;
- return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);
- }
-
- /**
- * Disables sends and/or receives.
- *
- * Params:
- * how = What to disable.
- *
- * See_Also:
- * $(D_PSYMBOL Shutdown)
- */
- void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow
- {
- .shutdown(handle_, cast(int)how);
- }
-
- /**
- * Immediately drop any connections and release socket resources.
- * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended
- * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no
- * longer usable after $(D_PSYMBOL close).
- */
- void close() nothrow @trusted @nogc
- {
- version(Windows)
- {
- .closesocket(handle_);
- }
- else version(Posix)
- {
- .close(handle_);
- }
- handle_ = socket_t.init;
- }
-
- /**
- * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you
- * can $(D_PSYMBOL listen).
- *
- * Params:
- * backlog = Request of how many pending incoming connections are
- * queued until $(D_PSYMBOL accept)ed.
- */
- void listen(int backlog) const @trusted @nogc
- {
- if (.listen(handle_, backlog) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to listen on socket");
- }
- }
-
- /**
- * Compare handles.
- *
- * Params:
- * that = Another handle.
- *
- * Returns: Comparision result.
- */
- int opCmp(size_t that) const pure nothrow @safe @nogc
- {
- return handle_ < that ? -1 : handle_ > that ? 1 : 0;
- }
+ {
+ return getOption(level, option, (&result.clinger)[0..1]);
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
+ out Duration result) const @trusted @nogc
+ {
+ // WinSock returns the timeout values as a milliseconds DWORD,
+ // while Linux and BSD return a timeval struct.
+ version (Posix)
+ {
+ timeval tv;
+ auto ret = getOption(level, option, (&tv)[0..1]);
+ result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
+ }
+ else version (Windows)
+ {
+ int msecs;
+ auto ret = getOption(level, option, (&msecs)[0 .. 1]);
+ if (option == SocketOption.RCVTIMEO)
+ {
+ msecs += WINSOCK_TIMEOUT_SKEW;
+ }
+ result = dur!"msecs"(msecs);
+ }
+ return ret;
+ }
+
+ /**
+ * Set a socket option.
+ *
+ * Params:
+ * level = Protocol level at that the option exists.
+ * option = Option.
+ * value = Option value.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on error.
+ */
+ protected void setOption(SocketOptionLevel level,
+ SocketOption option,
+ void[] value) const @trusted @nogc
+ {
+ if (setsockopt(handle_,
+ cast(int)level,
+ cast(int)option,
+ value.ptr,
+ cast(uint) value.length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to set socket option");
+ }
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, size_t value)
+ const @trusted @nogc
+ {
+ setOption(level, option, (&value)[0..1]);
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, Linger value)
+ const @trusted @nogc
+ {
+ setOption(level, option, (&value.clinger)[0..1]);
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, Duration value)
+ const @trusted @nogc
+ {
+ version (Posix)
+ {
+ timeval tv;
+ value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
+ setOption(level, option, (&tv)[0..1]);
+ }
+ else version (Windows)
+ {
+ auto msecs = cast(int) value.total!"msecs";
+ if (msecs > 0 && option == SocketOption.RCVTIMEO)
+ {
+ msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);
+ }
+ setOption(level, option, msecs);
+ }
+ }
+
+ /**
+ * Returns: Socket's blocking flag.
+ */
+ @property inout(bool) blocking() inout const nothrow @nogc
+ {
+ version (Posix)
+ {
+ return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK);
+ }
+ else version (Windows)
+ {
+ return blocking_;
+ }
+ }
+
+ /**
+ * Params:
+ * yes = Socket's blocking flag.
+ */
+ @property void blocking(bool yes) @nogc
+ {
+ version (Posix)
+ {
+ int fl = fcntl(handle_, F_GETFL, 0);
+
+ if (fl != SOCKET_ERROR)
+ {
+ fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
+ fl = fcntl(handle_, F_SETFL, fl);
+ }
+ if (fl == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to set socket blocking");
+ }
+ }
+ else version (Windows)
+ {
+ uint num = !yes;
+ if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to set socket blocking");
+ }
+ blocking_ = yes;
+ }
+ }
+
+ /**
+ * Returns: The socket's address family.
+ */
+ @property AddressFamily addressFamily() const @nogc @safe pure nothrow
+ {
+ return family;
+ }
+
+ /**
+ * Returns: $(D_KEYWORD true) if this is a valid, alive socket.
+ */
+ @property bool isAlive() @trusted const nothrow @nogc
+ {
+ int type;
+ socklen_t typesize = cast(socklen_t) type.sizeof;
+ return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);
+ }
+
+ /**
+ * Disables sends and/or receives.
+ *
+ * Params:
+ * how = What to disable.
+ *
+ * See_Also:
+ * $(D_PSYMBOL Shutdown)
+ */
+ void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow
+ {
+ .shutdown(handle_, cast(int)how);
+ }
+
+ /**
+ * Immediately drop any connections and release socket resources.
+ * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended
+ * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no
+ * longer usable after $(D_PSYMBOL close).
+ */
+ void close() nothrow @trusted @nogc
+ {
+ version(Windows)
+ {
+ .closesocket(handle_);
+ }
+ else version(Posix)
+ {
+ .close(handle_);
+ }
+ handle_ = socket_t.init;
+ }
+
+ /**
+ * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you
+ * can $(D_PSYMBOL listen).
+ *
+ * Params:
+ * backlog = Request of how many pending incoming connections are
+ * queued until $(D_PSYMBOL accept)ed.
+ */
+ void listen(int backlog) const @trusted @nogc
+ {
+ if (.listen(handle_, backlog) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to listen on socket");
+ }
+ }
+
+ /**
+ * Compare handles.
+ *
+ * Params:
+ * that = Another handle.
+ *
+ * Returns: Comparision result.
+ */
+ int opCmp(size_t that) const pure nothrow @safe @nogc
+ {
+ return handle_ < that ? -1 : handle_ > that ? 1 : 0;
+ }
}
/**
@@ -999,119 +1001,123 @@ abstract class Socket
*/
interface ConnectionOrientedSocket
{
- /**
- * Flags may be OR'ed together.
- */
- enum Flag : int
- {
- none = 0, /// No flags specified
- outOfBand = MSG_OOB, /// Out-of-band stream data
- peek = MSG_PEEK, /// Peek at incoming data without removing it from the queue, only for receiving
- dontRoute = MSG_DONTROUTE, /// Data should not be subject to routing; this flag may be ignored. Only for sending
- }
-
- alias Flags = BitFlags!Flag;
+ /**
+ * Flags may be OR'ed together.
+ */
+ enum Flag : int
+ {
+ /// No flags specified.
+ none = 0,
+ /// Out-of-band stream data.
+ outOfBand = MSG_OOB,
+ /// Peek at incoming data without removing it from the queue, only for receiving.
+ peek = MSG_PEEK,
+ /// Data should not be subject to routing; this flag may be ignored. Only for sending.
+ dontRoute = MSG_DONTROUTE,
+ }
+
+ alias Flags = BitFlags!Flag;
}
class StreamSocket : Socket, ConnectionOrientedSocket
{
- /**
- * Create a socket.
- *
- * Params:
- * af = Address family.
- */
- this(AddressFamily af) @trusted @nogc
- {
- auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
- if (handle == socket_t.init)
- {
- throw defaultAllocator.make!SocketException("Unable to create socket");
- }
- super(handle, af);
- }
-
- /**
- * Associate a local address with this socket.
- *
- * Params:
- * address = Local address.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to bind.
- */
- void bind(Address address) const @trusted @nogc
- {
- if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to bind socket");
- }
- }
-
- /**
- * Accept an incoming connection.
- *
- * The blocking mode is always inherited.
- *
- * Returns: $(D_PSYMBOL Socket) for the accepted connection or
- * $(D_KEYWORD null) if the call would block on a
- * non-blocking socket.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to accept.
- */
- ConnectedSocket accept() @trusted @nogc
- {
- socket_t sock;
-
- version (linux)
- {
- int flags;
- if (!blocking)
- {
- flags |= SOCK_NONBLOCK;
- }
- sock = cast(socket_t).accept4(handle_, null, null, flags);
- }
- else
- {
- sock = cast(socket_t).accept(handle_, null, null);
- }
-
- if (sock == socket_t.init)
- {
- if (wouldHaveBlocked())
- {
- return null;
- }
- throw make!SocketException(defaultAllocator,
- "Unable to accept socket connection");
- }
-
- auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
-
- version (linux)
- { // Blocking mode already set
- }
- else version (Posix)
- {
- if (!blocking)
- {
- try
- {
- newSocket.blocking = blocking;
- }
- catch (SocketException e)
- {
- defaultAllocator.dispose(newSocket);
- throw e;
- }
- }
- }
- else version (Windows)
- { // Inherits blocking mode
- newSocket.blocking_ = blocking;
- }
- return newSocket;
- }
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * af = Address family.
+ */
+ this(AddressFamily af) @trusted @nogc
+ {
+ auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
+ if (handle == socket_t.init)
+ {
+ throw defaultAllocator.make!SocketException("Unable to create socket");
+ }
+ super(handle, af);
+ }
+
+ /**
+ * Associate a local address with this socket.
+ *
+ * Params:
+ * address = Local address.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to bind.
+ */
+ void bind(Address address) const @trusted @nogc
+ {
+ if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to bind socket");
+ }
+ }
+
+ /**
+ * Accept an incoming connection.
+ *
+ * The blocking mode is always inherited.
+ *
+ * Returns: $(D_PSYMBOL Socket) for the accepted connection or
+ * $(D_KEYWORD null) if the call would block on a
+ * non-blocking socket.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to accept.
+ */
+ ConnectedSocket accept() @trusted @nogc
+ {
+ socket_t sock;
+
+ version (linux)
+ {
+ int flags;
+ if (!blocking)
+ {
+ flags |= SOCK_NONBLOCK;
+ }
+ sock = cast(socket_t).accept4(handle_, null, null, flags);
+ }
+ else
+ {
+ sock = cast(socket_t).accept(handle_, null, null);
+ }
+
+ if (sock == socket_t.init)
+ {
+ if (wouldHaveBlocked())
+ {
+ return null;
+ }
+ throw make!SocketException(defaultAllocator,
+ "Unable to accept socket connection");
+ }
+
+ auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
+
+ version (linux)
+ { // Blocking mode already set
+ }
+ else version (Posix)
+ {
+ if (!blocking)
+ {
+ try
+ {
+ newSocket.blocking = blocking;
+ }
+ catch (SocketException e)
+ {
+ defaultAllocator.dispose(newSocket);
+ throw e;
+ }
+ }
+ }
+ else version (Windows)
+ { // Inherits blocking mode
+ newSocket.blocking_ = blocking;
+ }
+ return newSocket;
+ }
}
/**
@@ -1119,124 +1125,124 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/
class ConnectedSocket : Socket, ConnectionOrientedSocket
{
- /**
- * $(D_KEYWORD true) if the stream socket peer has performed an orderly
- * shutdown.
- */
- protected bool disconnected_;
-
- /**
- * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly
- * shutdown.
- */
- @property inout(bool) disconnected() inout const pure nothrow @safe @nogc
- {
- return disconnected_;
- }
-
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- {
- super(handle, af);
- }
-
- version (Windows)
- {
- private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc
- {
- // Windows uses int instead of size_t for length arguments.
- // Luckily, the send/recv functions make no guarantee that
- // all the data is sent, so we use that to send at most
- // int.max bytes.
- return size > size_t (int.max) ? int.max : cast(int) size;
- }
- }
- else
- {
- private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc
- {
- return size;
- }
- }
-
- /**
- * Receive data on the connection.
- *
- * Params:
- * buf = Buffer to save received data.
- * flags = Flags.
- *
- * Returns: The number of bytes received or 0 if nothing received
- * because the call would block.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
- {
- ptrdiff_t ret;
- if (!buf.length)
- {
- return 0;
- }
-
- ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags);
- if (ret == 0)
- {
- disconnected_ = true;
- }
- else if (ret == SOCKET_ERROR)
- {
- if (wouldHaveBlocked())
- {
- return 0;
- }
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return ret;
- }
-
- /**
- * Send data on the connection. If the socket is blocking and there is no
- * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns
- * 0 in this case.
- *
- * Params:
- * buf = Data to be sent.
- * flags = Flags.
- *
- * Returns: The number of bytes actually sent.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to send.
- */
- ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
- const @trusted @nogc
- {
- int sendFlags = cast(int) flags;
- ptrdiff_t sent;
-
- static if (is(typeof(MSG_NOSIGNAL)))
- {
- sendFlags |= MSG_NOSIGNAL;
- }
-
- sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
- if (sent != SOCKET_ERROR)
- {
- return sent;
- }
- else if (wouldHaveBlocked())
- {
- return 0;
- }
- throw defaultAllocator.make!SocketException("Unable to send");
- }
+ /**
+ * $(D_KEYWORD true) if the stream socket peer has performed an orderly
+ * shutdown.
+ */
+ protected bool disconnected_;
+
+ /**
+ * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly
+ * shutdown.
+ */
+ @property inout(bool) disconnected() inout const pure nothrow @safe @nogc
+ {
+ return disconnected_;
+ }
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ {
+ super(handle, af);
+ }
+
+ version (Windows)
+ {
+ private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc
+ {
+ // Windows uses int instead of size_t for length arguments.
+ // Luckily, the send/recv functions make no guarantee that
+ // all the data is sent, so we use that to send at most
+ // int.max bytes.
+ return size > size_t (int.max) ? int.max : cast(int) size;
+ }
+ }
+ else
+ {
+ private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc
+ {
+ return size;
+ }
+ }
+
+ /**
+ * Receive data on the connection.
+ *
+ * Params:
+ * buf = Buffer to save received data.
+ * flags = Flags.
+ *
+ * Returns: The number of bytes received or 0 if nothing received
+ * because the call would block.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
+ {
+ ptrdiff_t ret;
+ if (!buf.length)
+ {
+ return 0;
+ }
+
+ ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags);
+ if (ret == 0)
+ {
+ disconnected_ = true;
+ }
+ else if (ret == SOCKET_ERROR)
+ {
+ if (wouldHaveBlocked())
+ {
+ return 0;
+ }
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return ret;
+ }
+
+ /**
+ * Send data on the connection. If the socket is blocking and there is no
+ * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns
+ * 0 in this case.
+ *
+ * Params:
+ * buf = Data to be sent.
+ * flags = Flags.
+ *
+ * Returns: The number of bytes actually sent.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to send.
+ */
+ ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
+ const @trusted @nogc
+ {
+ int sendFlags = cast(int) flags;
+ ptrdiff_t sent;
+
+ static if (is(typeof(MSG_NOSIGNAL)))
+ {
+ sendFlags |= MSG_NOSIGNAL;
+ }
+
+ sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
+ if (sent != SOCKET_ERROR)
+ {
+ return sent;
+ }
+ else if (wouldHaveBlocked())
+ {
+ return 0;
+ }
+ throw defaultAllocator.make!SocketException("Unable to send");
+ }
}
/**
@@ -1244,132 +1250,132 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
*/
abstract class Address
{
- /**
- * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
- */
- abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;
-
- /**
- * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
- */
- abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;
+ /**
+ * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;
+
+ /**
+ * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;
}
class InternetAddress : Address
{
- version (Windows)
- {
- /// Internal internet address representation.
- protected SOCKADDR_STORAGE storage;
- }
- else version (Posix)
- {
- /// Internal internet address representation.
- protected sockaddr_storage storage;
- }
- immutable ushort port_;
-
- enum
- {
- anyPort = 0,
- }
-
- this(in string host, ushort port = anyPort) @nogc
- {
- if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
- {
- throw make!SocketException(defaultAllocator,
- "Address info lookup is not available on this system");
- }
- addrinfo* ai_res;
- port_ = port;
-
- // Make C-string from host.
- auto node = cast(char[]) allocator.allocate(host.length + 1);
- node[0.. $ - 1] = host;
- node[$ - 1] = '\0';
- scope (exit)
- {
- allocator.deallocate(node);
- }
-
- // Convert port to a C-string.
- char[6] service = [0, 0, 0, 0, 0, 0];
- const(char)* servicePointer;
- if (port)
- {
- ushort start;
- for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
- {
- ushort rest = port % 10;
- if (rest != 0)
- {
- service[i] = cast(char) (rest + '0');
- start = i;
- }
- port /= 10;
- }
- servicePointer = service[start..$].ptr;
- }
-
- auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
- if (ret)
- {
- throw defaultAllocator.make!SocketException("Address info lookup failed");
- }
- scope (exit)
- {
- freeaddrinfoPointer(ai_res);
- }
-
- ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;
- for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++)
- {
- *dp = *sp;
- }
- if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
- {
- throw defaultAllocator.make!SocketException("Wrong address family");
- }
- }
-
- /**
- * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
- */
- override @property inout(sockaddr)* name() inout pure nothrow @nogc
- {
- return cast(sockaddr*) &storage;
- }
-
- /**
- * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
- */
- override @property inout(socklen_t) length() inout const pure nothrow @nogc
- {
- // FreeBSD wants to know the exact length of the address on bind.
- switch (family)
- {
- case AddressFamily.INET:
- return sockaddr_in.sizeof;
- case AddressFamily.INET6:
- return sockaddr_in6.sizeof;
- default:
- assert(false);
- }
- }
-
- /**
- * Returns: Family of this address.
- */
- @property inout(AddressFamily) family() inout const pure nothrow @nogc
- {
- return cast(AddressFamily) storage.ss_family;
- }
-
- @property inout(ushort) port() inout const pure nothrow @nogc
- {
- return port_;
- }
+ version (Windows)
+ {
+ /// Internal internet address representation.
+ protected SOCKADDR_STORAGE storage;
+ }
+ else version (Posix)
+ {
+ /// Internal internet address representation.
+ protected sockaddr_storage storage;
+ }
+ immutable ushort port_;
+
+ enum
+ {
+ anyPort = 0,
+ }
+
+ this(in string host, ushort port = anyPort) @nogc
+ {
+ if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Address info lookup is not available on this system");
+ }
+ addrinfo* ai_res;
+ port_ = port;
+
+ // Make C-string from host.
+ auto node = cast(char[]) allocator.allocate(host.length + 1);
+ node[0.. $ - 1] = host;
+ node[$ - 1] = '\0';
+ scope (exit)
+ {
+ allocator.deallocate(node);
+ }
+
+ // Convert port to a C-string.
+ char[6] service = [0, 0, 0, 0, 0, 0];
+ const(char)* servicePointer;
+ if (port)
+ {
+ ushort start;
+ for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
+ {
+ ushort rest = port % 10;
+ if (rest != 0)
+ {
+ service[i] = cast(char) (rest + '0');
+ start = i;
+ }
+ port /= 10;
+ }
+ servicePointer = service[start..$].ptr;
+ }
+
+ auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
+ if (ret)
+ {
+ throw defaultAllocator.make!SocketException("Address info lookup failed");
+ }
+ scope (exit)
+ {
+ freeaddrinfoPointer(ai_res);
+ }
+
+ ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;
+ for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++)
+ {
+ *dp = *sp;
+ }
+ if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
+ {
+ throw defaultAllocator.make!SocketException("Wrong address family");
+ }
+ }
+
+ /**
+ * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ override @property inout(sockaddr)* name() inout pure nothrow @nogc
+ {
+ return cast(sockaddr*) &storage;
+ }
+
+ /**
+ * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ override @property inout(socklen_t) length() inout const pure nothrow @nogc
+ {
+ // FreeBSD wants to know the exact length of the address on bind.
+ switch (family)
+ {
+ case AddressFamily.INET:
+ return sockaddr_in.sizeof;
+ case AddressFamily.INET6:
+ return sockaddr_in6.sizeof;
+ default:
+ assert(false);
+ }
+ }
+
+ /**
+ * Returns: Family of this address.
+ */
+ @property inout(AddressFamily) family() inout const pure nothrow @nogc
+ {
+ return cast(AddressFamily) storage.ss_family;
+ }
+
+ @property inout(ushort) port() inout const pure nothrow @nogc
+ {
+ return port_;
+ }
}
/**
@@ -1383,16 +1389,16 @@ class InternetAddress : Address
*/
bool wouldHaveBlocked() nothrow @trusted @nogc
{
- version (Posix)
- {
- return errno == EAGAIN || errno == EWOULDBLOCK;
- }
- else version (Windows)
- {
- return WSAGetLastError() == ERROR_IO_PENDING
- || WSAGetLastError() == EWOULDBLOCK
- || WSAGetLastError() == ERROR_IO_INCOMPLETE;
- }
+ version (Posix)
+ {
+ return errno == EAGAIN || errno == EWOULDBLOCK;
+ }
+ else version (Windows)
+ {
+ return WSAGetLastError() == ERROR_IO_PENDING
+ || WSAGetLastError() == EWOULDBLOCK
+ || WSAGetLastError() == ERROR_IO_INCOMPLETE;
+ }
}
/**
@@ -1400,12 +1406,12 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
*/
private @property int lastError() nothrow @safe @nogc
{
- version (Windows)
- {
- return WSAGetLastError();
- }
- else
- {
- return errno;
- }
+ version (Windows)
+ {
+ return WSAGetLastError();
+ }
+ else
+ {
+ return errno;
+ }
}
diff --git a/source/tanya/network/url.d b/source/tanya/network/url.d
index d0377d4..0cbec34 100644
--- a/source/tanya/network/url.d
+++ b/source/tanya/network/url.d
@@ -3,10 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * URL parser.
+ *
+ * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
- * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network.url;
@@ -18,8 +20,8 @@ import tanya.memory;
version (unittest) private
{
- import std.typecons;
- static Tuple!(string, string[string], ushort)[] URLTests;
+ import std.typecons;
+ static Tuple!(string, string[string], ushort)[] URLTests;
}
static this()
@@ -632,445 +634,445 @@ static this()
*/
struct URL
{
- /** The URL scheme. */
- const(char)[] scheme;
-
- /** The username. */
- const(char)[] user;
-
- /** The password. */
- const(char)[] pass;
-
- /** The hostname. */
- const(char)[] host;
-
- /** The port number. */
- ushort port;
-
- /** The path. */
- const(char)[] path;
-
- /** The query string. */
- const(char)[] query;
-
- /** The anchor. */
- const(char)[] fragment;
-
- /**
- * Attempts to parse an URL from a string.
- * Output string data (scheme, user, etc.) are just slices of input string (e.g., no memory allocation and copying).
- *
- * Params:
- * source = The string containing the URL.
- *
- * Throws: $(D_PSYMBOL URIException) if the URL is malformed.
- */
- this(in char[] source)
- {
- auto value = source;
- ptrdiff_t pos = -1, endPos = value.length, start;
-
- foreach (i, ref c; source)
- {
- if (pos == -1 && c == ':')
- {
- pos = i;
- }
- if (endPos == value.length && (c == '?' || c == '#'))
- {
- endPos = i;
- }
- }
-
- // Check if the colon is a part of the scheme or the port and parse
- // the appropriate part
- if (value.length > 1 && value[0] == '/' && value[1] == '/')
- {
- // Relative scheme
- start = 2;
- }
- else if (pos > 0)
- {
- // Validate scheme
- // [ toLower(alpha) | digit | "+" | "-" | "." ]
- foreach (ref c; value[0..pos])
- {
- if (!c.isAlphaNum && c != '+' && c != '-' && c != '.')
- {
- if (endPos > pos)
- {
- if (!parsePort(value[pos..$]))
- {
- throw defaultAllocator.make!URIException("Failed to parse port");
- }
- }
- goto ParsePath;
- }
- }
-
- if (value.length == pos + 1) // only scheme is available
- {
- scheme = value[0 .. $ - 1];
- return;
- }
- else if (value.length > pos + 1 && value[pos + 1] == '/')
- {
- scheme = value[0..pos];
-
- if (value.length > pos + 2 && value[pos + 2] == '/')
- {
- start = pos + 3;
- if (scheme == "file" && value.length > start && value[start] == '/')
- {
- // Windows drive letters
- if (value.length - start > 2 && value[start + 2] == ':')
- {
- ++start;
- }
- goto ParsePath;
- }
- }
- else
- {
- start = pos + 1;
- goto ParsePath;
- }
- }
- else // certain schemas like mailto: and zlib: may not have any / after them
- {
-
- if (!parsePort(value[pos..$]))
- {
- scheme = value[0..pos];
- start = pos + 1;
- goto ParsePath;
- }
- }
- }
- else if (pos == 0 && parsePort(value[pos..$]))
- {
- // An URL shouldn't begin with a port number
- throw defaultAllocator.make!URIException("URL begins with port");
- }
- else
- {
- goto ParsePath;
- }
-
- // Parse host
- pos = -1;
- for (ptrdiff_t i = start; i < value.length; ++i)
- {
- if (value[i] == '@')
- {
- pos = i;
- }
- else if (value[i] == '/')
- {
- endPos = i;
- break;
- }
- }
-
- // Check for login and password
- if (pos != -1)
- {
- // *( unreserved / pct-encoded / sub-delims / ":" )
- foreach (i, c; value[start..pos])
- {
- if (c == ':')
- {
- if (user is null)
- {
- user = value[start .. start + i];
- pass = value[start + i + 1 .. pos];
- }
- }
- else if (!c.isAlpha &&
- !c.isNumber &&
- c != '!' &&
- c != ';' &&
- c != '=' &&
- c != '_' &&
- c != '~' &&
- !(c >= '$' && c <= '.'))
- {
- if (scheme !is null)
- {
- scheme = null;
- }
- if (user !is null)
- {
- user = null;
- }
- if (pass !is null)
- {
- pass = null;
- }
- throw make!URIException(defaultAllocator,
- "Restricted characters in user information");
- }
- }
- if (user is null)
- {
- user = value[start..pos];
- }
-
- start = ++pos;
- }
-
- pos = endPos;
- if (endPos <= 1 || value[start] != '[' || value[endPos - 1] != ']')
- {
- // Short circuit portscan
- // IPv6 embedded address
- for (ptrdiff_t i = endPos - 1; i >= start; --i)
- {
- if (value[i] == ':')
- {
- pos = i;
- if (port == 0 && !parsePort(value[i..endPos]))
- {
- if (scheme !is null)
- {
- scheme = null;
- }
- if (user !is null)
- {
- user = null;
- }
- if (pass !is null)
- {
- pass = null;
- }
- throw defaultAllocator.make!URIException("Invalid port");
- }
- break;
- }
- }
- }
-
- // Check if we have a valid host, if we don't reject the string as url
- if (pos <= start)
- {
- if (scheme !is null)
- {
- scheme = null;
- }
- if (user !is null)
- {
- user = null;
- }
- if (pass !is null)
- {
- pass = null;
- }
- throw defaultAllocator.make!URIException("Invalid host");
- }
-
- host = value[start..pos];
-
- if (endPos == value.length)
- {
- return;
- }
-
- start = endPos;
-
- ParsePath:
- endPos = value.length;
- pos = -1;
- foreach (i, ref c; value[start..$])
- {
- if (c == '?' && pos == -1)
- {
- pos = start + i;
- }
- else if (c == '#')
- {
- endPos = start + i;
- break;
- }
- }
- if (pos == -1)
- {
- pos = endPos;
- }
-
- if (pos > start)
- {
- path = value[start..pos];
- }
- if (endPos >= ++pos)
- {
- query = value[pos..endPos];
- }
- if (++endPos <= value.length)
- {
- fragment = value[endPos..$];
- }
- }
-
-~this()
-{
- if (scheme !is null)
- {
- scheme = null;
- }
- if (user !is null)
- {
- user = null;
- }
- if (pass !is null)
- {
- pass = null;
- }
- if (host !is null)
- {
- host = null;
- }
- if (path !is null)
- {
- path = null;
- }
- if (query !is null)
- {
- query = null;
- }
- if (fragment !is null)
- {
- fragment = null;
- }
-}
+ /** The URL scheme. */
+ const(char)[] scheme;
+
+ /** The username. */
+ const(char)[] user;
+
+ /** The password. */
+ const(char)[] pass;
+
+ /** The hostname. */
+ const(char)[] host;
+
+ /** The port number. */
+ ushort port;
+
+ /** The path. */
+ const(char)[] path;
+
+ /** The query string. */
+ const(char)[] query;
+
+ /** The anchor. */
+ const(char)[] fragment;
+
+ /**
+ * Attempts to parse an URL from a string.
+ * Output string data (scheme, user, etc.) are just slices of input string (e.g., no memory allocation and copying).
+ *
+ * Params:
+ * source = The string containing the URL.
+ *
+ * Throws: $(D_PSYMBOL URIException) if the URL is malformed.
+ */
+ this(in char[] source)
+ {
+ auto value = source;
+ ptrdiff_t pos = -1, endPos = value.length, start;
+
+ foreach (i, ref c; source)
+ {
+ if (pos == -1 && c == ':')
+ {
+ pos = i;
+ }
+ if (endPos == value.length && (c == '?' || c == '#'))
+ {
+ endPos = i;
+ }
+ }
+
+ // Check if the colon is a part of the scheme or the port and parse
+ // the appropriate part
+ if (value.length > 1 && value[0] == '/' && value[1] == '/')
+ {
+ // Relative scheme
+ start = 2;
+ }
+ else if (pos > 0)
+ {
+ // Validate scheme
+ // [ toLower(alpha) | digit | "+" | "-" | "." ]
+ foreach (ref c; value[0..pos])
+ {
+ if (!c.isAlphaNum && c != '+' && c != '-' && c != '.')
+ {
+ if (endPos > pos)
+ {
+ if (!parsePort(value[pos..$]))
+ {
+ throw defaultAllocator.make!URIException("Failed to parse port");
+ }
+ }
+ goto ParsePath;
+ }
+ }
+
+ if (value.length == pos + 1) // only scheme is available
+ {
+ scheme = value[0 .. $ - 1];
+ return;
+ }
+ else if (value.length > pos + 1 && value[pos + 1] == '/')
+ {
+ scheme = value[0..pos];
+
+ if (value.length > pos + 2 && value[pos + 2] == '/')
+ {
+ start = pos + 3;
+ if (scheme == "file" && value.length > start && value[start] == '/')
+ {
+ // Windows drive letters
+ if (value.length - start > 2 && value[start + 2] == ':')
+ {
+ ++start;
+ }
+ goto ParsePath;
+ }
+ }
+ else
+ {
+ start = pos + 1;
+ goto ParsePath;
+ }
+ }
+ else // certain schemas like mailto: and zlib: may not have any / after them
+ {
+
+ if (!parsePort(value[pos..$]))
+ {
+ scheme = value[0..pos];
+ start = pos + 1;
+ goto ParsePath;
+ }
+ }
+ }
+ else if (pos == 0 && parsePort(value[pos..$]))
+ {
+ // An URL shouldn't begin with a port number
+ throw defaultAllocator.make!URIException("URL begins with port");
+ }
+ else
+ {
+ goto ParsePath;
+ }
+
+ // Parse host
+ pos = -1;
+ for (ptrdiff_t i = start; i < value.length; ++i)
+ {
+ if (value[i] == '@')
+ {
+ pos = i;
+ }
+ else if (value[i] == '/')
+ {
+ endPos = i;
+ break;
+ }
+ }
+
+ // Check for login and password
+ if (pos != -1)
+ {
+ // *( unreserved / pct-encoded / sub-delims / ":" )
+ foreach (i, c; value[start..pos])
+ {
+ if (c == ':')
+ {
+ if (user is null)
+ {
+ user = value[start .. start + i];
+ pass = value[start + i + 1 .. pos];
+ }
+ }
+ else if (!c.isAlpha &&
+ !c.isNumber &&
+ c != '!' &&
+ c != ';' &&
+ c != '=' &&
+ c != '_' &&
+ c != '~' &&
+ !(c >= '$' && c <= '.'))
+ {
+ if (scheme !is null)
+ {
+ scheme = null;
+ }
+ if (user !is null)
+ {
+ user = null;
+ }
+ if (pass !is null)
+ {
+ pass = null;
+ }
+ throw make!URIException(defaultAllocator,
+ "Restricted characters in user information");
+ }
+ }
+ if (user is null)
+ {
+ user = value[start..pos];
+ }
+
+ start = ++pos;
+ }
+
+ pos = endPos;
+ if (endPos <= 1 || value[start] != '[' || value[endPos - 1] != ']')
+ {
+ // Short circuit portscan
+ // IPv6 embedded address
+ for (ptrdiff_t i = endPos - 1; i >= start; --i)
+ {
+ if (value[i] == ':')
+ {
+ pos = i;
+ if (port == 0 && !parsePort(value[i..endPos]))
+ {
+ if (scheme !is null)
+ {
+ scheme = null;
+ }
+ if (user !is null)
+ {
+ user = null;
+ }
+ if (pass !is null)
+ {
+ pass = null;
+ }
+ throw defaultAllocator.make!URIException("Invalid port");
+ }
+ break;
+ }
+ }
+ }
+
+ // Check if we have a valid host, if we don't reject the string as url
+ if (pos <= start)
+ {
+ if (scheme !is null)
+ {
+ scheme = null;
+ }
+ if (user !is null)
+ {
+ user = null;
+ }
+ if (pass !is null)
+ {
+ pass = null;
+ }
+ throw defaultAllocator.make!URIException("Invalid host");
+ }
+
+ host = value[start..pos];
+
+ if (endPos == value.length)
+ {
+ return;
+ }
+
+ start = endPos;
+
+ ParsePath:
+ endPos = value.length;
+ pos = -1;
+ foreach (i, ref c; value[start..$])
+ {
+ if (c == '?' && pos == -1)
+ {
+ pos = start + i;
+ }
+ else if (c == '#')
+ {
+ endPos = start + i;
+ break;
+ }
+ }
+ if (pos == -1)
+ {
+ pos = endPos;
+ }
+
+ if (pos > start)
+ {
+ path = value[start..pos];
+ }
+ if (endPos >= ++pos)
+ {
+ query = value[pos..endPos];
+ }
+ if (++endPos <= value.length)
+ {
+ fragment = value[endPos..$];
+ }
+ }
+
+ ~this()
+ {
+ if (scheme !is null)
+ {
+ scheme = null;
+ }
+ if (user !is null)
+ {
+ user = null;
+ }
+ if (pass !is null)
+ {
+ pass = null;
+ }
+ if (host !is null)
+ {
+ host = null;
+ }
+ if (path !is null)
+ {
+ path = null;
+ }
+ if (query !is null)
+ {
+ query = null;
+ }
+ if (fragment !is null)
+ {
+ fragment = null;
+ }
+ }
- /**
- * Attempts to parse and set the port.
- *
- * Params:
- * port = String beginning with a colon followed by the port number and
- * an optional path (query string and/or fragment), like:
- * `:12345/some_path` or `:12345`.
- *
- * Returns: Whether the port could be found.
- */
- private bool parsePort(in char[] port) pure nothrow @safe @nogc
- {
- ptrdiff_t i = 1;
- float lPort = 0;
-
- for (; i < port.length && port[i].isDigit() && i <= 6; ++i)
- {
- lPort += (port[i] - '0') / cast(float)(10 ^^ (i - 1));
- }
- if (i == 1 && (i == port.length || port[i] == '/'))
- {
- return true;
- }
- else if (i == port.length || port[i] == '/')
- {
- lPort *= 10 ^^ (i - 2);
- if (lPort > ushort.max)
- {
- return false;
- }
- this.port = cast(ushort)lPort;
- return true;
- }
- return false;
- }
+ /**
+ * Attempts to parse and set the port.
+ *
+ * Params:
+ * port = String beginning with a colon followed by the port number and
+ * an optional path (query string and/or fragment), like:
+ * `:12345/some_path` or `:12345`.
+ *
+ * Returns: Whether the port could be found.
+ */
+ private bool parsePort(in char[] port) pure nothrow @safe @nogc
+ {
+ ptrdiff_t i = 1;
+ float lPort = 0;
+
+ for (; i < port.length && port[i].isDigit() && i <= 6; ++i)
+ {
+ lPort += (port[i] - '0') / cast(float)(10 ^^ (i - 1));
+ }
+ if (i == 1 && (i == port.length || port[i] == '/'))
+ {
+ return true;
+ }
+ else if (i == port.length || port[i] == '/')
+ {
+ lPort *= 10 ^^ (i - 2);
+ if (lPort > ushort.max)
+ {
+ return false;
+ }
+ this.port = cast(ushort)lPort;
+ return true;
+ }
+ return false;
+ }
}
///
unittest
{
- auto u = URL("example.org");
- assert(u.path == "example.org");
-
- u = URL("relative/path");
- assert(u.path == "relative/path");
-
- // Host and scheme
- u = URL("https://example.org");
- assert(u.scheme == "https");
- assert(u.host == "example.org");
- assert(u.path is null);
- assert(u.port == 0);
- assert(u.fragment is null);
-
- // With user and port and path
- u = URL("https://hilary:putnam@example.org:443/foo/bar");
- assert(u.scheme == "https");
- assert(u.host == "example.org");
- assert(u.path == "/foo/bar");
- assert(u.port == 443);
- assert(u.user == "hilary");
- assert(u.pass == "putnam");
- assert(u.fragment is null);
-
- // With query string
- u = URL("https://example.org/?login=true");
- assert(u.scheme == "https");
- assert(u.host == "example.org");
- assert(u.path == "/");
- assert(u.query == "login=true");
- assert(u.fragment is null);
-
- // With query string and fragment
- u = URL("https://example.org/?login=false#label");
- assert(u.scheme == "https");
- assert(u.host == "example.org");
- assert(u.path == "/");
- assert(u.query == "login=false");
- assert(u.fragment == "label");
-
- u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
- assert(u.scheme == "redis");
- assert(u.user == "root");
- assert(u.pass == "password");
- assert(u.host == "localhost");
- assert(u.port == 2201);
- assert(u.path == "/path");
- assert(u.query == "query=value");
- assert(u.fragment == "fragment");
+ auto u = URL("example.org");
+ assert(u.path == "example.org");
+
+ u = URL("relative/path");
+ assert(u.path == "relative/path");
+
+ // Host and scheme
+ u = URL("https://example.org");
+ assert(u.scheme == "https");
+ assert(u.host == "example.org");
+ assert(u.path is null);
+ assert(u.port == 0);
+ assert(u.fragment is null);
+
+ // With user and port and path
+ u = URL("https://hilary:putnam@example.org:443/foo/bar");
+ assert(u.scheme == "https");
+ assert(u.host == "example.org");
+ assert(u.path == "/foo/bar");
+ assert(u.port == 443);
+ assert(u.user == "hilary");
+ assert(u.pass == "putnam");
+ assert(u.fragment is null);
+
+ // With query string
+ u = URL("https://example.org/?login=true");
+ assert(u.scheme == "https");
+ assert(u.host == "example.org");
+ assert(u.path == "/");
+ assert(u.query == "login=true");
+ assert(u.fragment is null);
+
+ // With query string and fragment
+ u = URL("https://example.org/?login=false#label");
+ assert(u.scheme == "https");
+ assert(u.host == "example.org");
+ assert(u.path == "/");
+ assert(u.query == "login=false");
+ assert(u.fragment == "label");
+
+ u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
+ assert(u.scheme == "redis");
+ assert(u.user == "root");
+ assert(u.pass == "password");
+ assert(u.host == "localhost");
+ assert(u.port == 2201);
+ assert(u.path == "/path");
+ assert(u.query == "query=value");
+ assert(u.fragment == "fragment");
}
private unittest
{
- foreach(t; URLTests)
- {
- if (t[1].length == 0 && t[2] == 0)
- {
- try
- {
- URL(t[0]);
- assert(0);
- }
- catch (URIException e)
- {
- assert(1);
- }
- }
- else
- {
- auto u = URL(t[0]);
- assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
- t[0]);
- assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
- assert("pass" in t[1] ? u.pass == t[1]["pass"] : u.pass is null, t[0]);
- assert("host" in t[1] ? u.host == t[1]["host"] : u.host is null, t[0]);
- assert(u.port == t[2], t[0]);
- assert("path" in t[1] ? u.path == t[1]["path"] : u.path is null, t[0]);
- assert("query" in t[1] ? u.query == t[1]["query"] : u.query is null, t[0]);
- if ("fragment" in t[1])
- {
- assert(u.fragment == t[1]["fragment"], t[0]);
- }
- else
- {
- assert(u.fragment is null, t[0]);
- }
- }
- }
+ foreach(t; URLTests)
+ {
+ if (t[1].length == 0 && t[2] == 0)
+ {
+ try
+ {
+ URL(t[0]);
+ assert(0);
+ }
+ catch (URIException e)
+ {
+ assert(1);
+ }
+ }
+ else
+ {
+ auto u = URL(t[0]);
+ assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
+ t[0]);
+ assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
+ assert("pass" in t[1] ? u.pass == t[1]["pass"] : u.pass is null, t[0]);
+ assert("host" in t[1] ? u.host == t[1]["host"] : u.host is null, t[0]);
+ assert(u.port == t[2], t[0]);
+ assert("path" in t[1] ? u.path == t[1]["path"] : u.path is null, t[0]);
+ assert("query" in t[1] ? u.query == t[1]["query"] : u.query is null, t[0]);
+ if ("fragment" in t[1])
+ {
+ assert(u.fragment == t[1]["fragment"], t[0]);
+ }
+ else
+ {
+ assert(u.fragment is null, t[0]);
+ }
+ }
+ }
}
/**
@@ -1079,111 +1081,111 @@ private unittest
*/
enum Component : string
{
- scheme = "scheme",
- host = "host",
- port = "port",
- user = "user",
- pass = "pass",
- path = "path",
- query = "query",
- fragment = "fragment",
+ scheme = "scheme",
+ host = "host",
+ port = "port",
+ user = "user",
+ pass = "pass",
+ path = "path",
+ query = "query",
+ fragment = "fragment",
}
/**
* Attempts to parse an URL from a string.
*
* Params:
- * T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a
- * struct with all components.
- * source = The string containing the URL.
+ * T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a
+ * struct with all components.
+ * source = The string containing the URL.
*
* Returns: Requested URL components.
*/
URL parseURL(typeof(null) T)(in char[] source)
{
- return URL(source);
+ return URL(source);
}
/// Ditto.
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
- if (T == "scheme"
- || T =="host"
- || T == "user"
- || T == "pass"
- || T == "path"
- || T == "query"
- || T == "fragment")
+ if (T == "scheme"
+ || T == "host"
+ || T == "user"
+ || T == "pass"
+ || T == "path"
+ || T == "query"
+ || T == "fragment")
{
- auto ret = URL(source);
- return mixin("ret." ~ T);
+ auto ret = URL(source);
+ return mixin("ret." ~ T);
}
/// Ditto.
ushort parseURL(immutable(char)[] T)(in char[] source)
- if (T == "port")
+ if (T == "port")
{
- auto ret = URL(source);
- return ret.port;
+ auto ret = URL(source);
+ return ret.port;
}
unittest
{
- assert(parseURL!(Component.port)("http://example.org:5326") == 5326);
+ assert(parseURL!(Component.port)("http://example.org:5326") == 5326);
}
private unittest
{
- foreach(t; URLTests)
- {
- if (t[1].length == 0 && t[2] == 0)
- {
- try
- {
- parseURL!(Component.port)(t[0]);
- parseURL!(Component.user)(t[0]);
- parseURL!(Component.pass)(t[0]);
- parseURL!(Component.host)(t[0]);
- parseURL!(Component.path)(t[0]);
- parseURL!(Component.query)(t[0]);
- parseURL!(Component.fragment)(t[0]);
- assert(0);
- }
- catch (URIException e)
- {
- assert(1);
- }
- }
- else
- {
- ushort port = parseURL!(Component.port)(t[0]);
- auto component = parseURL!(Component.scheme)(t[0]);
- assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
- t[0]);
- component = parseURL!(Component.user)(t[0]);
- assert("user" in t[1] ? component == t[1]["user"] : component is null,
- t[0]);
- component = parseURL!(Component.pass)(t[0]);
- assert("pass" in t[1] ? component == t[1]["pass"] : component is null,
- t[0]);
- component = parseURL!(Component.host)(t[0]);
- assert("host" in t[1] ? component == t[1]["host"] : component is null,
- t[0]);
- assert(port == t[2], t[0]);
- component = parseURL!(Component.path)(t[0]);
- assert("path" in t[1] ? component == t[1]["path"] : component is null,
- t[0]);
- component = parseURL!(Component.query)(t[0]);
- assert("query" in t[1] ? component == t[1]["query"] : component is null,
- t[0]);
- component = parseURL!(Component.fragment)(t[0]);
- if ("fragment" in t[1])
- {
- assert(component == t[1]["fragment"], t[0]);
- }
- else
- {
- assert(component is null, t[0]);
- }
- }
- }
+ foreach(t; URLTests)
+ {
+ if (t[1].length == 0 && t[2] == 0)
+ {
+ try
+ {
+ parseURL!(Component.port)(t[0]);
+ parseURL!(Component.user)(t[0]);
+ parseURL!(Component.pass)(t[0]);
+ parseURL!(Component.host)(t[0]);
+ parseURL!(Component.path)(t[0]);
+ parseURL!(Component.query)(t[0]);
+ parseURL!(Component.fragment)(t[0]);
+ assert(0);
+ }
+ catch (URIException e)
+ {
+ assert(1);
+ }
+ }
+ else
+ {
+ ushort port = parseURL!(Component.port)(t[0]);
+ auto component = parseURL!(Component.scheme)(t[0]);
+ assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
+ t[0]);
+ component = parseURL!(Component.user)(t[0]);
+ assert("user" in t[1] ? component == t[1]["user"] : component is null,
+ t[0]);
+ component = parseURL!(Component.pass)(t[0]);
+ assert("pass" in t[1] ? component == t[1]["pass"] : component is null,
+ t[0]);
+ component = parseURL!(Component.host)(t[0]);
+ assert("host" in t[1] ? component == t[1]["host"] : component is null,
+ t[0]);
+ assert(port == t[2], t[0]);
+ component = parseURL!(Component.path)(t[0]);
+ assert("path" in t[1] ? component == t[1]["path"] : component is null,
+ t[0]);
+ component = parseURL!(Component.query)(t[0]);
+ assert("query" in t[1] ? component == t[1]["query"] : component is null,
+ t[0]);
+ component = parseURL!(Component.fragment)(t[0]);
+ if ("fragment" in t[1])
+ {
+ assert(component == t[1]["fragment"], t[0]);
+ }
+ else
+ {
+ assert(component is null, t[0]);
+ }
+ }
+ }
}