diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java index decc81b7..20c8e386 100644 --- a/src/addon/java/lua/addon/luacompat/LuaCompat.java +++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java @@ -39,7 +39,7 @@ public class LuaCompat extends LFunction { globals.put( "math", math ); - LTable string = new LTable(); + LTable string = LString.getMetatable(); for ( int i = 0; i < STRING_NAMES.length; ++i ) { string.put( STRING_NAMES[i], new LuaCompat( STRING_BASE + i ) ); } diff --git a/src/main/java/lua/value/LString.java b/src/main/java/lua/value/LString.java index 385fd145..8e696f5c 100644 --- a/src/main/java/lua/value/LString.java +++ b/src/main/java/lua/value/LString.java @@ -1,7 +1,6 @@ package lua.value; import lua.Lua; -import lua.StackState; public class LString extends LValue { @@ -10,6 +9,8 @@ public class LString extends LValue { final String m_string; final int m_hash; + private static LTable s_stringMT; + public LString(String string) { this.m_string = string; this.m_hash = string.hashCode(); @@ -91,5 +92,24 @@ public class LString extends LValue { public LString luaGetType() { return TYPE_NAME; } - + + public LTable luaGetMetatable() { + synchronized ( LString.class ) { + return s_stringMT; + } + } + + /** + * Get the metatable for all string values. Creates the table if it does not + * exist yet, and sets its __index entry to point to itself. + * + * @return metatable that will be used for all strings + */ + public static synchronized LTable getMetatable() { + if ( s_stringMT == null ) { + s_stringMT = new LTable(); + s_stringMT.put( TM_INDEX, s_stringMT ); + } + return s_stringMT; + } } diff --git a/src/main/java/lua/value/LTable.java b/src/main/java/lua/value/LTable.java index 6b8c6772..83b5e43f 100644 --- a/src/main/java/lua/value/LTable.java +++ b/src/main/java/lua/value/LTable.java @@ -23,12 +23,6 @@ public class LTable extends LValue { public static final LString TYPE_NAME = new LString("table"); - /** Metatable tag for intercepting table gets */ - private static final LString TM_INDEX = new LString("__index"); - - /** Metatable tag for intercepting table sets */ - private static final LString TM_NEWINDEX = new LString("__newindex"); - /** * Zero-length array to use instead of null, so that we don't need to * check for null everywhere. @@ -229,28 +223,22 @@ public class LTable extends LValue { final int slot = findSlot( key ); return m_hashKeys[ slot ] != null; } - + public void luaGetTable(VM vm, LValue table, LValue key) { LValue v = get(key); if ( v == LNil.NIL && m_metatable != null ) { - LValue event = m_metatable.get( TM_INDEX ); - if ( event != null && event != LNil.NIL ) { - event.luaGetTable( vm, table, key ); - return; - } + super.luaGetTable( vm, table, key ); + } else { + vm.push(v); } - vm.push(v); } public void luaSetTable(VM vm, LValue table, LValue key, LValue val) { if ( !containsKey( key ) && m_metatable != null ) { - LValue event = m_metatable.get( TM_NEWINDEX ); - if ( event != null && event != LNil.NIL ) { - event.luaSetTable( vm, table, key, val ); - return; - } + super.luaSetTable( vm, table, key, val ); + } else { + put(key, val); } - put(key, val); } /** @@ -269,13 +257,14 @@ public class LTable extends LValue { } /** Valid for tables */ - public LValue luaGetMetatable() { + public LTable luaGetMetatable() { return this.m_metatable; } /** Valid for tables */ public void luaSetMetatable(LValue metatable) { - this.m_metatable = (LTable) metatable; + this.m_metatable = ( metatable != null && metatable != LNil.NIL ) ? + (LTable) metatable : null; } public String luaAsString() { diff --git a/src/main/java/lua/value/LUserData.java b/src/main/java/lua/value/LUserData.java index edee4f0c..ab26121d 100644 --- a/src/main/java/lua/value/LUserData.java +++ b/src/main/java/lua/value/LUserData.java @@ -4,6 +4,7 @@ public class LUserData extends LValue { public static final LString TYPE_NAME = new LString("userdata"); public final Object m_instance; + public LTable m_metatable; public LUserData(Object obj) { m_instance = obj; @@ -25,4 +26,8 @@ public class LUserData extends LValue { public LString luaGetType() { return TYPE_NAME; } + + public LTable luaGetMetatable() { + return m_metatable; + } } diff --git a/src/main/java/lua/value/LValue.java b/src/main/java/lua/value/LValue.java index 465caa0f..c730b9d8 100644 --- a/src/main/java/lua/value/LValue.java +++ b/src/main/java/lua/value/LValue.java @@ -6,6 +6,12 @@ import lua.VM; abstract public class LValue { + /** Metatable tag for intercepting table gets */ + public static final LString TM_INDEX = new LString("__index"); + + /** Metatable tag for intercepting table sets */ + public static final LString TM_NEWINDEX = new LString("__newindex"); + protected static LValue luaUnsupportedOperation() { throw new java.lang.RuntimeException( "not supported" ); } @@ -79,22 +85,39 @@ public class LValue { } /** set a value in a table + * For non-tables, goes straight to the meta-table. * @param vm the calling vm * @param table the table to operate on * @param the key to set * @param the value to set */ public void luaSetTable(VM vm, LValue table, LValue key, LValue val) { - luaUnsupportedOperation(); + LTable mt = luaGetMetatable(); + if ( mt != null ) { + LValue event = mt.get( TM_NEWINDEX ); + if ( event != null && event != LNil.NIL ) { + event.luaSetTable( vm, table, key, val ); + return; + } + } + vm.push( LNil.NIL ); } - + /** Get a value from a table * @param vm the calling vm * @param table the table from which to get the value * @param key the key to look up */ public void luaGetTable(VM vm, LValue table, LValue key) { - luaUnsupportedOperation(); + LTable mt = luaGetMetatable(); + if ( mt != null ) { + LValue event = mt.get( TM_INDEX ); + if ( event != null && event != LNil.NIL ) { + event.luaGetTable( vm, table, key ); + return; + } + } + vm.push(LNil.NIL); } /** Get the value as a String @@ -135,9 +158,18 @@ public class LValue { return luaUnsupportedOperation(); } - /** Valid for tables */ - public LValue luaGetMetatable() { - return luaUnsupportedOperation(); + /** + * Valid for all types: get a metatable. Only tables and userdata can have a + * different metatable per instance, though, other types are restricted to + * one metatable per type. + * + * Since metatables on non-tables can only be set through Java and not Lua, + * this function should be overridden for each value type as necessary. + * + * @return null if there is no meta-table + */ + public LTable luaGetMetatable() { + return null; } /** Valid for tables */ @@ -147,4 +179,5 @@ public class LValue { /** Valid for all types: return the type of this value as an LString */ public abstract LString luaGetType(); + } diff --git a/src/test/java/lua/LuaJTest.java b/src/test/java/lua/LuaJTest.java index 74571f59..319b3fea 100644 --- a/src/test/java/lua/LuaJTest.java +++ b/src/test/java/lua/LuaJTest.java @@ -64,6 +64,10 @@ public class LuaJTest extends TestCase { runTest( "compare" ); } + public void testMetatables() throws IOException, InterruptedException { + runTest( "metatables" ); + } + public void testSelect() throws IOException, InterruptedException { runTest( "select" ); } diff --git a/src/test/res/metatables.lua b/src/test/res/metatables.lua new file mode 100644 index 00000000..1b7faea3 --- /dev/null +++ b/src/test/res/metatables.lua @@ -0,0 +1,5 @@ +-- The purpose of this test case is to demonstrate that +-- basic metatable operations on non-table types work. +-- i.e. that s.sub(s,...) could be used in place of string.sub(s,...) +local s = "hello" +print(s:sub(2,4)) diff --git a/src/test/res/metatables.luac b/src/test/res/metatables.luac new file mode 100644 index 00000000..36b47850 Binary files /dev/null and b/src/test/res/metatables.luac differ