diff --git a/.classpath b/.classpath
index fc39bf58..1ed5c4b3 100644
--- a/.classpath
+++ b/.classpath
@@ -6,5 +6,6 @@
+
diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java
new file mode 100644
index 00000000..3c956e59
--- /dev/null
+++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java
@@ -0,0 +1,195 @@
+package lua.addon.luacompat;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import lua.CallFrame;
+import lua.GlobalState;
+import lua.StackState;
+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;
+import lua.value.LTable;
+import lua.value.LValue;
+
+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 ) );
+ }
+ }
+
+ public static final String[] NAMES = {
+ "assert",
+ "collectgarbage",
+ "loadfile",
+ "tonumber",
+ "rawget",
+ "setfenv"
+ };
+
+ 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 final int id;
+ private LuaCompat( int id ) {
+ this.id = id;
+ }
+
+ public void luaStackCall( CallFrame call, int base, int top, int nresults ) {
+ switch ( id ) {
+ case ASSERT: {
+ LValue v = call.stack[base+1];
+ if ( !v.luaAsBoolean() ) {
+ String message;
+ if ( top > base+2 ) {
+ message = call.stack[base+2].luaAsString();
+ } else {
+ message = "assertion failed!";
+ }
+ throw new RuntimeException(message);
+ }
+ call.top = base;
+ } break;
+ case COLLECTGARBAGE:
+ System.gc();
+ call.top = base;
+ break;
+ case LOADFILE:
+ call.stack[base] = loadfile(call, ( top > base ) ? call.stack[base+1] : null);
+ call.top = base+1;
+ break;
+ case TONUMBER:
+ call.stack[base] = toNumber( call.stack, base+1, top );
+ call.top = base+1;
+ break;
+ case RAWGET: {
+ LValue t = call.stack[base+1];
+ LValue k = call.stack[base+2];
+ LValue result = LNil.NIL;
+ if ( t instanceof LTable ) {
+ LValue v = (LValue) ( (LTable) t ).m_hash.get( k );
+ if ( v != null ) {
+ result = v;
+ }
+ }
+ call.stack[base] = result;
+ call.top = base+1;
+ } break;
+ case SETFENV:
+ call.top = base + setfenv( call.stack, base, base+1, top, call.state );
+ break;
+ default:
+ luaUnsupportedOperation();
+ }
+ if (nresults >= 0)
+ call.adjustTop(base + nresults);
+ }
+
+ private LValue toNumber( LValue[] stack, int first, int top ) {
+ LValue result = LNil.NIL;
+ if ( first < top ) {
+ LValue input = stack[first];
+ if ( input instanceof LNumber ) {
+ result = input;
+ } else if ( input instanceof LString ) {
+ int base = 10;
+ if ( first+1 < top ) {
+ base = stack[first+1].luaAsInt();
+ }
+ if ( base >= 2 && base <= 36 ) {
+ String str = input.luaAsString().trim();
+ try {
+ result = new LInteger( Integer.parseInt( str, base ) );
+ } catch ( NumberFormatException nfe ) {
+ if ( base == 10 ) {
+ try {
+ result = new LDouble( Double.parseDouble( str ) );
+ } catch ( NumberFormatException nfe2 ) {
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private int setfenv( LValue[] stack, int result, int argbase, int arglimit, StackState state ) {
+ LValue f = stack[argbase];
+ LValue newenv = stack[argbase+1];
+
+ Closure c = null;
+
+ // Lua reference manual says that first argument, f, can be a "Lua
+ // function" or an integer. Lots of things extend LFunction, but only
+ // instances of Closure are "Lua functions".
+ if ( f instanceof Closure ) {
+ c = (Closure) f;
+ } else {
+ int callStackDepth = f.luaAsInt();
+ if ( callStackDepth > 0 ) {
+ CallFrame frame = state.getStackFrame( callStackDepth );
+ if ( frame != null ) {
+ c = frame.cl;
+ }
+ } else {
+ // This is supposed to set the environment of the current
+ // "thread". But, we have not implemented coroutines yet.
+ throw new RuntimeException( "not implemented" );
+ }
+ }
+
+ if ( c != null ) {
+ if ( newenv instanceof LTable ) {
+ c.env = (LTable) newenv;
+ }
+ stack[ result ] = c;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private LValue loadfile( CallFrame call, LValue fileName ) {
+ InputStream is;
+
+ String script;
+ if ( fileName != null ) {
+ script = fileName.luaAsString();
+ is = getClass().getResourceAsStream( "/"+script );
+ } else {
+ is = System.in;
+ script = "-";
+ }
+
+ if ( is != null ) {
+ try {
+ Proto p = LoadState.undump(call.state, is, script);
+ return new Closure(call.state, p);
+ } catch (IOException e) {
+ } finally {
+ if ( is != System.in ) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ return LNil.NIL;
+ }
+}
diff --git a/src/test/java/lua/StandardTest.java b/src/test/java/lua/StandardTest.java
index 7bcb2096..91356c66 100644
--- a/src/test/java/lua/StandardTest.java
+++ b/src/test/java/lua/StandardTest.java
@@ -14,6 +14,7 @@ import junit.framework.TestCase;
import junit.framework.TestSuite;
import lua.Builtin;
import lua.StackState;
+import lua.addon.luacompat.LuaCompat;
import lua.io.Closure;
import lua.io.LoadState;
import lua.io.Proto;
@@ -68,6 +69,8 @@ public class StandardTest extends TestCase {
}
public void runTest() {
+ GlobalState.resetGlobals();
+ LuaCompat.install();
StackState state = new StackState();
Closure c = new Closure( state, code );