Various changes:

(1) New lua compatibility bindings, including select() and math functions
(2) fix some VM bugs
(3) fix some table bugs, and attempt to restore metatable functionality.
This commit is contained in:
Ian Farmer
2007-08-01 04:15:27 +00:00
parent 68b3e68169
commit 737c5e2855
13 changed files with 252 additions and 69 deletions

View File

@@ -5,12 +5,15 @@ import java.io.InputStream;
import lua.CallInfo; import lua.CallInfo;
import lua.GlobalState; import lua.GlobalState;
import lua.Lua;
import lua.StackState; import lua.StackState;
import lua.VM; import lua.VM;
import lua.io.Closure; import lua.io.Closure;
import lua.io.LoadState; import lua.io.LoadState;
import lua.io.Proto; import lua.io.Proto;
import lua.value.LDouble;
import lua.value.LFunction; import lua.value.LFunction;
import lua.value.LInteger;
import lua.value.LNil; import lua.value.LNil;
import lua.value.LNumber; import lua.value.LNumber;
import lua.value.LString; import lua.value.LString;
@@ -21,33 +24,61 @@ public class LuaCompat extends LFunction {
public static void install() { public static void install() {
LTable globals = GlobalState.getGlobalsTable(); LTable globals = GlobalState.getGlobalsTable();
for ( int i = 0; i < NAMES.length; ++i ) { for ( int i = 0; i < GLOBAL_NAMES.length; ++i ) {
globals.put( NAMES[i], new LuaCompat( 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", "assert",
"collectgarbage",
"loadfile", "loadfile",
"tonumber", "tonumber",
"rawget", "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 ASSERT = 0;
private static final int COLLECTGARBAGE = 1; private static final int LOADFILE = 1;
private static final int LOADFILE = 2; private static final int TONUMBER = 2;
private static final int TONUMBER = 3; private static final int RAWGET = 3;
private static final int RAWGET = 4; private static final int SETFENV = 4;
private static final int SETFENV = 5; 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 final int id;
private LuaCompat( int id ) { private LuaCompat( int id ) {
this.id = id; this.id = id;
} }
public void luaStackCall( VM vm ) { public boolean luaStackCall( VM vm ) {
switch ( id ) { switch ( id ) {
case ASSERT: { case ASSERT: {
if ( !vm.getArgAsBoolean(0) ) { if ( !vm.getArgAsBoolean(0) ) {
@@ -61,10 +92,6 @@ public class LuaCompat extends LFunction {
} }
vm.setResult(); vm.setResult();
} break; } break;
case COLLECTGARBAGE:
System.gc();
vm.setResult();
break;
case LOADFILE: case LOADFILE:
vm.setResult( loadfile(vm, vm.getArg(0)) ); vm.setResult( loadfile(vm, vm.getArg(0)) );
break; break;
@@ -86,9 +113,82 @@ public class LuaCompat extends LFunction {
case SETFENV: case SETFENV:
setfenv( (StackState) vm ); setfenv( (StackState) vm );
break; 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: default:
luaUnsupportedOperation(); 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 ) { private LValue toNumber( VM vm ) {

View File

@@ -53,7 +53,7 @@ public final class LuaJava extends LFunction {
} }
// perform a lua call // perform a lua call
public void luaStackCall(VM vm) { public boolean luaStackCall(VM vm) {
String className; String className;
switch ( id ) { switch ( id ) {
case BINDCLASS: case BINDCLASS:
@@ -87,6 +87,7 @@ public final class LuaJava extends LFunction {
default: default:
luaUnsupportedOperation(); luaUnsupportedOperation();
} }
return false;
} }
public static class ParamsList { public static class ParamsList {
@@ -159,7 +160,7 @@ public final class LuaJava extends LFunction {
public String toString() { public String toString() {
return clazz.getName()+"."+s+"()"; return clazz.getName()+"."+s+"()";
} }
public void luaStackCall(VM vm) { public boolean luaStackCall(VM vm) {
try { try {
// find the method // find the method
ParamsList params = new ParamsList( vm ); ParamsList params = new ParamsList( vm );
@@ -171,7 +172,7 @@ public final class LuaJava extends LFunction {
// coerce the result // coerce the result
vm.setResult( CoerceJavaToLua.coerce(result) ); vm.setResult( CoerceJavaToLua.coerce(result) );
return false;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -37,7 +37,7 @@ final class Builtin extends LFunction {
} }
// perform a lua call // perform a lua call
public void luaStackCall(VM vm) { public boolean luaStackCall(VM vm) {
switch ( id ) { switch ( id ) {
case PRINT: case PRINT:
int n = vm.getArgCount(); int n = vm.getArgCount();
@@ -66,6 +66,7 @@ final class Builtin extends LFunction {
default: default:
luaUnsupportedOperation(); luaUnsupportedOperation();
} }
return false;
} }
static void redirectOutput( OutputStream newStdOut ) { static void redirectOutput( OutputStream newStdOut ) {

View File

@@ -1,7 +1,6 @@
package lua; package lua;
import lua.io.Closure; import lua.io.Closure;
import lua.value.LTable;
import lua.value.LValue; import lua.value.LValue;
public interface VM { public interface VM {
@@ -29,17 +28,17 @@ public interface VM {
public void push( String value ); public void push( String value );
/** /**
* Perform a call that has been set up on the stack. * Create a call frame for a call that has been set up on
* The first value on the stack must be a Closure, * the stack. The first value on the stack must be a Closure,
* and subsequent values are arguments to the closure. * and subsequent values are arguments to the closure.
*/ */
public void stackCall(); public void prepStackCall();
/** /**
* Execute bytecodes until the current call completes * Execute bytecodes until the current call completes
* or the vm yields. * or the vm yields.
*/ */
public void exec(); public void execute();
/** /**
* Put the closure on the stack with arguments, * Put the closure on the stack with arguments,
@@ -50,7 +49,25 @@ public interface VM {
* @param values * @param values
*/ */
public void doCall(Closure c, LValue[] 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 // ================ interfaces for getting arguments when called
/** /**

View File

@@ -20,7 +20,8 @@ public class Closure extends LFunction {
// called by vm when there is an OP_CALL // called by vm when there is an OP_CALL
// in this case, we are on the stack, // in this case, we are on the stack,
// and simply need to cue the VM to treat it as a stack call // and simply need to cue the VM to treat it as a stack call
public void luaStackCall(VM vm) { public boolean luaStackCall(VM vm) {
vm.stackCall(); vm.prepStackCall();
return true;
} }
} }

View File

@@ -16,14 +16,22 @@ public class LFunction extends LValue {
vm.push( table ); vm.push( table );
vm.push( key ); vm.push( key );
vm.push( val ); 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) { public void luaGetTable(VM vm, LValue table, LValue key) {
vm.push( this ); vm.push( this );
vm.push( table ); vm.push( table );
vm.push( key ); vm.push( key );
this.luaStackCall(vm); vm.setExpectedResultCount( 1 );
if ( this.luaStackCall( vm ) )
vm.execute();
else
vm.adjustResults();
} }
public LString luaGetType() { public LString luaGetType() {

View File

@@ -11,12 +11,10 @@ public class LTable extends LValue {
public static final LString TYPE_NAME = new LString("table"); public static final LString TYPE_NAME = new LString("table");
/** Metatable tag for intercepting table gets */ /** 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 */ /** 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(); private Hashtable m_hash = new Hashtable();
@@ -46,10 +44,10 @@ public class LTable extends LValue {
public void rawSet(LValue key, LValue val) { public void rawSet(LValue key, LValue val) {
if (key instanceof LInteger) { if (key instanceof LInteger) {
int iKey = ((LInteger) key).luaAsInt(); int iKey = ((LInteger) key).luaAsInt() - 1;
// implementation where m_vector stores keys // implementation where m_vector stores keys
// {0, ..., size-1} // {1, ..., size}
// where size = m_vector.size() // where size = m_vector.size()
// //
if (m_vector == null) { 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 */ /** Utility method to directly get the value in a table, without metatable calls */
public LValue rawGet(LValue key) { public LValue rawGet(LValue key) {
if (m_vector != null) { if (m_vector != null) {
if (key instanceof LInteger) { if (key instanceof LInteger) {
int iKey = ((LInteger) key).luaAsInt(); int iKey = ((LInteger) key).luaAsInt() - 1;
// implementation where m_vector stores keys // implementation where m_vector stores keys
// {0, ..., size-1} // {0, ..., size-1}
@@ -147,33 +157,32 @@ public class LTable extends LValue {
} }
} }
return (LValue) m_hash.get(key); LValue v = (LValue) m_hash.get(key);
return ( v != null ) ? v : LNil.NIL;
/* 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;
*/
} }
public void luaGetTable(VM vm, LValue table, LValue key) { 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? // TODO: table is unused -- is this correct?
// stack.stack[base] = val; // stack.stack[base] = val;
LValue val = rawGet(key); vm.push(v!=null? v: LNil.NIL);
vm.push(val!=null? val: LNil.NIL);
} }
public void luaSetTable(VM vm, LValue table, LValue key, LValue val) { 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); rawSet(key, val);
} }
@@ -222,18 +231,18 @@ public class LTable extends LValue {
} }
// perform a lua call // perform a lua call
public void luaStackCall(VM vm) { public boolean luaStackCall(VM vm) {
if ( e.hasMoreElements() ) { vm.setResult();
LValue key = (LValue) e.nextElement(); if ((i >= 0) && (i < t.m_vector.size())) {
vm.setResult(); vm.push(new LInteger(i+1));
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));
vm.push((LValue) t.m_vector.get(i)); vm.push((LValue) t.m_vector.get(i));
++i; ++i;
} else if ( e.hasMoreElements() ) {
LValue key = (LValue) e.nextElement();
vm.push( key );
vm.push((LValue) t.m_hash.get(key));
} }
return false;
} }
} }

View File

@@ -19,9 +19,11 @@ public class LValue {
return true; return true;
} }
// perform a lua call, return number of results actually produced // perform a lua call, return true if the call is to a lua function, false
public void luaStackCall(VM vm) { // if it ran to completion.
public boolean luaStackCall(VM vm) {
luaUnsupportedOperation(); luaUnsupportedOperation();
return false;
} }
// unsupported except for numbers // unsupported except for numbers

View File

@@ -4,10 +4,10 @@ import java.io.InputStream;
import lua.StackState; import lua.StackState;
import lua.VM; import lua.VM;
import lua.addon.luacompat.LuaCompat;
import lua.io.Closure; import lua.io.Closure;
import lua.io.LoadState; import lua.io.LoadState;
import lua.io.Proto; import lua.io.Proto;
import lua.value.LString;
import lua.value.LValue; import lua.value.LValue;
/** /**
@@ -23,6 +23,9 @@ public class LuacRunner {
String script = (args.length>0? args[0]: "/test2.luac"); String script = (args.length>0? args[0]: "/test2.luac");
System.out.println("loading '"+script+"'"); System.out.println("loading '"+script+"'");
// add LuaCompat bindings
LuaCompat.install();
// new lua state // new lua state
StackState state = new StackState(); StackState state = new StackState();
VM vm = state; VM vm = state;

View File

@@ -6,6 +6,7 @@ import java.io.OutputStream;
import junit.framework.TestCase; import junit.framework.TestCase;
import lua.StackState; import lua.StackState;
import lua.addon.luacompat.LuaCompat;
import lua.addon.luajava.LuaJava; import lua.addon.luajava.LuaJava;
import lua.io.Closure; import lua.io.Closure;
import lua.io.LoadState; import lua.io.LoadState;
@@ -58,7 +59,11 @@ public class LuaJTest extends TestCase {
public void testCompare() throws IOException, InterruptedException { public void testCompare() throws IOException, InterruptedException {
runTest( "compare" ); runTest( "compare" );
} }
public void testSelect() throws IOException, InterruptedException {
runTest( "select" );
}
public void testSetlist() throws IOException, InterruptedException { public void testSetlist() throws IOException, InterruptedException {
runTest( "setlist" ); runTest( "setlist" );
} }
@@ -82,6 +87,9 @@ public class LuaJTest extends TestCase {
// add LuaJava bindings // add LuaJava bindings
LuaJava.install(); LuaJava.install();
// add LuaCompat bindings
LuaCompat.install();
// new lua state // new lua state
StackState state = new StackState(); StackState state = new StackState();

View File

@@ -18,6 +18,8 @@ import lua.addon.luacompat.LuaCompat;
import lua.io.Closure; import lua.io.Closure;
import lua.io.LoadState; import lua.io.LoadState;
import lua.io.Proto; import lua.io.Proto;
import lua.value.LNil;
import lua.value.LString;
import lua.value.LValue; import lua.value.LValue;
public class StandardTest extends TestCase { public class StandardTest extends TestCase {
@@ -71,6 +73,12 @@ public class StandardTest extends TestCase {
public void runTest() { public void runTest() {
GlobalState.resetGlobals(); GlobalState.resetGlobals();
LuaCompat.install(); 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(); StackState state = new StackState();
Closure c = new Closure( state, code ); Closure c = new Closure( state, code );

25
src/test/res/select.lua Normal file
View File

@@ -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)

BIN
src/test/res/select.luac Normal file

Binary file not shown.