6 Commits

14 changed files with 257 additions and 211 deletions

3
.gitignore vendored
View File

@ -5,3 +5,6 @@
.dub
__test__*__
__test__*__.core
/docs/
/docs.json

View File

@ -5,8 +5,7 @@
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
Tanya is a general purpose library for D programming language that doesn't
rely on the Garbage Collector.
Tanya is a general purpose library for D programming language.
Its aim is to simplify the manual memory management in D and to provide a
guarantee with @nogc attribute that there are no hidden allocations on the
@ -36,12 +35,12 @@ helper functions).
The library is currently under development, but some parts of it can already be
used.
`network` and `async` exist for quite some time and could be better tested than
other components.
Containers were newly reworked and the API won't change significantly, but will
be only extended. The same is true for the `memory` package.
`network` and `async` packages should be reviewed in the future and the API may
change.
`math` package contains an arbitrary precision integer implementation that has
a stable API (that mostly consists of operator overloads), but still needs
testing and work on its performance.

View File

@ -159,7 +159,7 @@ class EpollLoop : SelectorLoop
}
else if (io.output.length)
{
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
}
else if (events[i].events & EPOLLOUT)

View File

@ -218,11 +218,11 @@ class IOCPLoop : Loop
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
connection.incoming.insertBack(io);
connection.incoming.enqueue(io);
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
swapPendings.insertBack(connection);
swapPendings.enqueue(connection);
listener.beginAccept(overlapped);
break;
case OverlappedSocketEvent.read:
@ -264,7 +264,7 @@ class IOCPLoop : Loop
{
transport.socket.beginReceive(io.output[], overlapped);
}
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
break;
case OverlappedSocketEvent.write:

View File

@ -271,7 +271,7 @@ class KqueueLoop : SelectorLoop
}
else if (io.output.length)
{
swapPendings.insertBack(io);
swapPendings.enqueue(io);
}
}
else if (events[i].filter == EVFILT_WRITE)

View File

@ -248,12 +248,12 @@ abstract class SelectorLoop : Loop
}
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.insertBack(io);
connection.incoming.enqueue(io);
}
if (!connection.incoming.empty)
{
swapPendings.insertBack(connection);
swapPendings.enqueue(connection);
}
}
}

View File

