diff --git a/src/addon/java/lua/addon/luacompat/CoroutinesLib.java b/src/addon/java/lua/addon/luacompat/CoroutinesLib.java
new file mode 100644
index 00000000..c0e34de6
--- /dev/null
+++ b/src/addon/java/lua/addon/luacompat/CoroutinesLib.java
@@ -0,0 +1,95 @@
+package lua.addon.luacompat;
+
+import lua.GlobalState;
+import lua.VM;
+import lua.io.Closure;
+import lua.value.LFunction;
+import lua.value.LTable;
+import lua.value.LThread;
+
+
+public class CoroutinesLib extends LFunction {
+
+ public static void install() {
+ LTable lib = new LTable(0,6);
+ lib.put("create", new CoroutinesLib(1));
+ lib.put("resume", new CoroutinesLib(2));
+ lib.put("running", new CoroutinesLib(3));
+ lib.put("status", new CoroutinesLib(4));
+ lib.put("wrap", new CoroutinesLib(5));
+ lib.put("yield", new CoroutinesLib(6));
+ GlobalState.getGlobalsTable().put("coroutine",lib);
+ }
+
+ private int id = 0;
+
+ private static LThread running;
+
+ public CoroutinesLib() {
+ this(0);
+ }
+
+ private CoroutinesLib( int id ) {
+ this.id = id;
+ }
+
+ public boolean luaStackCall( VM vm ) {
+ switch ( id ) {
+ case 0: { // load lib
+ install();
+ vm.pushnil();
+ break;
+ }
+ case 1: { // create
+ Closure c = (Closure) vm.topointer(2);
+ vm.pushlvalue( new LThread(c) );
+ break;
+ }
+ case 2: {// resume
+ LThread t = (LThread) vm.topointer(2);
+ LThread prior = running;
+ try {
+ // whatever is left on the stack by the resumeFrom() implementation
+ // becomes return values!
+ running = t;
+ t.resumeFrom( vm, prior );
+ return false;
+ } finally {
+ running = prior;
+ }
+ }
+ case 3: { // running
+ if ( running != null ) {
+ vm.pushlvalue( running );
+ } else {
+ vm.pushnil();
+ }
+ break;
+ }
+ case 4: { // status
+ vm.pushstring( ((LThread) vm.topointer(2)).getStatus() );
+ break;
+ }
+ case 5: { // wrap
+ vm.error( "wrap() not supported" );
+ return false;
+ }
+ case 6: { // yield
+ if ( running == null )
+ vm.error("main thread can't yield");
+ else {
+ return running.yield();
+ }
+ }
+ case 7: { // wrapped resume
+ vm.error( "wrap() not supported" );
+ return false;
+ }
+ }
+ vm.insert(1);
+ vm.settop(1);
+ return false;
+ }
+
+
+}
diff --git a/src/addon/java/lua/addon/luacompat/LuaCompat.java b/src/addon/java/lua/addon/luacompat/LuaCompat.java
index dd4c6423..9195ad62 100644
--- a/src/addon/java/lua/addon/luacompat/LuaCompat.java
+++ b/src/addon/java/lua/addon/luacompat/LuaCompat.java
@@ -55,6 +55,9 @@ public class LuaCompat extends LFunction {
LTable table = new LTable();
installNames( pckg, TABLE_NAMES, TABLES_BASE );
globals.put( "table", pckg );
+
+ // coroutines
+ CoroutinesLib.install();
}
private static void installNames( LTable table, String[] names, int indexBase ) {
diff --git a/src/main/java/lua/VM.java b/src/main/java/lua/VM.java
index bbbd1898..d3675080 100644
--- a/src/main/java/lua/VM.java
+++ b/src/main/java/lua/VM.java
@@ -1382,7 +1382,7 @@ public interface VM {
* This function pops n values from the stack
* from, and pushes them onto the stack to.
*/
- public void xmove(StackState to, int n);
+ public void xmove(VM to, int n);
/**
* Yields a coroutine. [-?, +?, -]
diff --git a/src/main/java/lua/io/Closure.java b/src/main/java/lua/io/Closure.java
index 10fefc29..6fab028e 100644
--- a/src/main/java/lua/io/Closure.java
+++ b/src/main/java/lua/io/Closure.java
@@ -3,20 +3,35 @@ package lua.io;
import lua.StackState;
import lua.VM;
import lua.value.LFunction;
-import lua.value.LValue;
+import lua.value.LTable;
public class Closure extends LFunction {
- public LValue env;
+ public LTable env;
public Proto p;
public UpVal[] upVals;
- // TODO: change arg type to VM?
+ /**
+ * @deprecated construct with environment instead
+ * @param state
+ * @param p
+ */
public Closure(StackState state, Proto p) {
this.env = state._G;
this.p = p;
upVals = new UpVal[p.nups];
}
+ /**
+ * Construct using a prototype and initial environment.
+ * @param p
+ * @param env
+ */
+ public Closure(Proto p, LTable env) {
+ this.p = p;
+ this.env = env;
+ upVals = new UpVal[p.nups];
+ }
+
// 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
diff --git a/src/main/java/lua/value/LThread.java b/src/main/java/lua/value/LThread.java
index e6cf7010..3c47cd17 100644
--- a/src/main/java/lua/value/LThread.java
+++ b/src/main/java/lua/value/LThread.java
@@ -1,9 +1,33 @@
package lua.value;
import lua.Lua;
+import lua.StackState;
+import lua.VM;
+import lua.io.Closure;
public class LThread extends LValue {
+
+ private static final int STATUS_SUSPENDED = 1;
+ private static final int STATUS_NORMAL = 2;
+ private static final int STATUS_ACTIVE = 3;
+ private static final int STATUS_DEAD = 4;
+ private static final String[] NAMES = {
+ "suspended",
+ "normal",
+ "active",
+ "dead" };
+ private int status = STATUS_SUSPENDED;
+
+ private StackState threadVm;
+
+
+ public LThread(Closure c) {
+ // TODO: inherit globals!
+ threadVm = new StackState();
+ threadVm.pushlvalue(new Closure(c.p, threadVm._G));
+ }
+
public int luaGetType() {
return Lua.LUA_TTHREAD;
}
@@ -11,4 +35,74 @@ public class LThread extends LValue {
public String toJavaString() {
return "thread: "+hashCode();
}
+
+ public String getStatus() {
+ return NAMES[status];
+ }
+
+ /** This needs to leave any values returned by yield in the corouting
+ * on the calling vm stack
+ * @param vm
+ * @param prior
+ */
+ public void resumeFrom(VM vm, LThread prior) {
+
+ if ( status == STATUS_DEAD ) {
+ vm.settop(0);
+ vm.pushboolean(false);
+ vm.pushstring("cannot resume dead coroutine");
+ return;
+ }
+
+ // set prior thread to normal status while we are running
+ if ( prior != null )
+ prior.status = STATUS_NORMAL;
+
+ try {
+ // copy args in
+ if ( threadVm.cc < 0 ) {
+ vm.xmove(threadVm, vm.gettop() - 2);
+ threadVm.prepStackCall();
+ } else {
+ threadVm.settop(0);
+ vm.xmove(threadVm, vm.gettop() - 2);
+ }
+
+ // run this vm until it yields
+ status = STATUS_ACTIVE;
+ while ( threadVm.cc >= 0 && status == STATUS_ACTIVE )
+ threadVm.exec();
+
+ // copy return values from yielding stack state
+ vm.settop(0);
+ vm.pushboolean(true);
+ if ( threadVm.cc >= 0 ) {
+ threadVm.xmove(vm, threadVm.gettop() - 1);
+ } else {
+ threadVm.base = 0;
+ threadVm.xmove(vm, threadVm.gettop());
+ }
+
+ } catch ( Throwable t ) {
+ status = STATUS_DEAD;
+ vm.settop(0);
+ vm.pushboolean(false);
+ vm.pushstring("thread: "+t);
+
+ } finally {
+ if ( threadVm.cc < 0 )
+ status = STATUS_DEAD;
+
+ // reset prior thread status
+ if ( prior != null )
+ prior.status = STATUS_ACTIVE;
+ }
+
+ }
+
+ public boolean yield() {
+ if ( status == STATUS_ACTIVE )
+ status = STATUS_SUSPENDED;
+ return true;
+ }
}
diff --git a/src/test/java/lua/LuaJTest.java b/src/test/java/lua/LuaJTest.java
index 520b9445..f463ba71 100644
--- a/src/test/java/lua/LuaJTest.java
+++ b/src/test/java/lua/LuaJTest.java
@@ -60,6 +60,10 @@ public class LuaJTest extends TestCase {
runTest( "coercions" );
}
+ public void testCoroutines() throws IOException, InterruptedException {
+ runTest( "coroutines" );
+ }
+
public void testCompare() throws IOException, InterruptedException {
runTest( "compare" );
}
diff --git a/src/test/res/coroutines.lua b/src/test/res/coroutines.lua
new file mode 100644
index 00000000..9de0fc7c
--- /dev/null
+++ b/src/test/res/coroutines.lua
@@ -0,0 +1,18 @@
+function foo (a)
+ print("foo", a)
+ return coroutine.yield(2*a)
+end
+
+co = coroutine.create(function (a,b)
+ print("co-body", a, b)
+ local r = foo(a+1)
+ print("co-body", r)
+ local r, s = coroutine.yield(a+b, a-b)
+ print("co-body", r, s)
+ return b, "end"
+end)
+
+print("main", coroutine.resume(co, 1, 10))
+print("main", coroutine.resume(co, "r"))
+print("main", coroutine.resume(co, "x", "y"))
+print("main", coroutine.resume(co, "x", "y"))
\ No newline at end of file
diff --git a/src/test/res/coroutines.luac b/src/test/res/coroutines.luac
new file mode 100644
index 00000000..a450f1f8
Binary files /dev/null and b/src/test/res/coroutines.luac differ