Weak table implementation.

This commit is contained in:
James Roseborough
2008-04-08 21:55:16 +00:00
parent 720c6027d6
commit 09aa37a837
14 changed files with 501 additions and 270 deletions

View File

@@ -119,7 +119,7 @@ public class DumpState {
dumpInt(n); dumpInt(n);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
final LValue o = f.k[i]; final LValue o = f.k[i];
if (o == LNil.NIL) { if (o.isNil()) {
writer.write(Lua.LUA_TNIL); writer.write(Lua.LUA_TNIL);
// do nothing more // do nothing more
} else if (o instanceof LBoolean) { } else if (o instanceof LBoolean) {

View File

@@ -55,6 +55,7 @@ public class BaseLib extends LFunction {
"tostring", "tostring",
"unpack", "unpack",
"next", "next",
"_inext", // not public
}; };
private static final int INSTALL = 0; private static final int INSTALL = 0;
@@ -81,10 +82,16 @@ public class BaseLib extends LFunction {
private static final int TOSTRING = 21; private static final int TOSTRING = 21;
private static final int UNPACK = 22; private static final int UNPACK = 22;
private static final int NEXT = 23; private static final int NEXT = 23;
private static final int INEXT = 24;
private static LFunction next;
private static LFunction inext;
public static void install(LTable globals) { public static void install(LTable globals) {
for ( int i=1; i<NAMES.length; i++ ) for ( int i=1, n=NAMES.length; i<n; i++ )
globals.put( NAMES[i], new BaseLib(i) ); globals.put( NAMES[i], new BaseLib(i) );
next = new BaseLib(NEXT);
inext = new BaseLib(INEXT);
globals.put("_VERSION", new LString("Lua 5.1")); globals.put("_VERSION", new LString("Lua 5.1"));
} }
@@ -142,13 +149,23 @@ public class BaseLib extends LFunction {
vm.resettop(); vm.resettop();
break; break;
} }
case PAIRS: case IPAIRS:
case IPAIRS: { case PAIRS: {
checkargtype(vm,2,Lua.LUA_TTABLE); checkargtype(vm,2,Lua.LUA_TTABLE);
LTable v = vm.totable(2); LTable t = vm.totable(2);
LValue r = v.luaPairs(id==PAIRS);
vm.resettop(); vm.resettop();
vm.pushlvalue( r ); vm.pushjavafunction( (id==IPAIRS)? inext: next );
vm.pushlvalue( t );
vm.pushnil();
break;
}
case INEXT:
case NEXT: {
checkargtype(vm,2,Lua.LUA_TTABLE);
LTable t = vm.totable(2);
LValue v = vm.topointer(3);
vm.resettop();
t.next(vm,v,(id==INEXT));
break; break;
} }
case GETMETATABLE: { case GETMETATABLE: {
@@ -358,14 +375,6 @@ public class BaseLib extends LFunction {
vm.pushlvalue(list.get(k)); vm.pushlvalue(list.get(k));
break; break;
} }
case NEXT: {
checkargtype(vm,2,Lua.LUA_TTABLE);
LTable t = vm.totable(2);
LValue v = vm.topointer(3);
vm.resettop();
t.next(vm,v);
break;
}
default: default:
LuaState.vmerror( "bad base id" ); LuaState.vmerror( "bad base id" );
} }
@@ -472,7 +481,7 @@ public class BaseLib extends LFunction {
return false; return false;
} }
LValue v = vm.topointer(2); LValue v = vm.topointer(2);
if ( v == LNil.NIL ) if ( v.isNil() )
break; break;
LString s = v.luaAsString(); LString s = v.luaAsString();
s.write(baos, 0, s.length()); s.write(baos, 0, s.length());

View File

@@ -229,7 +229,7 @@ public class PackageLib extends LFunction {
e = fname.m_length; e = fname.m_length;
LString key = fname.substring(b, e); LString key = fname.substring(b, e);
LValue val = table.get(key); LValue val = table.get(key);
if ( val == LNil.NIL ) { /* no such field? */ if ( val.isNil() ) { /* no such field? */
LTable field = new LTable(); /* new table for field */ LTable field = new LTable(); /* new table for field */
table.put(key, field); table.put(key, field);
table = field; table = field;

View File

@@ -28,6 +28,7 @@ import org.luaj.vm.LFunction;
import org.luaj.vm.LString; import org.luaj.vm.LString;
import org.luaj.vm.LTable; import org.luaj.vm.LTable;
import org.luaj.vm.LValue; import org.luaj.vm.LValue;
import org.luaj.vm.LWeakTable;
import org.luaj.vm.LuaState; import org.luaj.vm.LuaState;

View File

@@ -29,6 +29,10 @@ public final class LNil extends LValue {
return luaGetTypeName(); return luaGetTypeName();
} }
public boolean isNil() {
return true;
}
public boolean toJavaBoolean() { public boolean toJavaBoolean() {
return false; return false;
} }

View File

@@ -57,34 +57,36 @@ public class LTable extends LValue {
* Elements of m_hashKeys are never LNil.NIL - they are null to indicate * 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. * the hash slot is empty and some non-null, non-nil value otherwise.
*/ */
private LValue[] m_hashKeys; protected LValue[] m_hashKeys;
/** /**
* Values in the hash part. Must be null when m_hashKeys is null and equal * Values in the hash part. Must be null when m_hashKeys is null and equal
* in size otherwise. * in size otherwise.
*/ */
private LValue[] m_hashValues; protected LValue[] m_hashValues;
/** /**
* m_hashEntries is the number of slots that are used. Must always be less * m_hashEntries is the number of slots that are used. Must always be less
* than m_hashKeys.length. * than m_hashKeys.length.
*/ */
private int m_hashEntries; protected int m_hashEntries;
/** /**
* Array of values to store the "array part" of the table, that is the * 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" * entries with positive integer keys. Elements must never be null: "empty"
* slots are set to LNil.NIL. * slots are set to LNil.NIL.
*/ */
private LValue[] m_vector; protected LValue[] m_vector;
/** /**
* Number of values in m_vector that non-nil. * Number of values in m_vector that non-nil.
*/ */
private int m_arrayEntries; protected int m_arrayEntries;
private LTable m_metatable; private LTable m_metatable;
private static final int INVALID_KEY_TO_NEXT = -2;
/** Construct an empty LTable with no initial capacity. */ /** Construct an empty LTable with no initial capacity. */
public LTable() { public LTable() {
m_vector = EMPTY_ARRAY; m_vector = EMPTY_ARRAY;
@@ -123,7 +125,7 @@ public class LTable extends LValue {
if ( key.isInteger() ) { if ( key.isInteger() ) {
// call the integer-specific put method // call the integer-specific put method
put( key.toJavaInt(), val ); put( key.toJavaInt(), val );
} else if ( val == null || val == LNil.NIL ) { } else if ( val == null || val.isNil() ) {
// Remove the key if the value is nil. This comes after the check // Remove the key if the value is nil. This comes after the check
// for an integer key so that values are properly removed from // for an integer key so that values are properly removed from
// the array part. // the array part.
@@ -159,7 +161,7 @@ public class LTable extends LValue {
* any. * any.
*/ */
public void put( int key, LValue value ) { public void put( int key, LValue value ) {
if (value == null || value == LNil.NIL) { if (value == null || value.isNil()) {
remove( key ); remove( key );
return; return;
} }
@@ -167,7 +169,7 @@ public class LTable extends LValue {
final int index = key - 1; final int index = key - 1;
for ( ;; ) { for ( ;; ) {
if ( index < m_vector.length ) { if ( index < m_vector.length ) {
if ( m_vector[index] == LNil.NIL ) { if ( m_vector[index].isNil() ) {
++m_arrayEntries; ++m_arrayEntries;
} }
m_vector[index] = value; m_vector[index] = value;
@@ -231,8 +233,7 @@ public class LTable extends LValue {
if ( m_vector.length > 0 && key.isInteger() ) { if ( m_vector.length > 0 && key.isInteger() ) {
final int index = key.toJavaInt() - 1; final int index = key.toJavaInt() - 1;
if ( index >= 0 && index < m_vector.length ) { if ( index >= 0 && index < m_vector.length ) {
final LValue v = m_vector[index]; return ! m_vector[index].isNil();
return v != LNil.NIL;
} }
} }
if ( m_hashKeys == null ) if ( m_hashKeys == null )
@@ -243,7 +244,7 @@ public class LTable extends LValue {
public void luaGetTable(LuaState vm, LValue table, LValue key) { public void luaGetTable(LuaState vm, LValue table, LValue key) {
LValue v = get(key); LValue v = get(key);
if ( v == LNil.NIL && m_metatable != null ) { if ( v.isNil() && m_metatable != null ) {
super.luaGetTable( vm, table, key ); super.luaGetTable( vm, table, key );
} else { } else {
vm.pushlvalue(v); vm.pushlvalue(v);
@@ -264,8 +265,8 @@ public class LTable extends LValue {
*/ */
public int luaLength() { public int luaLength() {
for ( int i = Math.max( 0, m_arrayEntries-1 ); i < m_vector.length; ++i ) { for ( int i = Math.max( 0, m_arrayEntries-1 ); i < m_vector.length; ++i ) {
if ( m_vector[i] != LNil.NIL && if ( ! m_vector[i].isNil() &&
( i+1 == m_vector.length || m_vector[i+1] == LNil.NIL ) ) { ( i+1 == m_vector.length || m_vector[i+1].isNil() ) ) {
return i+1; return i+1;
} }
} }
@@ -278,15 +279,24 @@ public class LTable extends LValue {
} }
/** Valid for tables */ /** Valid for tables */
public void luaSetMetatable(LValue metatable) { public LValue luaSetMetatable(LValue metatable) {
if ( m_metatable != null && m_metatable.containsKey(TM_METATABLE) ) if ( m_metatable != null && m_metatable.containsKey(TM_METATABLE) )
throw new LuaErrorException("cannot change a protected metatable"); throw new LuaErrorException("cannot change a protected metatable");
if ( metatable == null || metatable == LNil.NIL ) if ( metatable == null || metatable.isNil() )
this.m_metatable = null; this.m_metatable = null;
else if ( metatable.luaGetType() == Lua.LUA_TTABLE ) else if ( metatable.luaGetType() == Lua.LUA_TTABLE ) {
this.m_metatable = (LTable) metatable; LTable t = (LTable) metatable;
LValue m = t.get(TM_MODE);
if ( "v".equals(m.toJavaString()) ) {
LTable n = new LWeakTable(this);
n.m_metatable = t;
return n;
}
this.m_metatable = t;
}
else else
throw new LuaErrorException("nil or table expected, got "+metatable.luaGetTypeName()); throw new LuaErrorException("nil or table expected, got "+metatable.luaGetTypeName());
return null;
} }
public String toJavaString() { public String toJavaString() {
@@ -297,47 +307,6 @@ public class LTable extends LValue {
return Lua.LUA_TTABLE; return Lua.LUA_TTABLE;
} }
/** Valid for tables */
public LValue luaPairs(boolean isPairs) {
return new LTableIterator(isPairs);
}
/** Iterator for tables */
private final class LTableIterator extends LFunction {
private int arrayIndex;
private int hashIndex;
private final boolean isPairs;
private LTableIterator(boolean isPairs) {
this.arrayIndex = 0;
this.hashIndex = 0;
this.isPairs = isPairs;
}
// perform a lua call
public boolean luaStackCall(LuaState vm) {
vm.resettop();
int i;
while ( ( i = arrayIndex++ ) < m_vector.length ) {
if ( m_vector[i] != LNil.NIL ) {
vm.pushinteger( arrayIndex );
vm.pushlvalue( m_vector[ i ] );
return false;
}
}
if ( isPairs && (m_hashKeys != null) ) {
while ( ( i = hashIndex++ ) < m_hashKeys.length ) {
if ( m_hashKeys[i] != null ) {
vm.pushlvalue( m_hashKeys[i] );
vm.pushlvalue( m_hashValues[i] );
return false;
}
}
}
return false;
}
}
/** /**
* Helper method to get all the keys in this table in an array. Meant to be * Helper method to get all the keys in this table in an array. Meant to be
* used instead of keys() (which returns an enumeration) when an array is * used instead of keys() (which returns an enumeration) when an array is
@@ -349,7 +318,7 @@ public class LTable extends LValue {
int out = 0; int out = 0;
for ( int i = 0; i < m_vector.length; ++i ) { for ( int i = 0; i < m_vector.length; ++i ) {
if ( m_vector[ i ] != LNil.NIL ) { if ( ! m_vector[ i ].isNil() ) {
keys[ out++ ] = LInteger.valueOf( i + 1 ); keys[ out++ ] = LInteger.valueOf( i + 1 );
} }
} }
@@ -365,11 +334,11 @@ public class LTable extends LValue {
} }
/** Remove the value in the table with the given integer key. */ /** Remove the value in the table with the given integer key. */
private void remove( int key ) { protected void remove( int key ) {
if ( key > 0 ) { if ( key > 0 ) {
final int index = key - 1; final int index = key - 1;
if ( index < m_vector.length ) { if ( index < m_vector.length ) {
if ( m_vector[ index ] != LNil.NIL ) { if ( ! m_vector[ index ].isNil() ) {
m_vector[ index ] = LNil.NIL; m_vector[ index ] = LNil.NIL;
--m_arrayEntries; --m_arrayEntries;
} }
@@ -383,7 +352,7 @@ public class LTable extends LValue {
} }
} }
private void remove( LValue key ) { protected void remove( LValue key ) {
if ( m_hashKeys != null ) { if ( m_hashKeys != null ) {
int slot = findSlot( key ); int slot = findSlot( key );
clearSlot( slot ); clearSlot( slot );
@@ -522,7 +491,7 @@ public class LTable extends LValue {
} else { } else {
for ( int i = newCapacity; i < oldCapacity; ++i ) { for ( int i = newCapacity; i < oldCapacity; ++i ) {
LValue v = m_vector[i]; LValue v = m_vector[i];
if ( v != LNil.NIL ) { if ( ! v.isNil() ) {
if (checkLoadFactor()) if (checkLoadFactor())
rehash(); rehash();
final int slot = findSlot( i+1 ); final int slot = findSlot( i+1 );
@@ -557,7 +526,7 @@ public class LTable extends LValue {
final int index = Math.max(0,pos==0? m_arrayEntries: pos-1); final int index = Math.max(0,pos==0? m_arrayEntries: pos-1);
if ( m_arrayEntries + 1 > m_vector.length ) if ( m_arrayEntries + 1 > m_vector.length )
resize( ( m_arrayEntries + 1 ) * 2 ); resize( ( m_arrayEntries + 1 ) * 2 );
if ( m_vector[index] != LNil.NIL ) { if ( ! m_vector[index].isNil() ) {
System.arraycopy(m_vector, index, m_vector, index+1, m_vector.length-1-index); System.arraycopy(m_vector, index, m_vector, index+1, m_vector.length-1-index);
} }
m_vector[index] = value; m_vector[index] = value;
@@ -572,7 +541,7 @@ public class LTable extends LValue {
public LValue luaRemovePos(int pos) { public LValue luaRemovePos(int pos) {
if ( pos > m_arrayEntries ) { if ( pos > m_arrayEntries ) {
LValue val = get( pos ); LValue val = get( pos );
if ( val != LNil.NIL ) if ( ! val.isNil() )
put( pos, LNil.NIL ); put( pos, LNil.NIL );
return val; return val;
} else { } else {
@@ -592,7 +561,7 @@ public class LTable extends LValue {
LValue result = LInteger.valueOf(0); LValue result = LInteger.valueOf(0);
for ( int i = m_vector.length - 1; i >= 0; i-- ) { for ( int i = m_vector.length - 1; i >= 0; i-- ) {
if ( m_vector[i] != LNil.NIL ) { if ( ! m_vector[i].isNil() ) {
result = LInteger.valueOf(i + 1); result = LInteger.valueOf(i + 1);
break; break;
} }
@@ -648,7 +617,7 @@ public class LTable extends LValue {
} }
private boolean compare(int i, int j, LuaState vm, LValue cmpfunc) { private boolean compare(int i, int j, LuaState vm, LValue cmpfunc) {
if ( cmpfunc != LNil.NIL ) { if ( ! cmpfunc.isNil() ) {
vm.pushlvalue(cmpfunc); vm.pushlvalue(cmpfunc);
vm.pushlvalue(m_vector[i]); vm.pushlvalue(m_vector[i]);
vm.pushlvalue(m_vector[j]); vm.pushlvalue(m_vector[j]);
@@ -670,63 +639,75 @@ public class LTable extends LValue {
/** /**
* Leave key,value pair on top, or nil if at end of list. * Leave key,value pair on top, or nil if at end of list.
* @param vm the LuaState to leave the values on * @param vm the LuaState to leave the values on
* @param indexedonly TODO
* @param index index to start search * @param index index to start search
* @return true if next exists, false if at end of list
*/ */
public void next(LuaState vm, LValue key ) { public boolean next(LuaState vm, LValue key, boolean indexedonly ) {
// find place to start looking int n = (m_vector != null? m_vector.length: 0);
int start = nextKey2StartIndex( vm, key ); int i = findindex(key, n, indexedonly);
if ( i == INVALID_KEY_TO_NEXT )
vm.error( "invalid key to 'next'" );
// look in vector part // check vector part
int n = m_vector.length; for ( ++i; i<n; ++i ) {
if ( start < n ) { if ( ! m_vector[i].isNil() ) {
for ( int i=start; i<n; i++ ) {
if ( m_vector[i] != LNil.NIL ) {
vm.pushinteger(i+1); vm.pushinteger(i+1);
vm.pushlvalue(m_vector[i]); vm.pushlvalue(m_vector[i]);
return; return true;
} else if ( indexedonly ) {
vm.pushnil();
return false;
} }
} }
start = n;
}
// look in hash part // check hash part
if ( m_hashKeys != null ) { if ( (! indexedonly) && (m_hashKeys != null) ) {
for ( int i=start-n; i<m_hashKeys.length; i++ ) { int m = m_hashKeys.length;
for ( i-=n; i<m; ++i ) {
if ( m_hashKeys[i] != null ) { if ( m_hashKeys[i] != null ) {
vm.pushlvalue(m_hashKeys[i]); vm.pushlvalue(m_hashKeys[i]);
vm.pushlvalue(m_hashValues[i]); vm.pushlvalue(m_hashValues[i]);
return; return true;
} }
} }
} }
// nothing found, return nil. // nothing found, push nil, return nil.
vm.pushnil(); vm.pushnil();
return false;
} }
private int nextKey2StartIndex( LuaState vm, LValue key ) { private int findindex (LValue key, int n, boolean indexedonly) {
if ( key == LNil.NIL )
return 0;
int n = m_vector.length; // first iteration
if ( n > 0 && key.isInteger() ) { if ( key.isNil() )
final int index = key.toJavaInt() - 1; return -1;
if ( index >= 0 && index < n ) {
if ( m_vector[index] == LNil.NIL ) // is `key' inside array part?
vm.error( "invalid key to 'next'" ); if ( key.isInteger() ) {
return index + 1; int i = key.toJavaInt();
if ( (0 < i) && (i <= n) ) {
if ( m_vector[i-1] == LNil.NIL )
return INVALID_KEY_TO_NEXT;
return i-1;
} }
} }
// vector only?
if ( indexedonly )
return n;
if ( m_hashKeys == null ) if ( m_hashKeys == null )
vm.error( "invalid key to 'next'" ); return INVALID_KEY_TO_NEXT;
int slot = findSlot( key ); // find slot
int slot = findSlot(key);
if ( m_hashKeys[slot] == null ) if ( m_hashKeys[slot] == null )
vm.error( "invalid key to 'next'" ); return INVALID_KEY_TO_NEXT;
return n + slot + 1; return n + slot;
} }
@@ -738,35 +719,26 @@ public class LTable extends LValue {
* *
* @param vm * @param vm
* @param function * @param function
* @param isforeachi is a table.foreachi() call, not a table.foreach() call * @param indexedonly is a table.foreachi() call, not a table.foreach() call
* @return * @return
*/ */
public LValue foreach(LuaState vm, LFunction function, boolean isforeachi) { public LValue foreach(LuaState vm, LFunction function, boolean indexedonly) {
for ( int i = 0; i < m_vector.length; ++i ) {
if ( m_vector[ i ] != LNil.NIL ) {
if ( foreachitem( vm, function, LInteger.valueOf(i+1), m_vector[i] ) )
return vm.topointer(1);
}
}
if ( (! isforeachi) && m_hashKeys != null ) { LValue key = LNil.NIL;
for ( int i = 0; i < m_hashKeys.length; ++i ) { while ( true ) {
if ( m_hashKeys[ i ] != null ) { // push function onto stack
if ( foreachitem( vm, function, m_hashKeys[i], m_hashValues[i] ) )
return vm.topointer(1);
}
}
}
return LNil.NIL;
}
private static boolean foreachitem(LuaState vm, LFunction f, LValue key, LValue value) {
vm.resettop(); vm.resettop();
vm.pushlvalue( f ); vm.pushlvalue(function);
vm.pushlvalue( key );
vm.pushlvalue( value ); // get next value
if ( ! next(vm,key,indexedonly) )
return LNil.NIL;
key = vm.topointer(2);
// call function
vm.call(2, 1); vm.call(2, 1);
return ! vm.isnil(1); if ( ! vm.isnil(-1) )
return vm.poplvalue();
}
} }
} }

View File

@@ -34,6 +34,9 @@ public class LValue {
/** Metatable tag for intercepting table sets */ /** Metatable tag for intercepting table sets */
public static final LString TM_METATABLE = new LString("__metatable"); public static final LString TM_METATABLE = new LString("__metatable");
/** Metatable tag for setting table mode */
public static final LString TM_MODE = new LString("__mode");
protected void conversionError(String target) { protected void conversionError(String target) {
throw new LuaErrorException( "bad conversion: "+luaGetTypeName()+" to "+target ); throw new LuaErrorException( "bad conversion: "+luaGetTypeName()+" to "+target );
} }
@@ -59,6 +62,11 @@ public class LValue {
return false; return false;
} }
/** Return true if this value is LNil.NIL, false otherwise */
public boolean isNil() {
return false;
}
// perform a lua call, return true if the call is to a lua function, false // perform a lua call, return true if the call is to a lua function, false
// if it ran to completion. // if it ran to completion.
public boolean luaStackCall(LuaState vm) { public boolean luaStackCall(LuaState vm) {
@@ -124,7 +132,7 @@ public class LValue {
LTable mt = luaGetMetatable(); LTable mt = luaGetMetatable();
if ( mt != null ) { if ( mt != null ) {
LValue event = mt.get( TM_NEWINDEX ); LValue event = mt.get( TM_NEWINDEX );
if ( event != null && event != LNil.NIL ) { if ( event != null && ! event.isNil() ) {
event.luaSetTable( vm, table, key, val ); event.luaSetTable( vm, table, key, val );
return; return;
} }
@@ -141,7 +149,7 @@ public class LValue {
LTable mt = luaGetMetatable(); LTable mt = luaGetMetatable();
if ( mt != null ) { if ( mt != null ) {
LValue event = mt.get( TM_INDEX ); LValue event = mt.get( TM_INDEX );
if ( event != null && event != LNil.NIL ) { if ( event != null && ! event.isNil() ) {
event.luaGetTable( vm, table, key ); event.luaGetTable( vm, table, key );
return; return;
} }
@@ -184,13 +192,14 @@ public class LValue {
return null; return null;
} }
/** Valid for tables */ /** Valid for tables
public void luaSetMetatable(LValue metatable) { * @return TODO*/
public LValue luaSetMetatable(LValue metatable) {
throw new LuaErrorException( "bad argument #1 to 'setmetatable' (table expected, got "+metatable.luaGetTypeName()+")"); throw new LuaErrorException( "bad argument #1 to 'setmetatable' (table expected, got "+metatable.luaGetTypeName()+")");
} }
/** Valid for all types: return the int value identifying the type of this value */ /** Valid for all types: return the int value identifying the type of this value */
public abstract int luaGetType(); abstract public int luaGetType();
/** Valid for all types: return the type of this value as an LString */ /** Valid for all types: return the type of this value as an LString */
@@ -309,4 +318,8 @@ public class LValue {
return LNil.NIL; return LNil.NIL;
} }
/** Dereference a potentially weak reference, and return the value */
public LValue toStrongReference() {
return this;
}
} }

View File

@@ -0,0 +1,110 @@
package org.luaj.vm;
import java.lang.ref.WeakReference;
public class LWeakTable extends LTable {
private static class LWeakValue extends LValue {
private WeakReference ref;
private LWeakValue(LValue v) {
ref = new WeakReference(v);
}
public LValue toStrongReference() {
return (LValue) ref.get();
}
public LValue value() {
LValue v = (LValue) ref.get();
return (v!=null? v: LNil.NIL);
}
public String toJavaString() {
return value().toJavaString();
}
public int luaGetType() {
return value().luaGetType();
}
public boolean isNil() {
return value().isNil();
}
}
private static void makestrong(LuaState vm) {
LValue v = vm.poplvalue();
v = v.toStrongReference();
vm.pushlvalue(v!=null? v: LNil.NIL);
}
/** Construct a new LTable with weak-reference keys */
public LWeakTable() {
}
/** Copy constructor */
public LWeakTable(LTable copy) {
this.m_arrayEntries = copy.m_arrayEntries;
this.m_hashEntries = copy.m_hashEntries;
if ( copy.m_vector != null ) {
int n = copy.m_vector.length;
this.m_vector = new LValue[n];
for ( int i=0; i<n; i++ ) {
this.m_vector[i] = ( copy.m_vector[i] != null?
this.m_vector[i] = new LWeakValue(copy.m_vector[i]):
LNil.NIL );
}
}
if ( copy.m_hashKeys != null ) {
int n = copy.m_hashKeys.length;
this.m_hashKeys = new LValue[n];
this.m_hashValues = new LValue[n];
for ( int i=0; i<n; i++ ) {
if ( copy.m_hashKeys[i] != null ) {
this.m_hashKeys[i] = copy.m_hashKeys[i];
this.m_hashValues[i] = new LWeakValue(copy.m_hashValues[i]);
}
}
}
}
public LValue get(int key) {
LValue v = super.get(key).toStrongReference();
if ( v == null ) {
super.remove(key);
return LNil.NIL;
}
return v;
}
public LValue get(LValue key) {
LValue v = super.get(key).toStrongReference();
if ( v == null ) {
super.remove(key);
return LNil.NIL;
}
return v;
}
public void luaInsertPos(int pos, LValue value) {
super.luaInsertPos(pos, new LWeakValue(value));
}
public void put(int key, LValue value) {
super.put(key, new LWeakValue(value));
}
public void put(LValue key, LValue val) {
super.put(key, new LWeakValue(val));
}
public void put(String key, LValue value) {
super.put(key, new LWeakValue(value));
}
public boolean next(LuaState vm, LValue key, boolean indexedonly) {
while ( super.next(vm, key, indexedonly) ) {
makestrong(vm);
if ( ! vm.isnil(-1) )
return true;
vm.pop(1);
key = vm.poplvalue();
}
return false;
}
}

View File

@@ -810,7 +810,7 @@ public class LuaState extends Lua {
adjustTop( cb + c ); adjustTop( cb + c );
// test for continuation // test for continuation
if (this.stack[cb] != LNil.NIL ) { // continue? if (!this.stack[cb].isNil() ) { // continue?
this.stack[cb-1] = this.stack[cb]; // save control variable this.stack[cb-1] = this.stack[cb]; // save control variable
} else { } else {
ci.pc++; // skip over jump ci.pc++; // skip over jump
@@ -1463,7 +1463,7 @@ public class LuaState extends Lua {
* 0&nbsp;otherwise. * 0&nbsp;otherwise.
*/ */
public boolean isnil(int index) { public boolean isnil(int index) {
return topointer(index) == LNil.NIL; return topointer(index).isNil();
} }
/** /**
@@ -1499,7 +1499,7 @@ public class LuaState extends Lua {
* string convertible to a number, and 0&nbsp;otherwise. * string convertible to a number, and 0&nbsp;otherwise.
*/ */
public boolean isnumber(int index) { public boolean isnumber(int index) {
return tolnumber(index) != LNil.NIL; return ! tolnumber(index).isNil();
} }
/** /**
@@ -1689,7 +1689,7 @@ public class LuaState extends Lua {
for ( int i=0; i<n; i++ ) for ( int i=0; i<n; i++ )
stack[--top] = null; stack[--top] = null;
} }
private LValue poplvalue() { public LValue poplvalue() {
LValue p = stack[--top]; LValue p = stack[--top];
stack[top] = null; stack[top] = null;
return p; return p;
@@ -2034,7 +2034,11 @@ public class LuaState extends Lua {
public void setmetatable(int index) { public void setmetatable(int index) {
LTable t = totable(index); LTable t = totable(index);
LValue v = poplvalue(); LValue v = poplvalue();
t.luaSetMetatable(v); LValue n = t.luaSetMetatable(v);
if ( n != null ) {
pushlvalue(n);
replace(index);
}
} }
/** /**

View File

@@ -50,7 +50,7 @@ public class CoerceLuaToJava {
return value.toJavaBoolean()? Boolean.TRUE: Boolean.FALSE; return value.toJavaBoolean()? Boolean.TRUE: Boolean.FALSE;
} }
public int score(LValue value) { public int score(LValue value) {
if ( value instanceof LBoolean || value == LNil.NIL ) if ( value instanceof LBoolean || value.isNil() )
return 0; return 0;
if ( value instanceof LNumber ) if ( value instanceof LNumber )
return 1; return 1;
@@ -66,7 +66,7 @@ public class CoerceLuaToJava {
return 0; return 0;
if ( value instanceof LNumber ) if ( value instanceof LNumber )
return 1; return 1;
if ( value instanceof LBoolean || value == LNil.NIL ) if ( value instanceof LBoolean || value.isNil() )
return 2; return 2;
return 4; return 4;
} }
@@ -80,7 +80,7 @@ public class CoerceLuaToJava {
return 0; return 0;
if ( value instanceof LNumber ) if ( value instanceof LNumber )
return 1; return 1;
if ( value instanceof LBoolean || value == LNil.NIL ) if ( value instanceof LBoolean || value.isNil() )
return 2; return 2;
return 4; return 4;
} }
@@ -107,7 +107,7 @@ public class CoerceLuaToJava {
return new Double(value.toJavaDouble()); return new Double(value.toJavaDouble());
if ( value instanceof LBoolean ) if ( value instanceof LBoolean )
return Boolean.valueOf(value.toJavaBoolean()); return Boolean.valueOf(value.toJavaBoolean());
if ( value == LNil.NIL ) if ( value.isNil() )
return null; return null;
return value; return value;
} }

View File

@@ -77,6 +77,7 @@ public class LuaRunner {
System.err.println(script+" lua error, "+lee.getMessage() ); System.err.println(script+" lua error, "+lee.getMessage() );
} catch ( Throwable t ) { } catch ( Throwable t ) {
System.err.println(script+" threw "+t); System.err.println(script+" threw "+t);
t.printStackTrace();
} finally { } finally {
System.out.flush(); System.out.flush();
System.err.flush(); System.err.flush();

View File

@@ -0,0 +1,53 @@
package org.luaj.vm;
import java.util.Random;
import junit.framework.TestCase;
public class LWeakTableTest extends TestCase {
Random random = new Random(0);
Runtime rt = Runtime.getRuntime();
private void runTest(int n,int i0,int i1,int di) {
System.out.println("------- testing "+n+" keys up to "+i1+" bytes each ("+(n*i1)+" bytes total)");
LTable t = new LWeakTable();
for ( int i=0; i<n; i++ )
t.put(i, new LString(new byte[1]));
for ( int i=i0; i<=i1; i+=di ) {
int hits = 0;
for ( int j=0; j<100; j++ ) {
int k = random.nextInt(n);
LValue v = t.get(k);
if ( v != LNil.NIL )
hits++;
t.put(i, new LString(new byte[i]));
}
long total = rt.totalMemory() / 1000;
long free = rt.freeMemory() / 1000;
long used = (rt.totalMemory() - rt.freeMemory()) / 1000;
System.out.println("keys="+n+" bytes="+i+" mem u(f,t)="+used+"("+free+"/"+total+") hits="+hits+"/100");
}
}
public void testWeakTable5000() {
runTest(100,0,5000,500);
}
public void testWeakTable10000() {
runTest(100,0,10000,1000);
}
public void testWeakTable100() {
runTest(100,0,100,10);
}
public void testWeakTable1000() {
runTest(100,0,1000,100);
}
public void testWeakTable2000() {
runTest(100,0,2000,200);
}
}

View File

@@ -18,69 +18,70 @@ tryconcat( { a='aaa', b='bbb', c='ccc', d='ddd', e='eee' } )
tryconcat( { [501]="one", [502]="two", [503]="three", [504]="four", [505]="five" } ) tryconcat( { [501]="one", [502]="two", [503]="three", [504]="four", [505]="five" } )
tryconcat( {} ) tryconcat( {} )
-- print the elements of a table in a platform-independent way
function eles(t,f)
f = f or pairs
all = {}
for k,v in f(t) do
table.insert( all, "["..tostring(k).."]="..tostring(v) )
end
table.sort( all )
return "{"..table.concat(all,',').."}"
end
-- insert, maxn -- insert, maxn
print( '-- insert, maxn tests' ) print( '-- insert, maxn tests' )
local t = { "one", "two", "three", a='aaa', b='bbb', c='ccc' } local t = { "one", "two", "three", a='aaa', b='bbb', c='ccc' }
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) ) print( eles(t) )
table.insert(t,'six') table.insert(t,'six'); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) ) table.insert(t,1,'seven'); print( eles(t) )
table.insert(t,1,'seven') table.insert(t,4,'eight'); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) ) table.insert(t,7,'nine'); print( eles(t) )
table.insert(t,4,'eight') table.insert(t,10,'ten'); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) )
table.insert(t,7,'nine')
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) )
table.insert(t,10,'ten')
print( table.concat(t,'-'), table.maxn(t), #t, table.getn(t) )
print( t[10] )
print( table.maxn({}), #{} )
-- remove -- remove
print( '-- remove tests' ) print( '-- remove tests' )
t = { "one", "two", "three", "four", "five", "six", "seven", [10]="ten", a='aaa', b='bbb', c='ccc' } t = { "one", "two", "three", "four", "five", "six", "seven", [10]="ten", a='aaa', b='bbb', c='ccc' }
print( table.concat(t,'-'), table.maxn(t), #t ) print( eles(t) )
print( table.remove(t) ) print( 'table.remove(t)', table.remove(t) ); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t) ) print( 'table.remove(t,1)', table.remove(t,1) ); print( eles(t) )
print( table.remove(t,1) ) print( 'table.remove(t,3)', table.remove(t,3) ); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t) ) print( 'table.remove(t,5)', table.remove(t,5) ); print( eles(t) )
print( table.remove(t,3) ) print( 'table.remove(t,10)', table.remove(t,10) ); print( eles(t) )
print( table.concat(t,'-'), table.maxn(t) ) print( 'table.remove(t,-1)', table.remove(t,-1) ); print( eles(t) )
print( table.remove(t,5) ) print( 'table.remove(t,-1)', table.remove(t,-1) ) ; print( eles(t) )
print( table.concat(t,'-'), table.maxn(t), t[10] )
print( table.remove(t,10) )
print( table.concat(t,'-'), table.maxn(t), t[10] )
print( table.remove(t,-1) )
print( table.concat(t,'-'), table.maxn(t), t[10] )
print( table.remove(t,-1) )
print( table.concat(t,'-'), table.maxn(t), t[10] )
-- sort -- sort
print( '-- sort tests' ) print( '-- sort tests' )
t = { "one", "two", "three", a='aaa', b='bbb', c='ccc' } function sorttest(t,f)
print( table.concat(t,'-'), table.maxn(t), #t ) t = (t)
table.sort(t) print( table.concat(t,'-') )
print( table.concat(t,'-'), table.maxn(t), #t ) if f then
t = { "zzz", "yyy", "xxx", "www", "vvv", "uuu", "ttt", "sss" } table.sort(t,f)
print( table.concat(t,'-'), table.maxn(t), #t ) else
table.sort(t) table.sort(t)
print( table.concat(t,'-'), table.maxn(t), #t ) end
table.sort(t,function(a,b) return b<a end) print( table.concat(t,'-') )
print( table.concat(t,'-'), table.maxn(t), #t ) end
sorttest{ "one", "two", "three" }
sorttest{ "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" }
sorttest( { "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" }, function(a,b) return b<a end)
-- getn -- getn
t0 = {} t0 = {}
t1 = { 'one', 'two', 'three' } t1 = { 'one', 'two', 'three' }
t2 = { a='aa', b='bb', c='cc' } t2 = { a='aa', b='bb', c='cc' }
t3 = { 'one', 'two', 'three', a='aa', b='bb', c='cc' } t3 = { 'one', 'two', 'three', a='aa', b='bb', c='cc' }
print( 'getn(t0)', pcall( table.getn, t0 ) ) print( 'getn('..eles(t0)..')', pcall( table.getn, t0 ) )
print( 'getn(t0)', pcall( table.getn, t1 ) ) print( 'getn('..eles(t1)..')', pcall( table.getn, t1 ) )
print( 'getn(t0)', pcall( table.getn, t2 ) ) print( 'getn('..eles(t2)..')', pcall( table.getn, t2 ) )
print( 'getn(t0)', pcall( table.getn, t3 ) ) print( 'getn('..eles(t3)..')', pcall( table.getn, t3 ) )
-- foreach -- foreach
function test( f, t, result, name ) function test( f, t, result, name )
status, value = pcall( f, t, function(...) status, value = pcall( f, t, function(...)
print(name,...) print(' -- ',...)
print(' next',next(t,(...)))
return result return result
end ) end )
print( name, 's,v', status, value ) print( name, 's,v', status, value )
@@ -90,12 +91,32 @@ function testall( f, t, name )
test( f, t, false, name..'fls' ) test( f, t, false, name..'fls' )
test( f, t, 100, name..'100' ) test( f, t, 100, name..'100' )
end end
testall( table.foreach, t0, 'table.foreach(t0)' ) testall( table.foreach, t0, 'table.foreach('..eles(t0)..')' )
testall( table.foreach, t1, 'table.foreach(t1)' ) testall( table.foreach, t1, 'table.foreach('..eles(t1)..')' )
testall( table.foreach, t2, 'table.foreach(t2)' ) testall( table.foreach, t2, 'table.foreach('..eles(t2)..')' )
testall( table.foreach, t3, 'table.foreach(t3)' ) testall( table.foreach, t3, 'table.foreach('..eles(t3)..')' )
testall( table.foreachi, t0, 'table.foreachi(t0)' ) testall( table.foreachi, t0, 'table.foreachi('..eles(t0)..')' )
testall( table.foreachi, t1, 'table.foreachi(t1)' ) testall( table.foreachi, t1, 'table.foreachi('..eles(t1)..')' )
testall( table.foreachi, t2, 'table.foreachi(t2)' ) testall( table.foreachi, t2, 'table.foreachi('..eles(t2)..')' )
testall( table.foreachi, t3, 'table.foreachi(t3)' ) testall( table.foreachi, t3, 'table.foreachi('..eles(t3)..')' )
-- pairs, ipairs
function testpairs(f, t, name)
print( name )
for a,b in f(t) do
print( ' ', a, b )
end
end
function testbothpairs(t)
testpairs( pairs, t, 'pairs( '..eles(t)..' )' )
testpairs( ipairs, t, 'ipairs( '..eles(t)..' )' )
end
for i,t in ipairs({t0,t1,t2,t3}) do
testbothpairs(t)
end
t = { 'one', 'two', 'three', 'four', 'five' }
testbothpairs(t)
t[6] = 'six'
testbothpairs(t)
t[4] = nil
testbothpairs(t)

View File

@@ -1,12 +1,14 @@
-- concat
print( '-- weak table tests' )
-- construct new weak table
function newweak(t) function newtable(t)
return setmetatable(t,{__mode="v"}) n = setmetatable(t,{__mode="v"})
for k,v in pairs(t) do
n[k] = v
end
return n;
end end
-- print the elements of a table in a platform-independent way -- normalized printing
function eles(t,f) function eles(t,f)
f = f or pairs f = f or pairs
all = {} all = {}
@@ -14,12 +16,47 @@ function eles(t,f)
table.insert( all, "["..tostring(k).."]="..tostring(v) ) table.insert( all, "["..tostring(k).."]="..tostring(v) )
end end
table.sort( all ) table.sort( all )
return "{"..table.concat(all,',').."}" return tostring(t).."{"..table.concat(all,',').."}"
end end
-- basic weak-reference table test
local src = "return { 'one', 'two', 'three', 'four', a='aaa', b='bbb', c='ccc', d='ddd'}"
local weak = newtable( loadstring(src)() )
local strong = { weak[1], weak[3], a=weak.a, c=weak.c }
print( 'before, weak:', eles(weak) )
print( 'before, strong:', eles(strong) )
print( 'gc', pcall( collectgarbage, "collect" ) )
print( 'after, weak:', eles(weak) )
print( 'after, strong:', eles(strong) )
print( 'gc', pcall( collectgarbage, "collect" ) )
print( 'after, weak:', eles(weak) )
print( 'after, strong:', eles(strong) )
print( '-- concat tests' )
function tryconcat(t)
print( table.concat(t) )
print( table.concat(t,'--') )
print( table.concat(t,',',2) )
print( table.concat(t,',',2,2) )
print( table.concat(t,',',5,2) )
end
tryconcat( newtable{ "one", "two", "three", a='aaa', b='bbb', c='ccc' } )
tryconcat( newtable{ "one", "two", "three", "four", "five" } )
function tryconcat(t)
print( table.concat(t) )
print( table.concat(t,'--') )
print( table.concat(t,',',2) )
end
tryconcat( newtable{ a='aaa', b='bbb', c='ccc', d='ddd', e='eee' } )
tryconcat( newtable{ [501]="one", [502]="two", [503]="three", [504]="four", [505]="five" } )
tryconcat( newtable{} )
-- insert, maxn -- insert, maxn
print( '-- insert, maxn tests' ) print( '-- insert, maxn tests' )
local t = newweak{ "one", "two", "three", a='aaa', b='bbb', c='ccc' } local t = newtable{ "one", "two", "three", a='aaa', b='bbb', c='ccc' }
print( eles(t) ) print( eles(t) )
table.insert(t,'six'); print( eles(t) ) table.insert(t,'six'); print( eles(t) )
table.insert(t,1,'seven'); print( eles(t) ) table.insert(t,1,'seven'); print( eles(t) )
@@ -29,20 +66,20 @@ table.insert(t,10,'ten'); print( eles(t) )
-- remove -- remove
print( '-- remove tests' ) print( '-- remove tests' )
t = newweak{ "one", "two", "three", "four", "five", "six", "seven", [10]="ten", a='aaa', b='bbb', c='ccc' } t = newtable{ "one", "two", "three", "four", "five", "six", "seven", [10]="ten", a='aaa', b='bbb', c='ccc' }
print( eles(t) ) print( eles(t) )
print( table.remove(t) ); print( eles(t) ) print( 'table.remove(t)', table.remove(t) ); print( eles(t) )
print( table.remove(t,1) ); print( eles(t) ) print( 'table.remove(t,1)', table.remove(t,1) ); print( eles(t) )
print( table.remove(t,3) ); print( eles(t) ) print( 'table.remove(t,3)', table.remove(t,3) ); print( eles(t) )
print( table.remove(t,5) ); print( eles(t) ) print( 'table.remove(t,5)', table.remove(t,5) ); print( eles(t) )
print( table.remove(t,10) ); print( eles(t) ) print( 'table.remove(t,10)', table.remove(t,10) ); print( eles(t) )
print( table.remove(t,-1) ); print( eles(t) ) print( 'table.remove(t,-1)', table.remove(t,-1) ); print( eles(t) )
print( table.remove(t,-1) ) ; print( eles(t) ) print( 'table.remove(t,-1)', table.remove(t,-1) ) ; print( eles(t) )
-- sort -- sort
print( '-- sort tests' ) print( '-- sort tests' )
function sorttest(t,f) function sorttest(t,f)
t = newweak(t) t = (t)
print( table.concat(t,'-') ) print( table.concat(t,'-') )
if f then if f then
table.sort(t,f) table.sort(t,f)
@@ -51,24 +88,27 @@ function sorttest(t,f)
end end
print( table.concat(t,'-') ) print( table.concat(t,'-') )
end end
sorttest{ "one", "two", "three" } --[[
sorttest{ "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" } sorttest( newtable{ "one", "two", "three" } )
sorttest( { "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" }, function(a,b) return b<a end) sorttest( newtable{ "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" } )
sorttest( newtable{ "www", "vvv", "uuu", "ttt", "sss", "zzz", "yyy", "xxx" }, function(a,b) return b<a end)
--]]
-- getn -- getn
t0 = newweak{} t0 = newtable{}
t1 = newweak{ 'one', 'two', 'three' } t1 = newtable{ 'one', 'two', 'three' }
t2 = newweak{ a='aa', b='bb', c='cc' } t2 = newtable{ a='aa', b='bb', c='cc' }
t3 = newweak{ 'one', 'two', 'three', a='aa', b='bb', c='cc' } t3 = newtable{ 'one', 'two', 'three', a='aa', b='bb', c='cc' }
print( 'getn(t0)', pcall( table.getn, t0 ) ) print( 'getn('..eles(t0)..')', pcall( table.getn, t0 ) )
print( 'getn(t1)', pcall( table.getn, t1 ) ) print( 'getn('..eles(t1)..')', pcall( table.getn, t1 ) )
print( 'getn(t2)', pcall( table.getn, t2 ) ) print( 'getn('..eles(t2)..')', pcall( table.getn, t2 ) )
print( 'getn(t3)', pcall( table.getn, t3 ) ) print( 'getn('..eles(t3)..')', pcall( table.getn, t3 ) )
-- foreach -- foreach
function test( f, t, result, name ) function test( f, t, result, name )
status, value = pcall( f, t, function(...) status, value = pcall( f, t, function(...)
print(name,...) print(' -- ',...)
print(' next',next(t,(...)))
return result return result
end ) end )
print( name, 's,v', status, value ) print( name, 's,v', status, value )
@@ -78,29 +118,32 @@ function testall( f, t, name )
test( f, t, false, name..'fls' ) test( f, t, false, name..'fls' )
test( f, t, 100, name..'100' ) test( f, t, 100, name..'100' )
end end
testall( table.foreach, t0, 'table.foreach(t0)' ) testall( table.foreach, t0, 'table.foreach('..eles(t0)..')' )
testall( table.foreach, t1, 'table.foreach(t1)' ) testall( table.foreach, t1, 'table.foreach('..eles(t1)..')' )
testall( table.foreach, t2, 'table.foreach(t2)' ) testall( table.foreach, t2, 'table.foreach('..eles(t2)..')' )
testall( table.foreach, t3, 'table.foreach(t3)' ) testall( table.foreach, t3, 'table.foreach('..eles(t3)..')' )
testall( table.foreachi, t0, 'table.foreachi(t0)' ) testall( table.foreachi, t0, 'table.foreachi('..eles(t0)..')' )
testall( table.foreachi, t1, 'table.foreachi(t1)' ) testall( table.foreachi, t1, 'table.foreachi('..eles(t1)..')' )
testall( table.foreachi, t2, 'table.foreachi(t2)' ) testall( table.foreachi, t2, 'table.foreachi('..eles(t2)..')' )
testall( table.foreachi, t3, 'table.foreachi(t3)' ) testall( table.foreachi, t3, 'table.foreachi('..eles(t3)..')' )
-- pairs, ipairs -- pairs, ipairs
function testpairs(f, t, name) function testpairs(f, t, name)
print( name, unpack(t) ) print( name )
for a,b in f(t) do for a,b in f(t) do
print( a, b ) print( ' ', a, b )
end end
end end
testpairs( pairs, t0, 'pairs(t0)' ) function testbothpairs(t)
testpairs( pairs, t1, 'pairs(t1)' ) testpairs( pairs, t, 'pairs( '..eles(t)..' )' )
testpairs( pairs, t2, 'pairs(t2)' ) testpairs( ipairs, t, 'ipairs( '..eles(t)..' )' )
testpairs( pairs, t3, 'pairs(t3)' ) end
testpairs( ipairs, t0, 'ipairs(t0)' ) for i,t in ipairs({t0,t1,t2,t3}) do
testpairs( ipairs, t1, 'ipairs(t1)' ) testbothpairs(t)
testpairs( ipairs, t2, 'ipairs(t2)' ) end
testpairs( ipairs, t3, 'ipairs(t3)' ) t = newtable{ 'one', 'two', 'three', 'four', 'five' }
testbothpairs(t)
t[6] = 'six'
testbothpairs(t)
t[4] = nil
testbothpairs(t)