@ -184,8 +184,10 @@ abstract class Loop
poll();
// Invoke pendings
swapPendings.each!((ref p) @nogc => p.invoke());
foreach (ref w; *swapPendings)
{
w.invoke();
}
swap(pendings, swapPendings);
}
while (!done_);
@ -283,7 +285,7 @@ abstract class Loop
defaultAllocator.dispose(watcher.socket);
MmapPool.instance.dispose(watcher.transport);
watcher.exception = exception;
swapPendings.insertBack(watcher);
swapPendings.enqueue(watcher);
}
/**

View File

@ -632,7 +632,7 @@ struct WriteBuffer(T = ubyte)
* written.
*
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*
@ -677,7 +677,7 @@ struct WriteBuffer(T = ubyte)
* written.
*
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*

View File

@ -10,8 +10,9 @@
*/
module tanya.container.queue;
import tanya.container.entry;
import std.traits;
import std.algorithm.mutation;
import tanya.container.entry;
import tanya.memory;
/**
@ -27,32 +28,24 @@ struct Queue(T)
*/
~this()
{
clear();
while (!empty)
{
dequeue();
}
}
/**
* Removes all elements from the queue.
*/
deprecated
void clear()
{
while (!empty)
{
popFront();
dequeue();
}
}
///
unittest
{
Queue!int q;
assert(q.empty);
q.insertBack(8);
q.insertBack(9);
q.clear();
assert(q.empty);
}
/**
* Returns how many elements are in the queue. It iterates through the queue
* to count the elements.
@ -75,18 +68,18 @@ struct Queue(T)
Queue!int q;
assert(q.length == 0);
q.insertBack(5);
q.enqueue(5);
assert(q.length == 1);
q.insertBack(4);
q.enqueue(4);
assert(q.length == 2);
q.insertBack(9);
q.enqueue(9);
assert(q.length == 3);
q.popFront();
q.dequeue();
assert(q.length == 2);
q.popFront();
q.dequeue();
assert(q.length == 1);
q.popFront();
q.dequeue();
assert(q.length == 0);
}
@ -97,14 +90,17 @@ struct Queue(T)
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
deprecated
int opEquals(ref typeof(this) that);
/// Ditto.
deprecated
int opEquals(typeof(this) that);
}
else static if (!hasMember!(T, "opEquals")
|| (functionAttributes!(T.opEquals) & FunctionAttribute.const_))
{
deprecated
bool opEquals(in ref typeof(this) that) const
{
const(Entry!T)* i = first.next;
@ -121,7 +117,7 @@ struct Queue(T)
return i is null && j is null;
}
/// Ditto.
deprecated
bool opEquals(in typeof(this) that) const
{
return opEquals(that);
@ -129,11 +125,7 @@ struct Queue(T)
}
else
{
/**
* Compares two queues. Checks if all elements of the both queues are equal.
*
* Returns: How many elements are in the queue.
*/
deprecated
bool opEquals(ref typeof(this) that)
{
Entry!T* i = first.next;
@ -150,46 +142,17 @@ struct Queue(T)
return i is null && j is null;
}
/// Ditto.
deprecated
bool opEquals(typeof(this) that)
{
return opEquals(that);
}
}
///
unittest
{
Queue!int q1, q2;
q1.insertBack(5);
q1.insertBack(4);
q2.insertBack(5);
assert(q1 != q2);
q2.insertBack(4);
assert(q1 == q2);
q2.popFront();
assert(q1 != q2);
q1.popFront();
assert(q1 == q2);
q1.popFront();
q2.popFront();
assert(q1 == q2);
}
private unittest
{
static assert(is(Queue!ConstEqualsStruct));
static assert(is(Queue!MutableEqualsStruct));
static assert(is(Queue!NoEqualsStruct));
}
/**
* Returns: First element.
*/
deprecated("Use dequeue instead.")
@property ref inout(T) front() inout
in
{
@ -205,13 +168,12 @@ struct Queue(T)
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
void insertBack(ref T x)
ref typeof(this) enqueue(ref T x)
{
auto temp = allocator.make!(Entry!T);
temp.content = x;
auto temp = allocator.make!(Entry!T)(x);
if (empty)
{
first.next = rear = temp;
@ -221,16 +183,20 @@ struct Queue(T)
rear.next = temp;
rear = rear.next;
}
return this;
}
/// Ditto.
void insertBack(T x)
ref typeof(this) enqueue(T x)
{
insertBack(x);
return enqueue(x);
}
/// Ditto.
alias insert = insertBack;
deprecated("Use enqueue instead.")
alias insert = enqueue;
deprecated("Use enqueue instead.")
alias insertBack = enqueue;
///
unittest
@ -238,10 +204,9 @@ struct Queue(T)
Queue!int q;
assert(q.empty);
q.insertBack(8);
assert(q.front == 8);
q.insertBack(9);
assert(q.front == 8);
q.enqueue(8).enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
@ -259,14 +224,16 @@ struct Queue(T)
int value = 7;
assert(q.empty);
q.insertBack(value);
q.enqueue(value);
assert(!q.empty);
}
/**
* Move the position to the next element.
*
* Returns: Dequeued element.
*/
void popFront()
T dequeue()
in
{
assert(!empty);
@ -275,21 +242,24 @@ struct Queue(T)
body
{
auto n = first.next.next;
T ret = move(first.next.content);
dispose(allocator, first.next);
first.next = n;
return ret;
}
deprecated("Use dequeue instead.")
alias popFront = dequeue;
///
unittest
{
Queue!int q;
q.insertBack(8);
q.insertBack(9);
assert(q.front == 8);
q.popFront();
assert(q.front == 9);
q.enqueue(8).enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
@ -305,11 +275,11 @@ struct Queue(T)
for (size_t i = 0; !empty; ++i)
{
if ((result = dg(i, front)) != 0)
auto e = dequeue();
if ((result = dg(i, e)) != 0)
{
return result;
}
popFront();
}
return result;
}
@ -321,11 +291,11 @@ struct Queue(T)
while (!empty)
{
if ((result = dg(front)) != 0)
auto e = dequeue();
if ((result = dg(e)) != 0)
{
return result;
}
popFront();
}
return result;
}
@ -336,9 +306,7 @@ struct Queue(T)
Queue!int q;
size_t j;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
q.enqueue(5).enqueue(4).enqueue(9);
foreach (i, e; q)
{
assert(i != 2 || e == 9);
@ -350,9 +318,7 @@ struct Queue(T)
assert(q.empty);
j = 0;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
q.enqueue(5).enqueue(4).enqueue(9);
foreach (e; q)
{
assert(j != 2 || e == 9);
@ -378,17 +344,12 @@ unittest
{
Queue!int q;
q.insertBack(5);
q.enqueue(5);
assert(!q.empty);
q.insertBack(4);
assert(q.front == 5);
q.enqueue(4).enqueue(9);
q.insertBack(9);
assert(q.front == 5);
q.popFront();
assert(q.front == 4);
assert(q.dequeue() == 5);
foreach (i, ref e; q)
{

View File

@ -10,9 +10,10 @@
*/
module tanya.container.vector;
import core.stdc.string;
import core.checkedint;
import core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.range.primitives;
import std.meta;
@ -271,8 +272,7 @@ private struct Range(E)
}
Range opSliceAssign(R)(R value, in size_t i, in size_t j)
if ((isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
|| is(R == Vector))
if (isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
{
return opSliceAssign(value[], i, j);
}
@ -297,30 +297,6 @@ struct Vector(T)
assert(capacity_ == 0 || vector !is null);
}
// Reserves memory to store len objects and initializes it.
// Doesn't change the length.
private void initialize(in size_t len)
{
reserve(len);
if (capacity_ < len)
{
onOutOfMemoryError();
}
const init = typeid(T).initializer();
if (init.ptr)
{
const T* end = vector + len;
for (void* v = vector + length_; v != end; v += init.length)
{
memcpy(v, init.ptr, init.length);
}
}
else
{
memset(vector + length_, 0, (len - length_) * T.sizeof);
}
}
/**
* Creates a new $(D_PSYMBOL Vector).
*
@ -330,16 +306,15 @@ struct Vector(T)
* to generate a list.
* allocator = Allocator.
*/
this(R)(auto ref R init, shared Allocator allocator = defaultAllocator)
if ((isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
|| is(R == Vector))
this(R)(auto in ref R init, shared Allocator allocator = defaultAllocator)
if (isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
{
this(allocator);
insertBack(init[]);
}
/// Ditto.
this(R)(R init, shared Allocator allocator = defaultAllocator)
this(R)(auto in ref R init, shared Allocator allocator = defaultAllocator)
if (!isInfinite!R
&& isInputRange!R
&& isImplicitlyConvertible!(ElementType!R, T))
@ -348,12 +323,78 @@ struct Vector(T)
insertBack(init);
}
/**
* Initializes this vector from another one.
*
* If $(D_PARAM init) is passed by value, it won't be copied, but moved
* If the allocator of ($D_PARAM init) matches $(D_PARAM allocator),
* $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s
* storage, otherwise, the storage will be allocated with
* $(D_PARAM allocator) and all elements will be moved;
* $(D_PARAM init) will be destroyed at the end.
*
* If $(D_PARAM init) is passed by reference, it will be copied.
*
* Params:
* init = Source vector.
* allocator = Allocator.
*/
this(ref Vector init, shared Allocator allocator = defaultAllocator) @trusted
{
this(allocator);
insertBack(init[]);
}
/// Ditto.
this(Vector init, shared Allocator allocator = defaultAllocator) @trusted
{
if (allocator is init.allocator)
{
// Just steal all references and the allocator.
this(init.allocator);
vector = init.vector;
length_ = init.length_;
capacity_ = init.capacity_;
// Reset the source vector, so it can't destroy the moved storage.
init.length_ = init.capacity_ = 0;
init.vector = null;
}
else
{
// Move each element.
this(allocator);
reserve(init.length);
const T* end = vector + init.length;
for (T* src = init.vector, dest = vector; dest != end; ++src, ++dest)
{
moveEmplace(*src, *dest);
}
length_ = init.length;
// Destructor of init should destroy it here.
}
}
///
@nogc @safe unittest
{
auto v1 = Vector!int(IL(1, 2, 3));
auto v2 = Vector!int(v1);
assert(v1.vector !is v2.vector);
assert(v1 == v2);
auto v3 = Vector!int(Vector!int(IL(1, 2, 3)));
assert(v1 == v3);
assert(v3.length == 3);
assert(v3.capacity == 3);
}
/**
* Creates a new $(D_PSYMBOL Vector).
*
* Params:
* len = Initial length of the vector.
* init = Initial value to fill the vector with.
* allocator = Allocator.
*/
this(size_t len, shared Allocator allocator = defaultAllocator) @trusted
@ -365,11 +406,19 @@ struct Vector(T)
{
return;
}
initialize(len);
capacity_ = length_ = len;
reserve(len);
initializeAll(vector[0 .. len]);
length_ = len;
}
/// Ditto.
/**
* Creates a new $(D_PSYMBOL Vector).
*
* Params:
* len = Initial length of the vector.
* init = Initial value to fill the vector with.
* allocator = Allocator.
*/
this(size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted
{
this(allocator);
@ -379,9 +428,9 @@ struct Vector(T)
{
return;
}
initialize(len);
capacity_ = length_ = len;
opSliceAssign(init, 0, length_);
reserve(len);
uninitializedFill(vector[0 .. len], init);
length_ = len;
}
/// Ditto.
@ -415,6 +464,11 @@ struct Vector(T)
assert(v[0] == 5 && v[1] == 5 && v[2] == 5);
}
@safe unittest
{
auto v1 = Vector!int(defaultAllocator);
}
/**
* Destroys this $(D_PSYMBOL Vector).
*/
@ -497,7 +551,8 @@ struct Vector(T)
}
else if (len > length_)
{
initialize(len);
reserve(len);
initializeAll(vector[length_ .. len]);
}
else
{
@ -543,13 +598,39 @@ struct Vector(T)
*/
void reserve(in size_t size) @trusted
{
if (capacity_ < size)
if (capacity_ >= size)
{
void[] buf = vector[0 .. capacity_];
allocator.reallocate(buf, size * T.sizeof);
vector = cast(T*) buf;
capacity_ = size;
return;
}
bool overflow;
immutable byteSize = mulu(size, T.sizeof, overflow);
assert(!overflow);
void[] buf = vector[0 .. capacity_];
if (!allocator.expand(buf, byteSize))
{
buf = allocator.allocate(byteSize);
if (buf is null)
{
onOutOfMemoryErrorNoGC();
}
scope (failure)
{
allocator.deallocate(buf);
}
const T* end = vector + length_;
for (T* src = vector, dest = cast(T*) buf; src != end; ++src, ++dest)
{
moveEmplace(*src, *dest);
static if (hasElaborateDestructor!T)
{
destroy(*src);
}
}
allocator.deallocate(vector[0 .. capacity_]);
vector = cast(T*) buf;
}
capacity_ = size;
}
///
@ -712,10 +793,6 @@ struct Vector(T)
if (allSatisfy!(ApplyRight!(isImplicitlyConvertible, T), R))
{
reserve(length_ + el.length);
if (capacity_ <= length_)
{
onOutOfMemoryError();
}
foreach (i; el)
{
emplace(vector + length_, i);
@ -732,11 +809,11 @@ struct Vector(T)
{
immutable rLen = walkLength(el);
initialize(length_ + rLen);
reserve(length_ + rLen);
T* pos = vector + length_;
foreach (e; el)
{
*pos = e;
emplace(pos, e);
++length_, ++pos;
}
return rLen;
@ -892,7 +969,6 @@ struct Vector(T)
* Comparison for equality.
*
* Params:
* R = Right hand side type.
* v = The vector to compare with.
*
* Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false)
@ -952,7 +1028,16 @@ struct Vector(T)
return true;
}
/// Ditto.
/**
* Comparison for equality.
*
* Params:
* R = Right hand side type.
* v = The vector to compare with.
*
* Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false)
* otherwise.
*/
bool opEquals(R)(Range!R v) const @trusted
if (is(Unqual!R == T))
{
@ -1239,14 +1324,14 @@ struct Vector(T)
* Slicing assignment.
*
* Params:
* value = New value.
* value = New value (single value or input range).
* i = Slice start.
* j = Slice end.
*
* Returns: Slice with the assigned part of the vector.
*
* Precondition: $(D_INLINECODE i <= j && j <= length);
* The lenghts of the ranges and slices match.
* The lenghts of the range and slice match.
*/
Range!T opSliceAssign(ref T value, in size_t i, in size_t j) @trusted
in
@ -1256,11 +1341,7 @@ struct Vector(T)
}
body
{
const T* end = vector + j;
for (T* v = vector + i; v != end; ++v)
{
*v = value;
}
vector[i .. j].fill(value);
return opSlice(i, j);
}
@ -1277,6 +1358,8 @@ struct Vector(T)
&& isImplicitlyConvertible!(ElementType!R, T))
in
{
assert(i <= j);
assert(j <= length);
assert(j - i == walkLength(value));
}
body
@ -1291,8 +1374,7 @@ struct Vector(T)
/// Ditto.
Range!T opSliceAssign(R)(R value, in size_t i, in size_t j)
if ((isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
|| is(R == Vector))
if (isStaticArray!R && isImplicitlyConvertible!(ElementType!R, T))
{
return opSliceAssign(value[], i, j);
}

View File

@ -36,7 +36,7 @@ else version (Windows)
* block as free and only if all blocks in the region are free, the complete
* region is deallocated.
*
* ----------------------------------------------------------------------------
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* | | | | | || | | |
* | |prev <----------- | || | | |
* | R | B | | B | || R | B | |
@ -46,7 +46,7 @@ else version (Windows)
* | O | K | | K | prev O | K | |
* | N | -----------> next| || N | | |
* | | | | | || | | |
* --------------------------------------------------- ------------------------
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
final class MmapPool : Allocator
{

View File

@ -374,9 +374,10 @@ private unittest
* object).
*
* Params:
* T = Type of the constructed object.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* args = Constructor arguments of $(D_PARAM T).
* T = Type of the constructed object.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*/

View File

@ -812,7 +812,7 @@ abstract class Socket
* Params:
* level = Protocol level at that the option exists.
* option = Option.
* result = Option value.
* value = Option value.
*
* Throws: $(D_PSYMBOL SocketException) on error.
*/

View File

@ -630,32 +630,31 @@ static this()
/**
* A Unique Resource Locator.
*/
struct URL(U = string)
if (isSomeString!U)
struct URL
{
/** The URL scheme. */
U scheme;
const(char)[] scheme;
/** The username. */
U user;
const(char)[] user;
/** The password. */
U pass;
const(char)[] pass;
/** The hostname. */
U host;
const(char)[] host;
/** The port number. */
ushort port;
/** The path. */
U path;
const(char)[] path;
/** The query string. */
U query;
const(char)[] query;
/** The anchor. */
U fragment;
const(char)[] fragment;
/**
* Attempts to parse an URL from a string.
@ -666,7 +665,7 @@ struct URL(U = string)
*
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
*/
this(U source)
this(in char[] source)
{
auto value = source;
ptrdiff_t pos = -1, endPos = value.length, start;
@ -954,7 +953,7 @@ struct URL(U = string)
*
* Returns: Whether the port could be found.
*/
private bool parsePort(U port) pure nothrow @safe @nogc
private bool parsePort(in char[] port) pure nothrow @safe @nogc
{
ptrdiff_t i = 1;
float lPort = 0;
@ -984,14 +983,14 @@ struct URL(U = string)
///
unittest
{
auto u = URL!()("example.org");
auto u = URL("example.org");
assert(u.path == "example.org");
u = URL!()("relative/path");
u = URL("relative/path");
assert(u.path == "relative/path");
// Host and scheme
u = URL!()("https://example.org");
u = URL("https://example.org");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path is null);
@ -999,7 +998,7 @@ unittest
assert(u.fragment is null);
// With user and port and path
u = URL!()("https://hilary:putnam@example.org:443/foo/bar");
u = URL("https://hilary:putnam@example.org:443/foo/bar");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/foo/bar");
@ -1009,7 +1008,7 @@ unittest
assert(u.fragment is null);
// With query string
u = URL!()("https://example.org/?login=true");
u = URL("https://example.org/?login=true");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
@ -1017,14 +1016,14 @@ unittest
assert(u.fragment is null);
// With query string and fragment
u = URL!()("https://example.org/?login=false#label");
u = URL("https://example.org/?login=false#label");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
assert(u.query == "login=false");
assert(u.fragment == "label");
u = URL!()("redis://root:password@localhost:2201/path?query=value#fragment");
u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
assert(u.scheme == "redis");
assert(u.user == "root");
assert(u.pass == "password");
@ -1043,7 +1042,7 @@ private unittest
{
try
{
URL!()(t[0]);
URL(t[0]);
assert(0);
}
catch (URIException e)
@ -1053,7 +1052,7 @@ private unittest
}
else
{
auto u = URL!()(t[0]);
auto u = URL(t[0]);
assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
t[0]);
assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
@ -1100,31 +1099,30 @@ enum Component : string
*
* Returns: Requested URL components.
*/
URL parseURL(U)(in U source)
if (isSomeString!U)
URL parseURL(typeof(null) T)(in char[] source)
{
return URL!U(source);
return URL(source);
}
/// Ditto.
string parseURL(string T, U)(in U source)
if ((T == "scheme"
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
if (T == "scheme"
|| T =="host"
|| T == "user"
|| T == "pass"
|| T == "path"
|| T == "query"
|| T == "fragment") && isSomeString!U)
|| T == "fragment")
{
auto ret = URL!U(source);
auto ret = URL(source);
return mixin("ret." ~ T);
}
/// Ditto.
ushort parseURL(string T, U)(in U source)
if (T == "port" && isSomeString!U)
ushort parseURL(immutable(char)[] T)(in char[] source)
if (T == "port")
{
auto ret = URL!U(source);
auto ret = URL(source);
return ret.port;
}
@ -1158,7 +1156,7 @@ private unittest
else
{
ushort port = parseURL!(Component.port)(t[0]);
string component = parseURL!(Component.scheme)(t[0]);
auto component = parseURL!(Component.scheme)(t[0]);
assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
t[0]);
component = parseURL!(Component.user)(t[0]);