dyaml/source/dyaml/queue.d
2018-04-09 22:57:14 -03:00

254 lines
6.9 KiB
D

// Copyright Ferdinand Majerech 2011-2014.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module dyaml.queue;
/// Queue collection.
import core.stdc.stdlib;
import core.memory;
import std.container;
import std.traits;
package:
/// Simple queue implemented as a singly linked list with a tail pointer.
///
/// Needed in some D:YAML code that needs a queue-like structure without too much
/// reallocation that goes with an array.
///
/// This should be replaced once Phobos has a decent queue/linked list.
///
/// Uses manual allocation through malloc/free.
///
/// Also has some features uncommon for a queue, e.g. iteration. Couldn't bother with
/// implementing a range, as this is used only as a placeholder until Phobos gets a
/// decent replacement.
struct Queue(T)
if(!hasMember!(T, "__dtor"))
{
private:
/// Linked list node containing one element and pointer to the next node.
struct Node
{
T payload_;
Node* next_ = null;
}
/// Start of the linked list - first element added in time (end of the queue).
Node* first_ = null;
/// Last element of the linked list - last element added in time (start of the queue).
Node* last_ = null;
/// Cursor pointing to the current node in iteration.
Node* cursor_ = null;
/// The first element of a linked list of freed Nodes available for recycling.
Node* freeList_ = null;
/// Length of the queue.
size_t length_ = 0;
public:
@disable void opAssign(ref Queue);
@disable bool opEquals(ref Queue);
@disable int opCmp(ref Queue);
/// Destroy the queue, deallocating all its elements.
@trusted nothrow ~this()
{
while(!empty) { pop(); }
while(freeList_ !is null)
{
auto toFree = freeList_;
freeList_ = toFree.next_;
free(toFree);
}
cursor_ = last_ = first_ = null;
length_ = 0;
}
/// Start iterating over the queue.
void startIteration() @safe pure nothrow @nogc
{
cursor_ = first_;
}
/// Get next element in the queue.
ref const(T) next() @safe pure nothrow @nogc
in
{
assert(!empty);
assert(cursor_ !is null);
}
body
{
const previous = cursor_;
cursor_ = cursor_.next_;
return previous.payload_;
}
/// Are we done iterating?
bool iterationOver() @safe pure nothrow const @nogc
{
return cursor_ is null;
}
/// Push new item to the queue.
void push(T item) @trusted nothrow
{
Node* newLast = newNode(item, null);
if(last_ !is null) { last_.next_ = newLast; }
if(first_ is null) { first_ = newLast; }
last_ = newLast;
++length_;
}
/// Insert a new item putting it to specified index in the linked list.
void insert(T item, const size_t idx) @trusted nothrow
in
{
assert(idx <= length_);
}
body
{
if(idx == 0)
{
first_ = newNode(item, first_);
++length_;
}
// Adding before last added element, so we can just push.
else if(idx == length_) { push(item); }
else
{
// Get the element before one we're inserting.
Node* current = first_;
foreach(i; 1 .. idx) { current = current.next_; }
// Insert a new node after current, and put current.next_ behind it.
current.next_ = newNode(item, current.next_);
++length_;
}
}
/// Return the next element in the queue and remove it.
T pop() @trusted nothrow
in
{
assert(!empty, "Trying to pop an element from an empty queue");
}
body
{
T result = peek();
Node* popped = first_;
first_ = first_.next_;
Node* oldFree = freeList_;
freeList_ = popped;
freeList_.next_ = oldFree;
if(--length_ == 0)
{
assert(first_ is null);
last_ = null;
}
return result;
}
/// Return the next element in the queue.
ref inout(T) peek() @safe pure nothrow inout @nogc
in
{
assert(!empty, "Trying to peek at an element in an empty queue");
}
body
{
return first_.payload_;
}
/// Is the queue empty?
bool empty() @safe pure nothrow const @nogc
{
return first_ is null;
}
/// Return number of elements in the queue.
size_t length() @safe pure nothrow const @nogc
{
return length_;
}
private:
/// Get a new (or recycled) node with specified item and next node pointer.
///
/// Tries to reuse a node from freeList_, allocates a new node if not possible.
Node* newNode(ref T item, Node* next) @trusted nothrow
{
if(freeList_ !is null)
{
auto node = freeList_;
freeList_ = freeList_.next_;
*node = Node(item, next);
return node;
}
return allocate!Node(item, next);
}
}
private:
/// Allocate a struct, passing arguments to its constructor or default initializer.
T* allocate(T, Args...)(Args args) @system nothrow
{
T* ptr = cast(T*)malloc(T.sizeof);
*ptr = T(args);
// The struct might contain references to GC-allocated memory, so tell the GC about it.
static if(hasIndirections!T) { GC.addRange(cast(void*)ptr, T.sizeof); }
return ptr;
}
/// Deallocate struct pointed at by specified pointer.
void free(T)(T* ptr) @system nothrow
{
// GC doesn't need to care about any references in this struct anymore.
static if(hasIndirections!T) { GC.removeRange(cast(void*)ptr); }
core.stdc.stdlib.free(ptr);
}
@safe unittest
{
auto queue = Queue!int();
assert(queue.empty);
foreach(i; 0 .. 65)
{
queue.push(5);
assert(queue.pop() == 5);
assert(queue.empty);
assert(queue.length_ == 0);
}
int[] array = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5];
foreach(i; array)
{
queue.push(i);
}
array = 42 ~ array[0 .. 3] ~ 42 ~ array[3 .. $] ~ 42;
queue.insert(42, 3);
queue.insert(42, 0);
queue.insert(42, queue.length);
int[] array2;
while(!queue.empty)
{
array2 ~= queue.pop();
}
assert(array == array2);
}