-
- // 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;
-
-
- import std.traits : hasMember, hasIndirections;
-
- 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.
- ///
- /// Allocations are non-GC and are damped by a free-list based on the nodes
- /// that are removed. Note that elements lifetime must be managed
- /// outside.
- struct Queue(T)
- if (!hasMember!(T, "__xdtor"))
- {
-
- private:
-
- // Linked list node containing one element and pointer to the next node.
- struct Node
- {
- T payload_;
- Node* next_;
- }
-
- // Start of the linked list - first element added in time (end of the queue).
- Node* first_;
- // Last element of the linked list - last element added in time (start of the queue).
- Node* last_;
- // free-list
- Node* stock;
-
- // Length of the queue.
- size_t length_;
-
- // allocate a new node or recycle one from the stock.
- Node* makeNewNode(T thePayload, Node* theNext = null) @trusted nothrow @nogc
- {
- import std.experimental.allocator : make;
- import std.experimental.allocator.mallocator : Mallocator;
-
- Node* result;
- if (stock !is null)
- {
- result = stock;
- stock = result.next_;
- result.payload_ = thePayload;
- result.next_ = theNext;
- }
- else
- {
- result = Mallocator.instance.make!(Node)(thePayload, theNext);
- // GC can dispose T managed member if it thinks they are no used...
- static if (hasIndirections!T)
- {
- import core.memory : GC;
- GC.addRange(result, Node.sizeof);
- }
- }
- return result;
- }
-
- // free the stock of available free nodes.
- void freeStock() @trusted @nogc nothrow
- {
- import std.experimental.allocator.mallocator : Mallocator;
-
- while (stock !is null)
- {
- Node* toFree = stock;
- stock = stock.next_;
- static if (hasIndirections!T)
- {
- import core.memory : GC;
- GC.removeRange(toFree);
- }
- Mallocator.instance.deallocate((cast(ubyte*) toFree)[0 .. Node.sizeof]);
- }
- }
-
- public:
-
- @disable void opAssign(ref Queue);
- @disable bool opEquals(ref Queue);
- @disable int opCmp(ref Queue);
-
- this(this) @safe nothrow @nogc
- {
- auto node = first_;
- first_ = null;
- last_ = null;
- while (node !is null)
- {
- Node* newLast = makeNewNode(node.payload_);
- if (last_ !is null)
- last_.next_ = newLast;
- if (first_ is null)
- first_ = newLast;
- last_ = newLast;
- node = node.next_;
- }
- }
-
- ~this() @safe nothrow @nogc
- {
- freeStock();
- stock = first_;
- freeStock();
- }
-
- /// Returns a forward range iterating over this queue.
- auto range() @safe pure nothrow @nogc
- {
- static struct Result
- {
- private Node* cursor;
-
- void popFront() @safe pure nothrow @nogc
- {
- cursor = cursor.next_;
- }
- ref T front() @safe pure nothrow @nogc
- in(cursor !is null)
- {
- return cursor.payload_;
- }
- bool empty() @safe pure nothrow @nogc const
- {
- return cursor is null;
- }
- }
- return Result(first_);
- }
-
- /// Push a new item to the queue.
- void push(T item) @nogc @safe nothrow
- {
- Node* newLast = makeNewNode(item);
- 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) @safe nothrow
- in
- {
- assert(idx <= length_);
- }
- do
- {
- if (idx == 0)
- {
- first_ = makeNewNode(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_;
-
- assert(current);
- // Insert a new node after current, and put current.next_ behind it.
- current.next_ = makeNewNode(item, current.next_);
- ++length_;
- }
- }
-
- /// Returns: The next element in the queue and remove it.
- T pop() @safe nothrow
- in
- {
- assert(!empty, "Trying to pop an element from an empty queue");
- }
- do
- {
- T result = peek();
-
- Node* oldStock = stock;
- Node* old = first_;
- first_ = first_.next_;
-
- // start the stock from the popped element
- stock = old;
- old.next_ = null;
- // add the existing "old" stock to the new first stock element
- if (oldStock !is null)
- stock.next_ = oldStock;
-
- if (--length_ == 0)
- {
- assert(first_ is null);
- last_ = null;
- }
-
- return result;
- }
-
- /// Returns: 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");
- }
- do
- {
- return first_.payload_;
- }
-
- /// Returns: true of the queue empty, false otherwise.
- bool empty() @safe pure nothrow const @nogc
- {
- return first_ is null;
- }
-
- /// Returns: The number of elements in the queue.
- size_t length() @safe pure nothrow const @nogc
- {
- return length_;
- }
- }
-
- @safe nothrow 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);
- }