22 Commits

Author SHA1 Message Date
278e851414 Rename String.toString to String.get()
Last changed it only by the Range and forgotten about the string itself.
2017-06-08 07:59:16 +02:00
6f549df243 Update README description 2017-06-07 08:04:50 +02:00
4633bcc680 Set: Fix comparing with removed elements 2017-06-07 07:57:22 +02:00
dc39efd316 Add some unit tests for InternetAddress 2017-06-03 15:18:53 +02:00
260937e4fb Put socket overlapped I/O docs into a D_Ddoc block 2017-06-03 13:20:32 +02:00
e17fff2881 Update 2.074 compiler 2017-06-02 22:01:13 +02:00
bc32511254 Fix template parameters for Set 2017-06-01 22:36:38 +02:00
f30972f948 Add basic constructors and opAssign to Set 2017-06-01 06:26:06 +02:00
ea33ca62c8 Implement lookups in the Set 2017-05-31 10:29:07 +02:00
0f365758e1 Add optional fieldnames for Pair 2017-05-30 20:20:20 +02:00
2815b53a88 Implement Set Range 2017-05-30 15:52:18 +02:00
6c0588164a Rename String.toString back to get()
Since it is expected that the return type of toString() is
immutable(char)[] and not char[] or const(char)[].
2017-05-29 11:41:49 +02:00
8ee1d647ce Close issue 212
Introduce Range and constRange aliases for containers.
2017-05-29 11:26:39 +02:00
25791775e6 Add information about the Set to README 2017-05-29 10:58:37 +02:00
f013e2f1f4 Implement a Set container first 2017-05-29 10:50:01 +02:00
ac3935d71b Merge branch 'master' into horton-table 2017-05-28 10:15:02 +02:00
b1c217e272 Fix kqueue to work with SocketType 2017-05-25 22:21:45 +02:00
d007aaa310 Rename socket_t to SocketType 2017-05-25 21:59:40 +02:00
8aaf9e14be Add HashTable struct 2017-05-23 22:17:35 +02:00
ae3e6b46f6 Import std.algorithm.comparison for network.socket on Windows 2017-05-21 10:25:54 +02:00
8687df1fbb Define AddressFamily in network.socket 2017-05-21 10:20:57 +02:00
ba0aff6737 Add tanya.typecons.Pair 2017-05-19 21:15:56 +02:00
14 changed files with 1369 additions and 238 deletions

View File

@ -7,7 +7,7 @@ os:
language: d
d:
- dmd-2.074.0
- dmd-2.074.1
- dmd-2.073.2
- dmd-2.072.2
- dmd-2.071.2

View File

