New implementation for Lua tables. Does not use Vector or Hashtable, instead

uses plain Java arrays directly to keep heap allocation to a minimum.
Includes some unit tests that seem to indicate the basic operations are
correct. However, the following things are not implemented:

* Shrinking the capacity when elements are removed
* Optimal storage of each element in array vs. hash when entries are
  added out of order. A junit test case is there for this.
This commit is contained in:
Ian Farmer
2007-09-03 00:38:38 +00:00
parent 79d5642bfd
commit b8571b93f9
8 changed files with 629 additions and 174 deletions

View File

@@ -103,10 +103,7 @@ public class LuaCompat extends LFunction {
LValue k = vm.getArg(1);
LValue result = LNil.NIL;
if ( t instanceof LTable ) {
LValue v = (LValue) ( (LTable) t ).rawGet( k );
if ( v != null ) {
result = v;
}
result = ( (LTable) t ).get( k );
}
vm.setResult( result );
} break;

View File

@@ -18,6 +18,12 @@ public class LDouble extends LNumber {
return String.valueOf(m_value);
}
public boolean isInteger() {
// Cast to int and then back to double and see if the value
// survives the round trip.
return ( (double) ( (int) m_value ) ) == m_value;
}
// binary operations on integers, first dispatch
public LValue luaBinOpUnknown(int opcode, LValue lhs) {
return lhs.luaBinOpDouble( opcode, this.m_value );

View File

@@ -10,8 +10,12 @@ public class LInteger extends LNumber {
this.m_value = value;
}
public int hashCode() {
return m_value;
public final int hashCode() {
return hashCodeOf( m_value );
}
public static int hashCodeOf( int v ) {
return v;
}
public int luaAsInt() {
@@ -22,6 +26,10 @@ public class LInteger extends LNumber {
return String.valueOf(m_value);
}
public boolean isInteger() {
return true;
}
// binary operations on integers, first dispatch
public LValue luaBinOpUnknown(int opcode, LValue lhs) {
return lhs.luaBinOpInteger( opcode, this.m_value );

View File

@@ -19,4 +19,9 @@ public class LNumber extends LValue {
return TYPE_NAME;
}
/**
* Returns false by default for non-LNumbers, but subclasses of LNumber must
* override.
*/
public abstract boolean isInteger();
}

View File

@@ -1,11 +1,21 @@
package lua.value;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import lua.Lua;
import lua.VM;
/**
* Simple implementation of table structure for Lua VM. Maintains both an array
* part and a hash part. Does not attempt to acheive the same performance as the
* C version.
*
* Java code can put values in the table or get values out (bypassing the
* metatable, if there is one) using put() and get(). There are specializations
* of put() and get() for integers and Strings to avoid allocating wrapper
* objects when possible.
*
* remove() methods are private: setting a key's value to nil is the correct way
* to remove an entry from the table.
*/
public class LTable extends LValue {
public static final LString TYPE_NAME = new LString("table");
@@ -16,191 +26,243 @@ public class LTable extends LValue {
/** Metatable tag for intercepting table sets */
private static final LString TM_NEWINDEX = new LString("__newindex");
public Hashtable m_hash = new Hashtable();
/**
* Zero-length array to use instead of null, so that we don't need to
* check for null everywhere.
*/
private static final LValue[] EMPTY_ARRAY = new LValue[0];
private Vector m_vector; // if non-null then size() > 0
/**
* Minimum legal capacity for the hash portion. Note that the hash portion
* must never be filled to capacity or findSlot() will run forever.
*/
private static final int MIN_HASH_CAPACITY = 2;
// stride and offset are needed only for the
// implementation where m_vector stores keys
// {offset, stride + offset, ..., (size-1)*stride + offset}
// where size = m_vector.size()
//
// private int stride = 1; // always non-0; if m_vector.size() == 1 then stride == 1
// private int offset; // if m_vector.size() == 1 then offset is the single integer key
/**
* Array of keys in the hash part. When there is no hash part this is null.
* Elements of m_hashKeys are never LNil.NIL - they are null to indicate
* the hash slot is empty and some non-null, non-nil value otherwise.
*/
private LValue[] m_hashKeys;
/**
* Values in the hash part. Must be null when m_hashKeys is null and equal
* in size otherwise.
*/
private LValue[] m_hashValues;
/**
* m_hashEntries is the number of slots that are used. Must always be less
* than m_hashKeys.length.
*/
private int m_hashEntries;
/**
* Array of values to store the "array part" of the table, that is the
* entries with positive integer keys. Elements must never be null: "empty"
* slots are set to LNil.NIL.
*/
private LValue[] m_vector;
/**
* Number of values in m_vector that non-nil.
*/
private int m_arrayEntries;
private LTable m_metatable;
/** Construct an empty LTable with no initial capacity. */
public LTable() {
m_vector = EMPTY_ARRAY;
}
/**
* Construct an empty LTable that is expected to contain entries with keys
* in the range 1 .. narray and nhash non-integer keys.
*/
public LTable( int narray, int nhash ) {
if ( nhash > 0 ) {
// Allocate arrays 25% bigger than nhash to account for load factor.
final int capacity = Math.max( nhash + ( nhash >> 2 ), nhash + 1 );
m_hashKeys = new LValue[capacity];
m_hashValues = new LValue[capacity];
}
/** Utility method for putting a string-keyed value directly, typically for initializing a table */
public void put(String key, LValue value) {
m_hash.put( new LString(key), value );
}
public void rawSet(LValue key, LValue val) {
if (key instanceof LInteger) {
int iKey = ((LInteger) key).luaAsInt() - 1;
// implementation where m_vector stores keys
// {1, ..., size}
// where size = m_vector.size()
//
if (m_vector == null) {
if (iKey == 0) {
m_vector = new Vector();
m_vector.addElement(val);
return;
}
} else if (iKey >= 0) {
int size = m_vector.size();
if (iKey < size) {
m_vector.setElementAt(val, iKey);
return;
} else if (iKey == size) {
m_vector.addElement(val);
return;
m_vector = new LValue[narray];
for ( int i = 0; i < narray; ++i ) {
m_vector[i] = LNil.NIL;
}
}
/*
// implementation where m_vector stores keys
// {offset, stride + offset, ..., (size-1)*stride + offset}
// where size = m_vector.size()
//
if (m_vector == null) {
offset = iKey;
m_vector = new Vector();
m_vector.add(val);
return;
/**
* Return total number of keys mapped to non-nil values. Not to be confused
* with luaLength, which returns some number n such that the value at n+1 is
* nil.
*/
public int size() {
return m_hashEntries + m_arrayEntries;
}
/**
* Generic put method for all types of keys, but does not use the metatable.
*/
public void put( LValue key, LValue val ) {
if ( key.isInteger() ) {
// call the integer-specific put method
put( key.luaAsInt(), val );
} else if ( val == null || val == LNil.NIL ) {
// Remove the key if the value is nil. This comes after the check
// for an integer key so that values are properly removed from
// the array part.
remove( key );
} else {
int size = m_vector.size();
int multiple = iKey - offset;
if (multiple >= 0) {
int i = multiple / stride;
if ((i < size) && (i * stride == multiple)) {
m_vector.set(i, val);
return;
}
} else if (size == 1) {
stride = iKey - offset;
m_vector.add(val);
return;
} else if (iKey == stride * size + offset) {
m_vector.add(val);
if ( checkLoadFactor() )
rehash();
int slot = findSlot( key );
if ( fillHashSlot( slot, val ) )
return;
m_hashKeys[slot] = key;
}
}
/**
* Utility method for putting a string-keyed value directly, typically for
* initializing a table. Bypasses the metatable, if any.
*/
}
m_hash.put( key, val );
/* TODO: this is old incorrect code, kept here until metatables are fixed
if ( m_metatable != null ) {
if ( ! m_hash.containsKey(key) ) {
LValue event = (LValue) m_metatable.m_hash.get( TM_NEWINDEX );
if ( event != null && event != LNil.NIL ) {
event.luaSetTable( vm, table, key, val );
public void put( String key, LValue value ) {
if (value == null || value == LNil.NIL) {
remove( key );
return;
}
}
}
*/
if (checkLoadFactor())
rehash();
int slot = findSlot( key );
if (fillHashSlot( slot, value ))
return;
m_hashKeys[slot] = new LString( key );
}
/**
* Method for putting an integer-keyed value. Bypasses the metatable, if
* any.
*/
public void put( int key, LValue value ) {
if (value == null || value == LNil.NIL) {
remove( key );
return;
}
if ( key > 0 ) {
final int index = key - 1;
for ( ;; ) {
if ( index < m_vector.length ) {
if ( m_vector[index] == LNil.NIL ) {
++m_arrayEntries;
}
m_vector[index] = value;
return;
} else if ( index < ( m_arrayEntries + 1 ) * 2 ) {
resize( ( m_arrayEntries + 1 ) * 2 );
} else {
break;
}
}
}
// No room in array part, use hash part instead.
if ( checkLoadFactor() )
rehash();
int slot = findSlot( key );
if ( fillHashSlot( slot, value ) )
return;
m_hashKeys[ slot ] = new LInteger( key );
}
/**
* Utility method to directly get the value in a table, without metatable
* calls. Must never return null, use LNil.NIL instead.
*/
public LValue get( LValue key ) {
if ( m_vector.length > 0 && key.isInteger() ) {
final int index = key.luaAsInt() - 1;
if ( index >= 0 && index < m_vector.length ) {
return m_vector[index];
}
}
if ( m_hashKeys == null )
return LNil.NIL;
int slot = findSlot( key );
return ( m_hashKeys[slot] != null ) ? m_hashValues[slot] : LNil.NIL;
}
/** Utility method for retrieving an integer-keyed value */
public LValue get( int key ) {
if ( key > 0 && key <= m_vector.length ) {
return m_vector[key - 1];
}
int slot = findSlot( key );
return ( m_hashKeys[slot] != null ) ? m_hashValues[slot] : LNil.NIL;
}
/**
* Return true if the table contains an entry with the given key, false if
* not. Ignores the metatable.
*/
public boolean containsKey( LValue key ) {
if (m_vector != null) {
if (key instanceof LInteger) {
int iKey = ((LInteger) key).luaAsInt();
if ((iKey >= 1) && (iKey <= m_vector.size())) {
return m_vector.elementAt(iKey-1) != LNil.NIL;
if ( m_vector.length > 0 && key.isInteger() ) {
final int index = key.luaAsInt() - 1;
if ( index >= 0 && index < m_vector.length ) {
final LValue v = m_vector[index];
return v != LNil.NIL;
}
}
}
return m_hash.containsKey( key );
}
/** Utility method to directly get the value in a table, without metatable calls */
public LValue rawGet(LValue key) {
if (m_vector != null) {
if (key instanceof LInteger) {
int iKey = ((LInteger) key).luaAsInt() - 1;
// implementation where m_vector stores keys
// {0, ..., size-1}
// where size = m_vector.size()
//
if ((iKey >= 0) && (iKey < m_vector.size())) {
return (LValue) m_vector.elementAt(iKey);
}
/*
// implementation where m_vector stores keys
// {offset, stride + offset, ..., (size-1)*stride + offset}
// where size = m_vector.size()
//
int multiple = iKey - offset;
if (multiple >= 0) {
int i = multiple / stride;
if ((i < m_vector.size()) && (i * stride == multiple)) {
vm.push((LValue) m_vector.get(i));
return;
}
}
*/
}
}
LValue v = (LValue) m_hash.get(key);
return ( v != null ) ? v : LNil.NIL;
if ( m_hashKeys == null )
return false;
final int slot = findSlot( key );
return m_hashKeys[ slot ] != null;
}
public void luaGetTable(VM vm, LValue table, LValue key) {
LValue v = rawGet(key);
LValue v = get(key);
if ( v == LNil.NIL && m_metatable != null ) {
LValue event = m_metatable.rawGet( TM_INDEX );
LValue event = m_metatable.get( TM_INDEX );
if ( event != null && event != LNil.NIL ) {
event.luaGetTable( vm, table, key );
return;
}
}
// TODO: table is unused -- is this correct?
// stack.stack[base] = val;
vm.push(v!=null? v: LNil.NIL);
vm.push(v);
}
public void luaSetTable(VM vm, LValue table, LValue key, LValue val) {
if ( !containsKey( key ) && m_metatable != null ) {
LValue event = m_metatable.rawGet( TM_NEWINDEX );
LValue event = m_metatable.get( TM_NEWINDEX );
if ( event != null && event != LNil.NIL ) {
event.luaSetTable( vm, table, key, val );
return;
}
}
rawSet(key, val);
put(key, val);
}
/* TODO: why was this overridden in the first place?
public String toString() {
return m_hash.toString();
}
/**
* Return the "length" of this table. This will not return the same result
* as the C version in all cases, but that's ok because the length operation
* on a table where the integer keys are sparse is vaguely defined.
*/
public String luaAsString() {
return "table: "+id();
}
/** Built-in opcode LEN, for Strings and Tables */
public LValue luaLength() {
int hashSize = m_hash.size();
return new LInteger(
m_vector == null ? hashSize : hashSize + m_vector.size());
for ( int i = Math.max( 0, m_arrayEntries-1 ); i < m_vector.length; ++i ) {
if ( m_vector[i] != LNil.NIL &&
( i+1 == m_vector.length || m_vector[i+1] == LNil.NIL ) ) {
return new LInteger( i+1 );
}
}
return new LInteger( 0 );
}
/** Valid for tables */
@@ -213,41 +275,276 @@ public class LTable extends LValue {
this.m_metatable = (LTable) metatable;
}
/** Valid for tables */
public LValue luaPairs() {
return new LTableIterator(this);
}
/** Iterator for tables */
private static final class LTableIterator extends LFunction {
private final LTable t;
private final Enumeration e;
private int i;
private LTableIterator(LTable t) {
this.t = t;
this.e = t.m_hash.keys();
this.i = (t.m_vector == null) ? -1 : 0;
}
// perform a lua call
public boolean luaStackCall(VM vm) {
vm.setResult();
if ((i >= 0) && (i < t.m_vector.size())) {
vm.push(new LInteger(i+1));
vm.push((LValue) t.m_vector.elementAt(i));
++i;
} else if ( e.hasMoreElements() ) {
LValue key = (LValue) e.nextElement();
vm.push( key );
vm.push((LValue) t.m_hash.get(key));
}
return false;
}
public String luaAsString() {
return "table: "+id();
}
public LString luaGetType() {
return TYPE_NAME;
}
/** Valid for tables */
public LValue luaPairs() {
return new LTableIterator(this);
}
/** Iterator for tables */
static final class LTableIterator extends LFunction {
private final LTable t;
private int arrayIndex;
private int hashIndex;
private LTableIterator(LTable t) {
this.t = t;
this.arrayIndex = 0;
this.hashIndex = 0;
}
// perform a lua call
public boolean luaStackCall(VM vm) {
vm.setResult();
int i;
while ( ( i = arrayIndex++ ) < t.m_vector.length ) {
if ( t.m_vector[i] != LNil.NIL ) {
vm.push( new LInteger( arrayIndex ) );
vm.push( t.m_vector[ i ] );
return false;
}
}
if ( t.m_hashKeys != null ) {
while ( ( i = hashIndex++ ) < t.m_hashKeys.length ) {
if ( t.m_hashKeys[i] != null ) {
vm.push( t.m_hashKeys[i] );
vm.push( t.m_hashValues[i] );
return false;
}
}
}
return false;
}
}
/** Remove the value in the table with the given integer key. */
private void remove( int key ) {
if ( key > 0 ) {
final int index = key - 1;
if ( index < m_vector.length ) {
if ( m_vector[ index ] != LNil.NIL ) {
--m_arrayEntries;
}
return;
}
}
if ( m_hashKeys != null ) {
int slot = findSlot( key );
clearSlot( slot );
}
}
private void remove( String key ) {
if ( m_hashKeys != null ) {
int slot = findSlot( key );
clearSlot( slot );
}
}
private void remove( LValue key ) {
if ( m_hashKeys != null ) {
int slot = findSlot( key );
clearSlot( slot );
}
}
private void clearSlot( int i ) {
if ( m_hashKeys[ i ] != null ) {
int j = i;
while ( m_hashKeys[ j = ( ( j + 1 ) % m_hashKeys.length ) ] != null ) {
final int k = hashToIndex( m_hashKeys[ j ].hashCode() );
if ( ( j > i && ( k <= i || k > j ) ) ||
( j < i && ( k <= i && k > j ) ) ) {
m_hashKeys[ i ] = m_hashKeys[ j ];
m_hashValues[ i ] = m_hashValues[ j ];
i = j;
}
}
--m_hashEntries;
m_hashKeys[ i ] = null;
m_hashValues[ i ] = null;
if ( m_hashEntries == 0 ) {
m_hashKeys = null;
m_hashValues = null;
}
}
}
private int findSlot( LValue key ) {
int i = hashToIndex( key.hashCode() );
// This loop is guaranteed to terminate as long as we never allow the
// table to get 100% full.
LValue k;
while ( ( k = m_hashKeys[i] ) != null &&
!key.luaBinCmpUnknown( Lua.OP_EQ, k ) ) {
i = ( i + 1 ) % m_hashKeys.length;
}
return i;
}
private int findSlot( String key ) {
// NOTE: currently LString uses the String's hashCode.
int i = hashToIndex( key.hashCode() );
// This loop is guaranteed to terminate as long as we never allow the
// table to get 100% full.
LValue k;
while ( ( k = m_hashKeys[i] ) != null &&
!k.luaBinCmpString( Lua.OP_EQ, key ) ) {
i = ( i + 1 ) % m_hashKeys.length;
}
return i;
}
private int findSlot( int key ) {
int i = hashToIndex( LInteger.hashCodeOf( key ) );
// This loop is guaranteed to terminate as long as we never allow the
// table to get 100% full.
LValue k;
while ( ( k = m_hashKeys[i] ) != null &&
!k.luaBinCmpInteger( Lua.OP_EQ, key ) ) {
i = ( i + 1 ) % m_hashKeys.length;
}
return i;
}
/**
* @return true if the given slot was already occupied, false otherwise.
*/
private boolean fillHashSlot( int slot, LValue value ) {
m_hashValues[ slot ] = value;
if ( m_hashKeys[ slot ] != null ) {
return true;
} else {
++m_hashEntries;
return false;
}
}
private int hashToIndex( int hash ) {
return ( hash & 0x7FFFFFFF ) % m_hashKeys.length;
}
/**
* Should be called before inserting a value into the hash.
*
* @return true if the hash portion of the LTable is at its capacity.
*/
private boolean checkLoadFactor() {
if ( m_hashKeys == null )
return true;
// Using a load factor of 2/3 because that is easy to compute without
// overflow or division.
final int hashCapacity = m_hashKeys.length;
return ( hashCapacity >> 1 ) >= ( hashCapacity - m_hashEntries );
}
private void rehash() {
final int oldCapacity = ( m_hashKeys != null ) ? m_hashKeys.length : 0;
final int newCapacity = ( oldCapacity > 0 ) ? 2 * oldCapacity : MIN_HASH_CAPACITY;
final LValue[] oldKeys = m_hashKeys;
final LValue[] oldValues = m_hashValues;
m_hashKeys = new LValue[ newCapacity ];
m_hashValues = new LValue[ newCapacity ];
for ( int i = 0; i < oldCapacity; ++i ) {
final LValue k = oldKeys[i];
if ( k != null ) {
final LValue v = oldValues[i];
final int slot = findSlot( k );
m_hashKeys[slot] = k;
m_hashValues[slot] = v;
}
}
}
private void resize( int newCapacity ) {
final int oldCapacity = m_vector.length;
LValue[] newVector = new LValue[ newCapacity ];
System.arraycopy( m_vector, 0, newVector, 0, Math.min( oldCapacity, newCapacity ) );
// We need to move keys from hash part to array part if array part is
// getting bigger, and from array part to hash part if array is getting
// smaller.
if ( newCapacity > oldCapacity ) {
if ( m_hashKeys != null ) {
for ( int i = oldCapacity; i < newCapacity; ++i ) {
int slot = findSlot( i+1 );
if ( m_hashKeys[ slot ] != null ) {
newVector[ i ] = m_hashValues[ slot ];
m_hashKeys[ i ] = null;
--m_hashEntries;
} else {
// Make sure all array-part values are initialized to nil
// so that we can just do one compare instead of two
// whenever we need to check if a slot is full or not.
newVector[ i ] = LNil.NIL;
}
}
} else {
for ( int i = oldCapacity; i < newCapacity; ++i ) {
newVector[ i ] = LNil.NIL;
}
}
} else {
for ( int i = newCapacity; i < oldCapacity; ++i ) {
LValue v = m_vector[i];
if ( v != LNil.NIL ) {
if (checkLoadFactor())
rehash();
final int slot = findSlot( i+1 );
m_hashKeys[ slot ] = new LInteger( i+1 );
m_hashValues[ slot ] = v;
++m_hashEntries;
}
}
}
m_vector = newVector;
}
// hooks for junit
int getHashCapacity() {
return ( m_hashKeys != null ) ? m_hashKeys.length : 0;
}
int getArrayCapacity() {
return m_vector.length;
}
LValue[] getKeys() {
LValue[] keys = new LValue[ m_arrayEntries + m_hashEntries ];
int out = 0;
for ( int i = 0; i < m_vector.length; ++i ) {
if ( m_vector[ i ] != LNil.NIL ) {
keys[ out++ ] = new LInteger( i + 1 );
}
}
if ( m_hashKeys != null ) {
for ( int i = 0; i < m_hashKeys.length; ++i ) {
if ( m_hashKeys[ i ] != null )
keys[ out++ ] = m_hashKeys[i];
}
}
return keys;
}
}

View File

@@ -19,6 +19,11 @@ public class LValue {
return true;
}
/** Return true if this value can be represented as an "int" */
public boolean isInteger() {
return false;
}
// perform a lua call, return true if the call is to a lua function, false
// if it ran to completion.
public boolean luaStackCall(VM vm) {

View File

@@ -78,7 +78,7 @@ public class StandardTest extends TestCase {
// relies on weak tables having their elements removed by
// the garbage collector. Until we implement that, remove the
// built-in collectgarbage function.
GlobalState.getGlobalsTable().rawSet( new LString("collectgarbage"), LNil.NIL );
GlobalState.getGlobalsTable().put( "collectgarbage", LNil.NIL );
StackState state = new StackState();
Closure c = new Closure( state, code );

View File

@@ -0,0 +1,137 @@
package lua.value;
import junit.framework.TestCase;
public class LTableTest extends TestCase {
public void testInOrderIntegerKeyInsertion() {
LTable t = new LTable();
for ( int i = 1; i <= 32; ++i ) {
t.put( i, new LString( "Test Value! "+i ) );
}
// Ensure capacities make sense
assertEquals( 0, t.getHashCapacity() );
assertTrue( t.getArrayCapacity() >= 32 );
assertTrue( t.getArrayCapacity() <= 64 );
// Ensure all keys are still there.
for ( int i = 1; i <= 32; ++i ) {
assertEquals( "Test Value! " + i, t.get( i ).luaAsString() );
}
}
public void testOutOfOrderIntegerKeyInsertion() {
LTable t = new LTable();
for ( int i = 32; i > 0; --i ) {
t.put( i, new LString( "Test Value! "+i ) );
}
// Ensure capacities make sense
assertEquals( 0, t.getHashCapacity() );
assertTrue( t.getArrayCapacity() >= 32 );
assertTrue( t.getArrayCapacity() <= 64 );
// Ensure all keys are still there.
for ( int i = 1; i <= 32; ++i ) {
assertEquals( "Test Value! " + i, t.get( i ).luaAsString() );
}
}
public void testStringAndIntegerKeys() {
LTable t = new LTable();
for ( int i = 0; i < 10; ++i ) {
LString str = new LString( String.valueOf( i ) );
t.put( i, str );
t.put( str, new LInteger( i ) );
}
assertTrue( t.getArrayCapacity() >= 9 ); // 1, 2, ..., 9
assertTrue( t.getArrayCapacity() <= 18 );
assertTrue( t.getHashCapacity() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.getHashCapacity() <= 33 );
LValue[] keys = t.getKeys();
int intKeys = 0;
int stringKeys = 0;
assertEquals( 20, keys.length );
for ( int i = 0; i < keys.length; ++i ) {
LValue k = keys[i];
if ( k instanceof LInteger ) {
final int ik = k.luaAsInt();
assertTrue( ik >= 0 && ik < 10 );
final int mask = 1 << ik;
assertTrue( ( intKeys & mask ) == 0 );
intKeys |= mask;
} else if ( k instanceof LString ) {
final int ik = Integer.parseInt( k.luaAsString() );
assertEquals( String.valueOf( ik ), k.luaAsString() );
assertTrue( ik >= 0 && ik < 10 );
final int mask = 1 << ik;
assertTrue( "Key \""+ik+"\" found more than once", ( stringKeys & mask ) == 0 );
stringKeys |= mask;
} else {
fail( "Unexpected type of key found" );
}
}
assertEquals( 0x03FF, intKeys );
assertEquals( 0x03FF, stringKeys );
}
public void testBadInitialCapacity() {
LTable t = new LTable(0, 1);
t.put( "test", new LString("foo") );
t.put( "explode", new LString("explode") );
assertEquals( 2, t.size() );
}
public void testRemove1() {
LTable t = new LTable(0, 1);
t.put( "test", new LString("foo") );
t.put( "explode", LNil.NIL );
t.put( 42, LNil.NIL );
t.put( new LTable(), LNil.NIL );
t.put( "test", LNil.NIL );
assertEquals( 0, t.size() );
t.put( 10, new LInteger( 5 ) );
t.put( 10, LNil.NIL );
assertEquals( 0, t.size() );
}
public void testRemove2() {
LTable t = new LTable(0, 1);
t.put( "test", new LString("foo") );
t.put( "string", new LInteger( 10 ) );
assertEquals( 2, t.size() );
t.put( "string", LNil.NIL );
t.put( "three", new LDouble( 3.14 ) );
assertEquals( 2, t.size() );
t.put( "test", LNil.NIL );
assertEquals( 1, t.size() );
t.put( 10, new LInteger( 5 ) );
assertEquals( 2, t.size() );
t.put( 10, LNil.NIL );
assertEquals( 1, t.size() );
t.put( "three", LNil.NIL );
assertEquals( 0, t.size() );
}
}