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.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 ) {

View File

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

View File

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

View File

@@ -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
/**

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

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.