net.sandius.rembulan.Table Maven / Gradle / Ivy
/*
* Copyright 2016 Miroslav Janíček
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sandius.rembulan;
import net.sandius.rembulan.runtime.Dispatch;
import net.sandius.rembulan.runtime.ExecutionContext;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
/**
* An abstract class representing a Lua table.
*
* Note on equality: according to §3.4.4 of the Lua Reference Manual,
* tables {@code a} and {@code b} are expected to be equal if and only if they are
* the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares
* tables using {@link Object#equals(Object)}. Exercise caution when overriding
* {@code equals()}.
*/
public abstract class Table extends LuaObject {
/**
* Retrieves the value associated with the given {@code key}, returning {@code null}
* when {@code key} has no value associated with it.
*
* Implementations of this method must ensure that the Lua rules for valid
* table keys are honoured, e.g. by normalising keys using
* {@link Conversions#normaliseKey(Object)}.
*
* This method provides raw access to the table. For non-raw access
* (i.e., handling the {@code __index} metamethod), use
* {@link Dispatch#index(ExecutionContext, Table, Object)}.
*
* @param key the key, may be {@code null}
* @return the value associated with {@code key}, or {@code null} when there is no
* value associated with {@code key} in this table
*/
public abstract Object rawget(Object key);
/**
* Retrieves the value associated with the given integer {@code idx}, returning
* {@code null} when {@code idx} has no value associated with it.
*
* This method must be functionally equivalent to {@link #rawget(Object)} with the
* corresponding boxed key. However, implementations of this method may optimise the retrieval
* in this case, since the type of the key is known at compile-time.
*
* This method provides raw access to the table. For non-raw access
* (i.e., handling the {@code __index} metamethod), use
* {@link Dispatch#index(ExecutionContext, Table, long)}.
*
* @param idx the integer key
* @return the value associated with {@code idx}, or {@code null} when there is no
* value associated with {@code idx} in this table
*/
public Object rawget(long idx) {
return rawget(Long.valueOf(idx));
}
/**
* Sets the value associated with the key {@code key} to {@code value}. When {@code value}
* is {@code null}, removes {@code key} from the table.
*
* When {@code key} is {@code null} (i.e., a nil) or a NaN,
* an {@link IllegalArgumentException} is thrown.
*
* This method provides raw access to the table. For non-raw access
* (i.e., handling the {@code __newindex} metamethod), use
* {@link Dispatch#setindex(ExecutionContext, Table, Object, Object)}.
*
* Implementation notes: Implementations of this method must ensure that
* the behaviour of this method conforms to the Lua semantics as delineated in the Lua
* Reference Manual. In particular:
*
* - float keys that have an integer value must be treated as integer keys
* (e.g. by using {@link Conversions#normaliseKey(Object)};
* - updates of the value associated with the key {@code "__mode"}
* must call {@link #updateBasetableModes(Object, Object)}.
*
*
* @param key the key, must not be {@code null} or NaN
* @param value the value to associate with {@code key}, may be {@code null}
*
* @throws IllegalArgumentException when {@code key} is {@code null} or a NaN
*/
public abstract void rawset(Object key, Object value);
/**
* Sets the value associated with the integer key {@code idx} to {@code value}.
* When {@code value} is {@code null}, removes {@code idx} from the table.
*
* This method must be functionally equivalent to {@link #rawset(Object,Object)} with the
* corresponding boxed key. However, implementations of this method may be more optimised
* than in the generic case, since the type of the key is known at compile-time.
*
* This method provides raw access to the table. For non-raw access
* (i.e., handling the {@code __newindex} metamethod), use
* {@link Dispatch#setindex(ExecutionContext, Table, long, Object)}.
*
* @param idx the integer key
* @param value the value to associate with {@code idx}, may be {@code null}
*/
public void rawset(long idx, Object value) {
rawset(Long.valueOf(idx), value);
}
/**
* If this table is a sequence, returns the length of this sequence.
*
* According to §2.1 of the Lua Reference Manual, a sequence is
*
* a table where the set of all positive numeric keys is equal to {1..n} for some
* non-negative integer n, which is called the length of the sequence
*
*
* Note that when this table is not a sequence, the return value of this method
* is undefined.
*
* @return the length of the sequence if this table is a sequence
*/
public long rawlen() {
long idx = 1;
while (idx >= 0 && rawget(idx) != null) {
idx <<= 1;
}
// if idx overflows (idx < 0), don't check rawget(idx)
if (idx == 1) {
return 0;
}
else {
// binary search in [idx >>> 1, idx]
long min = idx >>> 1;
long max = idx;
// invariant: (min > 0 && rawget(min) != null) && (max < 0 || rawget(max) == null)
while (min + 1 != max) {
// works even if max == (1 << 63)
long mid = (min + max) >>> 1;
if (rawget(mid) == null) {
max = mid;
}
else {
min = mid;
}
}
// min + 1 == max; given the invariant, min is the result
return min;
}
}
/**
* Returns the initial key for iterating through the set of keys in this table.
*
* Conceptually speaking, all keys in this table are totally ordered; this method
* returns the minimal key.
*
* The key returned by this method, together with the subsequent calls
* to {@link #successorKeyOf(Object)} will visit all keys in this table exactly once
* (in an unspecified order):
*
* Object k = table.initialIndex();
* while (k != null) {
* // process the key k
* k = table.nextIndex(k);
* }
* // at this point, we visited all keys in table exactly once
*
*
* @return an initial key for iteration through all keys in this table
*/
public abstract Object initialKey();
/**
* Returns the next key for iterating through the set of keys in this table.
*
* Conceptually speaking, all keys in this table are totally ordered; this method
* returns the immediate successor of {@code key}, or {@code null} if {@code key} is
* the maximal key.
*
* When no value is associated with the key {@code key} in this table,
* an {@link IllegalArgumentException} is thrown.
*
* To retrieve the initial key for iterating through this table, use
* {@link #initialKey()}.
*
* @param key the key to get the immediate successor of, must not be {@code null}
* @return the immediate successor of {@code key} in this table
*
* @throws IllegalArgumentException when no value is associated with {@code key}
* in this table, or {@code key} is {@code null}
*/
public abstract Object successorKeyOf(Object key);
/**
* The metatable of this table, may be {@code null}.
*/
private Table metatable;
/**
* A weak set containing the references to tables this table is a metatable of.
*
* Let M be the metatable of a table T. Then T is a *basetable* of M. M may have multiple
* basetables; this is the set of all basetables of this table.
*
* According to LRM §2.5.2, when a table T has a metatable M, the value associated
* with the key "__mode" in M determines whether T has weak keys, values or both. This means
* that an update of M["__mode"] may trigger a change in the weakness status of all basetables
* of M. Therefore, each table must keep track of its basetables.
*/
private final Set basetables = Collections.newSetFromMap(new WeakHashMap());
/**
* Sets the metatable of this table to {@code mt}. {@code mt} may be {@code null}:
* in that case, removes the metatable from this object.
*
* Returns the metatable previously associated with this object (i.e., the metatable
* before the call of this method; possibly {@code null}).
*
* This method maintains the weakness of this table by invoking
* {@link #setMode(boolean, boolean)} every time it is called.
*
* @param mt new metatable to attach to this object, may be {@code null}
* @return previous metatable associated with this object
*/
@Override
public Table setMetatable(Table mt) {
// not thread-safe!
Table old = metatable;
if (old != null) {
// update the basetable mapping
old.basetables.remove(this);
}
boolean wk = false;
boolean wv = false;
if (mt != null) {
mt.basetables.add(this);
Object m = mt.rawget(Metatables.MT_MODE);
if (m instanceof String) {
String s = (String) m;
wk = s.indexOf('k') > -1;
wv = s.indexOf('v') > -1;
}
}
metatable = mt;
setMode(wk, wv);
return old;
}
@Override
public Table getMetatable() {
// not thread-safe!
return metatable;
}
/**
* If {@code key} is equal to {@link Metatables#MT_MODE}, updates the weakness of the tables
* that use this table as their metatable (i.e., the basetables of this table).
* Otherwise, this method has no effect.
*
* Whenever applicable, this method must be called by the implementations
* of {@link #rawset(Object, Object)} in order to ensure that assignments to
* the {@link Metatables#MT_MODE} key update the weakness mode of the tables that use this
* table as a metatable, as required by §2.5.2 of the Lua Reference Manual.
*
* It is safe not to call this method when {@code key} is known not to be equal to
* {@link Metatables#MT_MODE}.
*
* @param key the key, may be {@code null}
* @param value the value, may be {@code null}
*/
protected void updateBasetableModes(Object key, Object value) {
// not thread-safe!
if (Metatables.MT_MODE.equals(key)) {
boolean wk = false;
boolean wv = false;
if (value instanceof String) {
String s = (String) value;
wk = s.indexOf('k') > -1;
wv = s.indexOf('v') > -1;
}
// update all tables
for (Table t : basetables) {
t.setMode(wk, wv);
}
}
}
/**
* Sets the weakness of this table. If {@code weakKeys} is {@code true}, the table will have
* weak keys (otherwise, the table will have non-weak keys). Similarly, if {@code weakValues}
* is {@code true}, the table will have weak values (and non-weak values if {@code false}).
*
* This method is not meant to be called directly: according to §2.5.2 of the Lua
* Reference Manual, the weakness of a table is fully determined by the value of the
* {@code "__mode"} field of its metatable. It is, however, meant to be called as part
* of maintenance of this requirement by {@link #setMetatable(Table)} and
* {@link #updateBasetableModes(Object, Object)}.
*
* @param weakKeys key mode ({@code true} for weak, {@code false} for non-weak keys)
* @param weakValues value mode ({@code true} for weak, {@code false} for non-weak values)
*/
protected abstract void setMode(boolean weakKeys, boolean weakValues);
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy