org.luaj.vm2.LuaTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luaj-jse Show documentation
Show all versions of luaj-jse Show documentation
Luaj 3.0-beta1 for the jse platform
/*******************************************************************************
* Copyright (c) 2009 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 java.util.Vector;
/**
* Subclass of {@link LuaValue} for representing lua tables.
*
* Almost all API's implemented in {@link LuaTable} are defined and documented in {@link LuaValue}.
*
* If a table is needed, the one of the type-checking functions can be used such as
* {@link #istable()},
* {@link #checktable()}, or
* {@link #opttable(LuaTable)}
*
* The main table operations are defined on {@link LuaValue}
* for getting and setting values with and without metatag processing:
*
* - {@link #get(LuaValue)}
* - {@link #set(LuaValue,LuaValue)}
* - {@link #rawget(LuaValue)}
* - {@link #rawset(LuaValue,LuaValue)}
* - plus overloads such as {@link #get(String)}, {@link #get(int)}, and so on
*
*
* To iterate over key-value pairs from Java, use
*
{@code
* LuaValue k = LuaValue.NIL;
* while ( true ) {
* Varargs n = table.next(k);
* if ( (k = n.arg1()).isnil() )
* break;
* LuaValue v = n.arg(2)
* process( k, v )
* }}
*
*
* As with other types, {@link LuaTable} instances should be constructed via one of the table constructor
* methods on {@link LuaValue}:
*
* - {@link LuaValue#tableOf()} empty table
* - {@link LuaValue#tableOf(int, int)} table with capacity
* - {@link LuaValue#listOf(LuaValue[])} initialize array part
* - {@link LuaValue#listOf(LuaValue[], Varargs)} initialize array part
* - {@link LuaValue#tableOf(LuaValue[])} initialize named hash part
* - {@link LuaValue#tableOf(Varargs, int)} initialize named hash part
* - {@link LuaValue#tableOf(LuaValue[], LuaValue[])} initialize array and named parts
* - {@link LuaValue#tableOf(LuaValue[], LuaValue[], Varargs)} initialize array and named parts
*
* @see LuaValue
*/
public class LuaTable extends LuaValue {
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 number of hash entries */
protected int hashEntries;
/** metatable for this table, or null */
protected LuaValue m_metatable;
/** Construct empty table */
public LuaTable() {
array = NOVALS;
hashKeys = NOVALS;
hashValues = NOVALS;
}
/**
* Construct table with preset capacity.
* @param narray capacity of array part
* @param nhash capacity of hash part
*/
public LuaTable(int narray, int nhash) {
presize(narray, nhash);
}
/**
* Construct table with named and unnamed parts.
* @param named Named elements in order {@code key-a, value-a, key-b, value-b, ... }
* @param unnamed Unnamed elements in order {@code value-1, value-2, ... }
* @param lastarg Additional unnamed values beyond {@code unnamed.length}
*/
public LuaTable(LuaValue[] named, LuaValue[] unnamed, Varargs lastarg) {
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));
for ( int i=0; i array.length )
array = resize( array, 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);
hashEntries = 0;
}
/** Resize the table */
private static LuaValue[] resize( LuaValue[] old, int n ) {
LuaValue[] v = new LuaValue[n];
System.arraycopy(old, 0, v, 0, old.length);
return v;
}
/**
* Get the length of the array part of the table.
* @return length of the array part, does not relate to count of objects in the table.
*/
protected int getArrayLength() {
return array.length;
}
/**
* Get the length of the hash part of the table.
* @return length of the hash part, does not relate to count of objects in the table.
*/
protected int getHashLength() {
return hashValues.length;
}
public LuaValue getmetatable() {
return m_metatable;
}
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);
}
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;
}
public LuaValue get( LuaValue key ) {
LuaValue v = rawget(key);
return v.isnil() && m_metatable!=null? gettable(this,key): v;
}
public LuaValue rawget( int key ) {
if ( key>0 && key<=array.length )
return array[key-1]!=null? array[key-1]: 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;
}
return hashget( key );
}
protected LuaValue hashget(LuaValue key) {
if ( hashEntries > 0 ) {
LuaValue v = hashValues[hashFindSlot(key)];
return v!=null? v: NIL;
}
return NIL;
}
public void set( int key, LuaValue value ) {
if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,LuaInteger.valueOf(key),value) )
rawset(key, value);
}
/** caller must ensure key is not nil */
public void set( LuaValue key, LuaValue value ) {
if (!key.isvalidkey() && !metatag(NEWINDEX).isfunction())
typerror("table index");
if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,key,value) )
rawset(key, value);
}
public void rawset( int key, LuaValue value ) {
if ( ! arrayset(key, value) )
hashset( LuaInteger.valueOf(key), value );
}
/** caller must ensure key is not nil */
public void rawset( LuaValue key, LuaValue value ) {
if ( !key.isinttype() || !arrayset(key.toint(), value) )
hashset( key, value );
}
/** 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;
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; i n)
return NONE;
LuaValue v = rawget(pos);
for ( LuaValue r=v; !r.isnil(); ) {
r = rawget(pos+1);
rawset(pos++, r);
}
return v.isnil()? NONE: v;
}
/** Insert an element at a position in a list-table
*
* @param pos the position to remove
* @param value The value to insert
*/
public void insert(int pos, LuaValue value) {
if ( pos == 0 )
pos = length()+1;
while ( ! value.isnil() ) {
LuaValue v = rawget( pos );
rawset(pos++, value);
value = v;
}
}
/** Concatenate the contents of a table efficiently, using {@link Buffer}
*
* @param sep {@link LuaString} separater to apply between elements
* @param i the first element index
* @param j the last element index, inclusive
* @return {@link LuaString} value of the concatenation
*/
public LuaValue concat(LuaString sep, int i, int j) {
Buffer sb = new Buffer ();
if ( i<=j ) {
sb.append( get(i).checkstring() );
while ( ++i<=j ) {
sb.append( sep );
sb.append( get(i).checkstring() );
}
}
return sb.tostring();
}
public int length() {
int a = getArrayLength();
int n = a+1,m=0;
while ( !rawget(n).isnil() ) {
m = n;
n += a+getHashLength()+1;
}
while ( n > m+1 ) {
int k = (n+m) / 2;
if ( !rawget(k).isnil() )
m = k;
else
n = k;
}
return m;
}
public LuaValue len() {
return LuaInteger.valueOf(length());
}
public int rawlen() {
return length();
}
/**
* Get the next element after a particular key in the table
* @return key,value or nil
*/
public Varargs next( LuaValue key ) {
int i = 0;
do {
// find current key index
if ( ! key.isnil() ) {
if ( key.isinttype() ) {
i = key.toint();
if ( i>0 && i<=array.length ) {
if ( array[i-1] == null )
error( "invalid key to 'next'" );
break;
}
}
if ( hashKeys.length == 0 )
error( "invalid key to 'next'" );
i = hashFindSlot(key);
if ( hashKeys[i] == null )
error( "invalid key to 'next'" );
i += 1+array.length;
}
} while ( false );
// check array part
for ( ; i 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;
}
}
--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));
}
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;
}
}
}
// ----------------- sort support -----------------------------
//
// implemented heap sort from wikipedia
//
// Only sorts the contiguous array part.
//
/** Sort the table using a comparator.
* @param comparator {@link LuaValue} to be called to compare elements.
*/
public void sort(LuaValue comparator) {
int n = array.length;
while ( n > 0 && array[n-1] == null )
--n;
if ( n > 1 )
heapSort(n, comparator);
}
private void heapSort(int count, LuaValue cmpfunc) {
heapify(count, cmpfunc);
for ( int end=count-1; end>0; ) {
swap(end, 0);
siftDown(0, --end, cmpfunc);
}
}
private void heapify(int count, LuaValue cmpfunc) {
for ( int start=count/2-1; start>=0; --start )
siftDown(start, count - 1, cmpfunc);
}
private void siftDown(int start, int end, LuaValue cmpfunc) {
for ( int root=start; root*2+1 <= end; ) {
int child = root*2+1;
if (child < end && compare(child, child + 1, cmpfunc))
++child;
if (compare(root, child, cmpfunc)) {
swap(root, child);
root = child;
} else
return;
}
}
private boolean compare(int i, int j, LuaValue cmpfunc) {
LuaValue a = array[i];
LuaValue b = array[j];
if ( a == null || b == null )
return false;
if ( ! cmpfunc.isnil() ) {
return cmpfunc.call(a,b).toboolean();
} else {
return a.lt_b(b);
}
}
private void swap(int i, int j) {
LuaValue a = array[i];
array[i] = array[j];
array[j] = a;
}
/** This may be deprecated in a future release.
* It is recommended to count via iteration over next() instead
* @return count of keys in the table
* */
public int keyCount() {
LuaValue k = LuaValue.NIL;
for ( int i=0; true; i++ ) {
Varargs n = next(k);
if ( (k = n.arg1()).isnil() )
return i;
}
}
/** This may be deprecated in a future release.
* It is recommended to use next() instead
* @return array of keys in the table
* */
public LuaValue[] keys() {
Vector l = new Vector();
LuaValue k = LuaValue.NIL;
while ( true ) {
Varargs n = next(k);
if ( (k = n.arg1()).isnil() )
break;
l.addElement( k );
}
LuaValue[] a = new LuaValue[l.size()];
l.copyInto(a);
return a;
}
// equality w/ metatable processing
public LuaValue eq( LuaValue val ) { return eq_b(val)? TRUE: FALSE; }
public boolean eq_b( LuaValue val ) {
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);
}
/** Unpack all the elements of this table */
public Varargs unpack() {
return unpack(1, this.length());
}
/** Unpack all the elements of this table from element i */
public Varargs unpack(int i) {
return unpack(i, this.length());
}
/** Unpack the elements from i to j inclusive */
public Varargs unpack(int i, int j) {
int n = j + 1 - i;
switch (n) {
case 0: return NONE;
case 1: return get(i);
case 2: return varargsOf(get(i), get(i+1));
default:
if (n < 0)
return NONE;
LuaValue[] v = new LuaValue[n];
while (--n >= 0)
v[n] = get(i+n);
return varargsOf(v);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy