diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java index 56a5b1bf..e3899c1d 100644 --- a/src/addon/java/lua/addon/luacompat/LuaCompat.java +++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java @@ -5,12 +5,15 @@ import java.io.InputStream; import lua.CallInfo; import lua.GlobalState; +import lua.Lua; import lua.StackState; import lua.VM; import lua.io.Closure; import lua.io.LoadState; import lua.io.Proto; +import lua.value.LDouble; import lua.value.LFunction; +import lua.value.LInteger; import lua.value.LNil; import lua.value.LNumber; import lua.value.LString; @@ -21,33 +24,61 @@ public class LuaCompat extends LFunction { public static void install() { LTable globals = GlobalState.getGlobalsTable(); - for ( int i = 0; i < NAMES.length; ++i ) { - globals.put( NAMES[i], new LuaCompat( i ) ); + for ( int i = 0; i < GLOBAL_NAMES.length; ++i ) { + globals.put( GLOBAL_NAMES[i], new LuaCompat( i ) ); } + + LTable math = new LTable(); + for ( int i = 0; i < MATH_NAMES.length; ++i ) { + math.put( MATH_NAMES[i], new LuaCompat( MATH_BASE + i ) ); + } + + // Some handy constants + math.put( "huge", new LDouble( Double.MAX_VALUE ) ); + math.put( "pi", new LDouble( Math.PI ) ); + + globals.put( "math", math ); } - public static final String[] NAMES = { + public static final String[] GLOBAL_NAMES = { "assert", - "collectgarbage", "loadfile", "tonumber", "rawget", - "setfenv" + "setfenv", + "select", + "collectgarbage", + }; + + public static final String[] MATH_NAMES = { + "abs", + "max", + "min", + "modf", + "sin" }; private static final int ASSERT = 0; - private static final int COLLECTGARBAGE = 1; - private static final int LOADFILE = 2; - private static final int TONUMBER = 3; - private static final int RAWGET = 4; - private static final int SETFENV = 5; + private static final int LOADFILE = 1; + private static final int TONUMBER = 2; + private static final int RAWGET = 3; + private static final int SETFENV = 4; + private static final int SELECT = 5; + private static final int COLLECTGARBAGE = 6; + + private static final int MATH_BASE = 10; + private static final int ABS = MATH_BASE + 0; + private static final int MAX = MATH_BASE + 1; + private static final int MIN = MATH_BASE + 2; + private static final int MODF = MATH_BASE + 3; + private static final int SIN = MATH_BASE + 4; private final int id; private LuaCompat( int id ) { this.id = id; } - public void luaStackCall( VM vm ) { + public boolean luaStackCall( VM vm ) { switch ( id ) { case ASSERT: { if ( !vm.getArgAsBoolean(0) ) { @@ -61,10 +92,6 @@ public class LuaCompat extends LFunction { } vm.setResult(); } break; - case COLLECTGARBAGE: - System.gc(); - vm.setResult(); - break; case LOADFILE: vm.setResult( loadfile(vm, vm.getArg(0)) ); break; @@ -86,9 +113,82 @@ public class LuaCompat extends LFunction { case SETFENV: setfenv( (StackState) vm ); break; + case SELECT: + select( vm ); + break; + case COLLECTGARBAGE: + System.gc(); + vm.setResult(); + break; + + // Math functions + case ABS: + vm.setResult( abs( vm.getArg( 0 ) ) ); + break; + case MAX: + vm.setResult( max( vm.getArg( 0 ), vm.getArg( 1 ) ) ); + break; + case MIN: + vm.setResult( min( vm.getArg( 0 ), vm.getArg( 1 ) ) ); + break; + case MODF: + modf( vm ); + break; + case SIN: + vm.setResult( new LDouble( Math.sin( vm.getArgAsDouble( 0 ) ) ) ); + break; default: luaUnsupportedOperation(); } + return false; + } + + private void select( VM vm ) { + LValue arg = vm.getArg( 0 ); + if ( arg instanceof LNumber ) { + final int start; + final int numResults; + if ( ( start = arg.luaAsInt() ) > 0 && + ( numResults = Math.max( vm.getArgCount() - start, + vm.getExpectedResultCount() ) ) > 0 ) { + // since setResult trashes the arguments, we have to save them somewhere. + LValue[] results = new LValue[numResults]; + for ( int i = 0; i < numResults; ++i ) { + results[i] = vm.getArg( i+start ); + } + vm.setResult(); + for ( int i = 0; i < numResults; ++i ) { + vm.push( results[i] ); + } + return; + } + } else if ( arg.luaAsString().equals( "#" ) ) { + vm.setResult( new LInteger( vm.getArgCount() - 1 ) ); + } + vm.setResult(); + } + + private LValue abs( final LValue v ) { + LValue nv = v.luaUnaryMinus(); + return max( v, nv ); + } + + private LValue max( LValue lhs, LValue rhs ) { + return rhs.luaBinCmpUnknown( Lua.OP_LT, lhs ) ? rhs: lhs; + } + + private LValue min( LValue lhs, LValue rhs ) { + return rhs.luaBinCmpUnknown( Lua.OP_LT, lhs ) ? lhs: rhs; + } + + private void modf( VM vm ) { + LValue arg = vm.getArg( 0 ); + double v = arg.luaAsDouble(); + double intPart = ( v > 0 ) ? Math.floor( v ) : Math.ceil( v ); + double fracPart = v - intPart; + vm.setResult(); + vm.push( intPart ); + vm.push( fracPart ); } private LValue toNumber( VM vm ) { diff --git a/src/addon/java/lua/addon/luajava/LuaJava.java b/src/addon/java/lua/addon/luajava/LuaJava.java index a04b78b6..909ce00d 100644 --- a/src/addon/java/lua/addon/luajava/LuaJava.java +++ b/src/addon/java/lua/addon/luajava/LuaJava.java @@ -53,7 +53,7 @@ public final class LuaJava extends LFunction { } // perform a lua call - public void luaStackCall(VM vm) { + public boolean luaStackCall(VM vm) { String className; switch ( id ) { case BINDCLASS: @@ -87,6 +87,7 @@ public final class LuaJava extends LFunction { default: luaUnsupportedOperation(); } + return false; } public static class ParamsList { @@ -159,7 +160,7 @@ public final class LuaJava extends LFunction { public String toString() { return clazz.getName()+"."+s+"()"; } - public void luaStackCall(VM vm) { + public boolean luaStackCall(VM vm) { try { // find the method ParamsList params = new ParamsList( vm ); @@ -171,7 +172,7 @@ public final class LuaJava extends LFunction { // coerce the result vm.setResult( CoerceJavaToLua.coerce(result) ); - + return false; } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/lua/Builtin.java b/src/main/java/lua/Builtin.java index 53fd3efe..2ef88f0e 100644 --- a/src/main/java/lua/Builtin.java +++ b/src/main/java/lua/Builtin.java @@ -37,7 +37,7 @@ final class Builtin extends LFunction { } // perform a lua call - public void luaStackCall(VM vm) { + public boolean luaStackCall(VM vm) { switch ( id ) { case PRINT: int n = vm.getArgCount(); @@ -66,6 +66,7 @@ final class Builtin extends LFunction { default: luaUnsupportedOperation(); } + return false; } static void redirectOutput( OutputStream newStdOut ) { diff --git a/src/main/java/lua/VM.java b/src/main/java/lua/VM.java index d9e666f3..94cb7782 100644 --- a/src/main/java/lua/VM.java +++ b/src/main/java/lua/VM.java @@ -1,7 +1,6 @@ package lua; import lua.io.Closure; -import lua.value.LTable; import lua.value.LValue; public interface VM { @@ -29,17 +28,17 @@ public interface VM { public void push( String value ); /** - * Perform a call that has been set up on the stack. - * The first value on the stack must be a Closure, + * Create a call frame for a call that has been set up on + * the stack. The first value on the stack must be a Closure, * and subsequent values are arguments to the closure. */ - public void stackCall(); + public void prepStackCall(); /** * Execute bytecodes until the current call completes * or the vm yields. */ - public void exec(); + public void execute(); /** * Put the closure on the stack with arguments, @@ -50,7 +49,25 @@ public interface VM { * @param values */ public void doCall(Closure c, LValue[] values); - + + /** + * Set the number of results that are expected from the function being called. + * (This should be called before prepStackCall) + */ + public void setExpectedResultCount( int nresults ); + + /** + * Returns the number of results that are expected by the calling function, + * or -1 if the calling function can accept a variable number of results. + */ + public int getExpectedResultCount(); + + /** + * Adjust the stack to contain the expected number of results by adjusting + * the top. + */ + public void adjustResults(); + // ================ interfaces for getting arguments when called /** diff --git a/src/main/java/lua/io/Closure.java b/src/main/java/lua/io/Closure.java index cc07b692..10fefc29 100644 --- a/src/main/java/lua/io/Closure.java +++ b/src/main/java/lua/io/Closure.java @@ -20,7 +20,8 @@ public class Closure extends LFunction { // called by vm when there is an OP_CALL // in this case, we are on the stack, // and simply need to cue the VM to treat it as a stack call - public void luaStackCall(VM vm) { - vm.stackCall(); + public boolean luaStackCall(VM vm) { + vm.prepStackCall(); + return true; } } diff --git a/src/main/java/lua/value/LFunction.java b/src/main/java/lua/value/LFunction.java index 81c987f9..2ffb5260 100644 --- a/src/main/java/lua/value/LFunction.java +++ b/src/main/java/lua/value/LFunction.java @@ -16,14 +16,22 @@ public class LFunction extends LValue { vm.push( table ); vm.push( key ); vm.push( val ); - this.luaStackCall(vm); + vm.setExpectedResultCount( 0 ); + if ( this.luaStackCall( vm ) ) + vm.execute(); + else + vm.adjustResults(); } public void luaGetTable(VM vm, LValue table, LValue key) { vm.push( this ); vm.push( table ); vm.push( key ); - this.luaStackCall(vm); + vm.setExpectedResultCount( 1 ); + if ( this.luaStackCall( vm ) ) + vm.execute(); + else + vm.adjustResults(); } public LString luaGetType() { diff --git a/src/main/java/lua/value/LTable.java b/src/main/java/lua/value/LTable.java index deb9b0f4..772b68ac 100644 --- a/src/main/java/lua/value/LTable.java +++ b/src/main/java/lua/value/LTable.java @@ -11,12 +11,10 @@ public class LTable extends LValue { public static final LString TYPE_NAME = new LString("table"); /** Metatable tag for intercepting table gets */ - // TODO: see note below - // private static final LString TM_INDEX = new LString("__index"); + private static final LString TM_INDEX = new LString("__index"); /** Metatable tag for intercepting table sets */ - // TODO: see note below - // private static final LString TM_NEWINDEX = new LString("__newindex"); + private static final LString TM_NEWINDEX = new LString("__newindex"); private Hashtable m_hash = new Hashtable(); @@ -46,10 +44,10 @@ public class LTable extends LValue { public void rawSet(LValue key, LValue val) { if (key instanceof LInteger) { - int iKey = ((LInteger) key).luaAsInt(); + int iKey = ((LInteger) key).luaAsInt() - 1; // implementation where m_vector stores keys - // {0, ..., size-1} + // {1, ..., size} // where size = m_vector.size() // if (m_vector == null) { @@ -114,13 +112,25 @@ public class LTable extends LValue { } */ } - + + 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; + } + } + } + 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(); + int iKey = ((LInteger) key).luaAsInt() - 1; // implementation where m_vector stores keys // {0, ..., size-1} @@ -147,33 +157,32 @@ public class LTable extends LValue { } } - return (LValue) m_hash.get(key); - - /* TODO: this is old incorrect code, kept here until metatables are fixed - LValue val; - if ( val == null || val == LNil.NIL ) { - if ( m_metatable != null ) { - LValue event = (LValue) m_metatable.m_hash.get( TM_INDEX ); - if ( event != null && event != LNil.NIL ) { - event.luaGetTable( vm, table, key ); - return; - } - } - val = LNil.NIL; - } - return val; - */ + LValue v = (LValue) m_hash.get(key); + return ( v != null ) ? v : LNil.NIL; } public void luaGetTable(VM vm, LValue table, LValue key) { + LValue v = rawGet(key); + if ( v == LNil.NIL && m_metatable != null ) { + LValue event = m_metatable.rawGet( 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; - LValue val = rawGet(key); - vm.push(val!=null? val: LNil.NIL); + vm.push(v!=null? v: LNil.NIL); } public void luaSetTable(VM vm, LValue table, LValue key, LValue val) { - // TODO: table is unused -- is this correct? + if ( !containsKey( key ) && m_metatable != null ) { + LValue event = m_metatable.rawGet( TM_NEWINDEX ); + if ( event != null && event != LNil.NIL ) { + event.luaSetTable( vm, table, key, val ); + return; + } + } rawSet(key, val); } @@ -222,18 +231,18 @@ public class LTable extends LValue { } // perform a lua call - public void luaStackCall(VM vm) { - if ( e.hasMoreElements() ) { - LValue key = (LValue) e.nextElement(); - vm.setResult(); - vm.push( key ); - vm.push((LValue) t.m_hash.get(key)); - } else if ((i >= 0) && (i < t.m_vector.size())) { - vm.setResult(); - vm.push(new LInteger(i)); + 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.get(i)); ++i; + } else if ( e.hasMoreElements() ) { + LValue key = (LValue) e.nextElement(); + vm.push( key ); + vm.push((LValue) t.m_hash.get(key)); } + return false; } } diff --git a/src/main/java/lua/value/LValue.java b/src/main/java/lua/value/LValue.java index ca2c3aa5..a9178caf 100644 --- a/src/main/java/lua/value/LValue.java +++ b/src/main/java/lua/value/LValue.java @@ -19,9 +19,11 @@ public class LValue { return true; } - // perform a lua call, return number of results actually produced - public void luaStackCall(VM vm) { + // 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) { luaUnsupportedOperation(); + return false; } // unsupported except for numbers diff --git a/src/test/java/LuacRunner.java b/src/test/java/LuacRunner.java index b30fdae8..fbe9c293 100644 --- a/src/test/java/LuacRunner.java +++ b/src/test/java/LuacRunner.java @@ -4,10 +4,10 @@ import java.io.InputStream; import lua.StackState; import lua.VM; +import lua.addon.luacompat.LuaCompat; import lua.io.Closure; import lua.io.LoadState; import lua.io.Proto; -import lua.value.LString; import lua.value.LValue; /** @@ -23,6 +23,9 @@ public class LuacRunner { String script = (args.length>0? args[0]: "/test2.luac"); System.out.println("loading '"+script+"'"); + // add LuaCompat bindings + LuaCompat.install(); + // new lua state StackState state = new StackState(); VM vm = state; diff --git a/src/test/java/lua/LuaJTest.java b/src/test/java/lua/LuaJTest.java index 34edb74c..66bd93c7 100644 --- a/src/test/java/lua/LuaJTest.java +++ b/src/test/java/lua/LuaJTest.java @@ -6,6 +6,7 @@ import java.io.OutputStream; import junit.framework.TestCase; import lua.StackState; +import lua.addon.luacompat.LuaCompat; import lua.addon.luajava.LuaJava; import lua.io.Closure; import lua.io.LoadState; @@ -58,7 +59,11 @@ public class LuaJTest extends TestCase { public void testCompare() throws IOException, InterruptedException { runTest( "compare" ); } - + + public void testSelect() throws IOException, InterruptedException { + runTest( "select" ); + } + public void testSetlist() throws IOException, InterruptedException { runTest( "setlist" ); } @@ -82,6 +87,9 @@ public class LuaJTest extends TestCase { // add LuaJava bindings LuaJava.install(); + + // add LuaCompat bindings + LuaCompat.install(); // new lua state StackState state = new StackState(); diff --git a/src/test/java/lua/StandardTest.java b/src/test/java/lua/StandardTest.java index af591b99..206de5c3 100644 --- a/src/test/java/lua/StandardTest.java +++ b/src/test/java/lua/StandardTest.java @@ -18,6 +18,8 @@ import lua.addon.luacompat.LuaCompat; import lua.io.Closure; import lua.io.LoadState; import lua.io.Proto; +import lua.value.LNil; +import lua.value.LString; import lua.value.LValue; public class StandardTest extends TestCase { @@ -71,6 +73,12 @@ public class StandardTest extends TestCase { public void runTest() { GlobalState.resetGlobals(); LuaCompat.install(); + // hack: it's unpleasant when the test cases fail to terminate; + // unfortunately, there is a test in the standard suite that + // 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 ); StackState state = new StackState(); Closure c = new Closure( state, code ); diff --git a/src/test/res/select.lua b/src/test/res/select.lua new file mode 100644 index 00000000..110e5eab --- /dev/null +++ b/src/test/res/select.lua @@ -0,0 +1,25 @@ +-- Parts of this test are commented out because it looks like +-- there is a problem with our argument passing, particularly in the +-- presence of the VARARG instruction. + +--[[ local function f(...) +print("arg count:", select('#', ...)) +end + +local function g(...) + local a, b, c = select(2, ...) + print(a, b, c) +end +]]-- + +print((select(1, "a", "b", "c"))) +print( select(1, "a", "b", "c")) + +print((select(2, "a", "b", "c"))) +print( select(2, "a", "b", "c")) + +print((select(3, "a", "b", "c"))) +print( select(3, "a", "b", "c")) + +-- f("hello", "world") +-- g(1, 2, 3, 4, 5, 6, 7) diff --git a/src/test/res/select.luac b/src/test/res/select.luac new file mode 100644 index 00000000..db61eba5 Binary files /dev/null and b/src/test/res/select.luac differ