Compare commits
108 Commits
Author | SHA1 | Date | |
---|---|---|---|
278e851414 | |||
6f549df243 | |||
4633bcc680 | |||
dc39efd316 | |||
260937e4fb | |||
e17fff2881 | |||
bc32511254 | |||
f30972f948 | |||
ea33ca62c8 | |||
0f365758e1 | |||
2815b53a88 | |||
6c0588164a | |||
8ee1d647ce | |||
25791775e6 | |||
f013e2f1f4 | |||
ac3935d71b | |||
b1c217e272 | |||
d007aaa310 | |||
8aaf9e14be | |||
ae3e6b46f6 | |||
8687df1fbb | |||
ba0aff6737 | |||
a648e2120a | |||
bc61809050 | |||
8c42cbfd63 | |||
58664570f9 | |||
decb82f437 | |||
357c7e279d | |||
32e19c8b58 | |||
f5c6c5b483 | |||
ba2d086fb8 | |||
7a0241b484 | |||
36dad80e18 | |||
29d883150e | |||
e2bed0cfcb | |||
38afeac071 | |||
001c7c3e33 | |||
d4ab339feb | |||
8477312769 | |||
67f90e137d | |||
f264fd5597 | |||
9e75620f1b | |||
45825946c0 | |||
8afb552d59 | |||
e4091669f8 | |||
1cb9349226 | |||
06620dc5df | |||
708d95db49 | |||
85d9361bfb | |||
a6a6f496eb | |||
db12f03264 | |||
231aedb8ad | |||
c3b63ee40d | |||
6f405c5e08 | |||
16cf8478cf | |||
8915a0c7a7 | |||
e5c7edb72c | |||
64e0d666ed | |||
f2aac680c5 | |||
65c3ca14ec | |||
4fa47153ba | |||
d629525a4b | |||
33d321f0d7 | |||
3d64d59ba9 | |||
4635835a99 | |||
8725ec5f20 | |||
9a4c8cea06 | |||
eb360bda38 | |||
4b1cd2cbfd | |||
628153e2e8 | |||
7aa9ac9f4a | |||
cd944a61b7 | |||
8156d0fe3a | |||
47ef787353 | |||
6436ad49df | |||
e1964e47a5 | |||
6e2ce5d686 | |||
ba6bf554fb | |||
b1d2b9bd9e | |||
9b953198fa | |||
bc2a6d2703 | |||
b458250ad7 | |||
b08d5e5d83 | |||
445b872e91 | |||
5e16fe98d6 | |||
647cfe03c2 | |||
4cd6126d6b | |||
b870179a35 | |||
aabb4fb534 | |||
4d8b95812e | |||
e921413249 | |||
49cae88645 | |||
402fdfae89 | |||
7892c1a930 | |||
b90517580e | |||
85380ac3fc | |||
b90c56395c | |||
d0ada39fa7 | |||
f4145abfd1 | |||
093d499729 | |||
f90a03501b | |||
c6a99b114e | |||
43319e4e3a | |||
33dbf042c2 | |||
3c23aca6a6 | |||
885fca9b5e | |||
074d027629 | |||
f4b90d8b51 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
@ -7,14 +7,17 @@ os:
|
||||
language: d
|
||||
|
||||
d:
|
||||
- dmd-2.073.0
|
||||
- dmd-2.074.1
|
||||
- dmd-2.073.2
|
||||
- dmd-2.072.2
|
||||
- dmd-2.071.2
|
||||
- dmd-2.070.2
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- ARCH=x86_64
|
||||
|
||||
script:
|
||||
- dub test --arch=$ARCH
|
||||
- dub test -b unittest-cov --arch=$ARCH
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
56
README.md
56
README.md
@ -1,6 +1,8 @@
|
||||
# Tanya
|
||||
|
||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||
[](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
|
||||
[](https://codecov.io/gh/caraus-ecms/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||
@ -13,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
|
||||
@ -21,46 +23,46 @@ data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||
Tanya consists of the following packages:
|
||||
|
||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||
* `container`: Queue, Vector, Singly linked list, buffers.
|
||||
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
|
||||
string, Hash set.
|
||||
* `math`: Arbitrary precision integer and a set of functions.
|
||||
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||
helper functions).
|
||||
* `network`: URL-Parsing, sockets.
|
||||
* `memory`: Tools for manual memory management (allocators, smart pointers).
|
||||
* `network`: URL-Parsing, sockets, utilities.
|
||||
|
||||
### Supported compilers
|
||||
|
||||
* dmd 2.073.0
|
||||
* dmd 2.072.2
|
||||
* dmd 2.071.2
|
||||
* dmd 2.070.2
|
||||
| dmd |
|
||||
|:-------:|
|
||||
| 2.074.1 |
|
||||
| 2.073.2 |
|
||||
| 2.072.2 |
|
||||
| 2.071.2 |
|
||||
|
||||
### Current status
|
||||
|
||||
The library is currently under development, but the API is becoming gradually
|
||||
stable.
|
||||
Following modules are under development:
|
||||
|
||||
`container`s are being extended to support ranges. Also following modules are
|
||||
coming soon:
|
||||
* UTF-8 string.
|
||||
* Hash table.
|
||||
|
||||
`math` package contains an arbitrary precision integer implementation that
|
||||
needs more test cases, better performance and some additional features
|
||||
(constructing from a string and an ubyte array, and converting it back).
|
||||
| Feature | Branch | Build status |
|
||||
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| BitArray | bitvector | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|
||||
| TLS | crypto | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
|
||||
| File IO | io | [](https://travis-ci.org/caraus-ecms/tanya) [](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.
|
||||
Feel free to contact me if you have any questions: info@caraus.de.
|
||||
|
52
appveyor.yml
Normal file
52
appveyor.yml
Normal file
@ -0,0 +1,52 @@
|
||||
platform: x64
|
||||
os: Visual Studio 2017
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- DC: dmd
|
||||
DVersion: 2.074.1
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.073.2
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.072.2
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.071.2
|
||||
arch: x86
|
||||
|
||||
skip_tags: true
|
||||
|
||||
install:
|
||||
- ps: function SetUpDCompiler
|
||||
{
|
||||
$env:toolchain = "msvc";
|
||||
$version = $env:DVersion;
|
||||
Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z";
|
||||
echo "finished.";
|
||||
pushd c:\\;
|
||||
7z x dmd.7z > $null;
|
||||
popd;
|
||||
}
|
||||
- ps: SetUpDCompiler
|
||||
|
||||
- ps: if($env:DVersion -eq "2.071.2"){
|
||||
Invoke-WebRequest "http://code.dlang.org/files/dub-1.2.1-windows-x86.zip" -OutFile "dub.zip";
|
||||
7z x dub.zip -odub > $null;
|
||||
Move-Item "dub/dub.exe" "C:\dmd2\windows\bin"
|
||||
}
|
||||
|
||||
before_build:
|
||||
- ps: $env:PATH += ";C:\dmd2\windows\bin;";
|
||||
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch%
|
||||
|
||||
build_script:
|
||||
- echo dummy build script - dont remove me
|
||||
|
||||
test_script:
|
||||
- echo %DC%
|
||||
- echo %PATH%
|
||||
- 'dub --version'
|
||||
- '%DC% --version'
|
||||
- dub test --arch=x86 --compiler=%DC%
|
2
dub.json
2
dub.json
@ -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": [
|
||||
|
@ -18,7 +18,7 @@ import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
@ -37,7 +37,7 @@ extern (C) nothrow @nogc
|
||||
final class EpollLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
private Vector!epoll_event events;
|
||||
private Array!epoll_event events;
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
@ -49,7 +49,7 @@ final class EpollLoop : SelectorLoop
|
||||
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
||||
}
|
||||
super();
|
||||
events = Vector!epoll_event(maxEvents, MmapPool.instance);
|
||||
events = Array!epoll_event(maxEvents, MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +50,7 @@ import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
@ -116,8 +116,8 @@ extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||
final class KqueueLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
private Vector!kevent_t events;
|
||||
private Vector!kevent_t changes;
|
||||
private Array!kevent_t events;
|
||||
private Array!kevent_t changes;
|
||||
private size_t changeCount;
|
||||
|
||||
/**
|
||||
@ -139,8 +139,8 @@ final class KqueueLoop : SelectorLoop
|
||||
throw make!BadLoopException(defaultAllocator,
|
||||
"kqueue initialization failed");
|
||||
}
|
||||
events = Vector!kevent_t(64, MmapPool.instance);
|
||||
changes = Vector!kevent_t(64, MmapPool.instance);
|
||||
events = Array!kevent_t(64, MmapPool.instance);
|
||||
changes = Array!kevent_t(64, MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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)
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
@ -78,7 +78,8 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||
return cast(ConnectedSocket) socket_;
|
||||
}
|
||||
|
||||
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
||||
private @property void socket(ConnectedSocket socket)
|
||||
pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
@ -133,7 +134,9 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||
void close() @nogc
|
||||
{
|
||||
closing = true;
|
||||
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write));
|
||||
loop.reify(this,
|
||||
EventMask(Event.read, Event.write),
|
||||
EventMask(Event.write));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,20 +208,20 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||
abstract class SelectorLoop : Loop
|
||||
{
|
||||
/// Pending connections.
|
||||
protected Vector!SocketWatcher connections;
|
||||
protected Array!SocketWatcher connections;
|
||||
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
||||
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
|
||||
}
|
||||
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (ref connection; connections)
|
||||
{
|
||||
// We want to free only the transports. ConnectionWatcher are created by the
|
||||
// user and should be freed by himself.
|
||||
// We want to free only the transports. ConnectionWatcher are
|
||||
// created by the user and should be freed by himself.
|
||||
if (cast(StreamTransport) connection !is null)
|
||||
{
|
||||
MmapPool.instance.dispose(connection);
|
||||
|
@ -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;
|
||||
* }
|
||||
*
|
||||
|
1652
source/tanya/container/array.d
Normal file
1652
source/tanya/container/array.d
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,11 +12,100 @@
|
||||
*/
|
||||
module tanya.container.entry;
|
||||
|
||||
import std.traits;
|
||||
import tanya.typecons;
|
||||
|
||||
package struct SEntry(T)
|
||||
{
|
||||
/// Item content.
|
||||
// Item content.
|
||||
T content;
|
||||
|
||||
/// Next item.
|
||||
// Next item.
|
||||
SEntry* next;
|
||||
}
|
||||
|
||||
package struct DEntry(T)
|
||||
{
|
||||
// Item content.
|
||||
T content;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,8 @@
|
||||
* 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).
|
||||
@ -10,7 +12,31 @@
|
||||
*/
|
||||
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.vector;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
* 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).
|
||||
|
710
source/tanya/container/set.d
Normal file
710
source/tanya/container/set.d
Normal 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));
|
||||
}
|
1506
source/tanya/container/string.d
Normal file
1506
source/tanya/container/string.d
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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.math;
|
||||
|
||||
@ -79,7 +79,7 @@ body
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
|
||||
I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
|
||||
if (is(I == Integer))
|
||||
in
|
||||
{
|
||||
@ -87,32 +87,33 @@ in
|
||||
}
|
||||
body
|
||||
{
|
||||
size_t i = y.length;
|
||||
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
|
||||
Integer result = Integer(x.allocator);
|
||||
size_t i;
|
||||
auto tmp1 = Integer(x, x.allocator);
|
||||
auto result = Integer(x.allocator);
|
||||
bool firstBit;
|
||||
|
||||
if (x.length == 0 && i != 0)
|
||||
if (x.size == 0 && y.size != 0)
|
||||
{
|
||||
i = 0;
|
||||
i = y.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
while (i)
|
||||
while (i < y.size)
|
||||
{
|
||||
--i;
|
||||
for (ubyte mask = 0x01; mask; mask <<= 1)
|
||||
for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
|
||||
{
|
||||
if (y.rep[i] & mask)
|
||||
{
|
||||
result *= tmp1;
|
||||
result %= z;
|
||||
}
|
||||
tmp2 = tmp1;
|
||||
auto tmp2 = tmp1;
|
||||
tmp1 *= tmp2;
|
||||
tmp1 %= z;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -134,15 +135,15 @@ pure nothrow @safe @nogc unittest
|
||||
///
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,3 +171,31 @@ unittest
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I abs(I)(const I x)
|
||||
if (isIntegral!I)
|
||||
{
|
||||
return x >= 0 ? x : -x;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* 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;
|
||||
|
||||
@ -156,7 +156,7 @@ version (linux)
|
||||
*
|
||||
* output = entropy.random;
|
||||
*
|
||||
* defaultAllocator.finalize(entropy);
|
||||
* defaultAllocator.dispose(entropy);
|
||||
* ---
|
||||
*/
|
||||
class Entropy
|
||||
@ -185,7 +185,7 @@ class Entropy
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator.resizeArray(sources, maxSources);
|
||||
allocator.resize(sources, maxSources);
|
||||
|
||||
version (linux)
|
||||
{
|
||||
|
185
source/tanya/memory/mallocator.d
Normal file
185
source/tanya/memory/mallocator.d
Normal file
@ -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_;
|
||||
}
|
@ -11,7 +11,8 @@
|
||||
module tanya.memory;
|
||||
|
||||
import core.exception;
|
||||
public import std.experimental.allocator : make, makeArray;
|
||||
import std.algorithm.iteration;
|
||||
public import std.experimental.allocator : make;
|
||||
import std.traits;
|
||||
public import tanya.memory.allocator;
|
||||
|
||||
@ -29,6 +30,8 @@ mixin template DefaultAllocator()
|
||||
/**
|
||||
* Params:
|
||||
* allocator = The allocator should be used.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator_ !is null)
|
||||
*/
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
@ -46,7 +49,7 @@ mixin template DefaultAllocator()
|
||||
*
|
||||
* Returns: Used allocator.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE allocator_ !is null)
|
||||
* Postcondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||
out (allocator)
|
||||
@ -135,116 +138,97 @@ template stateSize(T)
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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)
|
||||
if (length == 0)
|
||||
{
|
||||
immutable oldLength = array.length;
|
||||
if (allocator.deallocate(array))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
onOutOfMemoryErrorNoGC();
|
||||
}
|
||||
}
|
||||
|
||||
void[] buf = array;
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
static if (Throws)
|
||||
{
|
||||
onOutOfMemoryError;
|
||||
}
|
||||
return false;
|
||||
onOutOfMemoryErrorNoGC();
|
||||
}
|
||||
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||
array = cast(T[]) buf;
|
||||
|
||||
static if (Init)
|
||||
{
|
||||
if (oldLength < length)
|
||||
{
|
||||
array[oldLength .. $] = T.init;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return array;
|
||||
}
|
||||
package(tanya) alias resizeArray = resize;
|
||||
|
||||
///
|
||||
unittest
|
||||
private unittest
|
||||
{
|
||||
int[] p;
|
||||
|
||||
defaultAllocator.resizeArray(p, 20);
|
||||
p = defaultAllocator.resize(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
defaultAllocator.resizeArray(p, 30);
|
||||
p = defaultAllocator.resize(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
defaultAllocator.resizeArray(p, 10);
|
||||
p = defaultAllocator.resize(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
defaultAllocator.resizeArray(p, 0);
|
||||
p = defaultAllocator.resize(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
|
||||
* It is assumed the respective entities had been allocated with the same
|
||||
* allocator.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of $(D_PARAM p).
|
||||
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||
* p = Object or array to be destroyed.
|
||||
/*
|
||||
* Destroys the object.
|
||||
* Returns the memory should be freed.
|
||||
*/
|
||||
void dispose(T)(shared Allocator allocator, auto ref T* p)
|
||||
package(tanya) void[] finalize(T)(ref T* p)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
destroy(*p);
|
||||
}
|
||||
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
|
||||
p = null;
|
||||
return (cast(void*) p)[0 .. T.sizeof];
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||
package(tanya) void[] finalize(T)(ref T p)
|
||||
if (is(T == class) || is(T == interface))
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
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 "
|
||||
static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in "
|
||||
~ __PRETTY_FUNCTION__);
|
||||
}
|
||||
auto ob = cast(Object) p;
|
||||
@ -253,19 +237,13 @@ void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||
{
|
||||
alias ob = p;
|
||||
}
|
||||
auto ptr = cast(void *) ob;
|
||||
|
||||
auto ptr = cast(void*) ob;
|
||||
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||
scope (success)
|
||||
{
|
||||
() @trusted { allocator.deallocate(support); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
auto ppv = cast(void**) ptr;
|
||||
if (!*ppv)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
auto pc = cast(ClassInfo*) *ppv;
|
||||
scope (exit)
|
||||
@ -289,21 +267,35 @@ void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||
{
|
||||
_d_monitordelete(cast(Object) ptr, true);
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T[] p)
|
||||
package(tanya) void[] finalize(T)(ref T[] p)
|
||||
{
|
||||
static if (hasElaborateDestructor!(typeof(p[0])))
|
||||
{
|
||||
import std.algorithm.iteration;
|
||||
p.each!(e => destroy(e));
|
||||
p.each!((ref e) => destroy(e));
|
||||
}
|
||||
() @trusted { allocator.deallocate(p); }();
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
|
||||
* It is assumed the respective entities had been allocated with the same
|
||||
* allocator.
|
||||
*
|
||||
* Params:
|
||||
* 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)
|
||||
{
|
||||
() @trusted { allocator.deallocate(finalize(p)); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
unittest
|
||||
private unittest
|
||||
{
|
||||
struct S
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
@ -14,34 +14,28 @@ import core.exception;
|
||||
import std.algorithm.comparison;
|
||||
import std.algorithm.mutation;
|
||||
import std.conv;
|
||||
import std.range;
|
||||
import std.traits;
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
||||
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
||||
* when the reference count goes down to zero, frees the underlying store.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of the reference-counted value.
|
||||
*/
|
||||
struct RefCounted(T)
|
||||
private template Payload(T)
|
||||
{
|
||||
static if (is(T == class) || is(T == interface))
|
||||
static if (is(T == class) || is(T == interface) || isArray!T)
|
||||
{
|
||||
private alias Payload = T;
|
||||
alias Payload = T;
|
||||
}
|
||||
else
|
||||
{
|
||||
private alias Payload = T*;
|
||||
alias Payload = T*;
|
||||
}
|
||||
}
|
||||
|
||||
private class Storage
|
||||
{
|
||||
private Payload payload;
|
||||
private size_t counter = 1;
|
||||
final class RefCountedStore(T)
|
||||
{
|
||||
T payload;
|
||||
size_t counter = 1;
|
||||
|
||||
private final size_t opUnary(string op)() pure nothrow @safe @nogc
|
||||
size_t opUnary(string op)()
|
||||
if (op == "--" || op == "++")
|
||||
in
|
||||
{
|
||||
@ -52,7 +46,7 @@ struct RefCounted(T)
|
||||
mixin("return " ~ op ~ "counter;");
|
||||
}
|
||||
|
||||
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
|
||||
int opCmp(const size_t counter)
|
||||
{
|
||||
if (this.counter > counter)
|
||||
{
|
||||
@ -68,37 +62,47 @@ struct RefCounted(T)
|
||||
}
|
||||
}
|
||||
|
||||
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
|
||||
int opEquals(const size_t counter)
|
||||
{
|
||||
return this.counter == counter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class RefCountedStorage : Storage
|
||||
{
|
||||
private shared Allocator allocator;
|
||||
private void separateDeleter(T)(RefCountedStore!T storage,
|
||||
shared Allocator allocator)
|
||||
{
|
||||
allocator.dispose(storage.payload);
|
||||
allocator.dispose(storage);
|
||||
}
|
||||
|
||||
this(shared Allocator allocator) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
private void unifiedDeleter(T)(RefCountedStore!T storage,
|
||||
shared Allocator allocator)
|
||||
{
|
||||
auto ptr1 = finalize(storage);
|
||||
auto ptr2 = finalize(storage.payload);
|
||||
allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]);
|
||||
}
|
||||
|
||||
~this() nothrow @nogc
|
||||
{
|
||||
allocator.dispose(payload);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
||||
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
||||
* when the reference count goes down to zero, frees the underlying store.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of the reference-counted value.
|
||||
*/
|
||||
struct RefCounted(T)
|
||||
{
|
||||
private alias Storage = RefCountedStore!(Payload!T);
|
||||
|
||||
private Storage storage;
|
||||
private void function(Storage storage,
|
||||
shared Allocator allocator) @nogc deleter;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(storage is null || allocator_ !is null);
|
||||
assert(storage is null || deleter !is null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,11 +116,18 @@ struct RefCounted(T)
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
this(Payload value, shared Allocator allocator = defaultAllocator)
|
||||
this()(auto ref Payload!T value,
|
||||
shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this(allocator);
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
move(value, storage.payload);
|
||||
this.storage = allocator.make!Storage();
|
||||
this.deleter = &separateDeleter!(Payload!T);
|
||||
|
||||
move(value, this.storage.payload);
|
||||
static if (__traits(isRef, value))
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
@ -137,7 +148,7 @@ struct RefCounted(T)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
++storage;
|
||||
++this.storage;
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,9 +159,9 @@ struct RefCounted(T)
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
if (storage !is null && !(storage.counter && --storage))
|
||||
if (this.storage !is null && !(this.storage.counter && --this.storage))
|
||||
{
|
||||
allocator_.dispose(storage);
|
||||
deleter(this.storage, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,79 +172,74 @@ struct RefCounted(T)
|
||||
* 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).
|
||||
* To reset $(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).
|
||||
* rhs = New object.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ref typeof(this) opAssign(Payload rhs)
|
||||
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||
{
|
||||
if (storage is null)
|
||||
if (this.storage is null)
|
||||
{
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
this.storage = allocator.make!Storage();
|
||||
this.deleter = &separateDeleter!(Payload!T);
|
||||
}
|
||||
else if (storage > 1)
|
||||
else if (this.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);
|
||||
--this.storage;
|
||||
this.storage = allocator.make!Storage();
|
||||
this.deleter = &separateDeleter!(Payload!T);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.dispose(storage.payload);
|
||||
finalize(this.storage.payload);
|
||||
this.storage.payload = Payload!T.init;
|
||||
}
|
||||
move(rhs, storage.payload);
|
||||
move(rhs, this.storage.payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(null))
|
||||
{
|
||||
if (storage is null)
|
||||
if (this.storage is null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else if (storage > 1)
|
||||
else if (this.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;
|
||||
--this.storage;
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.dispose(storage.payload);
|
||||
deleter(this.storage, allocator);
|
||||
}
|
||||
this.storage = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(this) rhs)
|
||||
{
|
||||
swap(allocator_, rhs.allocator_);
|
||||
swap(storage, rhs.storage);
|
||||
swap(this.allocator_, rhs.allocator_);
|
||||
swap(this.storage, rhs.storage);
|
||||
swap(this.deleter, rhs.deleter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Reference to the owned object.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE cound > 0).
|
||||
*/
|
||||
inout(Payload) get() inout pure nothrow @safe @nogc
|
||||
Payload!T get() pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||
@ -243,7 +249,7 @@ struct RefCounted(T)
|
||||
return storage.payload;
|
||||
}
|
||||
|
||||
static if (isPointer!Payload)
|
||||
version (D_Ddoc)
|
||||
{
|
||||
/**
|
||||
* Params:
|
||||
@ -254,6 +260,11 @@ struct RefCounted(T)
|
||||
*
|
||||
* Returns: Reference to the pointed value.
|
||||
*/
|
||||
ref T opUnary(string op)()
|
||||
if (op == "*");
|
||||
}
|
||||
else static if (isPointer!(Payload!T))
|
||||
{
|
||||
ref T opUnary(string op)()
|
||||
if (op == "*")
|
||||
{
|
||||
@ -355,6 +366,44 @@ private unittest
|
||||
assert(rc.count == 1);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!int(5);
|
||||
assert(rc.count == 1);
|
||||
|
||||
void func(RefCounted!int rc)
|
||||
{
|
||||
assert(rc.count == 2);
|
||||
rc = null;
|
||||
assert(!rc.isInitialized);
|
||||
assert(rc.count == 0);
|
||||
}
|
||||
|
||||
assert(rc.count == 1);
|
||||
func(rc);
|
||||
assert(rc.count == 1);
|
||||
|
||||
rc = null;
|
||||
assert(!rc.isInitialized);
|
||||
assert(rc.count == 0);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!int(5);
|
||||
assert(*rc == 5);
|
||||
|
||||
void func(RefCounted!int rc)
|
||||
{
|
||||
assert(rc.count == 2);
|
||||
rc = defaultAllocator.refCounted!int(4);
|
||||
assert(*rc == 4);
|
||||
assert(rc.count == 1);
|
||||
}
|
||||
func(rc);
|
||||
assert(*rc == 5);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||
@ -380,15 +429,22 @@ private unittest
|
||||
* args = Constructor arguments of $(D_PARAM T).
|
||||
*
|
||||
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||
if (!is(T == interface) && !isAbstractClass!T
|
||||
&& !isArray!T && !isAssociativeArray!T)
|
||||
&& !isAssociativeArray!T && !isArray!T)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto rc = typeof(return)(allocator);
|
||||
|
||||
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
||||
immutable size = alignedSize(stateSize!T + storageSize);
|
||||
const storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
||||
const size = alignedSize(stateSize!T + storageSize);
|
||||
|
||||
auto mem = (() @trusted => allocator.allocate(size))();
|
||||
if (mem is null)
|
||||
@ -399,7 +455,7 @@ RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||
{
|
||||
() @trusted { allocator.deallocate(mem); }();
|
||||
}
|
||||
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
|
||||
rc.storage = emplace!((RefCounted!T.Storage))(mem[0 .. storageSize]);
|
||||
|
||||
static if (is(T == class))
|
||||
{
|
||||
@ -410,9 +466,38 @@ RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||
rc.storage.payload = emplace!T(ptr, args);
|
||||
}
|
||||
rc.deleter = &unifiedDeleter!(Payload!T);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||
* $(D_PSYMBOL RefCounted).
|
||||
*
|
||||
* Params:
|
||||
* T = Array type.
|
||||
* size = Array size.
|
||||
* allocator = Allocator.
|
||||
*
|
||||
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null
|
||||
* && size <= size_t.max / ElementType!T.sizeof)
|
||||
*/
|
||||
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
|
||||
@trusted
|
||||
if (isArray!T)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
assert(size <= size_t.max / ElementType!T.sizeof);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||
return RefCounted!T(payload, allocator);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
@ -456,3 +541,272 @@ private @nogc unittest
|
||||
assert(rc.count);
|
||||
}
|
||||
}
|
||||
|
||||
private @nogc unittest
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!(int[])(5);
|
||||
assert(rc.length == 5);
|
||||
}
|
||||
|
||||
private @nogc unittest
|
||||
{
|
||||
static bool destroyed = false;
|
||||
|
||||
struct F
|
||||
{
|
||||
~this() @nogc
|
||||
{
|
||||
destroyed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!F();
|
||||
}
|
||||
assert(destroyed);
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
|
||||
*
|
||||
* Params:
|
||||
* T = Value type.
|
||||
*/
|
||||
struct Scoped(T)
|
||||
{
|
||||
private Payload!T payload;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(payload 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()(auto ref Payload!T value,
|
||||
shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this(allocator);
|
||||
|
||||
move(value, this.payload);
|
||||
static if (__traits(isRef, value))
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator_ = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_PSYMBOL Scoped) is noncopyable.
|
||||
*/
|
||||
@disable this(this);
|
||||
|
||||
/**
|
||||
* Destroys the owned object.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
if (this.payload !is null)
|
||||
{
|
||||
allocator.dispose(this.payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized this $(D_PARAM Scoped) and takes ownership over
|
||||
* $(D_PARAM rhs).
|
||||
*
|
||||
* To reset $(D_PSYMBOL Scoped) 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 Scoped) and assign it.
|
||||
*
|
||||
* Params:
|
||||
* rhs = New object.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||
{
|
||||
allocator.dispose(this.payload);
|
||||
move(rhs, this.payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(null))
|
||||
{
|
||||
allocator.dispose(this.payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(this) rhs)
|
||||
{
|
||||
swap(this.allocator_, rhs.allocator_);
|
||||
swap(this.payload, rhs.payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Reference to the owned object.
|
||||
*/
|
||||
Payload!T get() pure nothrow @safe @nogc
|
||||
{
|
||||
return payload;
|
||||
}
|
||||
|
||||
version (D_Ddoc)
|
||||
{
|
||||
/**
|
||||
* 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 == "*");
|
||||
}
|
||||
else static if (isPointer!(Payload!T))
|
||||
{
|
||||
ref T opUnary(string op)()
|
||||
if (op == "*")
|
||||
{
|
||||
return *payload;
|
||||
}
|
||||
}
|
||||
|
||||
mixin DefaultAllocator;
|
||||
alias get this;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc unittest
|
||||
{
|
||||
auto p = defaultAllocator.make!int(5);
|
||||
auto s = Scoped!int(p, defaultAllocator);
|
||||
assert(p is null);
|
||||
assert(*s == 5);
|
||||
}
|
||||
|
||||
///
|
||||
@nogc unittest
|
||||
{
|
||||
static bool destroyed = false;
|
||||
|
||||
struct F
|
||||
{
|
||||
~this() @nogc
|
||||
{
|
||||
destroyed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
|
||||
}
|
||||
assert(destroyed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||
* $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for
|
||||
* the constructor of $(D_PARAM T).
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
|
||||
if (!is(T == interface) && !isAbstractClass!T
|
||||
&& !isAssociativeArray!T && !isArray!T)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto payload = allocator.make!(T, shared Allocator, A)(args);
|
||||
return Scoped!T(payload, allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||
* $(D_PSYMBOL Scoped).
|
||||
*
|
||||
* Params:
|
||||
* T = Array type.
|
||||
* size = Array size.
|
||||
* allocator = Allocator.
|
||||
*
|
||||
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null
|
||||
* && size <= size_t.max / ElementType!T.sizeof)
|
||||
*/
|
||||
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
|
||||
@trusted
|
||||
if (isArray!T)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
assert(size <= size_t.max / ElementType!T.sizeof);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||
return Scoped!T(payload, allocator);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
static assert(is(typeof(defaultAllocator.scoped!B(5))));
|
||||
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
auto s = defaultAllocator.scoped!int(5);
|
||||
assert(*s == 5);
|
||||
|
||||
s = null;
|
||||
assert(s is null);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
auto s = defaultAllocator.scoped!int(5);
|
||||
assert(*s == 5);
|
||||
|
||||
s = defaultAllocator.scoped!int(4);
|
||||
assert(*s == 4);
|
||||
}
|
||||
|
356
source/tanya/network/inet.d
Normal file
356
source/tanya/network/inet.d
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
17
source/tanya/network/package.d
Normal file
17
source/tanya/network/package.d
Normal file
@ -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;
|
@ -3,22 +3,25 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* Low-level 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)
|
||||
*/
|
||||
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)
|
||||
{
|
||||
@ -30,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)
|
||||
{
|
||||
@ -41,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)
|
||||
@ -181,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;
|
||||
}
|
||||
@ -217,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
|
||||
@ -266,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)
|
||||
{
|
||||
@ -307,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
|
||||
@ -337,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");
|
||||
@ -345,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)
|
||||
{
|
||||
@ -378,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);
|
||||
@ -410,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");
|
||||
}
|
||||
@ -443,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;
|
||||
|
||||
@ -463,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.
|
||||
@ -474,24 +554,116 @@ else version (Windows)
|
||||
*
|
||||
* 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]);
|
||||
OverlappedConnectedSocket endAccept(SocketState overlapped)
|
||||
@nogc @trusted;
|
||||
}
|
||||
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
|
||||
cast(socket_t) overlapped.handle,
|
||||
addressFamily);
|
||||
scope (failure)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
defaultAllocator.dispose(socket);
|
||||
time = timeout;
|
||||
}
|
||||
socket.setOption(SocketOptionLevel.SOCKET,
|
||||
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
||||
cast(size_t) handle);
|
||||
return socket;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,7 +695,7 @@ else version (DragonFlyBSD)
|
||||
|
||||
version (MacBSD)
|
||||
{
|
||||
enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
|
||||
enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted.
|
||||
}
|
||||
|
||||
private immutable
|
||||
@ -550,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,
|
||||
@ -585,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:
|
||||
@ -678,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
|
||||
{
|
||||
@ -701,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_;
|
||||
}
|
||||
@ -713,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
|
||||
{
|
||||
@ -757,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");
|
||||
}
|
||||
@ -769,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.
|
||||
@ -777,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.
|
||||
@ -790,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)
|
||||
@ -824,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");
|
||||
}
|
||||
@ -834,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.
|
||||
@ -852,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)
|
||||
{
|
||||
@ -876,7 +1068,7 @@ abstract class Socket
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
return blocking_;
|
||||
return this.blocking_;
|
||||
}
|
||||
}
|
||||
|
||||
@ -890,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");
|
||||
@ -904,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -961,7 +1153,7 @@ abstract class Socket
|
||||
{
|
||||
.close(handle_);
|
||||
}
|
||||
handle_ = socket_t.init;
|
||||
handle_ = SocketType.init;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -974,7 +1166,7 @@ abstract class Socket
|
||||
*/
|
||||
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");
|
||||
}
|
||||
@ -1004,10 +1196,14 @@ interface ConnectionOrientedSocket
|
||||
*/
|
||||
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
|
||||
/// 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;
|
||||
@ -1023,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");
|
||||
}
|
||||
@ -1041,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");
|
||||
}
|
||||
@ -1060,7 +1256,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
*/
|
||||
ConnectedSocket accept() @trusted @nogc
|
||||
{
|
||||
socket_t sock;
|
||||
SocketType sock;
|
||||
|
||||
version (linux)
|
||||
{
|
||||
@ -1069,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())
|
||||
{
|
||||
@ -1141,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);
|
||||
}
|
||||
@ -1190,7 +1386,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
{
|
||||
disconnected_ = true;
|
||||
}
|
||||
else if (ret == SOCKET_ERROR)
|
||||
else if (ret == socketError)
|
||||
{
|
||||
if (wouldHaveBlocked())
|
||||
{
|
||||
@ -1227,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;
|
||||
}
|
||||
@ -1267,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)
|
||||
{
|
||||
@ -1282,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)
|
||||
{
|
||||
@ -1298,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,12 +1521,23 @@ class InternetAddress : Address
|
||||
{
|
||||
*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.
|
||||
*/
|
||||
@ -1349,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);
|
||||
@ -1370,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
@ -911,8 +913,8 @@ struct URL
|
||||
}
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
~this()
|
||||
{
|
||||
if (scheme !is null)
|
||||
{
|
||||
scheme = null;
|
||||
@ -941,7 +943,7 @@ struct URL
|
||||
{
|
||||
fragment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse and set the port.
|
||||
@ -1107,7 +1109,7 @@ URL parseURL(typeof(null) T)(in char[] source)
|
||||
/// Ditto.
|
||||
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
|
||||
if (T == "scheme"
|
||||
|| T =="host"
|
||||
|| T == "host"
|
||||
|| T == "user"
|
||||
|| T == "pass"
|
||||
|| T == "path"
|
||||
|
107
source/tanya/typecons.d
Normal file
107
source/tanya/typecons.d
Normal 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")));
|
||||
}
|
Reference in New Issue
Block a user