@ -15,7 +15,7 @@ Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for
data structures and utilities that depend on the Garbage Collector in Phobos.
* [Bug tracker](https://issues.caraus.io/projects/tanya)
* [Bug tracker](https://issues.caraus.io/projects/tanya/issues)
* [Documentation](https://docs.caraus.io/tanya)
## Overview
@ -24,17 +24,16 @@ Tanya consists of the following packages:
* `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
string.
string, Hash set.
* `math`: Arbitrary precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocator, reference counting,
helper functions).
* `memory`: Tools for manual memory management (allocators, smart pointers).
* `network`: URL-Parsing, sockets, utilities.
### Supported compilers
| dmd |
|:-------:|
| 2.074.0 |
| 2.074.1 |
| 2.073.2 |
| 2.072.2 |
| 2.071.2 |
@ -43,30 +42,27 @@ helper functions).
Following modules are under development:
| Feature | Branch | Build status |
|--------------|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BitVector | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
| TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
| Hash table | horton-table | [![horton-table](https://travis-ci.org/caraus-ecms/tanya.svg?branch=horton-table)](https://travis-ci.org/caraus-ecms/tanya) [![horton-table](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/horton-table?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/horton-table) |
| Feature | Branch | Build status |
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BitArray | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
| TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
### Further characteristics
* Tanya is a native D library.
* Tanya is a native D library without any external dependencies.
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well.
* The library isn't thread-safe. Thread-safity should be added later.
* The library isn't thread-safe yet.
## Release management
3-week release cycle.
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
## Contributing
Since I'm mostly busy writing new code and implementing new features I would
appreciate, if anyone uses the library. It would help me to improve the
codebase and fix issues.
Feel free to contact me if you have any questions: info@caraus.de.

View File

@ -4,7 +4,7 @@ os: Visual Studio 2017
environment:
matrix:
- DC: dmd
DVersion: 2.074.0
DVersion: 2.074.1
arch: x86
- DC: dmd
DVersion: 2.073.2

View File

@ -1,6 +1,6 @@
{
"name": "tanya",
"description": "General purpose, @nogc library",
"description": "General purpose, @nogc library. Containers, networking, memory management, utilities",
"license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>",
"authors": [

View File

@ -151,7 +151,7 @@ final class KqueueLoop : SelectorLoop
close(fd);
}
private void set(socket_t socket, short filter, ushort flags) @nogc
private void set(SocketType socket, short filter, ushort flags) @nogc
{
if (changes.length <= changeCount)
{

View File

@ -38,11 +38,11 @@
*
* version (Windows)
* {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* }
* else
* {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false;
* }
*

View File

@ -160,7 +160,7 @@ struct Range(E)
return typeof(return)(*this.container, this.begin + i, this.begin + j);
}
inout(E[]) get() inout @trusted
inout(E)[] get() inout @trusted
{
return this.begin[0 .. length];
}
@ -174,6 +174,12 @@ struct Range(E)
*/
struct Array(T)
{
/// The range types for $(D_PSYMBOL Array).
alias Range = .Range!T;
/// Ditto.
alias ConstRange = .Range!(const T);
private size_t length_;
private T* data;
private size_t capacity_;
@ -638,7 +644,7 @@ struct Array(T)
*
* Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this).
*/
Range!T remove(Range!T r) @trusted
Range remove(Range r) @trusted
in
{
assert(r.container is &this);
@ -648,9 +654,9 @@ struct Array(T)
body
{
auto end = this.data + this.length;
moveAll(Range!T(this, r.end, end), Range!T(this, r.begin, end));
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);
return .Range!T(this, r.begin, this.data + length);
}
///
@ -788,7 +794,7 @@ struct Array(T)
*
* Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this).
*/
size_t insertAfter(R)(Range!T r, R el)
size_t insertAfter(R)(Range r, R el)
if (!isInfinite!R
&& isInputRange!R
&& isImplicitlyConvertible!(ElementType!R, T))
@ -808,7 +814,7 @@ struct Array(T)
}
/// Ditto.
size_t insertAfter(size_t R)(Range!T r, T[R] el)
size_t insertAfter(size_t R)(Range r, T[R] el)
in
{
assert(r.container is &this);
@ -821,7 +827,7 @@ struct Array(T)
}
/// Ditto.
size_t insertAfter(R)(Range!T r, auto ref R el)
size_t insertAfter(R)(Range r, auto ref R el)
if (isImplicitlyConvertible!(R, T))
in
{
@ -848,7 +854,7 @@ struct Array(T)
}
/// Ditto.
size_t insertBefore(R)(Range!T r, R el)
size_t insertBefore(R)(Range r, R el)
if (!isInfinite!R
&& isInputRange!R
&& isImplicitlyConvertible!(ElementType!R, T))
@ -860,11 +866,11 @@ struct Array(T)
}
body
{
return insertAfter(Range!T(this, this.data, r.begin), el);
return insertAfter(.Range!T(this, this.data, r.begin), el);
}
/// Ditto.
size_t insertBefore(size_t R)(Range!T r, T[R] el)
size_t insertBefore(size_t R)(Range r, T[R] el)
in
{
assert(r.container is &this);
@ -877,7 +883,7 @@ struct Array(T)
}
/// Ditto.
size_t insertBefore(R)(Range!T r, auto ref R el)
size_t insertBefore(R)(Range r, auto ref R el)
if (isImplicitlyConvertible!(R, T))
in
{
@ -993,7 +999,7 @@ struct Array(T)
}
/// Ditto.
Range!T opIndexAssign(E : T)(auto ref E value)
Range opIndexAssign(E : T)(auto ref E value)
{
return opSliceAssign(value, 0, length);
}
@ -1017,13 +1023,13 @@ struct Array(T)
*
* Precondition: $(D_INLINECODE length == value.length).
*/
Range!T opIndexAssign(size_t R)(T[R] value)
Range opIndexAssign(size_t R)(T[R] value)
{
return opSliceAssign!R(value, 0, length);
}
/// Ditto.
Range!T opIndexAssign(Range!T value)
Range opIndexAssign(Range value)
{
return opSliceAssign(value, 0, length);
}
@ -1066,13 +1072,13 @@ struct Array(T)
* Returns: Random access range that iterates over elements of the array,
* in forward order.
*/
Range!T opIndex() @trusted
Range opIndex() @trusted
{
return typeof(return)(this, this.data, this.data + length);
}
/// Ditto.
Range!(const T) opIndex() const @trusted
ConstRange opIndex() const @trusted
{
return typeof(return)(this, this.data, this.data + length);
}
@ -1105,13 +1111,13 @@ struct Array(T)
}
/// Ditto.
bool opEquals()(const auto ref typeof(this) that) const @trusted
bool opEquals()(auto ref const typeof(this) that) const @trusted
{
return equal(this.data[0 .. length], that.data[0 .. that.length]);
}
/// Ditto.
bool opEquals(Range!T that)
bool opEquals(Range that)
{
return equal(opIndex(), that);
}
@ -1126,8 +1132,8 @@ struct Array(T)
* Returns: $(D_KEYWORD true) if the array and the range are equal,
* $(D_KEYWORD false) otherwise.
*/
bool opEquals(R)(Range!R that) const
if (is(Unqual!R == T))
bool opEquals(R)(R that) const
if (is(R == Range) || is(R == ConstRange))
{
return equal(opIndex(), that);
}
@ -1216,7 +1222,7 @@ struct Array(T)
*
* Precondition: $(D_INLINECODE i <= j && j <= length).
*/
Range!T opSlice(const size_t i, const size_t j) @trusted
Range opSlice(const size_t i, const size_t j) @trusted
in
{
assert(i <= j);
@ -1228,7 +1234,7 @@ struct Array(T)
}
/// Ditto.
Range!(const T) opSlice(const size_t i, const size_t j) const @trusted
ConstRange opSlice(const size_t i, const size_t j) const @trusted
in
{
assert(i <= j);
@ -1298,7 +1304,7 @@ struct Array(T)
* Precondition: $(D_INLINECODE i <= j && j <= length
* && value.length == j - i)
*/
Range!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j)
Range opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j)
@trusted
in
{
@ -1312,7 +1318,7 @@ struct Array(T)
}
/// Ditto.
Range!T opSliceAssign(R : T)(auto ref R value, const size_t i, const size_t j)
Range opSliceAssign(R : T)(auto ref R value, const size_t i, const size_t j)
@trusted
in
{
@ -1326,7 +1332,7 @@ struct Array(T)
}
/// Ditto.
Range!T opSliceAssign(Range!T value, const size_t i, const size_t j) @trusted
Range opSliceAssign(Range value, const size_t i, const size_t j) @trusted
in
{
assert(i <= j);

View File

@ -12,6 +12,9 @@
*/
module tanya.container.entry;
import std.traits;
import tanya.typecons;
package struct SEntry(T)
{
// Item content.
@ -29,3 +32,80 @@ package struct DEntry(T)
// Previous and next item.
DEntry* next, prev;
}
package struct HashEntry(K, V)
{
this(ref K key, ref V value)
{
this.pair = Pair!(K, V)(key, value);
}
Pair!(K, V) pair;
HashEntry* next;
}
package enum BucketStatus : byte
{
deleted = -1,
empty = 0,
used = 1,
}
package struct Bucket(T)
{
this(ref T content)
{
this.content = content;
}
@property void content(ref T content)
{
this.content_ = content;
this.status = BucketStatus.used;
}
@property ref inout(T) content() inout
{
return this.content_;
}
bool opEquals(ref T content)
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref T content) const
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref typeof(this) that)
{
return this.content == that.content && this.status == that.status;
}
bool opEquals(ref typeof(this) that) const
{
return this.content == that.content && this.status == that.status;
}
void remove()
{
static if (hasElaborateDestructor!T)
{
destroy(this.content);
}
this.status = BucketStatus.deleted;
}
T content_;
BucketStatus status = BucketStatus.empty;
}

View File

@ -93,6 +93,12 @@ struct SRange(E)
*/
struct SList(T)
{
/// The range types for $(D_PSYMBOL SList).
alias Range = SRange!T;
/// Ditto.
alias ConstRange = SRange!(const T);
private alias Entry = SEntry!T;
// 0th element of the list.
@ -497,7 +503,7 @@ struct SList(T)
}
/// Ditto.
size_t insertBefore(SRange!T r, ref T el) @trusted
size_t insertBefore(Range r, ref T el) @trusted
in
{
assert(checkRangeBelonging(r));
@ -530,7 +536,7 @@ struct SList(T)
*
* Precondition: $(D_PARAM r) is extracted from this list.
*/
size_t insertBefore(size_t R)(SRange!T r, T[R] el)
size_t insertBefore(size_t R)(Range r, T[R] el)
{
return insertFront!(T[])(el[]);
}
@ -685,7 +691,7 @@ struct SList(T)
*
* Precondition: $(D_PARAM r) is extracted from this list.
*/
SRange!T remove(SRange!T r)
Range remove(Range r)
in
{
assert(checkRangeBelonging(r));
@ -716,13 +722,13 @@ struct SList(T)
* Returns: Range that iterates over all elements of the container, in
* forward order.
*/
SRange!T opIndex()
Range opIndex()
{
return typeof(return)(this.head);
}
/// Ditto.
SRange!(const T) opIndex() const
ConstRange opIndex() const
{
return typeof(return)(this.head);
}
@ -966,6 +972,12 @@ struct DRange(E)
*/
struct DList(T)
{
/// The range types for $(D_PSYMBOL DList).
alias Range = DRange!T;
/// Ditto.
alias ConstRange = DRange!(const T);
private alias Entry = DEntry!T;
// 0th and the last elements of the list.
@ -1536,7 +1548,7 @@ struct DList(T)
}
/// Ditto.
size_t insertBefore(DRange!T r, ref T el) @trusted
size_t insertBefore(Range r, ref T el) @trusted
in
{
assert(checkRangeBelonging(r));
@ -1569,7 +1581,7 @@ struct DList(T)
*
* Precondition: $(D_PARAM r) is extracted from this list.
*/
size_t insertBefore(size_t R)(DRange!T r, T[R] el)
size_t insertBefore(size_t R)(Range r, T[R] el)
{
return insertFront!(T[])(el[]);
}
@ -1728,7 +1740,7 @@ struct DList(T)
*
* Precondition: $(D_PARAM r) is extracted from this list.
*/
DRange!T remove(DRange!T r)
Range remove(Range r)
in
{
assert(checkRangeBelonging(r));
@ -1768,13 +1780,13 @@ struct DList(T)
* Returns: Range that iterates over all elements of the container, in
* forward order.
*/
DRange!T opIndex()
Range opIndex()
{
return typeof(return)(this.head, this.tail);
}
/// Ditto.
DRange!(const T) opIndex() const
ConstRange opIndex() const
{
return typeof(return)(this.head, this.tail);
}

View File

@ -14,6 +14,29 @@ module tanya.container;
public import tanya.container.array;
public import tanya.container.buffer;
public import tanya.container.set;
public import tanya.container.list;
public import tanya.container.string;
public import tanya.container.queue;
/**
* Thrown if $(D_PSYMBOL Set) cannot insert a new element because the container
* is full.
*/
class HashContainerFullException : Exception
{
/**
* 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 pure nothrow
{
super(msg, file, line, next);
}
}

View File

@ -0,0 +1,710 @@
/* 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/. */
/**
* This module implements a $(D_PSYMBOL Set) container that stores unique
* values without any particular order.
*
* 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.container.set;
import std.algorithm.mutation;
import std.traits;
import tanya.container;
import tanya.container.entry;
import tanya.memory;
/**
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
*
* Params:
* E = Element type.
*/
struct Range(E)
{
static if (isMutable!E)
{
private alias DataRange = Array!(Bucket!(Unqual!E)).Range;
}
else
{
private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange;
}
private DataRange dataRange;
@disable this();
private this(DataRange dataRange)
{
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
{
dataRange.popBack();
}
this.dataRange = dataRange;
}
@property Range save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
@property void popFront()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popBack();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(E) front() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
body
{
return dataRange.front.content;
}
@property ref inout(E) back() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
body
{
return dataRange.back.content;
}
Range opIndex()
{
return typeof(return)(this.dataRange[]);
}
Range!(const E) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Set is a data structure that stores unique values without any particular
* order.
*
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
* are resolved with linear probing.
*
* Currently works only with integral types.
*
* Params:
* T = Element type.
*/
struct Set(T)
if (isIntegral!T || is(Unqual!T == bool))
{
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T;
/// Ditto.
alias ConstRange = .Range!(const T);
invariant
{
assert(this.lengthIndex < primes.length);
assert(this.data.length == 0
|| this.data.length == primes[this.lengthIndex]);
}
/**
* Constructor.
*
* Params:
* n = Minimum number of buckets.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(const size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
{
this(allocator);
rehash(n);
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(allocator);
}
///
unittest
{
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
}
/**
* Initializes this $(D_PARAM Set) from another one.
*
* If $(D_PARAM init) is passed by reference, it will be copied.
* If $(D_PARAM init) is passed by value, it will be moved.
*
* Params:
* S = Source set type.
* init = Source set.
* allocator = Allocator.
*/
this(S)(ref S init, shared Allocator allocator = defaultAllocator)
if (is(Unqual!S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(init.data, allocator);
}
/// Ditto.
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(move(init.data), allocator);
this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0;
}
/**
* Assigns another set.
*
* If $(D_PARAM that) is passed by reference, it will be copied.
* If $(D_PARAM that) is passed by value, it will be moved.
*
* Params:
* S = Content type.
* that = The value should be assigned.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(S)(ref S that)
if (is(Unqual!S == Set))
{
this.data = that.data;
this.lengthIndex = that.lengthIndex;
return this;
}
/// Ditto.
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
{
swap(this.data, that.data);
swap(this.lengthIndex, that.lengthIndex);
return this;
}
/**
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator)
{
assert(allocator !is null);
}
body
{
return cast(shared Allocator) this.data.allocator;
}
/**
* Maximum amount of elements this $(D_PSYMBOL Set) can hold without
* resizing and rehashing. Note that it doesn't mean that the
* $(D_PSYMBOL Set) will hold $(I exactly) $(D_PSYMBOL capacity) elements.
* $(D_PSYMBOL capacity) tells the size of the container under a best-case
* distribution of elements.
*
* Returns: $(D_PSYMBOL Set) capacity.
*/
@property size_t capacity() const
{
return this.data.length;
}
///
unittest
{
Set!int set;
assert(set.capacity == 0);
set.insert(8);
assert(set.capacity == 3);
}
/**
* Iterates over the $(D_PSYMBOL Set) and counts the elements.
*
* Returns: Count of elements within the $(D_PSYMBOL Set).
*/
@property size_t length() const
{
size_t count;
foreach (ref e; this.data[])
{
if (e.status == BucketStatus.used)
{
++count;
}
}
return count;
}
///
unittest
{
Set!int set;
assert(set.length == 0);
set.insert(8);
assert(set.length == 1);
}
private static const size_t[41] primes = [
3, 7, 13, 23, 29, 37, 53, 71, 97, 131, 163, 193, 239, 293, 389, 521,
769, 919, 1103, 1327, 1543, 2333, 3079, 4861, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
];
/// The maximum number of buckets the container can have.
enum size_t maxBucketCount = primes[$ - 1];
static private size_t calculateHash(ref T value)
{
static if (isIntegral!T || isSomeChar!T || is(T == bool))
{
return (cast(size_t) value);
}
else
{
static assert(false);
}
}
static private size_t locateBucket(ref const DataType buckets, size_t hash)
{
return hash % buckets.length;
}
private enum InsertStatus : byte
{
found = -1,
failed = 0,
added = 1,
}
/*
* Inserts the value in an empty or deleted bucket. If the value is
* already in there, does nothing and returns InsertStatus.found. If the
* hash array is full returns InsertStatus.failed.
*/
private InsertStatus insertInUnusedBucket(ref T value)
{
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Already in the set.
{
return InsertStatus.found;
}
else if (e.status != BucketStatus.used) // Insert the value.
{
e.content = value;
return InsertStatus.added;
}
}
return InsertStatus.failed;
}
/**
* Inserts a new element.
*
* Params:
* value = Element value.
*
* Returns: Amount of new elements inserted.
*
* Throws: $(D_PSYMBOL HashContainerFullException) if the insertion failed.
*/
size_t insert(T value)
{
if (this.data.length == 0)
{
this.data = DataType(primes[0], allocator);
}
InsertStatus status = insertInUnusedBucket(value);
for (; !status; status = insertInUnusedBucket(value))
{
if ((this.primes.length - 1) == this.lengthIndex)
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
}
rehashToSize(this.lengthIndex + 1);
}
return status == InsertStatus.added;
}
///
unittest
{
Set!int set;
assert(8 !in set);
assert(set.insert(8) == 1);
assert(set.length == 1);
assert(8 in set);
assert(set.insert(8) == 0);
assert(set.length == 1);
assert(8 in set);
assert(set.remove(8));
assert(set.insert(8) == 1);
}
/**
* Removes an element.
*
* Params:
* value = Element value.
*
* Returns: Number of elements removed, which is in the container with
* unique values `1` if an element existed, and `0` otherwise.
*/
size_t remove(T value)
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
e.remove();
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
}
///
unittest
{
Set!int set;
assert(8 !in set);
set.insert(8);
assert(8 in set);
assert(set.remove(8) == 1);
assert(set.remove(8) == 0);
assert(8 !in set);
}
/**
* $(D_KEYWORD in) operator.
*
* Params:
* value = Element to be searched for.
*
* Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in")(auto ref T value)
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
/// Ditto.
bool opBinaryRight(string op : "in")(auto ref const T value) const
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e.status == BucketStatus.used && e.content == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
///
unittest
{
Set!int set;
assert(5 !in set);
set.insert(5);
assert(5 in set);
}
/**
* Sets the number of buckets in the container to at least $(D_PARAM n)
* and rearranges all the elements according to their hash values.
*
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL capacity)
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
* forced.
*
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
*
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
* is forced without resizing the container.
*
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
* function may have no effect.
*
* Rehashing is automatically performed whenever the container needs space
* to insert new elements.
*
* Params:
* n = Minimum number of buckets.
*/
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
rehashToSize(lengthIndex);
}
// Takes an index in the primes array.
private void rehashToSize(const size_t n)
{
auto storage = DataType(primes[n], allocator);
DataLoop: foreach (ref e1; this.data[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = locateBucket(storage,
calculateHash(e1.content));
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the value.
{
e2.content = e1.content;
continue DataLoop;
}
}
return; // Rehashing failed.
}
}
move(storage, this.data);
this.lengthIndex = n;
}
/**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
* elements.
*/
Range opIndex()
{
return typeof(return)(this.data[]);
}
/// Ditto.
ConstRange opIndex() const
{
return typeof(return)(this.data[]);
}
///
unittest
{
Set!int set;
assert(set[].empty);
set.insert(8);
assert(!set[].empty);
assert(set[].front == 8);
assert(set[].back == 8);
set.remove(8);
assert(set[].empty);
}
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
}
// Basic insertion logic.
private unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(5) == 0);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(9) == 1);
assert(set.data[0].content == 9 && set.data[0].status == BucketStatus.used);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(set.data[0].content == 7);
assert(set.data[1].content == 8);
assert(set.data[2].content == 9);
assert(set.data[3].status == BucketStatus.empty);
assert(set.data[5].content == 5);
assert(set.data.length == 7);
assert(set.insert(16) == 1);
assert(set.data[2].content == 9);
assert(set.data[3].content == 16);
assert(set.data[4].status == BucketStatus.empty);
}
// Static checks.
private unittest
{
import std.range.primitives;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}

View File

@ -199,12 +199,7 @@ struct ByCodeUnit(E)
return typeof(return)(*this.container, this.begin + i, this.begin + j);
}
const(E)[] toString() const @trusted
{
return this.begin[0 .. length];
}
E[] toString() @trusted
inout(E)[] get() inout @trusted
{
return this.begin[0 .. length];
}
@ -948,13 +943,7 @@ struct String
*
* Returns: The array representing the string.
*/
const(char)[] toString() const pure nothrow @trusted @nogc
{
return this.data[0 .. this.length_];
}
/// Ditto.
char[] toString() pure nothrow @trusted @nogc
inout(char)[] get() inout pure nothrow @trusted @nogc
{
return this.data[0 .. this.length_];
}

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Socket programming.
* Low-level socket programming.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
@ -12,15 +12,16 @@
*/
module tanya.network.socket;
import tanya.memory;
import core.stdc.errno;
import core.time;
import std.algorithm.comparison;
import std.algorithm.searching;
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
SocketType, AddressFamily, AddressInfo;
public import std.socket : SocketOptionLevel, SocketOption;
import std.traits;
import std.typecons;
import tanya.memory;
/// Value returned by socket operations on error.
enum int socketError = -1;
version (Posix)
{
@ -32,7 +33,12 @@ version (Posix)
import core.sys.posix.sys.time;
import core.sys.posix.unistd;
private enum SOCKET_ERROR = -1;
enum SocketType : int
{
init = -1,
}
private alias LingerField = int;
}
else version (Windows)
{
@ -43,16 +49,23 @@ else version (Windows)
import core.sys.windows.windef;
import core.sys.windows.winsock2;
enum SocketType : size_t
{
init = ~0,
}
private alias LingerField = ushort;
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_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.
IOC_INOUT = (IOC_IN | IOC_OUT), // Copy parameter into and get back.
}
template _WSAIO(int x, int y)
@ -183,19 +196,26 @@ else version (Windows)
LPINT,
SOCKADDR**,
LPINT);
const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
const GUID WSAID_GETACCEPTEXSOCKADDRS = {
0xb5367df2, 0xcbac, 0x11cf,
[ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 ],
};
struct WSABUF {
struct WSABUF
{
ULONG len;
CHAR* buf;
}
alias WSABUF* LPWSABUF;
struct WSAOVERLAPPED {
struct WSAOVERLAPPED
{
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
union
{
struct
{
DWORD Offset;
DWORD OffsetHigh;
}
@ -219,36 +239,13 @@ else version (Windows)
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
this(SocketType 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
@ -268,23 +265,13 @@ else version (Windows)
&overlapped.overlapped,
NULL);
if (result == SOCKET_ERROR && !wouldHaveBlocked)
if (result == socketError && !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)
{
@ -309,19 +296,6 @@ else version (Windows)
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
@ -339,7 +313,7 @@ else version (Windows)
&overlapped.overlapped,
NULL);
if (result == SOCKET_ERROR && !wouldHaveBlocked)
if (result == socketError && !wouldHaveBlocked)
{
disconnected_ = true;
throw defaultAllocator.make!SocketException("Unable to send");
@ -347,16 +321,6 @@ else version (Windows)
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)
{
@ -380,17 +344,9 @@ else version (Windows)
class OverlappedStreamSocket : StreamSocket
{
/// Accept extension function pointer.
// 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);
@ -412,28 +368,17 @@ else version (Windows)
&dwBytes,
NULL,
NULL);
if (!result == SOCKET_ERROR)
if (!result == socketError)
{
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)
auto socket = cast(SocketType) socket(addressFamily, 1, 0);
if (socket == SocketType.init)
{
throw defaultAllocator.make!SocketException("Unable to create socket");
}
@ -445,7 +390,7 @@ else version (Windows)
overlapped.handle = cast(HANDLE) socket;
overlapped.event = OverlappedSocketEvent.accept;
immutable len = (sockaddr_in.sizeof + 16) * 2;
const len = (sockaddr_in.sizeof + 16) * 2;
overlapped.buffer.len = len;
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
@ -465,6 +410,139 @@ else version (Windows)
return result == TRUE;
}
OverlappedConnectedSocket endAccept(SocketState overlapped)
@nogc @trusted
{
scope (exit)
{
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
}
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
cast(SocketType) overlapped.handle,
addressFamily);
scope (failure)
{
defaultAllocator.dispose(socket);
}
socket.setOption(SocketOptionLevel.SOCKET,
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
cast(size_t) handle);
return socket;
}
}
}
else version (D_Ddoc)
{
/// Native socket representation type.
enum SocketType;
/**
* Socket returned if a connection has been established.
*
* Note: Available only on Windows.
*/
class OverlappedConnectedSocket : ConnectedSocket
{
/**
* Create a socket.
*
* Params:
* handle = Socket handle.
* af = Address family.
*/
this(SocketType handle, AddressFamily af) @nogc;
/**
* 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;
/**
* Ends a pending asynchronous read.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes received.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*
* Postcondition: $(D_INLINECODE result >= 0).
*/
int endReceive(SocketState overlapped) @nogc @trusted;
/**
* 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;
/**
* Ends a pending asynchronous send.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes sent.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*
* Postcondition: $(D_INLINECODE result >= 0).
*/
int endSend(SocketState overlapped) @nogc @trusted;
}
/**
* Windows stream socket overlapped I/O.
*/
class OverlappedStreamSocket : StreamSocket
{
/**
* Create a socket.
*
* Params:
* af = Address family.
*
* Throws: $(D_PSYMBOL SocketException) on errors.
*/
this(AddressFamily af) @nogc @trusted;
/**
* 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;
/**
* Asynchronously accepts an incoming connection attempt and creates a
* new socket to handle remote host communication.
@ -476,24 +554,116 @@ else version (Windows)
*
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
*/
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
OverlappedConnectedSocket endAccept(SocketState overlapped)
@nogc @trusted;
}
}
/**
* Socket option that specifies what should happen when the socket that
* promises reliable delivery still has untransmitted messages when
* it is closed.
*/
struct Linger
{
/// If nonzero, $(D_PSYMBOL close) and $(D_PSYMBOL shutdown) block until
/// the data are transmitted or the timeout period has expired.
LingerField l_onoff;
/// Time, in seconds to wait before any buffered data to be sent is
/// discarded.
LingerField l_linger;
/**
* If $(D_PARAM timeout) is `0`, linger is disabled, otherwise enables the
* linger and sets the timeout.
*
* Params:
* timeout = Timeout, in seconds.
*/
this(const ushort timeout)
{
time = timeout;
}
///
unittest
{
{
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;
auto linger = Linger(5);
assert(linger.enabled);
assert(linger.time == 5);
}
{
auto linger = Linger(0);
assert(!linger.enabled);
}
{ // Default constructor.
Linger linger;
assert(!linger.enabled);
}
}
/**
* System dependent constructor.
*
* Params:
* l_onoff = $(D_PSYMBOL l_onoff) value.
* l_linger = $(D_PSYMBOL l_linger) value.
*/
this(LingerField l_onoff, LingerField l_linger)
{
this.l_onoff = l_onoff;
this.l_linger = l_linger;
}
///
unittest
{
auto linger = Linger(1, 5);
assert(linger.l_onoff == 1);
assert(linger.l_linger == 5);
}
/**
* Params:
* value = Whether to linger after the socket is closed.
*
* See_Also: $(D_PSYMBOL time).
*/
@property enabled(const bool value) pure nothrow @safe @nogc
{
this.l_onoff = value;
}
/**
* Returns: Whether to linger after the socket is closed.
*/
@property bool enabled() const pure nothrow @safe @nogc
{
return this.l_onoff != 0;
}
/**
* Returns: Timeout period, in seconds, to wait before closing the socket
* if the $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled).
*/
@property ushort time() const pure nothrow @safe @nogc
{
return this.l_linger & ushort.max;
}
/**
* Sets timeout period, to wait before closing the socket if the
* $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled), ignored otherwise.
*
* Params:
* timeout = Timeout period, in seconds.
*/
@property void time(const ushort timeout) pure nothrow @safe @nogc
{
this.l_onoff = timeout > 0;
this.l_linger = timeout;
}
}
@ -525,7 +695,7 @@ else version (DragonFlyBSD)
version (MacBSD)
{
enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted.
}
private immutable
@ -552,12 +722,32 @@ shared static this()
}
}
/**
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
* the protocol family which will be used for communication.
*/
enum AddressFamily : int
{
unspec = 0, /// Unspecified.
local = 1, /// Local to host (pipes and file-domain).
unix = local, /// POSIX name for PF_LOCAL.
inet = 2, /// IP protocol family.
ax25 = 3, /// Amateur Radio AX.25.
ipx = 4, /// Novell Internet Protocol.
appletalk = 5, /// Appletalk DDP.
netrom = 6, /// Amateur radio NetROM.
bridge = 7, /// Multiprotocol bridge.
atmpvc = 8, /// ATM PVCs.
x25 = 9, /// Reserved for X.25 project.
inet6 = 10, /// IP version 6.
}
/**
* Error codes for $(D_PSYMBOL Socket).
*/
enum SocketError : int
{
/// Unknown error
/// Unknown error.
unknown = 0,
/// Firewall rules forbid connection.
accessDenied = EPERM,
@ -587,12 +777,12 @@ enum SocketError : int
/**
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
* returns -1 or $(D_PSYMBOL SOCKET_ERROR) and sets $(D_PSYMBOL errno),
* because $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.
* $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because
* $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.
*/
class SocketException : Exception
{
immutable SocketError error = SocketError.unknown;
const SocketError error = SocketError.unknown;
/**
* Params:
@ -680,16 +870,16 @@ abstract class Socket
}
/// Socket handle.
protected socket_t handle_;
protected SocketType handle_;
/// Address family.
protected AddressFamily family;
private @property void handle(socket_t handle) @nogc
private @property void handle(SocketType handle) @nogc
in
{
assert(handle != socket_t.init);
assert(handle_ == socket_t.init, "Socket handle cannot be changed");
assert(handle != SocketType.init);
assert(handle_ == SocketType.init, "Socket handle cannot be changed");
}
body
{
@ -703,7 +893,7 @@ abstract class Socket
}
}
@property inout(socket_t) handle() inout const pure nothrow @safe @nogc
@property inout(SocketType) handle() inout const pure nothrow @safe @nogc
{
return handle_;
}
@ -715,10 +905,10 @@ abstract class Socket
* handle = Socket.
* af = Address family.
*/
this(socket_t handle, AddressFamily af) @nogc
this(SocketType handle, AddressFamily af) @nogc
in
{
assert(handle != socket_t.init);
assert(handle != SocketType.init);
}
body
{
@ -759,7 +949,7 @@ abstract class Socket
cast(int) level,
cast(int) option,
result.ptr,
&length) == SOCKET_ERROR)
&length) == socketError)
{
throw defaultAllocator.make!SocketException("Unable to get socket option");
}
@ -771,7 +961,7 @@ abstract class Socket
SocketOption option,
out size_t result) const @trusted @nogc
{
return getOption(level, option, (&result)[0..1]);
return getOption(level, option, (&result)[0 .. 1]);
}
/// Ditto.
@ -779,7 +969,7 @@ abstract class Socket
SocketOption option,
out Linger result) const @trusted @nogc
{
return getOption(level, option, (&result.clinger)[0..1]);
return getOption(level, option, (&result)[0 .. 1]);
}
/// Ditto.
@ -792,7 +982,7 @@ abstract class Socket
version (Posix)
{
timeval tv;
auto ret = getOption(level, option, (&tv)[0..1]);
auto ret = getOption(level, option, (&tv)[0 .. 1]);
result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
}
else version (Windows)
@ -826,7 +1016,7 @@ abstract class Socket
cast(int)level,
cast(int)option,
value.ptr,
cast(uint) value.length) == SOCKET_ERROR)
cast(uint) value.length) == socketError)
{
throw defaultAllocator.make!SocketException("Unable to set socket option");
}
@ -836,14 +1026,14 @@ abstract class Socket
void setOption(SocketOptionLevel level, SocketOption option, size_t value)
const @trusted @nogc
{
setOption(level, option, (&value)[0..1]);
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]);
setOption(level, option, (&value)[0 .. 1]);
}
/// Ditto.
@ -854,7 +1044,7 @@ abstract class Socket
{
timeval tv;
value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
setOption(level, option, (&tv)[0..1]);
setOption(level, option, (&tv)[0 .. 1]);
}
else version (Windows)
{
@ -878,7 +1068,7 @@ abstract class Socket
}
else version (Windows)
{
return blocking_;
return this.blocking_;
}
}
@ -892,12 +1082,12 @@ abstract class Socket
{
int fl = fcntl(handle_, F_GETFL, 0);
if (fl != SOCKET_ERROR)
if (fl != socketError)
{
fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
fl = fcntl(handle_, F_SETFL, fl);
}
if (fl == SOCKET_ERROR)
if (fl == socketError)
{
throw make!SocketException(defaultAllocator,
"Unable to set socket blocking");
@ -906,12 +1096,12 @@ abstract class Socket
else version (Windows)
{
uint num = !yes;
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
if (ioctlsocket(handle_, FIONBIO, &num) == socketError)
{
throw make!SocketException(defaultAllocator,
"Unable to set socket blocking");
}
blocking_ = yes;
this.blocking_ = yes;
}
}
@ -963,7 +1153,7 @@ abstract class Socket
{
.close(handle_);
}
handle_ = socket_t.init;
handle_ = SocketType.init;
}
/**
@ -971,12 +1161,12 @@ abstract class Socket
* can $(D_PSYMBOL listen).
*
* Params:
* backlog = Request of how many pending incoming connections are
* 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)
if (.listen(handle_, backlog) == socketError)
{
throw defaultAllocator.make!SocketException("Unable to listen on socket");
}
@ -1029,8 +1219,8 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/
this(AddressFamily af) @trusted @nogc
{
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
if (handle == socket_t.init)
auto handle = cast(SocketType) socket(af, 1, 0);
if (handle == SocketType.init)
{
throw defaultAllocator.make!SocketException("Unable to create socket");
}
@ -1047,7 +1237,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/
void bind(Address address) const @trusted @nogc
{
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
if (.bind(handle_, address.name, address.length) == socketError)
{
throw defaultAllocator.make!SocketException("Unable to bind socket");
}
@ -1066,7 +1256,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/
ConnectedSocket accept() @trusted @nogc
{
socket_t sock;
SocketType sock;
version (linux)
{
@ -1075,14 +1265,14 @@ class StreamSocket : Socket, ConnectionOrientedSocket
{
flags |= SOCK_NONBLOCK;
}
sock = cast(socket_t).accept4(handle_, null, null, flags);
sock = cast(SocketType).accept4(handle_, null, null, flags);
}
else
{
sock = cast(socket_t).accept(handle_, null, null);
sock = cast(SocketType).accept(handle_, null, null);
}
if (sock == socket_t.init)
if (sock == SocketType.init)
{
if (wouldHaveBlocked())
{
@ -1147,7 +1337,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
* handle = Socket.
* af = Address family.
*/
this(socket_t handle, AddressFamily af) @nogc
this(SocketType handle, AddressFamily af) @nogc
{
super(handle, af);
}
@ -1196,7 +1386,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
{
disconnected_ = true;
}
else if (ret == SOCKET_ERROR)
else if (ret == socketError)
{
if (wouldHaveBlocked())
{
@ -1233,7 +1423,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
}
sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
if (sent != SOCKET_ERROR)
if (sent != socketError)
{
return sent;
}
@ -1273,14 +1463,11 @@ class InternetAddress : Address
/// Internal internet address representation.
protected sockaddr_storage storage;
}
immutable ushort port_;
const ushort port_;
enum
{
anyPort = 0,
}
enum ushort anyPort = 0;
this(in string host, ushort port = anyPort) @nogc
this(string host, const ushort port = anyPort) @nogc
{
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
{
@ -1288,11 +1475,11 @@ class InternetAddress : Address
"Address info lookup is not available on this system");
}
addrinfo* ai_res;
port_ = port;
this.port_ = port;
// Make C-string from host.
auto node = cast(char[]) allocator.allocate(host.length + 1);
node[0.. $ - 1] = host;
node[0 .. $ - 1] = host;
node[$ - 1] = '\0';
scope (exit)
{
@ -1304,18 +1491,19 @@ class InternetAddress : Address
const(char)* servicePointer;
if (port)
{
ushort originalPort = port;
ushort start;
for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
{
ushort rest = port % 10;
ushort rest = originalPort % 10;
if (rest != 0)
{
service[i] = cast(char) (rest + '0');
start = i;
}
port /= 10;
originalPort /= 10;
}
servicePointer = service[start..$].ptr;
servicePointer = service[start .. $].ptr;
}
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
@ -1327,18 +1515,29 @@ class InternetAddress : Address
{
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)
if (ai_res.ai_family != AddressFamily.inet && ai_res.ai_family != AddressFamily.inet6)
{
throw defaultAllocator.make!SocketException("Wrong address family");
}
}
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1");
assert(address.port == InternetAddress.anyPort);
assert(address.name !is null);
assert(address.family == AddressFamily.inet);
defaultAllocator.dispose(address);
}
/**
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
*/
@ -1355,9 +1554,9 @@ class InternetAddress : Address
// FreeBSD wants to know the exact length of the address on bind.
switch (family)
{
case AddressFamily.INET:
case AddressFamily.inet:
return sockaddr_in.sizeof;
case AddressFamily.INET6:
case AddressFamily.inet6:
return sockaddr_in6.sizeof;
default:
assert(false);
@ -1376,6 +1575,15 @@ class InternetAddress : Address
{
return port_;
}
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
cast(ushort) 1234);
assert(address.port == 1234);
defaultAllocator.dispose(address);
}
}
/**

107
source/tanya/typecons.d Normal file
View File

@ -0,0 +1,107 @@
/* 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/. */
/**
* Type constructors.
*
* This module contains templates that allow to build new types from the
* available ones.
*
* 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.typecons;
import std.meta;
/**
* $(D_PSYMBOL Pair) can store two heterogeneous objects.
*
* The objects can by accessed by index as $(D_INLINECODE obj[0]) and
* $(D_INLINECODE obj[1]) or by optional names (e.g.
* $(D_INLINECODE obj.first)).
*
* $(D_PARAM Specs) contains a list of object types and names. First
* comes the object type, then an optional string containing the name.
* If you want the object be accessible only by its index (`0` or `1`),
* just skip the name.
*
* Params:
* Specs = Field types and names.
*/
template Pair(Specs...)
{
template parseSpecs(int fieldCount, Specs...)
{
static if (Specs.length == 0)
{
alias parseSpecs = AliasSeq!();
}
else static if (is(Specs[0]) && fieldCount < 2)
{
static if (is(typeof(Specs[1]) == string))
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
}
else
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
}
}
else
{
static assert(false, "Invalid argument: " ~ Specs[0].stringof);
}
}
struct Pair
{
/// Field types.
alias Types = parseSpecs!(0, Specs);
static assert(Types.length == 2, "Invalid argument count.");
// Create field aliases.
static if (is(typeof(Specs[1]) == string))
{
mixin("alias " ~ Specs[1] ~ " = expand[0];");
}
static if (is(typeof(Specs[2]) == string))
{
mixin("alias " ~ Specs[2] ~ " = expand[1];");
}
else static if (is(typeof(Specs[3]) == string))
{
mixin("alias " ~ Specs[3] ~ " = expand[1];");
}
/// Represents the values of the $(D_PSYMBOL Pair) as a list of values.
Types expand;
alias expand this;
}
}
///
unittest
{
static assert(is(Pair!(int, int)));
static assert(!is(Pair!(int, 5)));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, "first", int, "second")));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, int, "second")));
static assert(!is(Pair!("first", int, "second", int)));
static assert(!is(Pair!(int, int, int)));
static assert(!is(Pair!(int, "first")));
}