Moved monster files into the openmw repos
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@12 ea6a568a-9f4f-0410-981a-c910a81bb256actorid
parent
16e6da7d69
commit
888c552f1b
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
Monster - an advanced game scripting language
|
||||||
|
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||||
|
Email: <korslund@gmail.com>
|
||||||
|
WWW: http://monster.snaptoad.com/
|
||||||
|
|
||||||
|
This file (freelist.d) is part of the Monster script language
|
||||||
|
package.
|
||||||
|
|
||||||
|
Monster is distributed as free software: you can redistribute it
|
||||||
|
and/or modify it under the terms of the GNU General Public License
|
||||||
|
version 3, as published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
version 3 along with this program. If not, see
|
||||||
|
http://www.gnu.org/licenses/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
module monster.util.freelist;
|
||||||
|
|
||||||
|
// TODO: Create unittests
|
||||||
|
|
||||||
|
import monster.util.list;
|
||||||
|
import monster.util.growarray;
|
||||||
|
|
||||||
|
// This had to be moved outside FreeList to work around some
|
||||||
|
// irritating DMD template problems. (Can you say Aaargh!)
|
||||||
|
struct __FreeNode(T)
|
||||||
|
{
|
||||||
|
_lstNode!(T) data;
|
||||||
|
int index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A list that uses a freelist for allocation. Based on
|
||||||
|
// LinkedList. Very basic, only functions that are actually in use in
|
||||||
|
// my own code are implemented.
|
||||||
|
struct FreeList(T)
|
||||||
|
{
|
||||||
|
alias LinkedList!(T, NoAlloc) TList;
|
||||||
|
alias TList.Node TNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/*
|
||||||
|
static struct _FreeNode
|
||||||
|
{
|
||||||
|
TNode data;
|
||||||
|
int index;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
alias __FreeNode!(T) _FreeNode;
|
||||||
|
|
||||||
|
// This is the array that does all the actual allocations. It is
|
||||||
|
// used for quickly looking up indices.
|
||||||
|
static GrowArray!(_FreeNode) array;
|
||||||
|
|
||||||
|
// The freelist. This is shared between all template instances of
|
||||||
|
// the same type, as far as I know. DMD might have some strange
|
||||||
|
// behavior that I am not aware of, but the worst case is that we
|
||||||
|
// end up with multiple freelists, which is not the end of the world
|
||||||
|
// (although slightly inefficient.)
|
||||||
|
static TList freeList;
|
||||||
|
|
||||||
|
// The nodes belonging to THIS list
|
||||||
|
TList nodes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Get a new node (move from freelist to node list)
|
||||||
|
T* getNew()
|
||||||
|
{
|
||||||
|
// Is the freelist empty?
|
||||||
|
if(freeList.length == 0)
|
||||||
|
{
|
||||||
|
// Create a bunch of nodes and shove them into the freelist.
|
||||||
|
const makeSize = 100;
|
||||||
|
|
||||||
|
// Grow the growarray
|
||||||
|
uint len = array.length;
|
||||||
|
array.length = len + makeSize;
|
||||||
|
|
||||||
|
// Loop through the new nodes, number them, and insert them
|
||||||
|
// into freeList
|
||||||
|
for(int i=0; i < makeSize; i++)
|
||||||
|
{
|
||||||
|
_FreeNode *fn = array.getPtr(i+len);
|
||||||
|
fn.index = i + len;
|
||||||
|
freeList.insertNode(&fn.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the first element from the freelist into the node list.
|
||||||
|
auto node = freeList.getHead;
|
||||||
|
freeList.removeNode(node);
|
||||||
|
nodes.insertNodeFirst(node);
|
||||||
|
|
||||||
|
// Return the value pointer. Since the value is always at the
|
||||||
|
// begining of the Node struct, this is the same
|
||||||
|
// pointer. LinkedList lets us choose if we want to use T* or
|
||||||
|
// Node*.
|
||||||
|
return &node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the node corresponding to an index
|
||||||
|
static T* getNode(int index)
|
||||||
|
{
|
||||||
|
return &array.getPtr(index).data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the index from a node
|
||||||
|
static int getIndex(T *node)
|
||||||
|
{
|
||||||
|
return ( cast(_FreeNode*)node ).index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move a node back to the freelist ("delete" it)
|
||||||
|
void remove(T* node)
|
||||||
|
{
|
||||||
|
nodes.removeNode(node);
|
||||||
|
freeList.insertNodeFirst(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint length() { return nodes.length; }
|
||||||
|
static uint totLength() { return array.length; }
|
||||||
|
|
||||||
|
// Move the given node to another list
|
||||||
|
T* moveTo(ref FreeList fl, T* node)
|
||||||
|
{
|
||||||
|
nodes.removeNode(node);
|
||||||
|
fl.nodes.insertNodeFirst(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first element in the list
|
||||||
|
T* getHead() { return &nodes.getHead().value; }
|
||||||
|
|
||||||
|
// Loop through the structs in this list
|
||||||
|
int opApply(int delegate(ref T) dg)
|
||||||
|
{
|
||||||
|
return nodes.opApply(dg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
Monster - an advanced game scripting language
|
||||||
|
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||||
|
Email: <korslund@gmail.com>
|
||||||
|
WWW: http://monster.snaptoad.com/
|
||||||
|
|
||||||
|
This file (growarray.d) is part of the Monster script language
|
||||||
|
package.
|
||||||
|
|
||||||
|
Monster is distributed as free software: you can redistribute it
|
||||||
|
and/or modify it under the terms of the GNU General Public License
|
||||||
|
version 3, as published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
version 3 along with this program. If not, see
|
||||||
|
http://www.gnu.org/licenses/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
module monster.util.growarray;
|
||||||
|
|
||||||
|
// Array that grows without reallocations.
|
||||||
|
struct GrowArray(T)
|
||||||
|
{
|
||||||
|
const defSize = 128;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint listSize = defSize; // Size of new lists
|
||||||
|
uint elements; // Current number of elements
|
||||||
|
uint elemsAlloc; // Elements allocated
|
||||||
|
|
||||||
|
uint begin; // At what element to start counting. Used for
|
||||||
|
// slices.
|
||||||
|
|
||||||
|
T[][] listList;
|
||||||
|
|
||||||
|
// Make sure there is room for at least 'size' elements in total.
|
||||||
|
void alloc(uint size)
|
||||||
|
{
|
||||||
|
// Do nothing if the list is large enough
|
||||||
|
if(size <= elemsAlloc) return;
|
||||||
|
|
||||||
|
// If this is a slice, we must always reallocate when
|
||||||
|
// growing. Implement that later.
|
||||||
|
if(begin) assert(0, "Cannot grow a slice");
|
||||||
|
|
||||||
|
// Number of lists we need
|
||||||
|
uint lists = ((size-1) / listSize) + 1;
|
||||||
|
|
||||||
|
// The number of needed elements should never decrease
|
||||||
|
assert((listSize*lists) >= elemsAlloc);
|
||||||
|
|
||||||
|
// Number of elements we need allocated
|
||||||
|
elemsAlloc = listSize * lists;
|
||||||
|
|
||||||
|
// Make sure the list of lists is large enough
|
||||||
|
if(listList.length < lists)
|
||||||
|
listList.length = lists+30;
|
||||||
|
|
||||||
|
// Allocate the lists we need
|
||||||
|
for(int i=0; i<lists; i++)
|
||||||
|
listList[i].length = listSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint length() { return elements - begin; }
|
||||||
|
void length(uint newLen)
|
||||||
|
{
|
||||||
|
newLen += begin;
|
||||||
|
|
||||||
|
alloc(newLen);
|
||||||
|
|
||||||
|
elements = newLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize - set the current size
|
||||||
|
void initialize(uint size = 0, uint listSize = defSize)
|
||||||
|
{
|
||||||
|
assert(listList.length == 0);
|
||||||
|
this.listSize = listSize;
|
||||||
|
length(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GrowArray opCall(uint size = 0, uint listSize = defSize)
|
||||||
|
{
|
||||||
|
GrowArray a;
|
||||||
|
a.initialize(size, listSize);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opCatAssign(T t)
|
||||||
|
{
|
||||||
|
length = length + 1;
|
||||||
|
opIndexAssign(t, length-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opCatAssign(T[] list)
|
||||||
|
{
|
||||||
|
uint len = length;
|
||||||
|
length = len + list.length;
|
||||||
|
foreach(int i, ref T t; list)
|
||||||
|
opIndexAssign(t, len+i);
|
||||||
|
}
|
||||||
|
|
||||||
|
T opIndex(int index)
|
||||||
|
{
|
||||||
|
index += begin;
|
||||||
|
assert(index >= begin && index < elements,
|
||||||
|
"GrowArray index out of bounds");
|
||||||
|
|
||||||
|
return listList[index/listSize][index%listSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
T opIndexAssign(T value, int index)
|
||||||
|
{
|
||||||
|
index += begin;
|
||||||
|
assert(index >= begin && index < elements,
|
||||||
|
"GrowArray index out of bounds");
|
||||||
|
|
||||||
|
return (listList[index/listSize][index%listSize] = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* getPtr(int index)
|
||||||
|
{
|
||||||
|
index += begin;
|
||||||
|
assert(index >= begin && index < elements,
|
||||||
|
"GrowArray index out of bounds");
|
||||||
|
|
||||||
|
return &listList[index/listSize][index%listSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
GrowArray opSlice(int start, int stop)
|
||||||
|
{
|
||||||
|
assert(start<=stop, "Illegal GrowArray slice");
|
||||||
|
GrowArray ga = *this;
|
||||||
|
ga.begin = begin+start;
|
||||||
|
ga.length = stop-start;
|
||||||
|
return ga;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrowArray opSlice()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int opApply(int delegate(ref int, ref T) dg)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
int len = length;
|
||||||
|
int pos = begin%listSize;
|
||||||
|
int list = begin/listSize;
|
||||||
|
for(int i; i<len; i++)
|
||||||
|
{
|
||||||
|
res = dg(i, listList[list][pos++]);
|
||||||
|
if(res) break;
|
||||||
|
|
||||||
|
if(pos == listSize)
|
||||||
|
{
|
||||||
|
list++;
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int opApply(int delegate(ref T) dg)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
int len = length;
|
||||||
|
int pos = begin%listSize;
|
||||||
|
int list = begin/listSize;
|
||||||
|
for(int i; i<len; i++)
|
||||||
|
{
|
||||||
|
res = dg(listList[list][pos++]);
|
||||||
|
if(res) break;
|
||||||
|
|
||||||
|
if(pos == listSize)
|
||||||
|
{
|
||||||
|
list++;
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// Test default initialization
|
||||||
|
GrowArray!(int) arr;
|
||||||
|
assert(arr.begin == 0);
|
||||||
|
assert(arr.length == 0);
|
||||||
|
assert(arr.elements == 0);
|
||||||
|
assert(arr.elemsAlloc == 0);
|
||||||
|
assert(arr.listList.length == 0);
|
||||||
|
assert(arr.listSize == 128);
|
||||||
|
|
||||||
|
// Setting length
|
||||||
|
arr.length = 3;
|
||||||
|
assert(arr.length == 3);
|
||||||
|
assert(arr.elements == 3);
|
||||||
|
assert(arr.elemsAlloc == arr.listSize);
|
||||||
|
assert(arr.listList.length >= 1);
|
||||||
|
|
||||||
|
// Setting and reading elements
|
||||||
|
arr[0] = 1;
|
||||||
|
arr[1] = 2;
|
||||||
|
arr[2] = 3;
|
||||||
|
assert(arr[0] == 1);
|
||||||
|
assert(arr[1] == 2);
|
||||||
|
assert(arr[2] == 3);
|
||||||
|
assert(arr.listList[0][0] == 1);
|
||||||
|
assert(arr.listList[0][1] == 2);
|
||||||
|
assert(arr.listList[0][2] == 3);
|
||||||
|
|
||||||
|
// Test opCatAssign
|
||||||
|
arr ~= 4;
|
||||||
|
assert(arr.length == 4);
|
||||||
|
assert(arr[3] == 4);
|
||||||
|
|
||||||
|
// Foreach
|
||||||
|
int tmp = 0;
|
||||||
|
foreach(int i, int v; arr)
|
||||||
|
{
|
||||||
|
assert(v==i+1);
|
||||||
|
tmp++;
|
||||||
|
}
|
||||||
|
assert(tmp == 4);
|
||||||
|
|
||||||
|
tmp = 1;
|
||||||
|
foreach(int v; arr)
|
||||||
|
assert(v == tmp++);
|
||||||
|
assert(tmp == 5);
|
||||||
|
|
||||||
|
// Slicing the entire array
|
||||||
|
arr = arr[0..4];
|
||||||
|
assert(arr.length == 4);
|
||||||
|
assert(arr[3] == 4);
|
||||||
|
|
||||||
|
// Slicing part of the array
|
||||||
|
auto arrS = arr[1..3];
|
||||||
|
assert(arrS.length == 2);
|
||||||
|
assert(arrS[0] == 2);
|
||||||
|
assert(arrS[1] == 3);
|
||||||
|
arrS[0] = 10;
|
||||||
|
assert(arr[1] == 10);
|
||||||
|
|
||||||
|
// Slicing the slice
|
||||||
|
arrS = arrS[1..2];
|
||||||
|
assert(arrS.length == 1);
|
||||||
|
assert(arrS[0] == 3);
|
||||||
|
|
||||||
|
// Empty slice
|
||||||
|
arrS = arr[3..3];
|
||||||
|
assert(arrS.length == 0);
|
||||||
|
|
||||||
|
// Custom list size, and more than one list
|
||||||
|
auto arr2 = GrowArray!(byte)(3,2);
|
||||||
|
assert(arr2.length == 3);
|
||||||
|
assert(arr2.elements == 3);
|
||||||
|
assert(arr2.listSize == 2);
|
||||||
|
assert(arr2.elemsAlloc == 4);
|
||||||
|
assert(arr2.listList.length >= 2);
|
||||||
|
assert(arr2.listList[0].length == 2);
|
||||||
|
|
||||||
|
assert(arr2[0] == 0);
|
||||||
|
assert(arr2[1] == 0);
|
||||||
|
assert(arr2[2] == 0);
|
||||||
|
|
||||||
|
arr2[1]=2;
|
||||||
|
arr2[2]=4;
|
||||||
|
|
||||||
|
foreach(int i, byte v; arr2)
|
||||||
|
assert(v == 2*i);
|
||||||
|
|
||||||
|
// Check that boundry checking works (in non-release mode.)
|
||||||
|
bool err = false;
|
||||||
|
try{arr2[3];}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
assert(err == true);
|
||||||
|
|
||||||
|
err = false;
|
||||||
|
try{arr2[3] = 0;}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
assert(err == true);
|
||||||
|
}
|
@ -0,0 +1,640 @@
|
|||||||
|
/*
|
||||||
|
Monster - an advanced game scripting language
|
||||||
|
Copyright (C) 2007, 2008 Nicolay Korslund
|
||||||
|
Email: <korslund@gmail.com>
|
||||||
|
WWW: http://monster.snaptoad.com/
|
||||||
|
|
||||||
|
This file (list.d) is part of the Monster script language
|
||||||
|
package.
|
||||||
|
|
||||||
|
Monster is distributed as free software: you can redistribute it
|
||||||
|
and/or modify it under the terms of the GNU General Public License
|
||||||
|
version 3, as published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
version 3 along with this program. If not, see
|
||||||
|
http://www.gnu.org/licenses/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
module monster.util.list;
|
||||||
|
|
||||||
|
// Set this to enable some more extensive list checks. These will loop
|
||||||
|
// through the entire list on every insert and remove, so they are
|
||||||
|
// very slow for large lists. But they are very handy bug catchers
|
||||||
|
// when doing a little dirty list hacking, and they have saved me in
|
||||||
|
// the past.
|
||||||
|
|
||||||
|
// debug=slowcheck;
|
||||||
|
|
||||||
|
private import std.c.stdlib;
|
||||||
|
private import std.string;
|
||||||
|
|
||||||
|
typedef void GCAlloc;
|
||||||
|
|
||||||
|
alias malloc cmalloc;
|
||||||
|
alias free cfree;
|
||||||
|
|
||||||
|
class LinkedListException : Exception
|
||||||
|
{
|
||||||
|
this(char[] msg)
|
||||||
|
{
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal structure used by List
|
||||||
|
*/
|
||||||
|
|
||||||
|
align(1)
|
||||||
|
struct _lstNode(Value)
|
||||||
|
{
|
||||||
|
// It is essential that the value is first in the struct. This
|
||||||
|
// allows us to interchange pointers to the value with pointer to
|
||||||
|
// the Node. This is done for convenience - allowing us to use the
|
||||||
|
// value directly instead of using somePtr.value, This also
|
||||||
|
// sidesteps the fact that DMD isn't very good with template forward
|
||||||
|
// references, something that creates a lot of problems if we use
|
||||||
|
// LinkedList.Iterator for everything (trust me on this.)
|
||||||
|
Value value;
|
||||||
|
|
||||||
|
_lstNode* getNext() { return next; }
|
||||||
|
_lstNode* getPrev() { return prev; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
_lstNode* next; // Next node
|
||||||
|
_lstNode* prev; // Previous node
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a doubly linked list. It's not terribly advanced at the
|
||||||
|
* moment, but I don't need any more functionality right now.
|
||||||
|
*
|
||||||
|
* Alloc must have the following members:
|
||||||
|
* void* alloc(uint size)
|
||||||
|
* void free(void*)
|
||||||
|
* bool autoinit; // True if alloc automatically sets memory to zero.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Example allocator using malloc and free:
|
||||||
|
struct Malloc
|
||||||
|
{
|
||||||
|
static const bool autoinit = false; // malloc does not initialize memory
|
||||||
|
static const bool usefree = true; // We must call free() to release memory
|
||||||
|
static void* alloc(uint size) { return cmalloc(size); }
|
||||||
|
static void free(void* p) { cfree(p); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// A null allocator. Use if you only intend to move nodes into and out
|
||||||
|
// of the list, not to allocate them. Useful for a freelist, for
|
||||||
|
// example.
|
||||||
|
struct NoAlloc
|
||||||
|
{
|
||||||
|
static const bool autoinit = false;
|
||||||
|
static const bool usefree = true;
|
||||||
|
static void *alloc(uint size) { assert(0, "NoAlloc.alloc not allowed"); }
|
||||||
|
static void free(void *p) { assert(0, "NoAlloc.free not allowed"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LinkedList(Value, alias Alloc = GCAlloc)
|
||||||
|
{
|
||||||
|
alias _lstNode!(Value) Node;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Node *head; // This is the head of the linked list (first element)
|
||||||
|
Node *tail; // New nodes are inserted here
|
||||||
|
uint totalNum; // Number of elements
|
||||||
|
|
||||||
|
// Determine if the allocator automatically initializes memory
|
||||||
|
static if(is(Alloc == GCAlloc))
|
||||||
|
static const bool autoinit = true;
|
||||||
|
else static if(Alloc.autoinit)
|
||||||
|
static const bool autoinit = true;
|
||||||
|
else
|
||||||
|
static const bool autoinit = false;
|
||||||
|
|
||||||
|
// Determine if we have to manually free memory
|
||||||
|
static if(is(Alloc == GCAlloc))
|
||||||
|
static const bool usefree = false;
|
||||||
|
else static if(Alloc.usefree)
|
||||||
|
static const bool usefree = true;
|
||||||
|
else
|
||||||
|
static const bool usefree = false;
|
||||||
|
|
||||||
|
// Throw an exception
|
||||||
|
void fail(char[] msg)
|
||||||
|
{
|
||||||
|
msg = format("LinkedList!(%s) exception: %s", typeid(Value).toString, msg);
|
||||||
|
throw new LinkedListException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Whenever you find a bug that creates an invalid state, put it in
|
||||||
|
// here so we can safeguard against regressions
|
||||||
|
|
||||||
|
invariant()
|
||||||
|
{
|
||||||
|
if(head != null || tail != null || totalNum != 0)
|
||||||
|
{
|
||||||
|
assert(head != null);
|
||||||
|
assert(tail != null);
|
||||||
|
assert(totalNum != 0);
|
||||||
|
|
||||||
|
assert(head.prev == null);
|
||||||
|
assert(tail.next == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias Node* Iterator;
|
||||||
|
|
||||||
|
// Simply reset all pointers and variables, losing any nodes
|
||||||
|
// present.
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
head = tail = null;
|
||||||
|
totalNum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the list and delete all nodes
|
||||||
|
void deleteAll()
|
||||||
|
{
|
||||||
|
// If there is no need to free objects, then deleteAll() is
|
||||||
|
// equivalent to reset().
|
||||||
|
static if(usefree)
|
||||||
|
{
|
||||||
|
// Loop through the list and delete everything
|
||||||
|
Node *p = head;
|
||||||
|
while(p != null)
|
||||||
|
{
|
||||||
|
Node *next = p.next;
|
||||||
|
Alloc.free(p);
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator getHead() { return head; }
|
||||||
|
Iterator getTail() { return tail; }
|
||||||
|
|
||||||
|
// Check if the given iterator is part of the list
|
||||||
|
bool hasIterator(Node *v)
|
||||||
|
{
|
||||||
|
Node* p = head;
|
||||||
|
|
||||||
|
while(p != null)
|
||||||
|
{
|
||||||
|
if(p == v)
|
||||||
|
{
|
||||||
|
assert(length >= 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a value at the end of the list.
|
||||||
|
alias insert insertLast;
|
||||||
|
Iterator insert(Value v)
|
||||||
|
{
|
||||||
|
Node *p = createNode();
|
||||||
|
p.value = v;
|
||||||
|
return insertNode(p);
|
||||||
|
}
|
||||||
|
// Also allow ~= syntax for this
|
||||||
|
Iterator opCatAssign(Value v) { return insert(v); }
|
||||||
|
|
||||||
|
// Insert an existing node at the end of the list. The insertNode*()
|
||||||
|
// variants along with removeNode() are useful for removing and
|
||||||
|
// reinserting nodes without allocating more memory. This can for
|
||||||
|
// example be used for free lists and similar constructions. In
|
||||||
|
// other words, you can use these to move elements from one list to
|
||||||
|
// another.
|
||||||
|
alias insertNode insertNodeLast;
|
||||||
|
Iterator insertNode(Node *p)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
//debug(slowcheck)
|
||||||
|
assert(!hasIterator(p), "inserNode: Node is already in the list");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if(tail)
|
||||||
|
{
|
||||||
|
// Insert node at the end of the list
|
||||||
|
assert(head != null);
|
||||||
|
tail.next = p;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is the first element to be inserted
|
||||||
|
assert(head == null);
|
||||||
|
head = p;
|
||||||
|
}
|
||||||
|
p.prev = tail;
|
||||||
|
tail = p;
|
||||||
|
p.next = null;
|
||||||
|
|
||||||
|
totalNum++;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
// The Value* variants of the node functions work the same way as
|
||||||
|
// their Iterator (Node*) versions. The pointers are the same, they
|
||||||
|
// just need to be recast.
|
||||||
|
Value* insertNode(Value *p)
|
||||||
|
{ return &insertNode( cast(Node*)p ).value; }
|
||||||
|
|
||||||
|
// Beginning of the list
|
||||||
|
Iterator insertFirst(Value v)
|
||||||
|
{
|
||||||
|
Node *p = createNode();
|
||||||
|
p.value = v;
|
||||||
|
return insertNodeFirst(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator insertNodeFirst(Node *p)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
debug(slowcheck)
|
||||||
|
assert(!hasIterator(p), "inserNodeFirst: Node is already in the list");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if(head)
|
||||||
|
{
|
||||||
|
// Insert node at the beginning of the list
|
||||||
|
assert(tail != null);
|
||||||
|
head.prev = p;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is the first element to be inserted
|
||||||
|
assert(tail == null);
|
||||||
|
tail = p;
|
||||||
|
}
|
||||||
|
p.next = head;
|
||||||
|
head = p;
|
||||||
|
p.prev = null;
|
||||||
|
|
||||||
|
totalNum++;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
Value* insertNodeFirst(Value *p)
|
||||||
|
{ return &insertNodeFirst( cast(Node*)p ).value; }
|
||||||
|
|
||||||
|
// Insert after a given element
|
||||||
|
Iterator insertAfter(Iterator i, Value v)
|
||||||
|
{
|
||||||
|
Node *p = createNode();
|
||||||
|
p.value = v;
|
||||||
|
return insertNodeAfter(i, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert p after i
|
||||||
|
Iterator insertNodeAfter(Iterator i, Node *p)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
//debug(slowcheck)
|
||||||
|
{
|
||||||
|
assert(!hasIterator(p), "inserNodeAfter: Node is already in the list");
|
||||||
|
assert(hasIterator(i), "insertNodeAfter(): element i not part of the list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
// If i is the last element, then insertNodeLast already does a
|
||||||
|
// stellar job of inserting
|
||||||
|
if(i == tail)
|
||||||
|
return insertNodeLast(p);
|
||||||
|
|
||||||
|
// Make p point to the right elements
|
||||||
|
p.next = i.next;
|
||||||
|
p.prev = i;
|
||||||
|
|
||||||
|
// Random consistency check
|
||||||
|
assert(i == i.next.prev);
|
||||||
|
|
||||||
|
// Make the right elements point to p
|
||||||
|
i.next = p;
|
||||||
|
assert(p.next != null);
|
||||||
|
p.next.prev = p;
|
||||||
|
|
||||||
|
totalNum++;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
// Insert p after i
|
||||||
|
Value* insertNodeAfter(Value* p, Value* i)
|
||||||
|
{ return &insertNodeAfter( cast(Node*)p, cast(Node*)i ).value; }
|
||||||
|
|
||||||
|
|
||||||
|
// Insert value v before i
|
||||||
|
Iterator insertBefore(Iterator i, Value v)
|
||||||
|
{
|
||||||
|
Node *p = createNode();
|
||||||
|
p.value = v;
|
||||||
|
return insertNodeBefore(i, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert p before i
|
||||||
|
Iterator insertNodeBefore(Iterator i, Node *p)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
//debug(slowcheck)
|
||||||
|
{
|
||||||
|
assert(!hasIterator(p), "inserNodeBefore: Node is already in the list");
|
||||||
|
assert(hasIterator(i), "insertBefore(): element not part of the list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
// If i is the first, just insert at the beginning
|
||||||
|
if(i==head) return insertNodeFirst(p);
|
||||||
|
|
||||||
|
// If I mess it up, an assertion failure is easier to debug than
|
||||||
|
// a segfault.
|
||||||
|
assert(i.prev != null);
|
||||||
|
|
||||||
|
// Reuse insertAfter instead of reinventing the wheel
|
||||||
|
return insertNodeAfter(i.prev, p);
|
||||||
|
}
|
||||||
|
// Insert p before i
|
||||||
|
Value* insertNodeBefore(Value* p, Value* i)
|
||||||
|
{ return &insertNodeBefore( cast(Node*)p, cast(Node*)i ).value; }
|
||||||
|
|
||||||
|
|
||||||
|
// Swap position of element a and b
|
||||||
|
void swap(Iterator a, Iterator b)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
//debug(slowcheck)
|
||||||
|
assert(hasIterator(a) && hasIterator(b),
|
||||||
|
"swap(a,b): both elements must be in the list");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
Iterator tmp;
|
||||||
|
|
||||||
|
// Handle special cases first
|
||||||
|
|
||||||
|
// The same element? Do nothing.
|
||||||
|
if(a==b) return;
|
||||||
|
|
||||||
|
// Are they next to each other?
|
||||||
|
if(b.next == a)
|
||||||
|
{
|
||||||
|
// Swap it so we have a before b, then handle it below.
|
||||||
|
assert(a.prev == b);
|
||||||
|
tmp = a;
|
||||||
|
a = b;
|
||||||
|
b = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point a.prev to b
|
||||||
|
if(a.prev) a.prev.next = b;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(head == a);
|
||||||
|
head = b;
|
||||||
|
}
|
||||||
|
// Point to b.next a
|
||||||
|
if(b.next) b.next.prev = a;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(tail == b);
|
||||||
|
tail = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From this point on, if a is next to b it must be handled as a
|
||||||
|
// special case. We have already swapped them above so that a is
|
||||||
|
// before b.
|
||||||
|
if(a.next == b)
|
||||||
|
{
|
||||||
|
assert(b.prev == a);
|
||||||
|
|
||||||
|
// Assign outer pointers
|
||||||
|
b.prev = a.prev;
|
||||||
|
a.next = b.next;
|
||||||
|
|
||||||
|
// Assign inner pointers
|
||||||
|
a.prev = b;
|
||||||
|
b.next = a;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a is NOT next to b, continue the pointer orgy.
|
||||||
|
|
||||||
|
// Point a.next to b
|
||||||
|
if(a.next) a.next.prev = b;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(tail == a);
|
||||||
|
tail = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(b.prev) b.prev.next = a;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(head == b);
|
||||||
|
head = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, swap a and b's internal pointers
|
||||||
|
tmp = a.next;
|
||||||
|
a.next = b.next;
|
||||||
|
b.next = tmp;
|
||||||
|
|
||||||
|
tmp = a.prev;
|
||||||
|
a.prev = b.prev;
|
||||||
|
b.prev = tmp;
|
||||||
|
}
|
||||||
|
void swap(Value* a, Value* b)
|
||||||
|
{ swap( cast(Node*)a, cast(Node*)b ); }
|
||||||
|
|
||||||
|
// Remove a node from the list and delete it
|
||||||
|
void remove(Iterator p)
|
||||||
|
{
|
||||||
|
removeNode(p);
|
||||||
|
deleteNode(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just remove the node from the list, do not delete it.
|
||||||
|
void removeNode(Iterator p)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
//debug(slowcheck)
|
||||||
|
assert(hasIterator(p), "remove(): element not part of the list");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
// Remove from the list
|
||||||
|
if(p.next)
|
||||||
|
{
|
||||||
|
p.next.prev = p.prev;
|
||||||
|
|
||||||
|
// Make sure we are NOT tail
|
||||||
|
assert(tail != p);
|
||||||
|
}
|
||||||
|
else // We're the tail
|
||||||
|
{
|
||||||
|
assert(tail == p);
|
||||||
|
tail = p.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(p.prev)
|
||||||
|
{
|
||||||
|
p.prev.next = p.next;
|
||||||
|
|
||||||
|
// We are NOT the head, since we have a previous element
|
||||||
|
assert(head != p);
|
||||||
|
}
|
||||||
|
else // We're head
|
||||||
|
{
|
||||||
|
assert(head == p);
|
||||||
|
head = p.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalNum--;
|
||||||
|
}
|
||||||
|
void removeNode(Value *v)
|
||||||
|
{ removeNode( cast(Iterator)v ); }
|
||||||
|
|
||||||
|
// Free a node
|
||||||
|
static private void deleteNode(Node *p)
|
||||||
|
{
|
||||||
|
// For the GC, just release the
|
||||||
|
// pointer into the wild.
|
||||||
|
static if(usefree) Alloc.free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new node and return it's pointer. TODO: Make this
|
||||||
|
// static, and increase totalNum in the insert methods instead.
|
||||||
|
static private Node* createNode()
|
||||||
|
{
|
||||||
|
static if(is(Alloc == GCAlloc))
|
||||||
|
Node *p = new Node;
|
||||||
|
else
|
||||||
|
Node *p = cast(Node*)Alloc.alloc(Node.sizeof);
|
||||||
|
|
||||||
|
// Initialize next pointers
|
||||||
|
static if(!autoinit)
|
||||||
|
{
|
||||||
|
p.next = null;
|
||||||
|
p.prev = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the nodes in the order they were inserted
|
||||||
|
int opApply(int delegate(ref Value v) del)
|
||||||
|
{
|
||||||
|
Node *p = head;
|
||||||
|
uint safeGuard = 0;
|
||||||
|
while(p != null)
|
||||||
|
{
|
||||||
|
assert(safeGuard++ < totalNum);
|
||||||
|
int i = del(p.value);
|
||||||
|
if(i) return i;
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the nodes in the order they were inserted
|
||||||
|
int opApply(int delegate(ref int ind, ref Value v) del)
|
||||||
|
{
|
||||||
|
Node *p = head;
|
||||||
|
int ind = 0;
|
||||||
|
while(p != null)
|
||||||
|
{
|
||||||
|
assert(ind < totalNum);
|
||||||
|
int i = del(ind, p.value);
|
||||||
|
ind++;
|
||||||
|
if(i) return i;
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of elements
|
||||||
|
uint length() { return totalNum; }
|
||||||
|
|
||||||
|
char[] toString()
|
||||||
|
{
|
||||||
|
char[] res = "[";
|
||||||
|
foreach(int i, Value v; *this)
|
||||||
|
{
|
||||||
|
if(i < totalNum-1) res ~= format(" %s,", v);
|
||||||
|
else res ~= format(" %s ]", v);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This test is NOT very complete */
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
LinkedList!(float) ll;
|
||||||
|
|
||||||
|
assert(ll.length == 0);
|
||||||
|
ll.Iterator it = ll.insert(10.4);
|
||||||
|
assert(ll.length == 1);
|
||||||
|
ll.insert(23);
|
||||||
|
it = ll.insert(6.3);
|
||||||
|
ll.insert(-1000);
|
||||||
|
|
||||||
|
assert(ll.length == 4);
|
||||||
|
|
||||||
|
//foreach(float f; ll) writefln(f);
|
||||||
|
|
||||||
|
ll.remove(it);
|
||||||
|
|
||||||
|
assert(ll.length == 3);
|
||||||
|
|
||||||
|
ll.reset();
|
||||||
|
|
||||||
|
assert(ll.length == 0);
|
||||||
|
|
||||||
|
//foreach(int i, float f; ll) writefln(i, " ", f);
|
||||||
|
}
|
||||||
|
//import std.stdio;
|
||||||
|
|
||||||
|
// Array allocator. TODO: Put this and Malloc in their own place,
|
||||||
|
// extend list to be the same quality as aa.d and make a system out of
|
||||||
|
// it. Make some better unit tests.
|
||||||
|
struct ArrAlloc
|
||||||
|
{
|
||||||
|
ubyte[] data;
|
||||||
|
uint pos;
|
||||||
|
|
||||||
|
void reset() { pos = 0; }
|
||||||
|
|
||||||
|
const bool autoinit = false;
|
||||||
|
const bool usefree = false;
|
||||||
|
|
||||||
|
void* alloc(uint size)
|
||||||
|
{
|
||||||
|
if(pos+size > data.length)
|
||||||
|
data.length = pos+size+30;
|
||||||
|
|
||||||
|
void * ptr = &data[pos];
|
||||||
|
|
||||||
|
pos += size;
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(void* p) { }
|
||||||
|
}
|
@ -0,0 +1,305 @@
|
|||||||
|
/*
|
||||||
|
Monster - an advanced game scripting language
|
||||||
|
Copyright (C) 2004, 2007, 2008 Nicolay Korslund
|
||||||
|
Email: <korslund@gmail.com>
|
||||||
|
WWW: http://monster.snaptoad.com/
|
||||||
|
|
||||||
|
This file (string.d) is part of the Monster script language
|
||||||
|
package.
|
||||||
|
|
||||||
|
Monster is distributed as free software: you can redistribute it
|
||||||
|
and/or modify it under the terms of the GNU General Public License
|
||||||
|
version 3, as published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
version 3 along with this program. If not, see
|
||||||
|
http://www.gnu.org/licenses/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
module monster.util.string;
|
||||||
|
|
||||||
|
import std.utf;
|
||||||
|
import std.string;
|
||||||
|
|
||||||
|
bool begins(char[] str, char[] start)
|
||||||
|
{
|
||||||
|
if(str.length < start.length ||
|
||||||
|
str[0..start.length] != start) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert("heia".begins(""));
|
||||||
|
assert("heia".begins("h"));
|
||||||
|
assert("heia".begins("he"));
|
||||||
|
assert(!("heia".begins("H")));
|
||||||
|
assert(!("heia".begins("hE")));
|
||||||
|
assert("heia".begins("hei"));
|
||||||
|
assert("heia".begins("heia"));
|
||||||
|
assert(!("heia".begins("heia ")));
|
||||||
|
assert(!("heia".begins(" heia")));
|
||||||
|
assert(!("heia".begins("eia")));
|
||||||
|
|
||||||
|
assert(!("h".begins("ha")));
|
||||||
|
assert(!("h".begins("ah")));
|
||||||
|
assert(!("".begins("ah")));
|
||||||
|
assert("".begins(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ends(char[] str, char[] end)
|
||||||
|
{
|
||||||
|
if(str.length < end.length ||
|
||||||
|
str[$-end.length..$] != end) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert("heia".ends(""));
|
||||||
|
assert(!("heia".ends("h")));
|
||||||
|
assert("heia".ends("a"));
|
||||||
|
assert("heia".ends("ia"));
|
||||||
|
assert(!("heia".ends("A")));
|
||||||
|
assert(!("heia".ends("Ia")));
|
||||||
|
assert("heia".ends("eia"));
|
||||||
|
assert("heia".ends("heia"));
|
||||||
|
assert(!("heia".ends("heia ")));
|
||||||
|
assert(!("heia".ends(" heia")));
|
||||||
|
assert(!("heia".ends("hei")));
|
||||||
|
|
||||||
|
assert(!("h".ends("ha")));
|
||||||
|
assert(!("h".ends("ah")));
|
||||||
|
assert(!("".ends("ah")));
|
||||||
|
assert("".ends(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case insensitive version of begins()
|
||||||
|
bool iBegins(char[] str, char[] start)
|
||||||
|
{
|
||||||
|
if(str.length < start.length ||
|
||||||
|
icmp(str[0..start.length], start) != 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert("heia".iBegins(""));
|
||||||
|
assert("heia".iBegins("H"));
|
||||||
|
assert("heia".iBegins("hE"));
|
||||||
|
assert("heia".iBegins("hei"));
|
||||||
|
assert("HeIa".iBegins("hei"));
|
||||||
|
assert("heia".iBegins("heia"));
|
||||||
|
assert("hEia".iBegins("heiA"));
|
||||||
|
assert(!("heia".iBegins("heia ")));
|
||||||
|
assert(!("heIa".iBegins("heia ")));
|
||||||
|
assert(!("heia".iBegins("eia")));
|
||||||
|
|
||||||
|
assert(!("h".iBegins("ha")));
|
||||||
|
assert(!("h".iBegins("ah")));
|
||||||
|
assert(!("".iBegins("ah")));
|
||||||
|
assert("".iBegins(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case insensitive version of begins()
|
||||||
|
bool iEnds(char[] str, char[] end)
|
||||||
|
{
|
||||||
|
if(str.length < end.length ||
|
||||||
|
icmp(str[$-end.length..$], end) != 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert("heia".iEnds(""));
|
||||||
|
assert(!("heia".iEnds("h")));
|
||||||
|
assert("heia".iEnds("a"));
|
||||||
|
assert("heia".iEnds("ia"));
|
||||||
|
assert("heia".iEnds("A"));
|
||||||
|
assert("heia".iEnds("Ia"));
|
||||||
|
assert("heia".iEnds("EiA"));
|
||||||
|
assert("he ia".iEnds("HE IA"));
|
||||||
|
assert("heia".iEnds("eia"));
|
||||||
|
assert("heia".iEnds("heia"));
|
||||||
|
assert(!("heia".iEnds("heia ")));
|
||||||
|
assert(!("heia".iEnds(" heia")));
|
||||||
|
assert(!("heia".iEnds("hei")));
|
||||||
|
|
||||||
|
assert(!("h".iEnds("ha")));
|
||||||
|
assert(!("h".iEnds("ah")));
|
||||||
|
assert(!("".iEnds("ah")));
|
||||||
|
assert("".iEnds(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts any string to valid UTF8 so it can be safely printed. It
|
||||||
|
// does not translate from other encodings but simply replaces invalid
|
||||||
|
// characters with 'replace'. Does everything in place.
|
||||||
|
char[] makeUTF8(char[] str, char replace = '?')
|
||||||
|
{
|
||||||
|
size_t idx = 0;
|
||||||
|
while(idx < str.length)
|
||||||
|
{
|
||||||
|
try decode(str, idx);
|
||||||
|
catch(UtfException ue)
|
||||||
|
str[idx++] = replace;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] nextWord(ref char[] str, char delim = ' ')
|
||||||
|
{
|
||||||
|
int i = find(str, delim);
|
||||||
|
char[] result;
|
||||||
|
|
||||||
|
// No 'delim' found, return the entire string and set remainder to
|
||||||
|
// null.
|
||||||
|
if(i == -1)
|
||||||
|
{
|
||||||
|
result = str;
|
||||||
|
str = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A separator was found. Return everything upto 'delim' (index i),
|
||||||
|
// put the remainder of the string (not including the char at [i])
|
||||||
|
// in str.
|
||||||
|
result = str[0..i];
|
||||||
|
str = str[i+1..$];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
char[] test = "bjarne betjent er betent";
|
||||||
|
assert(nextWord(test) == "bjarne");
|
||||||
|
assert(test == "betjent er betent");
|
||||||
|
assert(nextWord(test) == "betjent");
|
||||||
|
assert(nextWord(test) == "er");
|
||||||
|
assert(test == "betent");
|
||||||
|
assert(nextWord(test) == "betent");
|
||||||
|
assert(test == "");
|
||||||
|
assert(nextWord(test) == "");
|
||||||
|
|
||||||
|
test = ";;foo;bar;";
|
||||||
|
assert(nextWord(test,';') == "");
|
||||||
|
assert(nextWord(test,';') == "");
|
||||||
|
assert(nextWord(test,';') == "foo");
|
||||||
|
assert(nextWord(test,';') == "bar");
|
||||||
|
assert(nextWord(test,';') == "");
|
||||||
|
assert(nextWord(test,';') == "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// An 'object oriented' interface to nextWord
|
||||||
|
class NextWord
|
||||||
|
{
|
||||||
|
char delim;
|
||||||
|
char[] str;
|
||||||
|
|
||||||
|
this(char[] str, char delim = ' ')
|
||||||
|
{
|
||||||
|
this.delim = delim;
|
||||||
|
this.str = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(char delim = ' ')
|
||||||
|
{ this.delim = delim; }
|
||||||
|
|
||||||
|
char[] next()
|
||||||
|
{ return nextWord(str, delim); }
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto n = new NextWord(";;foo;bar;",';');
|
||||||
|
assert(n.next == "");
|
||||||
|
assert(n.next == "");
|
||||||
|
assert(n.next == "foo");
|
||||||
|
assert(n.next == "bar");
|
||||||
|
assert(n.next == "");
|
||||||
|
assert(n.next == "");
|
||||||
|
n.str = "a;bc";
|
||||||
|
assert(n.next == "a");
|
||||||
|
assert(n.next == "bc");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip trailing zeros
|
||||||
|
char[] stripz(char [] s)
|
||||||
|
{
|
||||||
|
foreach(int i, char c; s)
|
||||||
|
if( c == 0 )
|
||||||
|
return s[0..i];
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert(stripz(" a b c ") == " a b c ");
|
||||||
|
char[8] str;
|
||||||
|
str[] = 0;
|
||||||
|
assert(stripz(str) == "");
|
||||||
|
str[2] = 'o';
|
||||||
|
assert(stripz(str) == "");
|
||||||
|
str[0] = 'f';
|
||||||
|
str[3] = 'd';
|
||||||
|
assert(stripz(str) == "f");
|
||||||
|
str[1] = 'o';
|
||||||
|
assert(stripz(str) == "food");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a long integer into a string using nice comma
|
||||||
|
// formatting. delim is the delimiter character, size is the number of
|
||||||
|
// digits in each group. See the unittest for examples.
|
||||||
|
char[] comma(long i, char delim=',', int size = 3)
|
||||||
|
{
|
||||||
|
char[] str = toString(i);
|
||||||
|
char[] res;
|
||||||
|
|
||||||
|
if(i<0) str=str[1..$];
|
||||||
|
|
||||||
|
str.reverse;
|
||||||
|
foreach(int j, char c; str)
|
||||||
|
{
|
||||||
|
if(j!=0 && j%size == 0)
|
||||||
|
res = delim ~ res;
|
||||||
|
res = c ~ res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i<0) res = "-" ~ res;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
//_________________
|
||||||
|
// Inkas ___ \_
|
||||||
|
// were here / \ \
|
||||||
|
assert(comma(1) == "1");
|
||||||
|
assert(comma(12) == "12");
|
||||||
|
assert(comma(123) == "123");
|
||||||
|
assert(comma(1234) == "1,234");
|
||||||
|
assert(comma(12345) == "12,345");
|
||||||
|
assert(comma(123456) == "123,456");
|
||||||
|
assert(comma(1234567) == "1,234,567");
|
||||||
|
assert(comma(12345678) == "12,345,678");
|
||||||
|
|
||||||
|
// Negative values
|
||||||
|
assert(comma(-1) == "-1");
|
||||||
|
assert(comma(-12) == "-12");
|
||||||
|
assert(comma(-123) == "-123");
|
||||||
|
assert(comma(-1234) == "-1,234");
|
||||||
|
assert(comma(-12345) == "-12,345");
|
||||||
|
|
||||||
|
// Different delimiter
|
||||||
|
assert(comma(-888888888888,'-') == "-888-888-888-888");
|
||||||
|
|
||||||
|
// Different size
|
||||||
|
assert(comma(1111111111,'.',4) == "11.1111.1111");
|
||||||
|
}
|
Loading…
Reference in New Issue