From 888c552f1bdf38bd98fa989543aa3aa21badbe15 Mon Sep 17 00:00:00 2001 From: nkorslund Date: Wed, 9 Jul 2008 13:13:55 +0000 Subject: [PATCH] Moved monster files into the openmw repos git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@12 ea6a568a-9f4f-0410-981a-c910a81bb256 --- monster/util/aa.d | 1016 ++++++++++++++++++++++++++++++++++++++ monster/util/freelist.d | 147 ++++++ monster/util/growarray.d | 297 +++++++++++ monster/util/list.d | 640 ++++++++++++++++++++++++ monster/util/string.d | 305 ++++++++++++ 5 files changed, 2405 insertions(+) create mode 100644 monster/util/aa.d create mode 100644 monster/util/freelist.d create mode 100644 monster/util/growarray.d create mode 100644 monster/util/list.d create mode 100644 monster/util/string.d diff --git a/monster/util/aa.d b/monster/util/aa.d new file mode 100644 index 000000000..90b8f6c10 --- /dev/null +++ b/monster/util/aa.d @@ -0,0 +1,1016 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + WWW: http://monster.snaptoad.com/ + + This file (aa.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.aa; + +private import std.string; +private import std.c.stdlib; + +alias malloc cmalloc; +alias free cfree; + +typedef void GCAlloc; +typedef void DefHash; + +class HashTableException : Exception +{ + this(char[] msg) + { + super(msg); + } +} + +/* + * Internal structure used by HashTable + */ + +struct _aaNode(Key, Value) +{ + uint hash; // Hash of this value + Key key; + Value value; + + _aaNode* next; // Next node in current bucket + _aaNode* prev; // Previous node + + _aaNode* nextCont; // Next node in the entire container + _aaNode* prevCont; // Previous node +} + +/* + * This hash table is actually a doubly linked list with added lookup + * capabilities. I could rewrite this to use LinkedList. + * + * The key type is assumed to be inexpensive to copy. For large key + * types use pointers and a custom hasher. Large value types will work + * fine, but you should probably use inList, getPtr and insertEdit for + * most lookup operations, since these return pointers instead of by + * value. + * + * 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 void* alloc(uint size) { return std.c.stdlib.malloc(size); } + * static void free(void* p) { std.c.stdlib.free(p); } + * } + * + * + * Hash must contain: + * uint hash(Key k) + * int isEqual(Key a, Key b) + * + * Example integer hasher that ignores sign (ie. treats n and -n as + * the same number): + * + * struct IgnoreSign + * { + * static uint hash(int i) { return i<0 ? -i : i; } + * static int isEqual(int i, int j) { return (i==j) || (i==-j); } + * } + * + * The keyFormat is used when constructing error messages. If set to + * true, it is assumed that format("%s", key) will work. If set to + * false, no attempt is made at converting the key to a string. + * + * Notice that the Hash functions are type specific, while the Alloc + * ones are not. + */ + +struct HashTable(Key, Value, Alloc = GCAlloc, Hash = DefHash, + bool keyFormat = true) +{ + private: + alias _aaNode!(Key, Value) Node; + + Node *head; // Nodes form a linked list, this is the head. + Node *tail; // New nodes are inserted here + + Node* array[]; // The hash table array + + uint bitmask; // The array size is a power of two, we perform a + // bitwise AND between the hash and this bitmask to + // look up in the array. + + uint totalNum;// Number of nodes currently in container + + // What size to choose if the user doesn't specify one. + static const int defaultSize = 500; + + // Max allowed array size. 25 bits corresponds to about 34 million + // entries. + static const int maxBit = 25; + static const uint maxSize = 1 << maxBit; + + // Minimum allowed size. 6 bits corresponds to 64 entries. + static const int minBit = 6; + + // Assumed maximum bucket size, used as a safeguard against infinite + // loops. + static const int bucketMax = 100000; + + // 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; + + // Throw an exception + void fail(char[] msg) + { + msg = format("HashTable!(%s,%s) exception: %s", + typeid(Key).toString, typeid(Value).toString, msg); + throw new HashTableException(msg); + } + + void fail(char[] msg, Key k) + { + static if(keyFormat) fail(format(msg, k)); + // Ignore key if we cannot display it + else fail(format(msg, "(unshowable)")); + } + + public: + + void reset() + { + *this = typeof(*this).init; + } + + // Returns number of buckets of each size. Mostly used for testing + // efficiency of hash functions. + int[] getStats() + { + int[] res; + + foreach(Node *n; array) + { + // Get bucket size + uint size = 0; + while(n != null) + { + size++; + n = n.next; + } + + if(size >= res.length) res.length = size+1; + + // Add this bucket size to the stat + res[size]++; + } + return res; + } + //*/ + + // Loop through all values and check that the stored hash values are + // correct. Mostly used for debugging. + // + // For example, a common problem is to use a string as a key, and + // then change the string afterwards. The HashTable does not store + // the string contents (only the reference char[]), so if you change + // the string contents later then there will be a mismatch between + // the key and the hashed value. This function will detect the + // problem. + void validate() + { + Node *p = head; + uint safeGuard = 0, hsh; + while(p != null) + { + assert(safeGuard++ < totalNum); + + static if(is(Hash == DefHash)) hsh = typeid(Key).getHash(&p.key); + else hsh = Hash.hash(p.key); + + if(hsh != p.hash) + fail("Validation failed");// on key %s", p.key); + + p = p.nextCont; + } + } + + // Look up a value. If found return true and set v to value, + // otherwise return false. + bool inList(Key k, ref Value v) + { + Node *p = lookupKey(k); + if(p) v = p.value; + return p != null; + } + + // Look up a value. If found return true, otherwise false. + bool inList(Key k) { return lookup(k) != null; } + + // Look up a value. If found return pointer, otherwise null. + Value* lookup(Key k) + { + Node *p = lookupKey(k); + if(p) return &p.value; + return null; + } + + // Get a value, throw an exception if it does not exist + Value get(Key k) + { + Node *p = lookupKey(k); + if(!p) fail("Cannot get key '%s', not found", k); + return p.value; + } + Value opIndex(Key k) { return get(k); } + + // Get a pointer to a value, throw an exception if it does not exist + Value* getPtr(Key k) + { + Node *p = lookupKey(k); + if(!p) fail("Cannot get key '%s', not found", k); + return &p.value; + } + + // Get a value, insert a new if it doesn't exist. This is handy for + // situations where the inserted values might or might not exist, + // and you don't want to overwrite the existing value. + Value get(Key k, Value def) + { + Node *p; + uint hash; + if(!lookupKey(k,hash,p)) + { + // Insert a node + p = insertNode(hash,p); + p.key = k; + p.value = def; + } + + return p.value; + } + Value opIndex(Key k, Value v) { return get(k, v); } + + // Get a value. If it doesn't exist, insert a new one and call + // apply(). Used in the same situation as get(Key, Value) but when + // setting up the new value is expensive or otherwise to be avoided + // when not needed. Handy for classes or when you wish to do + // in-place editing. See also insertEdit. + Value get(Key k, void delegate(ref Value v) apply) + { + Node *p; + uint hash; + if(!lookupKey(k,hash,p)) + { + // Insert a node and call apply + p = insertNode(hash,p); + p.key = k; + apply(p.value); + } + + return p.value; + } + + // Insert a new value, replace it if it already exists. Returns v. + Value insert(Key k, Value v) + { + Node *p; + uint hash; + if(lookupKey(k, hash, p)) + // Key already existed, overwrite value. + p.value = v; + else + { + // Insert a new node + p = insertNode(hash, p); + p.key = k; + p.value = v; + } + + return v; + } + Value opIndexAssign(Value v, Key k) { return insert(k, v); } + + // Get a pointer to value of given key, or insert a new. Useful for + // large value types when you want to avoid copying the value around + // needlessly. Also useful if you want to do in-place + // editing. Returns true if a value was inserted. + bool insertEdit(Key k, out Value *ptr) + { + Node *p; + uint hash; + if(!lookupKey(k,hash,p)) + { + // Insert it + p = insertNode(hash,p); + p.key = k; + ptr = &p.value; + return true; + } + ptr = &p.value; + return false; + } + + // TODO: No unittests for *ifReplace and replace yet + + // Insert a value. Return true if it replaced an existing value, + // otherwise false. + bool ifReplace(Key k, Value v) + { + Node *p; + uint hash; + if(!lookupKey(k,hash,p)) + { + // Insert node + p = insertNode(hash,p); + p.key = k; + p.value = v; + return false; // We did not replace anything, return false + } + // A node was already found + p.value = v; + return true; // We replaced a value + } + + // Replace an existing value with v, return the old value. Fail if + // no value existed. + Value replace(Key k, Value v) + { + Node *p = lookupKey(k); + if(p) + { + Value old = p.value; + p.value = v; + return old; + } + fail("Failed to replace key '%s', not found", k); + } + + // Remove key, return value. If key does not exist, throw an + // exception. + Value remove(Key k) + { + Value v; + if(!ifRemove(k, v)) + fail("Cannot remove key '%s', key not found", k); + + return v; + } + + // Remove a key if it exists. If it was removed, return true, + // otherwise false. + bool ifRemove(Key k) + { + Value v; + return ifRemove(k, v); + } + + // Remove a key if it exists. If it existed, returns true and places + // removed value in v. If not, return false; + bool ifRemove(Key k, ref Value v) + { + Node *p = lookupKey(k); + if(!p) return false; + + v = p.value; + removeNode(p); + return true; + } + + // Loop through the nodes in the order they were inserted + int opApply(int delegate(ref Key k, ref Value v) del) + { + Node *p = head; + uint safeGuard = 0; + while(p != null) + { + assert(safeGuard++ < totalNum); + int i = del(p.key, p.value); + if(i) return i; + p = p.nextCont; + } + return 0; + } + + // Same as above, but do not take Key as a parameter. + 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.nextCont; + } + return 0; + } + + // Number of elements + uint length() { return totalNum; } + + // Table size + uint tabSize() { return array.length; } + + // Rehash the array, ie. resize the array and reinsert the + // members. The parameter gives the requested number of elements. If + // it is zero (or omitted), current number of elements is used + // instead. This function never shrinks the array, use rehashShrink + // for that. + void rehash(uint size = 0) + { + if(size == 0) size = totalNum; + + // Do we need a rehash? + if(size > array.length) + // Let's do it. + rehashShrink(size); + } + + // Same as rehash, but allows the array to shrink + void rehashShrink(uint size = 0) + { + if(size == 0) size = totalNum; + + killArray(); + allocArray(size); + + Node *p = head; + uint safeGuard = 0; + while(p != null) + { + assert(safeGuard++ < totalNum); + rehashNode(p); + p = p.nextCont; + } + } + + // Place a node in the table. Assumes p.hash to be set. Does no + // equality check and does not touch the main linked list. + private void rehashNode(Node *p) + { + Node *n = array[p.hash & bitmask]; + int safeGuard = 0; + + p.next = null; + //p.top = p.bottom = null; + + if(n == null) + { + // We're all alone in this cold empty bucket + array[p.hash & bitmask] = p; + p.prev = null; + return; + } + + // Bucket is occupied + + // Find the last element + while(n.next != null) + { + n = n.next; + assert(safeGuard++ < bucketMax); + } + + // Place p at the end of the list + p.prev = n; + assert(n.next == null); + n.next = p; + } + + private void removeNode(Node *p) + { + // Remove from bucket + if(p.next) p.next.prev = p.prev; + if(p.prev) p.prev.next = p.next; + else // We're at the start of the bucket, remove from hash table + array[p.hash & bitmask] = p.next; + + // Remove from main list + if(p.nextCont) p.nextCont.prevCont = p.prevCont; + else // We're the tail + { + assert(tail == p); + tail = p.prevCont; + } + if(p.prevCont) p.prevCont.nextCont = p.nextCont; + else // We're head + { + assert(head == p); + head = p.nextCont; + } + + // Finally, delete the node. For the GC, just release the + // pointer into the wild. + static if(!is(Alloc == GCAlloc)) Alloc.free(p); + + totalNum--; + } + + // Allocate a new node and inserts it into the main list. Places it + // in the hash array as a successor of the given parent node. If + // parent is null, insert directly into the array using the given + // hash. Returns the newly inserted node. + private Node *insertNode(uint hash, Node *parent) + { + // First, make the new node. + static if(is(Alloc == GCAlloc)) + Node *p = new Node; + else + Node *p = cast(Node*)Alloc.alloc(Node.sizeof); + + p.hash = hash; + + // Is the bucket already occupied? + if(parent) + { + parent.next = p; + p.prev = parent; + } + else + { + // Nope, start our very own bucket. + assert(array[hash & bitmask] == null); + array[hash & bitmask] = p; + static if(!autoinit) p.prev = null; + } + + // Initialize pointers + static if(!autoinit) + { + p.next = null; + p.nextCont = null; + } + + // Insert node into the main linked list + if(tail) + { + assert(head != null); + tail.nextCont = p; + p.prevCont = tail; + } + else + { + // This is the first element to be inserted + assert(head == null); + head = p; + static if(!autoinit) p.prevCont = null; + } + + tail = p; + + totalNum++; + + // I thiiiink it's time to rehash + if(totalNum > 5*array.length) + rehash(); + + return p; + } + + /* Looks up k in the hash table. Returns Node pointer if found, null + * otherwise. This is identical in function to the other lookupKey + * below, except that it returns less information and uses + * marginally fewer instructions. + */ + private Node* lookupKey(Key k) + { + // Check if the array is initialized. + if(!array.length) return null; + + static if(is(Hash == DefHash)) + // Use TypeInfo.getHash to hash the key + uint hsh = typeid(Key).getHash(&k); + else + // Use provided hash function + uint hsh = Hash.hash(k); + + // Look up in the table + Node* p = array[hsh & bitmask]; + + // Search the bucket + int safeGuard = 0; + while(p) + { + assert(safeGuard++ < bucketMax); + + // First check if the hashes match + if(hsh == p.hash) + { + // They did, check if this is the correct one + static if(is(Hash == DefHash)) + { + // Use type specific compare + // This doesn't call opEquals. Fixed. + //if(!typeid(Key).compare(&k, &p.key)) return p; + if(k == p.key) return p; + } + else + { + // Use supplied compare + if(Hash.isEqual(k, p.key)) return p; + } + } + + // Next element + p = p.next; + } + + // End of the list, no node found + return null; + } + + /* Looks up k in the hash table. This is used internally in + * conjuntion with insertNode(). It has three outcomes: + * + * 1 - Key was found. Returns true. Ptr points to corresponding + * Node. + * + * 2 - Key was not found, but bucket is not empty. Returns + * false. Ptr points to last node in bucket list. + * + * 3 - Key was not found, bucket is empty. Returns false, ptr is + * null. + * + * Hash is in any case set to the correct hashed value of k. + */ + private bool lookupKey(Key k, ref uint hash, ref Node* ptr) + { + // Make sure the array is initialized. We do this here instead + // of in insertNode, because this function is always called + // first anyway. + if(!array.length) allocArray(defaultSize); + + static if(is(Hash == DefHash)) + // Use TypeInfo.getHash to hash the key + uint hsh = typeid(Key).getHash(&k); + else + // Use provided hash function + uint hsh = Hash.hash(k); + + // Return the value + hash = hsh; + + // Look up in the table + Node* p = array[hsh & bitmask]; + + // Bucket is empty + if(p == null) + { + ptr = null; + return false; + } + + // Search the bucket + int safeGuard = 0; + while(true) + { + assert(safeGuard++ < bucketMax); + + // First check if the hashes match + if(hsh == p.hash) + { + // They did, check if this is the One True Node + static if(is(Hash == DefHash)) + { + // Use type specific compare + //if(!typeid(Key).compare(&k, &p.key)) + if(k == p.key) + { + ptr = p; + return true; + } + } + else + { + // Use supplied compare + if(Hash.isEqual(k, p.key)) + { + ptr = p; + return true; + } + } + } + + // Break of at the end of the list + if(p.next == null) break; + + p = p.next; + } + + // Node was not found + ptr = p; + return false; + } + + // Deallocates the hash table. Does not touch the nodes. + private void killArray() + { + if(array == null) return; + + static if(!is(Alloc == GCAlloc)) + Alloc.free(array.ptr); + + // With the GC we just leave it dangeling in the wind. + array = null; + } + + // Allocates a new hash array, the old pointer is overwritten. The + // parameter gives the requested number of elements, the actual + // size will most likely be larger, since we must use a power of 2. + private void allocArray(uint number) + out + { + assert( array.length == bitmask+1 ); + assert( array.length != 0 ); + } + body + { + if(number > maxSize) number = maxSize; + + for(uint highBit = minBit; highBit < maxBit+1; highBit++) + { + uint len = 1 << highBit; + if(number < len) + { + static if(is(Alloc == GCAlloc)) array = new Node*[len]; + else + { + Node** p = cast(Node**)Alloc.alloc(len*(Node*).sizeof); + array = p[0..len]; + + // Initialize memory if we have to + static if(!Alloc.autoinit) + array[] = null; + } + bitmask = len-1; // Set all bits below highBit. + return; + } + } + assert(0); + } +} + +// Allocator using Malloc +struct Malloc +{ + static const bool autoinit = false; // malloc does not initialize memory + static void* alloc(uint size) { return cmalloc(size); } + static void free(void* p) { cfree(p); } +} + +// Simple but fast hash function for strings. Speed-wise on par with +// using the default hasher DefHash. Both are faster than the DMD +// built-in AAs. Use this as a base if you need to make special +// variations, such as the CITextHash hasher below. +struct SimpleTextHash +{ + static int isEqual(char[] a, char[] b) + { return !typeid(char[]).compare(&a, &b); } + + static uint hash(char[] s) + { + uint hash; + foreach (char c; s) hash = (hash * 37) + c; + return hash; + } +} + +// Case insensitive hash function, almost as fast as the above. +struct CITextHash +{ + static const char conv = 'a'-'A'; + + static int isEqual(char[] a, char[] b) + { return !icmp(a,b); } + + static uint hash(char[] s) + { + uint hash; + foreach (char c; s) + { + if(c <= 'Z' && c >= 'A') c += conv; + hash = (hash * 37) + c; + } + return hash; + } +} + +unittest +{ + // Test some basic template instants + alias HashTable!(int, int, Malloc) II; + alias HashTable!(char[], int, GCAlloc, SimpleTextHash) CI; + alias HashTable!(II,CI) COMPLEX; + + CI ci; + + int i = ci.tabSize(); + ci.rehash(); + assert(ci.tabSize == i); // A rehash should not do anything for so + // few values. + ci.rehashShrink(); + assert(ci.tabSize != i); // But it does if we allow it to shring + + // Lookup before the list is created + assert(!ci.inList("abcd", i)); + + // Assign some values and test + ci["Hei"] = 10; + ci["Hopp,"] = -34; + assert(ci.insert("Kall2", 5) == 5); + assert((ci["Kall2"] = 20) == 20); // Overwrite value + assert(ci.length == 3); + + // Test inList + assert(ci.inList("Hei")); + assert(ci.inList("Hopp,",i)); + assert(i == -34); + assert(!ci.inList("hei")); + + // Test default values + assert(ci["Kall2"] == 20); + assert(ci["Kall2", 123] == 20); + assert(ci["aa", 13] == 13); + assert(ci["aa", 31] == 13); + assert(ci["aa"] == 13); + + // Get a pointer + int *ip; + int *ip2; + assert(ci.insertEdit("bb", ip) == true); + *ip = 3; + assert(ci.insertEdit("bb", ip2) == false); + assert(ip == ip2); + *ip2 = 4; + assert(ci["bb"] == 4); + + // opApply + assert(ci.length == 5); + const char[][] str = ["Hei", "Hopp,", "Kall2", "aa", "bb"]; + const int[] ia = [10, -34, 20, 13, 4]; + i = 0; + foreach(char[] key, int val; ci) + { + assert(key == str[i]); + assert(val == ia[i]); + i++; + } + + ci.rehash(1000); + assert(ci.tabSize > 1000); + + // Remove elements + assert(!ci.ifRemove("arne")); // Remove something that never was there + + assert(ci.ifRemove("Hei", i)); // Remove from head + assert(i == 10); + assert(!ci.ifRemove("Hei")); + + assert(ci.remove("bb") == 4); // Remove from the tail + assert(!ci.ifRemove("bb")); + + ci.remove("Kall2"); // Remove from the middle + assert(!ci.inList("Kall2")); + + assert(ci.length == 2); + i = 1; + foreach(char[] key, int val; ci) + { + assert(key == str[i]); + assert(val == ia[i]); + i+=2; + } + + // Test for exceptions + i = 0; + try ci["hei"]; + catch(HashTableException e) + { + i = -1; + } + assert(i == -1); + + try ci.remove("bb"); + catch(HashTableException e) + { + i = -5; + } + assert(i == -5); + ci.validate(); + + // Insert more elements than the array length, to force collisions + char[] s = (cast(char*)&i)[0..4]; + for(i = 1; i + 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); + } +} diff --git a/monster/util/growarray.d b/monster/util/growarray.d new file mode 100644 index 000000000..b3ae52e07 --- /dev/null +++ b/monster/util/growarray.d @@ -0,0 +1,297 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + 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= 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= 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); +} diff --git a/monster/util/list.d b/monster/util/list.d new file mode 100644 index 000000000..0e0e9ae00 --- /dev/null +++ b/monster/util/list.d @@ -0,0 +1,640 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + 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) { } +} diff --git a/monster/util/string.d b/monster/util/string.d new file mode 100644 index 000000000..0dded5359 --- /dev/null +++ b/monster/util/string.d @@ -0,0 +1,305 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2004, 2007, 2008 Nicolay Korslund + Email: + 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"); +}