diff --git a/README.html b/README.html index 64ee12de..b0758103 100644 --- a/README.html +++ b/README.html @@ -71,9 +71,10 @@ It also includes miscellaneous improvements over luaj 2.0.x:
  • Better coroutine-related garbage collection.
  • Better debug reporting when using closures.
  • Line numbers in parse syntax tree. +
  • More compatible table behavior.

    Luaj 2.0.x

    -Support for luaj 5.1.x features, plus: +Support for lua 5.1.x features, plus:

    Luaj 1.0.x

    -Support for most luaj 5.1.x features. +Support for most lua 5.1.x features.

    Performance

    Good performance is a major goal of luaj. @@ -854,6 +855,10 @@ Files are no longer hosted at LuaForge.
  • Fix bug in luajava overload resolution.
  • Fix luastring bug where parsing did not check for overflow.
  • Fix luastring bug where circular dependency randomly caused NullPointerException.
  • +
  • Major refactor of table implementation.
  • +
  • Improved behavior of next() (fixes issue #7).
  • +
  • Existing tables can now be made weak (fixes issue #16).
  • +
  • More compatible allocation of table entries in array vs. hash (fixes issue #8).
  • diff --git a/src/core/org/luaj/vm2/LuaInteger.java b/src/core/org/luaj/vm2/LuaInteger.java index 008efdd4..6c4cb7ba 100644 --- a/src/core/org/luaj/vm2/LuaInteger.java +++ b/src/core/org/luaj/vm2/LuaInteger.java @@ -126,6 +126,10 @@ public class LuaInteger extends LuaNumber { return v; } + public static int hashCode(int x) { + return x; + } + // unary operators public LuaValue neg() { return valueOf(-(long)v); } diff --git a/src/core/org/luaj/vm2/LuaTable.java b/src/core/org/luaj/vm2/LuaTable.java index 6a7793fd..ddbecb18 100644 --- a/src/core/org/luaj/vm2/LuaTable.java +++ b/src/core/org/luaj/vm2/LuaTable.java @@ -21,6 +21,7 @@ ******************************************************************************/ package org.luaj.vm2; +import java.lang.ref.WeakReference; import java.util.Vector; /** @@ -69,30 +70,26 @@ import java.util.Vector; * * @see LuaValue */ -public class LuaTable extends LuaValue { +public class LuaTable extends LuaValue implements Metatable { private static final int MIN_HASH_CAPACITY = 2; private static final LuaString N = valueOf("n"); /** the array values */ protected LuaValue[] array; - /** the hash keys */ - protected LuaValue[] hashKeys; - - /** the hash values */ - protected LuaValue[] hashValues; + /** the hash part */ + protected Slot[] hash; /** the number of hash entries */ protected int hashEntries; /** metatable for this table, or null */ - protected LuaValue m_metatable; + protected Metatable m_metatable; /** Construct empty table */ public LuaTable() { array = NOVALS; - hashKeys = NOVALS; - hashValues = NOVALS; + hash = NOBUCKETS; } /** @@ -114,7 +111,7 @@ public class LuaTable extends LuaValue { int nn = (named!=null? named.length: 0); int nu = (unnamed!=null? unnamed.length: 0); int nl = (lastarg!=null? lastarg.narg(): 0); - presize(nu+nl, nn-(nn>>1)); + presize(nu+nl, nn>>1); for ( int i=0; i array.length ) - array = resize( array, narray ); + array = resize( array, 1 << log2(narray) ); } public void presize(int narray, int nhash) { if ( nhash > 0 && nhash < MIN_HASH_CAPACITY ) nhash = MIN_HASH_CAPACITY; - array = (narray>0? new LuaValue[narray]: NOVALS); - hashKeys = (nhash>0? new LuaValue[nhash]: NOVALS); - hashValues = (nhash>0? new LuaValue[nhash]: NOVALS); + // Size of both parts must be a power of two. + array = (narray>0? new LuaValue[1 << log2(narray)]: NOVALS); + hash = (nhash>0? new Slot[1 << log2(nhash)]: NOBUCKETS); hashEntries = 0; } @@ -201,37 +198,25 @@ public class LuaTable extends LuaValue { * @return length of the hash part, does not relate to count of objects in the table. */ protected int getHashLength() { - return hashValues.length; + return hash.length; } public LuaValue getmetatable() { - return m_metatable; + return ( m_metatable != null ) ? m_metatable.toLuaValue() : null; } public LuaValue setmetatable(LuaValue metatable) { - m_metatable = metatable; - LuaValue mode; - if ( m_metatable!=null && (mode=m_metatable.rawget(MODE)).isstring() ) { - String m = mode.tojstring(); - boolean k = m.indexOf('k')>=0; - boolean v = m.indexOf('v')>=0; - return changemode(k,v); + boolean hadWeakKeys = m_metatable != null && m_metatable.useWeakKeys(); + boolean hadWeakValues = m_metatable != null && m_metatable.useWeakValues(); + m_metatable = metatableOf( metatable ); + if ( ( hadWeakKeys != ( m_metatable != null && m_metatable.useWeakKeys() )) || + ( hadWeakValues != ( m_metatable != null && m_metatable.useWeakValues() ))) { + // force a rehash + rehash( 0 ); } return this; } - /** - * Change the mode of a table - * @param weakkeys true to make the table have weak keys going forward - * @param weakvalues true to make the table have weak values going forward - * @return {@code this} or a new {@link WeakTable} if the mode change requires copying. - */ - protected LuaTable changemode(boolean weakkeys, boolean weakvalues) { - if ( weakkeys || weakvalues ) - return new WeakTable(weakkeys, weakvalues, this); - return this; - } - public LuaValue get( int key ) { LuaValue v = rawget(key); return v.isnil() && m_metatable!=null? gettable(this,valueOf(key)): v; @@ -243,24 +228,33 @@ public class LuaTable extends LuaValue { } public LuaValue rawget( int key ) { - if ( key>0 && key<=array.length ) - return array[key-1]!=null? array[key-1]: NIL; + if ( key>0 && key<=array.length ) { + LuaValue v = m_metatable == null ? array[key-1] : m_metatable.arrayget(array, key-1); + return v != null ? v : NIL; + } return hashget( LuaInteger.valueOf(key) ); } - + public LuaValue rawget( LuaValue key ) { if ( key.isinttype() ) { int ikey = key.toint(); - if ( ikey>0 && ikey<=array.length ) - return array[ikey-1]!=null? array[ikey-1]: NIL; + if ( ikey>0 && ikey<=array.length ) { + LuaValue v = m_metatable == null + ? array[ikey-1] : m_metatable.arrayget(array, ikey-1); + return v != null ? v : NIL; + } } return hashget( key ); } - + protected LuaValue hashget(LuaValue key) { if ( hashEntries > 0 ) { - LuaValue v = hashValues[hashFindSlot(key)]; - return v!=null? v: NIL; + for ( Slot slot = hash[ hashSlot(key) ]; slot != null; slot = slot.rest() ) { + StrongSlot foundSlot; + if ( ( foundSlot = slot.find(key) ) != null ) { + return foundSlot.value(); + } + } } return NIL; } @@ -292,31 +286,13 @@ public class LuaTable extends LuaValue { /** Set an array element */ private boolean arrayset( int key, LuaValue value ) { if ( key>0 && key<=array.length ) { - array[key-1] = (value.isnil()? null: value); - return true; - } else if ( key==array.length+1 && !value.isnil() ) { - expandarray(); - array[key-1] = value; + array[key - 1] = value.isnil() ? null : + (m_metatable != null ? m_metatable.wrap(value) : value); return true; } return false; } - /** Expand the array part */ - private void expandarray() { - int n = array.length; - int m = Math.max(2,n*2); - array = resize(array, m); - for ( int i=n; i0 && i<=array.length ) { - if ( array[i-1] == null ) - error( "invalid key to 'next'" ); break; } } - if ( hashKeys.length == 0 ) + if ( hash.length == 0 ) error( "invalid key to 'next'" ); - i = hashFindSlot(key); - if ( hashKeys[i] == null ) + i = hashSlot( key ); + boolean found = false; + for ( Slot slot = hash[i]; slot != null; slot = slot.rest() ) { + if ( found ) { + StrongSlot nextEntry = slot.first(); + if ( nextEntry != null ) { + return nextEntry.toVarargs(); + } + } else if ( slot.keyeq( key ) ) { + found = true; + } + } + if ( !found ) { error( "invalid key to 'next'" ); + } i += 1+array.length; } } while ( false ); - + // check array part - for ( ; i 0 ) { + index = hashSlot( key ); + for ( Slot slot = hash[ index ]; slot != null; slot = slot.rest() ) { + StrongSlot foundSlot; + if ( ( foundSlot = slot.find( key ) ) != null ) { + hash[index] = hash[index].set( foundSlot, value ); + return; + } + } } - int slot = hashFindSlot( key ); - if ( hashFillSlot( slot, value ) ) - return; - hashKeys[slot] = key; - hashValues[slot] = value; - if ( checkLoadFactor() ) - rehash(); + if ( checkLoadFactor() ) { + if ( key.isinttype() && key.toint() > 0 ) { + // a rehash might make room in the array portion for this key. + rehash( key.toint() ); + if ( arrayset(key.toint(), value) ) + return; + } else { + rehash( -1 ); + } + index = hashSlot( key ); + } + Slot entry = ( m_metatable != null ) + ? m_metatable.entry( key, value ) + : defaultEntry( key, value ); + hash[ index ] = ( hash[index] != null ) ? hash[index].add( entry ) : entry; + ++hashEntries; + } + } + + public static int hashpow2( int hashCode, int mask ) { + return hashCode & mask; + } + + public static int hashmod( int hashCode, int mask ) { + return ( hashCode & 0x7FFFFFFF ) % mask; + } + + /** + * Find the hashtable slot index to use. + * @param key the key to look for + * @param hashMask N-1 where N is the number of hash slots (must be power of 2) + * @return the slot index + */ + public static int hashSlot( LuaValue key, int hashMask ) { + switch ( key.type() ) { + case TNUMBER: + case TTABLE: + case TTHREAD: + case TLIGHTUSERDATA: + case TUSERDATA: + return hashmod( key.hashCode(), hashMask ); + default: + return hashpow2( key.hashCode(), hashMask ); } } @@ -474,93 +514,259 @@ public class LuaTable extends LuaValue { * @param key key to look for * @return slot to use */ - public int hashFindSlot(LuaValue key) { - int i = ( key.hashCode() & 0x7FFFFFFF ) % hashKeys.length; - - // This loop is guaranteed to terminate as long as we never allow the - // table to get 100% full. - LuaValue k; - while ( ( k = hashKeys[i] ) != null && !k.raweq(key) ) { - i = ( i + 1 ) % hashKeys.length; - } - return i; + private int hashSlot(LuaValue key) { + return hashSlot( key, hash.length - 1 ); } - private boolean hashFillSlot( int slot, LuaValue value ) { - hashValues[ slot ] = value; - if ( hashKeys[ slot ] != null ) { - return true; - } else { - ++hashEntries; - return false; - } - } - private void hashRemove( LuaValue key ) { - if ( hashKeys.length > 0 ) { - int slot = hashFindSlot( key ); - hashClearSlot( slot ); - } - } - - /** - * Clear a particular slot in the table - * @param i slot to clear. - */ - protected void hashClearSlot( int i ) { - if ( hashKeys[ i ] != null ) { - - int j = i; - int n = hashKeys.length; - while ( hashKeys[ j = ( ( j + 1 ) % n ) ] != null ) { - final int k = ( ( hashKeys[ j ].hashCode() )& 0x7FFFFFFF ) % n; - if ( ( j > i && ( k <= i || k > j ) ) || - ( j < i && ( k <= i && k > j ) ) ) { - hashKeys[ i ] = hashKeys[ j ]; - hashValues[ i ] = hashValues[ j ]; - i = j; + if ( hash.length > 0 ) { + int index = hashSlot(key); + for ( Slot slot = hash[index]; slot != null; slot = slot.rest() ) { + StrongSlot foundSlot; + if ( ( foundSlot = slot.find( key ) ) != null ) { + hash[index] = hash[index].remove( foundSlot ); + --hashEntries; + return; } } - - --hashEntries; - hashKeys[ i ] = null; - hashValues[ i ] = null; - - if ( hashEntries == 0 ) { - hashKeys = NOVALS; - hashValues = NOVALS; - } } } private boolean checkLoadFactor() { - // Using a load factor of (n+1) >= 7/8 because that is easy to compute without - // overflow or division. - final int hashCapacity = hashKeys.length; - return hashEntries >= (hashCapacity - (hashCapacity>>3)); + return hashEntries >= hash.length; } - private void rehash() { - final int oldCapacity = hashKeys.length; - final int newCapacity = oldCapacity+(oldCapacity>>2)+MIN_HASH_CAPACITY; - - final LuaValue[] oldKeys = hashKeys; - final LuaValue[] oldValues = hashValues; - - hashKeys = new LuaValue[ newCapacity ]; - hashValues = new LuaValue[ newCapacity ]; - - for ( int i = 0; i < oldCapacity; ++i ) { - final LuaValue k = oldKeys[i]; - if ( k != null ) { - final LuaValue v = oldValues[i]; - final int slot = hashFindSlot( k ); - hashKeys[slot] = k; - hashValues[slot] = v; + private int countHashKeys() { + int keys = 0; + for ( int i = 0; i < hash.length; ++i ) { + for ( Slot slot = hash[i]; slot != null; slot = slot.rest() ) { + if ( slot.first() != null ) + keys++; } } + return keys; } - + + private void dropWeakArrayValues() { + for ( int i = 0; i < array.length; ++i ) { + m_metatable.arrayget(array, i); + } + } + + private int countIntKeys(int[] nums) { + int total = 0; + int i = 1; + + // Count integer keys in array part + for ( int bit = 0; bit < 31; ++bit ) { + if ( i > array.length ) + break; + int j = Math.min(array.length, 1 << bit); + int c = 0; + while ( i <= j ) { + if ( array[ i++ - 1 ] != null ) + c++; + } + nums[bit] = c; + total += c; + } + + // Count integer keys in hash part + for ( i = 0; i < hash.length; ++i ) { + for ( Slot s = hash[i]; s != null; s = s.rest() ) { + int k; + if ( ( k = s.arraykey(Integer.MAX_VALUE) ) > 0 ) { + nums[log2(k)]++; + total++; + } + } + } + + return total; + } + + // Compute ceil(log2(x)) + static int log2(int x) { + int lg = 0; + x -= 1; + if ( x < 0 ) + // 2^(-(2^31)) is approximately 0 + return Integer.MIN_VALUE; + if ( ( x & 0xFFFF0000 ) != 0 ) { + lg = 16; + x >>>= 16; + } + if ( ( x & 0xFF00 ) != 0 ) { + lg += 8; + x >>>= 8; + } + if ( ( x & 0xF0 ) != 0 ) { + lg += 4; + x >>>= 4; + } + switch (x) { + case 0x0: return 0; + case 0x1: lg += 1; break; + case 0x2: lg += 2; break; + case 0x3: lg += 2; break; + case 0x4: lg += 3; break; + case 0x5: lg += 3; break; + case 0x6: lg += 3; break; + case 0x7: lg += 3; break; + case 0x8: lg += 4; break; + case 0x9: lg += 4; break; + case 0xA: lg += 4; break; + case 0xB: lg += 4; break; + case 0xC: lg += 4; break; + case 0xD: lg += 4; break; + case 0xE: lg += 4; break; + case 0xF: lg += 4; break; + } + return lg; + } + + /* + * newKey > 0 is next key to insert + * newKey == 0 means number of keys not changing (__mode changed) + * newKey < 0 next key will go in hash part + */ + private void rehash(int newKey) { + if ( m_metatable != null && ( m_metatable.useWeakKeys() || m_metatable.useWeakValues() )) { + // If this table has weak entries, hashEntries is just an upper bound. + hashEntries = countHashKeys(); + if ( m_metatable.useWeakValues() ) { + dropWeakArrayValues(); + } + } + int[] nums = new int[32]; + int total = countIntKeys(nums); + if ( newKey > 0 ) { + total++; + nums[log2(newKey)]++; + } + + // Choose N such that N <= sum(nums[0..log(N)]) < 2N + int keys = nums[0]; + int newArraySize = 0; + for ( int log = 1; log < 32; ++log ) { + keys += nums[log]; + if (total * 2 < 1 << log) { + // Not enough integer keys. + break; + } else if (keys >= (1 << (log - 1))) { + newArraySize = 1 << log; + } + } + + final LuaValue[] oldArray = array; + final Slot[] oldHash = hash; + final LuaValue[] newArray; + final Slot[] newHash; + + // Copy existing array entries and compute number of moving entries. + int movingToArray = 0; + if ( newKey > 0 && newKey <= newArraySize ) { + movingToArray--; + } + if (newArraySize != oldArray.length) { + newArray = new LuaValue[newArraySize]; + if (newArraySize > oldArray.length) { + for (int i = log2(oldArray.length + 1), j = log2(newArraySize) + 1; i < j; ++i) { + movingToArray += nums[i]; + } + } else if (oldArray.length > newArraySize) { + for (int i = log2(newArraySize + 1), j = log2(oldArray.length) + 1; i < j; ++i) { + movingToArray -= nums[i]; + } + } + System.arraycopy(oldArray, 0, newArray, 0, Math.min(oldArray.length, newArraySize)); + } else { + newArray = array; + } + + final int newHashSize = hashEntries - movingToArray + + ((newKey < 0 || newKey > newArraySize) ? 1 : 0); // Make room for the new entry + final int oldCapacity = oldHash.length; + final int newCapacity; + final int newHashMask; + + if (newHashSize > 0) { + // round up to next power of 2. + newCapacity = ( newHashSize < MIN_HASH_CAPACITY ) + ? MIN_HASH_CAPACITY + : 1 << log2(newHashSize); + newHashMask = newCapacity - 1; + newHash = new Slot[ newCapacity ]; + } else { + newCapacity = 0; + newHashMask = 0; + newHash = NOBUCKETS; + } + + // Move hash buckets + for ( int i = 0; i < oldCapacity; ++i ) { + for ( Slot slot = oldHash[i]; slot != null; slot = slot.rest() ) { + int k; + if ( ( k = slot.arraykey( newArraySize ) ) > 0 ) { + StrongSlot entry = slot.first(); + if (entry != null) + newArray[ k - 1 ] = entry.value(); + } else { + int j = slot.keyindex( newHashMask ); + newHash[j] = slot.relink( newHash[j] ); + } + } + } + + // Move array values into hash portion + for ( int i = newArraySize; i < oldArray.length; ) { + LuaValue v; + if ( ( v = oldArray[ i++ ] ) != null ) { + int slot = hashmod( LuaInteger.hashCode( i ), newHashMask ); + Slot newEntry; + if ( m_metatable != null ) { + newEntry = m_metatable.entry( valueOf(i), v ); + if ( newEntry == null ) + continue; + } else { + newEntry = defaultEntry( valueOf(i), v ); + } + newHash[ slot ] = ( newHash[slot] != null ) + ? newHash[slot].add( newEntry ) : newEntry; + } + } + + hash = newHash; + array = newArray; + hashEntries -= movingToArray; + } + + public Slot entry( LuaValue key, LuaValue value ) { + return defaultEntry( key, value ); + } + + protected static boolean isLargeKey(LuaValue key) { + switch (key.type()) { + case TSTRING: + return key.rawlen() > LuaString.RECENT_STRINGS_MAX_LENGTH; + case TNUMBER: + case TBOOLEAN: + return false; + default: + return true; + } + } + + protected static Entry defaultEntry(LuaValue key, LuaValue value) { + if ( key.isinttype() ) { + return new IntKeyEntry( key.toint(), value ); + } else if (value.type() == TNUMBER) { + return new NumberValueEntry( key, value.todouble() ); + } else { + return new NormalEntry( key, value ); + } + } + // ----------------- sort support ----------------------------- // // implemented heap sort from wikipedia @@ -571,6 +777,9 @@ public class LuaTable extends LuaValue { * @param comparator {@link LuaValue} to be called to compare elements. */ public void sort(LuaValue comparator) { + if (m_metatable != null && m_metatable.useWeakValues()) { + dropWeakArrayValues(); + } int n = array.length; while ( n > 0 && array[n-1] == null ) --n; @@ -605,8 +814,14 @@ public class LuaTable extends LuaValue { } private boolean compare(int i, int j, LuaValue cmpfunc) { - LuaValue a = array[i]; - LuaValue b = array[j]; + LuaValue a, b; + if (m_metatable == null) { + a = array[i]; + b = array[j]; + } else { + a = m_metatable.arrayget(array, i); + b = m_metatable.arrayget(array, j); + } if ( a == null || b == null ) return false; if ( ! cmpfunc.isnil() ) { @@ -659,7 +874,7 @@ public class LuaTable extends LuaValue { if ( this == val ) return true; if ( m_metatable == null || !val.istable() ) return false; LuaValue valmt = val.getmetatable(); - return valmt!=null && LuaValue.eqmtcall(this, m_metatable, val, valmt); + return valmt!=null && LuaValue.eqmtcall(this, m_metatable.toLuaValue(), val, valmt); } /** Unpack all the elements of this table */ @@ -688,4 +903,458 @@ public class LuaTable extends LuaValue { return varargsOf(v); } } + + /** + * Represents a slot in the hash table. + */ + interface Slot { + + /** Return hash{pow2,mod}( first().key().hashCode(), sizeMask ) */ + int keyindex( int hashMask ); + + /** Return first Entry, if still present, or null. */ + StrongSlot first(); + + /** Compare given key with first()'s key; return first() if equal. */ + StrongSlot find( LuaValue key ); + + /** + * Compare given key with first()'s key; return true if equal. May + * return true for keys no longer present in the table. + */ + boolean keyeq( LuaValue key ); + + /** Return rest of elements */ + Slot rest(); + + /** + * Return first entry's key, iff it is an integer between 1 and max, + * inclusive, or zero otherwise. + */ + int arraykey( int max ); + + /** + * Set the value of this Slot's first Entry, if possible, or return a + * new Slot whose first entry has the given value. + */ + Slot set( StrongSlot target, LuaValue value ); + + /** + * Link the given new entry to this slot. + */ + Slot add( Slot newEntry ); + + /** + * Return a Slot with the given value set to nil; must not return null + * for next() to behave correctly. + */ + Slot remove( StrongSlot target ); + + /** + * Return a Slot with the same first key and value (if still present) + * and rest() equal to rest. + */ + Slot relink( Slot rest ); + } + + /** + * Subclass of Slot guaranteed to have a strongly-referenced key and value, + * to support weak tables. + */ + interface StrongSlot extends Slot { + /** Return first entry's key */ + LuaValue key(); + + /** Return first entry's value */ + LuaValue value(); + + /** Return varargsOf(key(), value()) or equivalent */ + Varargs toVarargs(); + } + + private static class LinkSlot implements StrongSlot { + private Entry entry; + private Slot next; + + LinkSlot( Entry entry, Slot next ) { + this.entry = entry; + this.next = next; + } + + public LuaValue key() { + return entry.key(); + } + + public int keyindex( int hashMask ) { + return entry.keyindex( hashMask ); + } + + public LuaValue value() { + return entry.value(); + } + + public Varargs toVarargs() { + return entry.toVarargs(); + } + + public StrongSlot first() { + return entry; + } + + public StrongSlot find(LuaValue key) { + return entry.keyeq(key) ? this : null; + } + + public boolean keyeq(LuaValue key) { + return entry.keyeq(key); + } + + public Slot rest() { + return next; + } + + public int arraykey( int max ) { + return entry.arraykey( max ); + } + + public Slot set(StrongSlot target, LuaValue value) { + if ( target == this ) { + entry = entry.set( value ); + return this; + } else { + return setnext(next.set( target, value )); + } + } + + public Slot add( Slot entry ) { + return setnext(next.add( entry )); + } + + public Slot remove( StrongSlot target ) { + if ( this == target ) { + return new DeadSlot( key(), next ); + } else { + this.next = next.remove( target ); + } + return this; + } + + public Slot relink(Slot rest) { + // This method is (only) called during rehash, so it must not change this.next. + return ( rest != null ) ? new LinkSlot(entry, rest) : (Slot)entry; + } + + // this method ensures that this.next is never set to null. + private Slot setnext(Slot next) { + if ( next != null ) { + this.next = next; + return this; + } else { + return entry; + } + } + + public String toString() { + return entry + "; " + next; + } + } + + /** + * Base class for regular entries. + * + *

    + * If the key may be an integer, the {@link #arraykey(int)} method must be + * overridden to handle that case. + */ + static abstract class Entry extends Varargs implements StrongSlot { + public abstract LuaValue key(); + public abstract LuaValue value(); + abstract Entry set(LuaValue value); + + public int arraykey( int max ) { + return 0; + } + + public LuaValue arg(int i) { + switch (i) { + case 1: return key(); + case 2: return value(); + } + return NIL; + } + + public int narg() { + return 2; + } + + /** + * Subclasses should redefine as "return this;" whenever possible. + */ + public Varargs toVarargs() { + return varargsOf(key(), value()); + } + + public LuaValue arg1() { + return key(); + } + + public Varargs subargs(int start) { + switch (start) { + case 1: return this; + case 2: return value(); + } + return NONE; + } + + public StrongSlot first() { + return this; + } + + public Slot rest() { + return null; + } + + public StrongSlot find(LuaValue key) { + return keyeq(key) ? this : null; + } + + public Slot set(StrongSlot target, LuaValue value) { + return set( value ); + } + + public Slot add( Slot entry ) { + return new LinkSlot( this, entry ); + } + + public Slot remove(StrongSlot target) { + return new DeadSlot( key(), null ); + } + + public Slot relink( Slot rest ) { + return ( rest != null ) ? new LinkSlot( this, rest ) : (Slot)this; + } + } + + static class NormalEntry extends Entry { + private final LuaValue key; + private LuaValue value; + + NormalEntry( LuaValue key, LuaValue value ) { + this.key = key; + this.value = value; + } + + public LuaValue key() { + return key; + } + + public LuaValue value() { + return value; + } + + public Entry set(LuaValue value) { + this.value = value; + return this; + } + + public Varargs toVarargs() { + return this; + } + + public int keyindex( int hashMask ) { + return hashSlot( key, hashMask ); + } + + public boolean keyeq(LuaValue key) { + return key.raweq(this.key); + } + } + + private static class IntKeyEntry extends Entry { + private final int key; + private LuaValue value; + + IntKeyEntry(int key, LuaValue value) { + this.key = key; + this.value = value; + } + + public LuaValue key() { + return valueOf( key ); + } + + public int arraykey(int max) { + return ( key >= 1 && key <= max ) ? key : 0; + } + + public LuaValue value() { + return value; + } + + public Entry set(LuaValue value) { + this.value = value; + return this; + } + + public int keyindex( int mask ) { + return hashmod( LuaInteger.hashCode( key ), mask ); + } + + public boolean keyeq(LuaValue key) { + return key.raweq( this.key ); + } + } + + /** + * Entry class used with numeric values, but only when the key is not an integer. + */ + private static class NumberValueEntry extends Entry { + private double value; + private final LuaValue key; + + NumberValueEntry(LuaValue key, double value) { + this.key = key; + this.value = value; + } + + public LuaValue key() { + return key; + } + + public LuaValue value() { + return valueOf(value); + } + + public Entry set(LuaValue value) { + LuaValue n = value.tonumber(); + if ( !n.isnil() ) { + this.value = n.todouble(); + return this; + } else { + return new NormalEntry( this.key, value ); + } + } + + public int keyindex( int mask ) { + return hashSlot( key, mask ); + } + + public boolean keyeq(LuaValue key) { + return key.raweq(this.key); + } + } + + /** + * A Slot whose value has been set to nil. The key is kept in a weak reference so that + * it can be found by next(). + */ + private static class DeadSlot implements Slot { + + private final Object key; + private Slot next; + + private DeadSlot( LuaValue key, Slot next ) { + this.key = isLargeKey(key) ? new WeakReference( key ) : (Object)key; + this.next = next; + } + + private LuaValue key() { + return (LuaValue) (key instanceof WeakReference ? ((WeakReference) key).get() : key); + } + + public int keyindex(int hashMask) { + // Not needed: this entry will be dropped during rehash. + return 0; + } + + public StrongSlot first() { + return null; + } + + public StrongSlot find(LuaValue key) { + return null; + } + + public boolean keyeq(LuaValue key) { + LuaValue k = key(); + return k != null && key.raweq(k); + } + + public Slot rest() { + return next; + } + + public int arraykey(int max) { + return -1; + } + + public Slot set(StrongSlot target, LuaValue value) { + Slot next = ( this.next != null ) ? this.next.set( target, value ) : null; + if ( key() != null ) { + // if key hasn't been garbage collected, it is still potentially a valid argument + // to next(), so we can't drop this entry yet. + this.next = next; + return this; + } else { + return next; + } + } + + public Slot add(Slot newEntry) { + return ( next != null ) ? next.add(newEntry) : newEntry; + } + + public Slot remove(StrongSlot target) { + if ( key() != null ) { + next = next.remove(target); + return this; + } else { + return next; + } + } + + public Slot relink(Slot rest) { + return rest; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("'); + if (next != null) { + buf.append("; "); + buf.append(next.toString()); + } + return buf.toString(); + } + }; + + private static final Slot[] NOBUCKETS = {}; + + // Metatable operations + + public boolean useWeakKeys() { + return false; + } + + public boolean useWeakValues() { + return false; + } + + public LuaValue toLuaValue() { + return this; + } + + public LuaValue wrap(LuaValue value) { + return value; + } + + public LuaValue arrayget(LuaValue[] array, int index) { + return array[index]; + } } diff --git a/src/core/org/luaj/vm2/LuaUserdata.java b/src/core/org/luaj/vm2/LuaUserdata.java index 300980a1..b6f58e09 100644 --- a/src/core/org/luaj/vm2/LuaUserdata.java +++ b/src/core/org/luaj/vm2/LuaUserdata.java @@ -35,7 +35,7 @@ public class LuaUserdata extends LuaValue { m_instance = obj; m_metatable = metatable; } - + public String tojstring() { return String.valueOf(m_instance); } @@ -120,7 +120,7 @@ public class LuaUserdata extends LuaValue { } // __eq metatag processing - public boolean eqmt( LuaValue val ) { + public boolean eqmt( LuaValue val ) { return m_metatable!=null && val.isuserdata()? LuaValue.eqmtcall(this, m_metatable, val, val.getmetatable()): false; } } diff --git a/src/core/org/luaj/vm2/LuaValue.java b/src/core/org/luaj/vm2/LuaValue.java index 4a0b4fb9..13164df5 100644 --- a/src/core/org/luaj/vm2/LuaValue.java +++ b/src/core/org/luaj/vm2/LuaValue.java @@ -1384,7 +1384,7 @@ public class LuaValue extends Varargs { * @see LuaFunction#s_metatable * @see LuaThread#s_metatable */ - public LuaValue getmetatable() { return null; }; + public LuaValue getmetatable() { return null; } /** * Set the metatable for this {@link LuaValue} @@ -3131,24 +3131,12 @@ public class LuaValue extends Varargs { */ public LuaString strvalue() { typerror("strValue"); return null; } - /** Return the key part of this value if it is a weak table entry, or {@link NIL} if it was weak and is no longer referenced. - * @return {@link LuaValue} key, or {@link NIL} if it was weak and is no longer referenced. - * @see WeakTable - */ - public LuaValue strongkey() { return strongvalue(); } - - /** Return this value as a strong reference, or {@link NIL} if it was weak and is no longer referenced. - * @return {@link LuaValue} referred to, or {@link NIL} if it was weak and is no longer referenced. + /** Return this value as a strong reference, or null if it was weak and is no longer referenced. + * @return {@link LuaValue} referred to, or null if it was weak and is no longer referenced. * @see WeakTable */ public LuaValue strongvalue() { return this; } - /** Test if this is a weak reference and its value no longer is referenced. - * @return true if this is a weak reference whose value no longer is referenced - * @see WeakTable - */ - public boolean isweaknil() { return false; } - /** Convert java boolean to a {@link LuaValue}. * * @param b boolean value to convert @@ -3215,7 +3203,7 @@ public class LuaValue extends Varargs { * @return new {@link LuaTable} instance with no values and no metatable, but preallocated for array and hashed elements. */ public static LuaTable tableOf(int narray, int nhash) { return new LuaTable(narray, nhash); } - + /** Construct a {@link LuaTable} initialized with supplied array values. * @param unnamedValues array of {@link LuaValue} containing the values to use in initialization * @return new {@link LuaTable} instance with sequential elements coming from the array. @@ -3229,7 +3217,7 @@ public class LuaValue extends Varargs { * @return new {@link LuaTable} instance with sequential elements coming from the array and varargs. */ public static LuaTable listOf(LuaValue[] unnamedValues,Varargs lastarg) { return new LuaTable(null,unnamedValues,lastarg); } - + /** Construct a {@link LuaTable} initialized with supplied named values. * @param namedValues array of {@link LuaValue} containing the keys and values to use in initialization * in order {@code {key-a, value-a, key-b, value-b, ...} } @@ -3365,6 +3353,26 @@ public class LuaValue extends Varargs { return h; } + /** Construct a Metatable instance from the given LuaValue */ + protected static Metatable metatableOf(LuaValue mt) { + if ( mt != null && mt.istable() ) { + LuaValue mode = mt.rawget(MODE); + if ( mode.isstring() ) { + String m = mode.tojstring(); + boolean weakkeys = m.indexOf('k') >= 0; + boolean weakvalues = m.indexOf('v') >= 0; + if ( weakkeys || weakvalues ) { + return new WeakTable(weakkeys, weakvalues, mt); + } + } + return (LuaTable)mt; + } else if ( mt != null ) { + return new NonTableMetatable( mt ); + } else { + return null; + } + } + /** Throw {@link LuaError} indicating index was attempted on illegal type * @throws LuaError when called. */ diff --git a/src/core/org/luaj/vm2/Metatable.java b/src/core/org/luaj/vm2/Metatable.java new file mode 100644 index 00000000..49c639b6 --- /dev/null +++ b/src/core/org/luaj/vm2/Metatable.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2013 Luaj.org. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ +package org.luaj.vm2; + +import org.luaj.vm2.LuaTable.Slot; + +/** + * Provides operations that depend on the __mode key of the metatable. + */ +interface Metatable { + + /** Return whether or not this table's keys are weak. */ + public boolean useWeakKeys(); + + /** Return whether or not this table's values are weak. */ + public boolean useWeakValues(); + + /** Return this metatable as a LuaValue. */ + public LuaValue toLuaValue(); + + /** Return an instance of Slot appropriate for the given key and value. */ + public Slot entry( LuaValue key, LuaValue value ); + + /** Returns the given value wrapped in a weak reference if appropriate. */ + public LuaValue wrap( LuaValue value ); + + /** + * Returns the value at the given index in the array, or null if it is a weak reference that + * has been dropped. + */ + public LuaValue arrayget(LuaValue[] array, int index); +} diff --git a/src/core/org/luaj/vm2/NonTableMetatable.java b/src/core/org/luaj/vm2/NonTableMetatable.java new file mode 100644 index 00000000..4cf112a8 --- /dev/null +++ b/src/core/org/luaj/vm2/NonTableMetatable.java @@ -0,0 +1,36 @@ +package org.luaj.vm2; + +import org.luaj.vm2.LuaTable.Slot; + +class NonTableMetatable implements Metatable { + + private final LuaValue value; + + public NonTableMetatable(LuaValue value) { + this.value = value; + } + + public boolean useWeakKeys() { + return false; + } + + public boolean useWeakValues() { + return false; + } + + public LuaValue toLuaValue() { + return value; + } + + public Slot entry(LuaValue key, LuaValue value) { + return LuaTable.defaultEntry(key, value); + } + + public LuaValue wrap(LuaValue value) { + return value; + } + + public LuaValue arrayget(LuaValue[] array, int index) { + return array[index]; + } +} diff --git a/src/core/org/luaj/vm2/WeakTable.java b/src/core/org/luaj/vm2/WeakTable.java index 73cb5b64..20606b37 100644 --- a/src/core/org/luaj/vm2/WeakTable.java +++ b/src/core/org/luaj/vm2/WeakTable.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009-2011 Luaj.org. All rights reserved. + * Copyright (c) 2009-2011, 2013 Luaj.org. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,8 @@ package org.luaj.vm2; import java.lang.ref.WeakReference; -import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.LuaTable.Slot; +import org.luaj.vm2.LuaTable.StrongSlot; /** * Subclass of {@link LuaTable} that provides weak key and weak value semantics. @@ -34,79 +35,276 @@ import org.luaj.vm2.lib.TwoArgFunction; * However, calling the constructors directly when weak tables are required from * Java will reduce overhead. */ -public class WeakTable extends LuaTable { - private boolean weakkeys,weakvalues; - - /** +public class WeakTable implements Metatable { + + private boolean weakkeys, weakvalues; + private LuaValue backing; + + public static LuaTable make(boolean weakkeys, boolean weakvalues) { + LuaString mode; + if ( weakkeys && weakvalues ) { + mode = LuaString.valueOf("kv"); + } else if ( weakkeys ) { + mode = LuaString.valueOf("k"); + } else if ( weakvalues ) { + mode = LuaString.valueOf("v"); + } else { + return LuaTable.tableOf(); + } + LuaTable table = LuaTable.tableOf(); + LuaTable mt = LuaTable.tableOf(new LuaValue[] { LuaValue.MODE, mode }); + table.setmetatable(mt); + return table; + } + + /** * Construct a table with weak keys, weak values, or both * @param weakkeys true to let the table have weak keys * @param weakvalues true to let the table have weak values */ - public WeakTable(boolean weakkeys, boolean weakvalues) { - this(weakkeys, weakvalues, 0, 0); - } - - /** - * Construct a table with weak keys, weak values, or both, and an initial capacity - * @param weakkeys true to let the table have weak keys - * @param weakvalues true to let the table have weak values - * @param narray capacity of array part - * @param nhash capacity of hash part - */ - protected WeakTable(boolean weakkeys, boolean weakvalues, int narray, int nhash) { - super(narray, nhash); + public WeakTable(boolean weakkeys, boolean weakvalues, LuaValue backing) { this.weakkeys = weakkeys; this.weakvalues = weakvalues; + this.backing = backing; } - /** - * Construct a table with weak keys, weak values, or both, and a source of initial data - * @param weakkeys true to let the table have weak keys - * @param weakvalues true to let the table have weak values - * @param source {@link LuaTable} containing the initial elements - */ - protected WeakTable(boolean weakkeys, boolean weakvalues, LuaTable source) { - this(weakkeys, weakvalues, source.getArrayLength(), source.getHashLength()); - Varargs n; - LuaValue k = NIL; - while ( !(k = ((n = source.next(k)).arg1())).isnil() ) - rawset(k, n.arg(2)); - m_metatable = source.m_metatable; - } - - public void presize( int narray ) { - super.presize(narray); + public boolean useWeakKeys() { + return weakkeys; } - /** - * Presize capacity of both array and hash parts. - * @param narray capacity of array part - * @param nhash capacity of hash part - */ - public void presize(int narray, int nhash) { - super.presize(narray, nhash); - } - - protected int getArrayLength() { - return super.getArrayLength(); + public boolean useWeakValues() { + return weakvalues; } - protected int getHashLength() { - return super.getHashLength(); + public LuaValue toLuaValue() { + return backing; } - - protected LuaTable changemode(boolean weakkeys, boolean weakvalues) { - this.weakkeys = weakkeys; - this.weakvalues = weakvalues; - return this; + + public Slot entry(LuaValue key, LuaValue value) { + value = value.strongvalue(); + if ( value == null ) + return null; + if ( weakkeys && !( key.isnumber() || key.isstring() || key.isboolean() )) { + if ( weakvalues && !( value.isnumber() || value.isstring() || value.isboolean() )) { + return new WeakKeyAndValueSlot( key, value, null ); + } else { + return new WeakKeySlot( key, value, null ); + } + } + if ( weakvalues && ! (value.isnumber() || value.isstring() || value.isboolean() )) { + return new WeakValueSlot( key, value, null ); + } + return LuaTable.defaultEntry( key, value ); } - + + public static abstract class WeakSlot implements Slot { + + protected Object key; + protected Object value; + protected Slot next; + + protected WeakSlot(Object key, Object value, Slot next) { + this.key = key; + this.value = value; + this.next = next; + } + + public abstract int keyindex( int hashMask ); + + public abstract Slot set(LuaValue value); + + public StrongSlot first() { + LuaValue key = strongkey(); + LuaValue value = strongvalue(); + if ( key != null && value != null ) { + return new LuaTable.NormalEntry(key, value); + } else { + this.key = null; + this.value = null; + return null; + } + } + + public StrongSlot find(LuaValue key) { + StrongSlot first = first(); + return ( first != null ) ? first.find( key ) : null; + } + + public boolean keyeq(LuaValue key) { + StrongSlot first = first(); + return ( first != null ) && first.keyeq( key ); + } + + public Slot rest() { + return next; + } + + public int arraykey(int max) { + // Integer keys can never be weak. + return 0; + } + + public Slot set(StrongSlot target, LuaValue value) { + LuaValue key = strongkey(); + if ( key != null && target.find( key ) != null ) { + return set( value ); + } else if ( key != null ) { + // Our key is still good. + next = next.set( target, value ); + return this; + } else { + // our key was dropped, remove ourselves from the chain. + return next.set( target, value ); + } + } + + public Slot add( Slot entry ) { + next = ( next != null ) ? next.add( entry ) : entry; + if ( strongkey() != null && strongvalue() != null ) { + return this; + } else { + return next; + } + } + + public Slot remove( StrongSlot target ) { + LuaValue key = strongkey(); + if ( key == null ) { + return next.remove( target ); + } else if ( target.keyeq( key ) ) { + this.value = null; + return this; + } else { + next = next.remove( target ); + return this; + } + } + + public Slot relink( Slot rest ) { + if ( strongkey() != null && strongvalue() != null ) { + if ( rest == null && this.next == null ) { + return this; + } else { + return copy( rest ); + } + } else { + return rest; + } + } + + public LuaValue strongkey() { + return (LuaValue) key; + } + + public LuaValue strongvalue() { + return (LuaValue) value; + } + + protected abstract WeakSlot copy( Slot next ); + } + + static class WeakKeySlot extends WeakSlot { + + private final int keyhash; + + protected WeakKeySlot( LuaValue key, LuaValue value, Slot next ) { + super(weaken(key), value, next); + keyhash = key.hashCode(); + } + + protected WeakKeySlot( WeakKeySlot copyFrom, Slot next ) { + super( copyFrom.key, copyFrom.value, next ); + this.keyhash = copyFrom.keyhash; + } + + public int keyindex( int mask ) { + return LuaTable.hashmod( keyhash, mask ); + } + + public Slot set(LuaValue value) { + this.value = value; + return this; + } + + public LuaValue strongkey() { + return strengthen( key ); + } + + protected WeakSlot copy( Slot rest ) { + return new WeakKeySlot( this, rest ); + } + } + + static class WeakValueSlot extends WeakSlot { + + protected WeakValueSlot( LuaValue key, LuaValue value, Slot next ) { + super( key, weaken(value), next); + } + + protected WeakValueSlot( WeakValueSlot copyFrom, Slot next ) { + super( copyFrom.key, copyFrom.value, next ); + } + + public int keyindex( int mask ) { + return LuaTable.hashSlot( strongkey(), mask ); + } + + public Slot set(LuaValue value) { + this.value = weaken(value); + return this; + } + + public LuaValue strongvalue() { + return strengthen( value ); + } + + protected WeakSlot copy(Slot next) { + return new WeakValueSlot( this, next ); + } + } + + static class WeakKeyAndValueSlot extends WeakSlot { + + private final int keyhash; + + protected WeakKeyAndValueSlot( LuaValue key, LuaValue value, Slot next ) { + super( weaken(key), weaken(value), next ); + keyhash = key.hashCode(); + } + + protected WeakKeyAndValueSlot(WeakKeyAndValueSlot copyFrom, Slot next) { + super( copyFrom.key, copyFrom.value, next ); + keyhash = copyFrom.keyhash; + } + + public int keyindex( int hashMask ) { + return LuaTable.hashmod( keyhash, hashMask ); + } + + public Slot set(LuaValue value) { + this.value = weaken(value); + return this; + } + + public LuaValue strongkey() { + return strengthen( key ); + } + + public LuaValue strongvalue() { + return strengthen( value ); + } + + protected WeakSlot copy( Slot next ) { + return new WeakKeyAndValueSlot( this, next ); + } + } + /** * Self-sent message to convert a value to its weak counterpart * @param value value to convert * @return {@link LuaValue} that is a strong or weak reference, depending on type of {@code value} */ - LuaValue weaken( LuaValue value ) { + protected static LuaValue weaken( LuaValue value ) { switch ( value.type() ) { case LuaValue.TFUNCTION: case LuaValue.TTHREAD: @@ -118,108 +316,28 @@ public class WeakTable extends LuaTable { return value; } } - - public void rawset( int key, LuaValue value ) { - if ( weakvalues ) - value = weaken( value ); - super.rawset(key, value); - } - - public void rawset( LuaValue key, LuaValue value ) { - if ( weakvalues ) - value = weaken( value ); - if ( weakkeys ) { - switch ( key.type() ) { - case LuaValue.TFUNCTION: - case LuaValue.TTHREAD: - case LuaValue.TTABLE: - case LuaValue.TUSERDATA: - key = value = new WeakEntry(this, key, value); - break; - default: - break; - } - } - super.rawset(key, value); - } - - - public LuaValue rawget( int key ) { - return super.rawget(key).strongvalue(); - } - - public LuaValue rawget( LuaValue key ) { - return super.rawget(key).strongvalue(); - } - - /** Get the hash value for a key - * key the key to look up - * */ - protected LuaValue hashget(LuaValue key) { - if ( hashEntries > 0 ) { - int i = hashFindSlot(key); - if ( hashEntries == 0 ) - return NIL; - LuaValue v = hashValues[i]; - return v!=null? v: NIL; - } - return NIL; - } - - - // override to remove values for weak keys as we search - public int hashFindSlot(LuaValue key) { - int i = ( key.hashCode() & 0x7FFFFFFF ) % hashKeys.length; - LuaValue k; - while ( ( k = hashKeys[i] ) != null ) { - if ( k.isweaknil() ) { - hashClearSlot(i); - if ( hashEntries == 0 ) - return 0; - } - else { - if ( k.raweq(key.strongkey()) ) - return i; - i = ( i + 1 ) % hashKeys.length; - } - } - return i; - } /** - * Get the next element after a particular key in the table - * @return key,value or nil + * Unwrap a LuaValue from a WeakReference and/or WeakUserdata. + * @param ref reference to convert + * @return LuaValue or null + * @see #weaken(LuaValue) */ - public Varargs next( LuaValue key ) { - while ( true ) { - Varargs n = super.next(key); - LuaValue k = n.arg1(); - if ( k.isnil() ) - return NIL; - LuaValue ks = k.strongkey(); - LuaValue vs = n.arg(2).strongvalue(); - if ( ks.isnil() || vs.isnil() ) { - super.rawset(k, NIL); - } else { - return varargsOf(ks,vs); - } + protected static LuaValue strengthen(Object ref) { + if ( ref instanceof WeakReference ) { + ref = ((WeakReference) ref).get(); } - } - - // ----------------- sort support ----------------------------- - public void sort(final LuaValue comparator) { - super.sort( new TwoArgFunction() { - public LuaValue call(LuaValue arg1, LuaValue arg2) { - return comparator.call( arg1.strongvalue(), arg2.strongvalue() ); - } - } ); + if ( ref instanceof WeakValue ) { + return ((WeakValue) ref).strongvalue(); + } + return (LuaValue) ref; } /** Internal class to implement weak values. * @see WeakTable */ static class WeakValue extends LuaValue { - final WeakReference ref; + WeakReference ref; protected WeakValue(LuaValue value) { ref = new WeakReference(value); @@ -234,26 +352,22 @@ public class WeakTable extends LuaTable { illegal("typename","weak value"); return null; } - + public String toString() { return "weak<"+ref.get()+">"; } - + public LuaValue strongvalue() { Object o = ref.get(); - return o!=null? (LuaValue)o: NIL; + return (LuaValue)o; } - + public boolean raweq(LuaValue rhs) { Object o = ref.get(); return o!=null && rhs.raweq((LuaValue)o); } - - public boolean isweaknil() { - return ref.get() == null; - } } - + /** Internal class to implement weak userdata values. * @see WeakTable */ @@ -266,79 +380,34 @@ public class WeakTable extends LuaTable { ob = new WeakReference(value.touserdata()); mt = value.getmetatable(); } - + public LuaValue strongvalue() { Object u = ref.get(); if ( u != null ) return (LuaValue) u; Object o = ob.get(); - return o!=null? userdataOf(o,mt): NIL; - } - - public boolean raweq(LuaValue rhs) { - if ( ! rhs.isuserdata() ) - return false; - LuaValue v = (LuaValue) ref.get(); - if ( v != null && v.raweq(rhs) ) - return true; - return rhs.touserdata() == ob.get(); - } - - public boolean isweaknil() { - return ob.get() == null || ref.get() == null; + if ( o != null ) { + LuaValue ud = LuaValue.userdataOf(o,mt); + ref = new WeakReference(ud); + return ud; + } else { + return null; + } } } - /** Internal class to implement weak table entries. - * @see WeakTable - */ - static final class WeakEntry extends LuaValue { - final LuaValue weakkey; - LuaValue weakvalue; - final int keyhash; + public LuaValue wrap(LuaValue value) { + return weakvalues ? weaken( value ) : value; + } - private WeakEntry(WeakTable table, LuaValue key, LuaValue weakvalue) { - this.weakkey = table.weaken(key); - this.keyhash = key.hashCode(); - this.weakvalue = weakvalue; - } - - public LuaValue strongkey() { - return weakkey.strongvalue(); - } - - // when looking up the value, look in the keys metatable - public LuaValue strongvalue() { - LuaValue key = weakkey.strongvalue(); - if ( key.isnil() ) - return weakvalue = NIL; - return weakvalue.strongvalue(); - } - - public int type() { - return TNONE; - } - - public String typename() { - illegal("typename","weak entry"); - return null; - } - - public String toString() { - return "weak<"+weakkey.strongvalue()+","+strongvalue()+">"; - } - - public int hashCode() { - return keyhash; - } - - public boolean raweq(LuaValue rhs) { - //return rhs.raweq(weakkey.strongvalue()); - return weakkey.raweq(rhs); - } - - public boolean isweaknil() { - return weakkey.isweaknil() || weakvalue.isweaknil(); + public LuaValue arrayget(LuaValue[] array, int index) { + LuaValue value = array[index]; + if (value != null) { + value = strengthen(value); + if (value == null) { + array[index] = null; + } } + return value; } } diff --git a/test/junit/org/luaj/vm2/TableHashTest.java b/test/junit/org/luaj/vm2/TableHashTest.java index 1f643896..b36b84cc 100644 --- a/test/junit/org/luaj/vm2/TableHashTest.java +++ b/test/junit/org/luaj/vm2/TableHashTest.java @@ -50,7 +50,7 @@ public class TableHashTest extends TestCase { String[] keys = { "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "wxy", "z01", "cd", "ef", "g", "hi", "jk", "lm", "no", "pq", "rs", }; - int[] capacities = { 0, 2, 4, 4, 7, 7, 7, 10, 10, 14, 14, 14, 14, 19, 19, 19, 19, 25, 25, 25 }; + int[] capacities = { 0, 2, 2, 4, 4, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 32 }; for ( int i = 0; i < keys.length; ++i ) { assertEquals( capacities[i], t.getHashLength() ); String si = "Test Value! "+i; @@ -242,4 +242,79 @@ public class TableHashTest extends TestCase { assertEquals( LuaValue.valueOf("bbb"), t.next(LuaValue.valueOf("aa")).arg(2) ); assertEquals( LuaValue.NIL, t.next(LuaValue.valueOf("bb")) ); } + + public void testLoopWithRemoval() { + final LuaTable t = new_Table(); + + t.set( LuaValue.valueOf(1), LuaValue.valueOf("1") ); + t.set( LuaValue.valueOf(3), LuaValue.valueOf("3") ); + t.set( LuaValue.valueOf(8), LuaValue.valueOf("4") ); + t.set( LuaValue.valueOf(17), LuaValue.valueOf("5") ); + t.set( LuaValue.valueOf(26), LuaValue.valueOf("6") ); + t.set( LuaValue.valueOf(35), LuaValue.valueOf("7") ); + t.set( LuaValue.valueOf(42), LuaValue.valueOf("8") ); + t.set( LuaValue.valueOf(60), LuaValue.valueOf("10") ); + t.set( LuaValue.valueOf(63), LuaValue.valueOf("11") ); + + Varargs entry = t.next(LuaValue.NIL); + while ( !entry.isnil(1) ) { + LuaValue k = entry.arg1(); + LuaValue v = entry.arg(2); + if ( ( k.toint() & 1 ) == 0 ) { + t.set( k, LuaValue.NIL ); + } + entry = t.next(k); + } + + int numEntries = 0; + entry = t.next(LuaValue.NIL); + while ( !entry.isnil(1) ) { + LuaValue k = entry.arg1(); + // Only odd keys should remain + assertTrue( ( k.toint() & 1 ) == 1 ); + numEntries++; + entry = t.next(k); + } + assertEquals( 5, numEntries ); + } + + public void testLoopWithRemovalAndSet() { + final LuaTable t = new_Table(); + + t.set( LuaValue.valueOf(1), LuaValue.valueOf("1") ); + t.set( LuaValue.valueOf(3), LuaValue.valueOf("3") ); + t.set( LuaValue.valueOf(8), LuaValue.valueOf("4") ); + t.set( LuaValue.valueOf(17), LuaValue.valueOf("5") ); + t.set( LuaValue.valueOf(26), LuaValue.valueOf("6") ); + t.set( LuaValue.valueOf(35), LuaValue.valueOf("7") ); + t.set( LuaValue.valueOf(42), LuaValue.valueOf("8") ); + t.set( LuaValue.valueOf(60), LuaValue.valueOf("10") ); + t.set( LuaValue.valueOf(63), LuaValue.valueOf("11") ); + + Varargs entry = t.next(LuaValue.NIL); + Varargs entry2 = entry; + while ( !entry.isnil(1) ) { + LuaValue k = entry.arg1(); + LuaValue v = entry.arg(2); + if ( ( k.toint() & 1 ) == 0 ) { + t.set( k, LuaValue.NIL ); + } else { + t.set( k, v.tonumber() ); + entry2 = t.next(entry2.arg1()); + } + entry = t.next(k); + } + + int numEntries = 0; + entry = t.next(LuaValue.NIL); + while ( !entry.isnil(1) ) { + LuaValue k = entry.arg1(); + // Only odd keys should remain + assertTrue( ( k.toint() & 1 ) == 1 ); + assertTrue( entry.arg(2).type() == LuaValue.TNUMBER ); + numEntries++; + entry = t.next(k); + } + assertEquals( 5, numEntries ); + } } diff --git a/test/junit/org/luaj/vm2/TableTest.java b/test/junit/org/luaj/vm2/TableTest.java index 785981fa..5f33496b 100644 --- a/test/junit/org/luaj/vm2/TableTest.java +++ b/test/junit/org/luaj/vm2/TableTest.java @@ -37,7 +37,7 @@ public class TableTest extends TestCase { } private int keyCount(LuaTable t) { - return keys(t).length; + return keys(t).length; } private LuaValue[] keys(LuaTable t) { @@ -88,8 +88,9 @@ public class TableTest extends TestCase { assertEquals(LuaInteger.valueOf(i), t.get(i)); } - assertTrue( t.getArrayLength() >= 0 && t.getArrayLength() <= 2 ); - assertTrue( t.getHashLength() >= 4 ); + assertTrue( t.getArrayLength() >= 3 ); + assertTrue( t.getArrayLength() <= 12 ); + assertTrue( t.getHashLength() <= 3 ); } public void testOutOfOrderIntegerKeyInsertion() { @@ -105,12 +106,8 @@ public class TableTest extends TestCase { } // Ensure capacities make sense - assertTrue( t.getArrayLength() >= 0 ); - assertTrue( t.getArrayLength() <= 6 ); - - assertTrue( t.getHashLength() >= 16 ); - assertTrue( t.getHashLength() <= 64 ); - + assertEquals( 32, t.getArrayLength() ); + assertEquals( 0, t.getHashLength() ); } public void testStringAndIntegerKeys() { @@ -122,8 +119,8 @@ public class TableTest extends TestCase { t.set( str, LuaInteger.valueOf( i ) ); } - assertTrue( t.getArrayLength() >= 9 ); // 1, 2, ..., 9 - assertTrue( t.getArrayLength() <= 18 ); + assertTrue( t.getArrayLength() >= 8 ); // 1, 2, ..., 9 + assertTrue( t.getArrayLength() <= 16 ); assertTrue( t.getHashLength() >= 11 ); // 0, "0", "1", ..., "9" assertTrue( t.getHashLength() <= 33 ); @@ -222,6 +219,41 @@ public class TableTest extends TestCase { assertEquals( 0, keyCount(t) ); } + public void testShrinkNonPowerOfTwoArray() { + LuaTable t = new_Table(6, 2); + + t.set(1, "one"); + t.set(2, "two"); + t.set(3, "three"); + t.set(4, "four"); + t.set(5, "five"); + t.set(6, "six"); + + t.set("aa", "aaa"); + t.set("bb", "bbb"); + + t.set(3, LuaValue.NIL); + t.set(4, LuaValue.NIL); + t.set(6, LuaValue.NIL); + + t.set("cc", "ccc"); + t.set("dd", "ddd"); + + assertEquals(4, t.getArrayLength()); + assertTrue(t.getHashLength() < 10); + assertEquals(5, t.hashEntries); + assertEquals("one", t.get(1).tojstring()); + assertEquals("two", t.get(2).tojstring()); + assertEquals(LuaValue.NIL, t.get(3)); + assertEquals(LuaValue.NIL, t.get(4)); + assertEquals("five", t.get(5).tojstring()); + assertEquals(LuaValue.NIL, t.get(6)); + assertEquals("aaa", t.get("aa").tojstring()); + assertEquals("bbb", t.get("bb").tojstring()); + assertEquals("ccc", t.get("cc").tojstring()); + assertEquals("ddd", t.get("dd").tojstring()); + } + public void testInOrderLuaLength() { LuaTable t = new_Table(); @@ -350,5 +382,4 @@ public class TableTest extends TestCase { compareLists(t,v); } } - } diff --git a/test/junit/org/luaj/vm2/WeakTableTest.java b/test/junit/org/luaj/vm2/WeakTableTest.java index d6f90f91..92eca050 100644 --- a/test/junit/org/luaj/vm2/WeakTableTest.java +++ b/test/junit/org/luaj/vm2/WeakTableTest.java @@ -55,8 +55,8 @@ abstract public class WeakTableTest extends TableTest { } public static class WeakValueTableTest extends WeakTableTest { - protected LuaTable new_Table() { return new WeakTable(false, true); } - protected LuaTable new_Table(int n,int m) { return new WeakTable(false, true); } + protected LuaTable new_Table() { return WeakTable.make(false, true); } + protected LuaTable new_Table(int n,int m) { return WeakTable.make(false, true); } public void testWeakValuesTable() { LuaTable t = new_Table(); @@ -64,17 +64,21 @@ abstract public class WeakTableTest extends TableTest { Object obj = new Object(); LuaTable tableValue = new LuaTable(); LuaString stringValue = LuaString.valueOf("this is a test"); + LuaTable tableValue2 = new LuaTable(); t.set("table", tableValue); t.set("userdata", LuaValue.userdataOf(obj, null)); t.set("string", stringValue); - t.set("string2", LuaString.valueOf("another string")); - assertTrue("table must have at least 4 elements", t.getHashLength() > 4); + t.set("string2", LuaValue.valueOf("another string")); + t.set(1, tableValue2); + assertTrue("table must have at least 4 elements", t.getHashLength() >= 4); + assertTrue("array part must have 1 element", t.getArrayLength() >= 1); // check that table can be used to get elements assertEquals(tableValue, t.get("table")); assertEquals(stringValue, t.get("string")); assertEquals(obj, t.get("userdata").checkuserdata()); + assertEquals(tableValue2, t.get(1)); // nothing should be collected, since we have strong references here collectGarbage(); @@ -83,10 +87,12 @@ abstract public class WeakTableTest extends TableTest { assertEquals(tableValue, t.get("table")); assertEquals(stringValue, t.get("string")); assertEquals(obj, t.get("userdata").checkuserdata()); + assertEquals(tableValue2, t.get(1)); // drop our strong references obj = null; tableValue = null; + tableValue2 = null; stringValue = null; // Garbage collection should cause weak entries to be dropped. @@ -95,16 +101,17 @@ abstract public class WeakTableTest extends TableTest { // check that they are dropped assertEquals(LuaValue.NIL, t.get("table")); assertEquals(LuaValue.NIL, t.get("userdata")); + assertEquals(LuaValue.NIL, t.get(1)); assertFalse("strings should not be in weak references", t.get("string").isnil()); } } public static class WeakKeyTableTest extends WeakTableTest { - protected LuaTable new_Table() { return new WeakTable(true, false); } - protected LuaTable new_Table(int n,int m) { return new WeakTable(true, false); } + protected LuaTable new_Table() { return WeakTable.make(true, false); } + protected LuaTable new_Table(int n,int m) { return WeakTable.make(true, false); } public void testWeakKeysTable() { - LuaTable t = new WeakTable(true, false); + LuaTable t = WeakTable.make(true, false); LuaValue key = LuaValue.userdataOf(new MyData(111)); LuaValue val = LuaValue.userdataOf(new MyData(222)); @@ -137,7 +144,7 @@ abstract public class WeakTableTest extends TableTest { } public void testNext() { - LuaTable t = new WeakTable(true, true); + LuaTable t = WeakTable.make(true, true); LuaValue key = LuaValue.userdataOf(new MyData(111)); LuaValue val = LuaValue.userdataOf(new MyData(222)); @@ -167,11 +174,11 @@ abstract public class WeakTableTest extends TableTest { } public static class WeakKeyValueTableTest extends WeakTableTest { - protected LuaTable new_Table() { return new WeakTable(true, true); } - protected LuaTable new_Table(int n,int m) { return new WeakTable(true, true); } - + protected LuaTable new_Table() { return WeakTable.make(true, true); } + protected LuaTable new_Table(int n,int m) { return WeakTable.make(true, true); } + public void testWeakKeysValuesTable() { - LuaTable t = new WeakTable(true, true); + LuaTable t = WeakTable.make(true, true); LuaValue key = LuaValue.userdataOf(new MyData(111)); LuaValue val = LuaValue.userdataOf(new MyData(222)); @@ -224,7 +231,7 @@ abstract public class WeakTableTest extends TableTest { } public void testReplace() { - LuaTable t = new WeakTable(true, true); + LuaTable t = WeakTable.make(true, true); LuaValue key = LuaValue.userdataOf(new MyData(111)); LuaValue val = LuaValue.userdataOf(new MyData(222));