You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/old_d_version/monster/vm/arrays.d

370 lines
10 KiB
D

/*
Monster - an advanced game scripting language
Copyright (C) 2007-2009 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (arrays.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.vm.arrays;
import monster.vm.stack;
import monster.util.freelist;
import monster.util.flags;
import monster.vm.error;
import monster.vm.mobject;
import std.string;
import std.uni;
import std.stdio;
import std.utf;
// An index to an array. Array indices may be 0, unlike object indices
// which span from 1 and upwards, and has 0 as the illegal 'null'
// reference. A null array will always refer to an empty array,
// because we insert an empty array at the first slot in the index
// list.
typedef int AIndex;
// Not all of these are used yet.
enum AFlags : int
{
None = 0x00,
Alive = 0x01, // This reference is not deleted
Const = 0x02, // Constant data
CanCollect = 0x04, // Can be colleted by the GC
RefCounted = 0x08, // Is reference counted
Marked = 0x10, // Was marked in the last GC sweep
Null = 0x20, // Is the null array
}
struct ArrayRef
{
union
{
int[] iarr;
float[] farr;
dchar[] carr;
AIndex[] aarr;
}
Flags!(AFlags) flags;
uint elemSize; // Size of each element (in ints)
AIndex getIndex()
{
return cast(AIndex)( Arrays.ArrayList.getIndex(this) );
}
// Array length, in terms of its element size
uint length()
{
if(isNull) return 0;
assert(elemSize != 0, "elemSize not set");
assert(iarr.length % elemSize == 0, "array length not divisible by element size");
return iarr.length / elemSize;
}
bool isAlive() { return flags.has(AFlags.Alive); }
bool isConst() { return flags.has(AFlags.Const); }
bool isNull() { return flags.has(AFlags.Null); }
}
Arrays arrays;
struct Arrays
{
alias FreeList!(ArrayRef) ArrayList;
private:
ArrayList arrList;
// Get a new array reference
ArrayRef *createArray()
{
ArrayRef *ar = arrList.getNew();
assert(!ar.isAlive);
// Set the "alive" flag
ar.flags.set(AFlags.Alive);
assert(!ar.isNull);
return ar;
}
// Put a reference back into the freelist
void destroyArray(ArrayRef *ar)
{
assert(ar.isAlive);
assert(!ar.isNull);
assert(!ar.isConst);
ar.flags.unset(AFlags.Alive);
arrList.remove(ar);
}
public:
// Set up this struct
void initialize()
{
// Make sure index zero is valid and is an empty array. Set
// more flags later.
auto ar = createArray();
ar.iarr = null;
ar.flags.set(AFlags.Null);
assert(ar.getIndex == 0);
}
// Get the reference to the empty array
ArrayRef *getZero()
{
return getRef(cast(AIndex)0);
}
ArrayRef *createT(T)(T[] data)
{
static if(T.sizeof == 4) return create(cast(int[])data, 1);
else static if(T.sizeof == 8) return create(cast(int[])data, 2);
else static assert(0);
}
alias createT!(int) create;
alias createT!(uint) create;
alias createT!(long) create;
alias createT!(ulong) create;
alias createT!(float) create;
alias createT!(double) create;
alias createT!(dchar) create;
alias createT!(AIndex) create;
alias createT!(MIndex) create;
ArrayRef *create(char[] arg)
{ return create(toUTF32(arg)); }
// Generic element size
ArrayRef *create(int[] data, int size)
{
assert(size > 0);
if(data.length == 0) return getZero();
ArrayRef *ar = createArray();
ar.iarr = data;
ar.elemSize = size;
if(data.length % size != 0)
fail("Array length not divisible by element size");
return ar;
}
ArrayRef *createConst(int[] data, int elem)
{
ArrayRef *arf = create(data, elem);
arf.flags.set(AFlags.Const);
return arf;
}
ArrayRef *getRef(AIndex index)
{
if(index < 0 || index >= getTotalArrays())
fail("Invalid array reference: " ~ toString(cast(int)index));
ArrayRef *arr = ArrayList.getNode(index);
if(!arr.isAlive)
fail("Dead array reference: " ~ toString(cast(int)index));
assert(arr.getIndex() == index);
if(index == 0) assert(arr.iarr.length == 0);
return arr;
}
// Get the number of array references in use
int getArrays()
{
return arrList.length();
}
// Get the total number of Array references ever allocated for the
// free list.
int getTotalArrays()
{
return ArrayList.totLength();
}
}
// Create a multi-dimensional array of rank 'rank' and innermost data
// initialized to 'initval'. The array lengths are popped of the
// script stack.
void createMultiDimArray(int rank, int init[])
{
if(rank <= 0 || rank >= 30)
fail("Invalid array nesting number " ~ toString(rank));
assert(init.length > 0);
int[30] lenbuf;
int[] lens = lenbuf[0..rank];
int[] data; // All the elements + overhead data
ulong totElem = 1; // Total number of elements. Set to 1 and
// multiplied with the length later.
ulong totSize = 0; // Total size of data to allocate
int[] currSlice; // Current slice of the data, used by getNext.
// Get the next 'count' ints of data, in the form of a newly created
// ArrayRef.
ArrayRef *getNext(int count, int elemSize=1)
{
assert(count <= currSlice.length);
int[] res = currSlice[0..count];
currSlice = currSlice[count..$];
return arrays.create(res, elemSize);
}
// Get the lengths, and calculate how much data we need. The first
// length is the outermost wrapper, the last is the number of
// actual elements in the innermost array wrapper.
foreach(int i, ref int len; lens)
{
len = stack.popInt();
// Do some sanity check on the length. The upper bound set here
// is pretty arbitrary, we might enlarge it later.
if(len <= 0 || len > 0x100000)
fail("Invalid array length " ~ toString(len));
// We could allow 0-length arrays here, but there's not much
// point really.
// Calculate in the element size in the last element
if(i == lens.length-1) len *= init.length;
// The total data is the cumulative value of totElem through all
// iterations. For example, if we have a k*m*n array, we must
// have k outer arrays, indexing a total of k*m subarrays,
// indexing a total of k*m*n elements. The total data size,
// assuming element sizes have been figured in, is
// k + k*m + k*m*n.
totElem *= len;
totSize += totElem;
}
// Allocate all the elements + overhead (data for the lookup arrays)
if(totSize)
{
assert(totElem >= 0 && totElem <= totSize);
// Let's slap a 10 meg sanity check on the total data size
if(totSize > 10*1024*1024)
fail("Total array size is too large: " ~ toString(totSize));
data.length = totSize;
// Set currSlice to point to the entire data
currSlice = data;
}
// Set up inner arrays recursively. This can be optimized heavily
// later (removing recursion, moving if-tests out of loops, avoiding
// double initialization, and so on.)
void setupArray(int lenIndex, ArrayRef *arr)
{
// Length of arrays at this level
int len = lens[lenIndex];
// Loop through the previous level and create the arrays of this level
foreach(ref AIndex ind; arr.aarr)
{
ArrayRef *narr;
if(lenIndex == rank-1)
// Remember to set the element size on the inner level
narr = getNext(len, init.length);
else
narr = getNext(len);
// Store the index or this array in the previous level
ind = narr.getIndex();
// Is this the innermost level?
if(lenIndex == rank-1)
{
// If so, this is an array of elements. Initialize them.
if(init.length == 1) narr.iarr[] = init[0];
else if(init.length == 2) (cast(long[])narr.iarr)[] = *(cast(long*)init.ptr);
else
for(int i=0; i<lens[0]; i+=init.length)
arr.iarr[i..i+init.length] = init[];
}
else
// If not, set up the indices in this array
setupArray(lenIndex+1, narr);
}
}
if(rank > 1)
{
// Create outer array and push it
ArrayRef *arr = getNext(lens[0]);
stack.pushArray(arr);
// Recursively set up the sub-arrays
setupArray(1, arr);
}
else
{
// Create outer array and push it. Element size has already been
// multiplied into the length.
ArrayRef *arr = getNext(lens[0], init.length);
stack.pushArray(arr);
// There is only one array level, so this IS the inner
// array. Initialize the elements. Optimize for element sizes 1
// and 2
if(init.length == 1) arr.iarr[] = init[0];
else if(init.length == 2) (cast(long[])arr.iarr)[] = *(cast(long*)init.ptr);
else
for(int i=0; i<lens[0]; i+=init.length)
arr.iarr[i..i+init.length] = init[];
}
// Make sure we used all the data!
assert(currSlice.length == 0);
}
// There's no phobos function that does unicode case insensitive
// string comparison, so let's make one ourselves. This can probably
// be optimized. toUniLower is in prinicple an expensive operation,
// but not so much if we assume most characters are ascii.
bool isUniCaseEqual(dchar[] a, dchar[] b)
{
if(a.length != b.length) return false;
foreach(int i, dchar ch; a)
if(ch != b[i] && toUniLower(ch) != toUniLower(b[i]))
return false;
return true;
}