aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-23 08:59:35 +0100
committerBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-23 10:40:04 +0100
commit567e12ce3f1ef413afa510cb583d6ac8442a7a4a (patch)
treea1911e7291e917de549bbdd2dead60aa513af87f
parent7c459409b078e38da07e2570a77945fdc0f55c2c (diff)
downloadfengari-567e12ce3f1ef413afa510cb583d6ac8442a7a4a.tar.gz
fengari-567e12ce3f1ef413afa510cb583d6ac8442a7a4a.tar.bz2
fengari-567e12ce3f1ef413afa510cb583d6ac8442a7a4a.zip
coroutines
-rw-r--r--README.md169
-rw-r--r--src/lapi.js130
-rw-r--r--src/lauxlib.js8
-rw-r--r--src/lcorolib.js86
-rw-r--r--src/ldo.js227
-rw-r--r--src/linit.js5
-rw-r--r--src/lstate.js55
-rw-r--r--src/lualib.js23
-rw-r--r--src/lvm.js96
-rw-r--r--tests/lcorolib.js53
10 files changed, 678 insertions, 174 deletions
diff --git a/README.md b/README.md
index f67e193..fd3b3bd 100644
--- a/README.md
+++ b/README.md
@@ -21,65 +21,73 @@
- [ ] `__tostring`
- [ ] `__pairs`
- [ ] C API
- - [x] lua_version
+ - [x] lua_absindex
- [x] lua_atpanic
- - [x] lua_newstate
- - [x] lua_pushnil
- - [x] lua_gettop
- - [x] lua_type
- - [x] lua_typename
- - [x] lua_pushboolean
- - [x] lua_pushinteger
- - [x] lua_pushnumber
- - [x] lua_pushlstring
- - [x] lua_pushstring
- - [x] lua_pushvalue
- - [x] lua_tointeger
- - [x] lua_tointegerx
- - [x] lua_tolstring
- - [x] lua_tonumber
- - [x] lua_tonumberx
- - [x] lua_toboolean
- - [x] lua_topointer
- - [x] lua_pushjsclosure (lua_pushcclosure)
- - [x] lua_pushjsfunction (lua_pushcfunction)
- - [x] lua_pop
- - [x] lua_load
- [x] lua_call
- [x] lua_callk
- - [x] lua_pcall
- - [x] lua_setglobal
- - [x] lua_upvalueindex
- - [x] lua_createtable
- - [x] lua_newtable
- - [x] lua_gettable
- - [x] lua_settable
- - [x] lua_absindex
- [x] lua_checkstack
+ - [x] lua_concat
+ - [x] lua_copy
+ - [x] lua_createtable
+ - [x] lua_error
- [x] lua_getfield
- [x] lua_getglobal
- [x] lua_getmetatable
- - [x] lua_setmetatable
+ - [x] lua_gettable
+ - [x] lua_gettop
+ - [x] lua_insert
+ - [x] lua_isstring
+ - [x] lua_istable
+ - [x] lua_isyieldable
+ - [x] lua_load
+ - [x] lua_newstate
+ - [x] lua_newtable
+ - [x] lua_newthread
+ - [x] lua_next
+ - [x] lua_pcall
+ - [x] lua_pop
+ - [x] lua_pushboolean
- [x] lua_pushglobaltable
+ - [x] lua_pushinteger
+ - [x] lua_pushjsclosure (lua_pushcclosure)
+ - [x] lua_pushjsfunction (lua_pushcfunction)
- [x] lua_pushliteral
+ - [x] lua_pushlstring
+ - [x] lua_pushnil
+ - [x] lua_pushnumber
+ - [x] lua_pushstring
+ - [x] lua_pushvalue
+ - [x] lua_rawequal
- [x] lua_rawget
- [x] lua_rawgeti
+ - [x] lua_rawlen
- [x] lua_rawset
- - [x] lua_setfield
- - [x] lua_settop
- - [x] lua_tostring
- - [x] lua_rawequal
- - [x] lua_error
- - [x] lua_concat
- - [x] lua_isstring
- - [x] lua_istable
- [x] lua_remove
+ - [x] lua_resume
- [x] lua_rotate
- - [x] lua_insert
+ - [x] lua_setfield
+ - [x] lua_setglobal
+ - [x] lua_setmetatable
+ - [x] lua_settable
+ - [x] lua_settop
+ - [x] lua_status
- [x] lua_stringtonumber
- - [x] lua_rawlen
- - [x] lua_next
- - [x] lua_copy
+ - [x] lua_toboolean
+ - [x] lua_tointeger
+ - [x] lua_tointegerx
+ - [x] lua_tolstring
+ - [x] lua_tonumber
+ - [x] lua_tonumberx
+ - [x] lua_topointer
+ - [x] lua_tostring
+ - [x] lua_tothread
+ - [x] lua_type
+ - [x] lua_typename
+ - [x] lua_upvalueindex
+ - [x] lua_version
+ - [x] lua_xmove
+ - [x] lua_yield
+ - [x] lua_yieldk
- [ ] lua_arith
- [ ] lua_close
- [ ] lua_compare
@@ -107,9 +115,7 @@
- [ ] lua_isnumber
- [ ] lua_isthread
- [ ] lua_isuserdata
- - [ ] lua_isyieldable
- [ ] lua_len
- - [ ] lua_newthread
- [ ] lua_newuserdata
- [ ] lua_numbertointeger
- [ ] lua_pcallk
@@ -122,44 +128,39 @@
- [ ] lua_rawsetp
- [ ] lua_register
- [ ] lua_replace
- - [ ] lua_resume
- [ ] lua_setallocf
- [ ] lua_sethook
- [ ] lua_seti
- [ ] lua_setlocal
- [ ] lua_setupvalue
- [ ] lua_setuservalue
- - [ ] lua_status
- [ ] lua_tocfunction
- - [ ] lua_tothread
- [ ] lua_touserdata
- [ ] lua_upvalueid
- [ ] lua_upvaluejoin
- - [ ] lua_xmove
- - [ ] lua_yield
- - [ ] lua_yieldk
- [ ] Auxiliary library
- - [x] luaL_newstate
- - [x] luaL_typename
+ - [x] luaL_argcheck
+ - [x] luaL_argerror
+ - [x] luaL_callmeta
- [x] luaL_checkany
+ - [x] luaL_checkinteger
+ - [x] luaL_checklstring
+ - [x] luaL_checkstack
- [x] luaL_checktype
- - [x] luaL_callmeta
+ - [x] luaL_error
- [x] luaL_getmetafield
- - [x] luaL_setfuncs
- - [x] luaL_checkstack
- - [x] luaL_tolstring
- - [x] luaL_openlibs
- [x] luaL_getsubtable
- - [x] luaL_requiref
- - [x] luaL_checkinteger
- - [x] luaL_checklstring
+ - [x] luaL_newlib
+ - [x] luaL_newstate
+ - [x] luaL_openlibs
- [x] luaL_opt
- [x] luaL_optinteger
- [x] luaL_optlstring
+ - [x] luaL_requiref
+ - [x] luaL_setfuncs
+ - [x] luaL_tolstring
+ - [x] luaL_typename
- [x] luaL_where
- - [x] luaL_argerror
- - [x] luaL_argcheck
- - [x] luaL_error
- [ ] luaL_addchar
- [ ] luaL_addlstring
- [ ] luaL_addsize
@@ -184,7 +185,6 @@
- [ ] luaL_loadfile
- [ ] luaL_loadfilex
- [ ] luaL_loadstring
- - [ ] luaL_newlib
- [ ] luaL_newlibtable
- [ ] luaL_newmetatable
- [ ] luaL_optnumber
@@ -200,29 +200,36 @@
- [ ] luaL_unref
- [ ] Standard library
- [ ] Base lib
- - [x] tostring
- - [x] print
- - [x] getmetatable
- - [x] setmetatable
- - [x] rawequal
- - [x] rawset
- - [x] rawget
- - [x] type
- - [x] error
- - [x] pcall
- - [x] xpcall
+ - [x] assert
- [x] collectgarbage (unavailable)
+ - [x] error
+ - [x] getmetatable
- [x] ipairs
+ - [x] next
- [x] pairs
+ - [x] pcall
+ - [x] print
+ - [x] rawequal
+ - [x] rawget
+ - [x] rawlen
+ - [x] rawset
- [x] select
+ - [x] setmetatable
- [x] tonumber
- - [x] assert
- - [x] rawlen
- - [x] next
+ - [x] tostring
+ - [x] type
+ - [x] xpcall
- [ ] dofile
- [ ] loadfile
- [ ] load
- - [ ] ...
+ - [ ] Coroutine
+ - [x] coroutine.create
+ - [x] coroutine.resume
+ - [x] coroutine.yield
+ - [ ] coroutine.isyieldable
+ - [ ] coroutine.running
+ - [ ] coroutine.status
+ - [ ] coroutine.wrap
- [ ] Debug (errors)
- [ ] DOM API binding
- [ ] Parse Lua
diff --git a/src/lapi.js b/src/lapi.js
index e9f15c2..2f2802f 100644
--- a/src/lapi.js
+++ b/src/lapi.js
@@ -82,6 +82,19 @@ const lua_checkstack = function(L, n) {
return L.stack.length < luaconf.LUAI_MAXSTACK;
};
+const lua_xmove = function(from, to, n) {
+ if (from === to) return;
+ assert(n < (from.top - from.ci.funcOff), "not enough elements in the stack");
+ assert(from.l_G === to.l_G, "moving among independent states");
+ assert(to.ci.top - to.top >= n, "stack overflow");
+
+ from.top -= n;
+ for (let i = 0; i < n; i++) {
+ to.stack[to.top] = from.stack[from.top + i];
+ to.top++;
+ }
+};
+
/*
** basic stack manipulation
*/
@@ -490,6 +503,11 @@ const lua_tonumber = function(L, idx) {
return lvm.tonumber(index2addr(L, idx))
};
+const lua_tothread = function(L, idx) {
+ let o = index2addr(L, idx);
+ return o.ttisthread() ? o.value : null;
+};
+
const lua_topointer = function(L, idx) {
let o = index2addr(L, idx);
switch (o.ttype()) {
@@ -564,8 +582,12 @@ const lua_load = function(L, data, chunckname) {
return status;
};
+const lua_status = function(L) {
+ return L.status;
+};
+
const lua_callk = function(L, nargs, nresults, ctx, k) {
- assert(k === null || !(L.ci.callstatus & CIST_LUA), "cannot use continuations inside hooks");
+ assert(k === null || !(L.ci.callstatus & lstate.CIST_LUA), "cannot use continuations inside hooks");
assert(nargs + 1 < L.top - L.ci.funcOff, "not enough elements in the stack");
assert(L.status === TS.LUA_OK, "cannot do calls on non-normal thread");
assert(nargs === lua.LUA_MULTRET || (L.ci.top - L.top >= nargs - nresults, "results from function overflow current stack size"));
@@ -640,7 +662,6 @@ const lua_pcall = function(L, n, r, f) {
return lua_pcallk(L, n, r, f, 0, null);
};
-
/*
** miscellaneous functions
*/
@@ -685,66 +706,69 @@ const lua_getextraspace = function () {
return null;
};
-module.exports.lua_pushvalue = lua_pushvalue;
-module.exports.lua_pushnil = lua_pushnil;
-module.exports.lua_pushnumber = lua_pushnumber;
-module.exports.lua_pushinteger = lua_pushinteger;
-module.exports.lua_pushlstring = lua_pushlstring;
-module.exports.lua_pushstring = lua_pushstring;
-module.exports.lua_pushliteral = lua_pushliteral;
-module.exports.lua_pushboolean = lua_pushboolean;
-module.exports.lua_pushcclosure = lua_pushcclosure;
-module.exports.lua_pushcfunction = lua_pushcfunction;
-module.exports.lua_pushjsclosure = lua_pushjsclosure;
-module.exports.lua_pushjsfunction = lua_pushjsfunction;
-module.exports.lua_version = lua_version;
+module.exports.index2addr = index2addr;
+module.exports.lua_absindex = lua_absindex;
module.exports.lua_atpanic = lua_atpanic;
+module.exports.lua_call = lua_call;
+module.exports.lua_callk = lua_callk;
+module.exports.lua_checkstack = lua_checkstack;
+module.exports.lua_concat = lua_concat;
+module.exports.lua_copy = lua_copy;
+module.exports.lua_createtable = lua_createtable;
+module.exports.lua_error = lua_error;
+module.exports.lua_gc = lua_gc;
+module.exports.lua_getallocf = lua_getallocf;
+module.exports.lua_getextraspace = lua_getextraspace;
+module.exports.lua_getfield = lua_getfield;
+module.exports.lua_getglobal = lua_getglobal;
+module.exports.lua_geti = lua_geti;
+module.exports.lua_getmetatable = lua_getmetatable;
+module.exports.lua_gettable = lua_gettable;
module.exports.lua_gettop = lua_gettop;
-module.exports.lua_typename = lua_typename;
-module.exports.lua_type = lua_type;
-module.exports.lua_tonumber = lua_tonumber;
-module.exports.lua_tointeger = lua_tointeger;
-module.exports.lua_toboolean = lua_toboolean;
-module.exports.lua_tolstring = lua_tolstring;
-module.exports.lua_tostring = lua_tostring;
-module.exports.lua_topointer = lua_topointer;
+module.exports.lua_insert = lua_insert;
+module.exports.lua_isstring = lua_isstring;
+module.exports.lua_istable = lua_istable;
module.exports.lua_load = lua_load;
-module.exports.lua_callk = lua_callk;
-module.exports.lua_call = lua_call;
-module.exports.lua_pcallk = lua_pcallk;
+module.exports.lua_newtable = lua_newtable;
+module.exports.lua_next = lua_next;
module.exports.lua_pcall = lua_pcall;
+module.exports.lua_pcallk = lua_pcallk;
module.exports.lua_pop = lua_pop;
-module.exports.lua_setglobal = lua_setglobal;
-module.exports.lua_istable = lua_istable;
-module.exports.lua_createtable = lua_createtable;
-module.exports.lua_newtable = lua_newtable;
-module.exports.lua_settable = lua_settable;
-module.exports.lua_gettable = lua_gettable;
-module.exports.lua_geti = lua_geti;
-module.exports.lua_absindex = lua_absindex;
-module.exports.index2addr = index2addr;
+module.exports.lua_pushboolean = lua_pushboolean;
+module.exports.lua_pushcclosure = lua_pushcclosure;
+module.exports.lua_pushcfunction = lua_pushcfunction;
+module.exports.lua_pushglobaltable = lua_pushglobaltable;
+module.exports.lua_pushinteger = lua_pushinteger;
+module.exports.lua_pushjsclosure = lua_pushjsclosure;
+module.exports.lua_pushjsfunction = lua_pushjsfunction;
+module.exports.lua_pushliteral = lua_pushliteral;
+module.exports.lua_pushlstring = lua_pushlstring;
+module.exports.lua_pushnil = lua_pushnil;
+module.exports.lua_pushnumber = lua_pushnumber;
+module.exports.lua_pushstring = lua_pushstring;
+module.exports.lua_pushvalue = lua_pushvalue;
+module.exports.lua_rawequal = lua_rawequal;
module.exports.lua_rawget = lua_rawget;
-module.exports.lua_rawset = lua_rawset;
+module.exports.lua_rawgeti = lua_rawgeti;
module.exports.lua_rawlen = lua_rawlen;
-module.exports.lua_isstring = lua_isstring;
-module.exports.lua_rotate = lua_rotate;
+module.exports.lua_rawset = lua_rawset;
module.exports.lua_remove = lua_remove;
-module.exports.lua_checkstack = lua_checkstack;
-module.exports.lua_rawgeti = lua_rawgeti;
-module.exports.lua_pushglobaltable = lua_pushglobaltable;
+module.exports.lua_rotate = lua_rotate;
module.exports.lua_setfield = lua_setfield;
-module.exports.lua_getfield = lua_getfield;
-module.exports.lua_getglobal = lua_getglobal;
-module.exports.lua_getmetatable = lua_getmetatable;
+module.exports.lua_setglobal = lua_setglobal;
module.exports.lua_setmetatable = lua_setmetatable;
+module.exports.lua_settable = lua_settable;
module.exports.lua_settop = lua_settop;
-module.exports.lua_rawequal = lua_rawequal;
-module.exports.lua_concat = lua_concat;
-module.exports.lua_error = lua_error;
-module.exports.lua_insert = lua_insert;
-module.exports.lua_gc = lua_gc;
-module.exports.lua_getallocf = lua_getallocf;
-module.exports.lua_getextraspace = lua_getextraspace;
+module.exports.lua_status = lua_status;
module.exports.lua_stringtonumber = lua_stringtonumber;
-module.exports.lua_copy = lua_copy;
-module.exports.lua_next = lua_next; \ No newline at end of file
+module.exports.lua_toboolean = lua_toboolean;
+module.exports.lua_tointeger = lua_tointeger;
+module.exports.lua_tolstring = lua_tolstring;
+module.exports.lua_tonumber = lua_tonumber;
+module.exports.lua_topointer = lua_topointer;
+module.exports.lua_tostring = lua_tostring;
+module.exports.lua_tothread = lua_tothread;
+module.exports.lua_type = lua_type;
+module.exports.lua_typename = lua_typename;
+module.exports.lua_version = lua_version;
+module.exports.lua_xmove = lua_xmove; \ No newline at end of file
diff --git a/src/lauxlib.js b/src/lauxlib.js
index 29b223f..a04f3be 100644
--- a/src/lauxlib.js
+++ b/src/lauxlib.js
@@ -310,6 +310,11 @@ const luaL_checkstack = function(L, space, msg) {
}
};
+const luaL_newlib = function(L, l) {
+ lapi.lua_createtable(L);
+ luaL_setfuncs(L, l, 0);
+};
+
module.exports.luaL_newstate = luaL_newstate;
module.exports.luaL_typename = luaL_typename;
module.exports.luaL_checkany = luaL_checkany;
@@ -331,4 +336,5 @@ module.exports.luaL_optinteger = luaL_optinteger;
module.exports.luaL_opt = luaL_opt;
module.exports.luaL_where = luaL_where;
module.exports.luaL_error = luaL_error;
-module.exports.luaL_argerror = luaL_argerror; \ No newline at end of file
+module.exports.luaL_argerror = luaL_argerror;
+module.exports.luaL_newlib = luaL_newlib; \ No newline at end of file
diff --git a/src/lcorolib.js b/src/lcorolib.js
new file mode 100644
index 0000000..73699a7
--- /dev/null
+++ b/src/lcorolib.js
@@ -0,0 +1,86 @@
+/* jshint esversion: 6 */
+"use strict";
+
+const assert = require('assert');
+
+const lua = require('./lua.js');
+const lapi = require('./lapi.js');
+const lauxlib = require('./lauxlib.js');
+const lstate = require('./lstate.js');
+const ldo = require('./ldo.js');
+const CT = lua.constant_types;
+const TS = lua.thread_status;
+
+const getco = function(L) {
+ let co = lapi.lua_tothread(L, 1);
+ lauxlib.luaL_argcheck(L, co, 1, "thread expected");
+ return co;
+};
+
+const auxresume = function(L, co, narg) {
+ if (!lapi.lua_checkstack(co, narg)) {
+ lapi.lua_pushliteral(L, "too many arguments to resume");
+ return -1; /* error flag */
+ }
+
+ if (lapi.lua_status(co) === TS.LUA_OK && lapi.lua_gettop(co) === 0) {
+ lapi.lua_pushliteral(L, "cannot resume dead coroutine");
+ return -1; /* error flag */
+ }
+
+ lapi.lua_xmove(L, co, narg);
+ let status = ldo.lua_resume(co, L, narg);
+ if (status === TS.LUA_OK || status === TS.LUA_YIELD) {
+ let nres = lapi.lua_gettop(L);
+ if (!lapi.lua_checkstack(L, nres + 1)) {
+ lapi.lua_pop(co, nres); /* remove results anyway */
+ lapi.lua_pushliteral(L, "too many results to resume");
+ return -1; /* error flag */
+ }
+
+ lapi.lua_xmove(co, L, nres); /* move yielded values */
+ return nres;
+ } else {
+ lapi.lua_xmove(co, L, 1); /* move error message */
+ return -1; /* error flag */
+ }
+};
+
+const luaB_resume = function(L) {
+ let co = getco(L);
+ let r = auxresume(L, co, lapi.lua_gettop(L) - 1);
+ if (r < 0) {
+ lapi.lua_pushboolean(L, 0);
+ lapi.lua_insert(L, -2);
+ return 2; /* return false + error message */
+ } else {
+ lapi.lua_pushboolean(L, 1);
+ lapi.lua_insert(L, -(r + 1));
+ return r + 1; /* return true + 'resume' returns */
+ }
+};
+
+const luaB_cocreate = function(L) {
+ lauxlib.luaL_checktype(L, 1, CT.LUA_TFUNCTION);
+ let NL = lstate.lua_newthread(L);
+ lapi.lua_pushvalue(L, 1); /* move function to top */
+ lapi.lua_xmove(L, NL, 1); /* move function from L to NL */
+ return 1;
+};
+
+const luaB_yield = function(L) {
+ return ldo.lua_yield(L, lapi.lua_gettop(L));
+};
+
+const co_funcs = {
+ "create": luaB_cocreate,
+ "yield": luaB_yield,
+ "resume": luaB_resume
+};
+
+const luaopen_coroutine = function(L) {
+ lauxlib.luaL_newlib(L, co_funcs);
+ return 1;
+};
+
+module.exports.luaopen_coroutine = luaopen_coroutine; \ No newline at end of file
diff --git a/src/ldo.js b/src/ldo.js
index 4333f5e..8d12c9f 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -27,6 +27,7 @@ const seterrorobj = function(L, errcode, oldtop) {
}
case TS.LUA_ERRERR: {
L.stack[oldtop] = new TValue(CT.LUA_TLNGSTR, "error in error handling");
+ break;
}
default: {
L.stack[oldtop] = L.stack[L.top - 1];
@@ -79,7 +80,6 @@ const luaD_precall = function(L, off, nresults) {
luaD_poscall(L, ci, L.top - n, n);
return true;
- break;
}
case CT.LUA_TLCL: {
let p = func.p;
@@ -260,7 +260,7 @@ const luaD_rawrunprotected = function(L, f, ud) {
try {
f(L, ud);
} catch (e) {
- if (lj.status == 0) lj.status = -1;
+ if (lj.status === 0) lj.status = -1;
}
L.errorJmp = lj.previous;
@@ -270,6 +270,209 @@ const luaD_rawrunprotected = function(L, f, ud) {
};
+/*
+** Completes the execution of an interrupted C function, calling its
+** continuation function.
+*/
+const finishCcall = function(L, status) {
+ let ci = L.ci;
+
+ /* must have a continuation and must be able to call it */
+ assert(ci.u.c.k !== null && L.nny === 0);
+ /* error status can only happen in a protected call */
+ assert(ci.callstatus & lstate.CIST_YPCALL || status === TS.LUA_YIELD);
+
+ if (ci.callstatus & TS.CIST_YPCALL) { /* was inside a pcall? */
+ ci.callstatus &= ~TS.CIST_YPCALL; /* continuation is also inside it */
+ L.errfunc = ci.u.c.old_errfunc; /* with the same error function */
+ }
+
+ /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already
+ handled */
+ if (ci.nresults === LUA_MULTRET && L.ci.top < L.top) L.ci.top = L.top;
+ let n = ci.u.c.k(L, status, ci.u.c.ctx); /* call continuation function */
+ assert(n < (L.top - L.ci.funcOff), "not enough elements in the stack");
+ luaD_poscall(L, ci, L.top - n, n); /* finish 'luaD_precall' */
+};
+
+/*
+** Executes "full continuation" (everything in the stack) of a
+** previously interrupted coroutine until the stack is empty (or another
+** interruption long-jumps out of the loop). If the coroutine is
+** recovering from an error, 'ud' points to the error status, which must
+** be passed to the first continuation function (otherwise the default
+** status is LUA_YIELD).
+*/
+const unroll = function(L, ud) {
+ if (ud !== null) /* error status? */
+ finishCcall(L, ud); /* finish 'lua_pcallk' callee */
+
+ while (L.ci !== L.base_ci) { /* something in the stack */
+ if (!(L.ci.callstatus & lstate.CIST_LUA)) /* C function? */
+ finishCcall(L, lstate.LUA_YIELD); /* complete its execution */
+ else { /* Lua function */
+ lvm.luaV_finishOp(L); /* finish interrupted instruction */
+ lvm.luaV_execute(L); /* execute down to higher C 'boundary' */
+ }
+ }
+};
+
+/*
+** Try to find a suspended protected call (a "recover point") for the
+** given thread.
+*/
+const findpcall = function(L) {
+ for (let ci = L.ci; ci !== null; ci = ci.previous) { /* search for a pcall */
+ if (ci.callstatus & lstate.CIST_YPCALL)
+ return ci;
+ }
+
+ return null; /* no pending pcall */
+};
+
+/*
+** Recovers from an error in a coroutine. Finds a recover point (if
+** there is one) and completes the execution of the interrupted
+** 'luaD_pcall'. If there is no recover point, returns zero.
+*/
+const recover = function(L, status) {
+ let ci = findpcall(L);
+ if (ci === null) return 0; /* no recovery point */
+ /* "finish" luaD_pcall */
+ let oldtop = L.stack[ci.extra];
+ lfunc.luaF_close(L, oldtop);
+ seterrorobj(L, status, oldtop);
+ L.ci = ci;
+ L.allowhook = ci.callstatus & lstate.CIST_OAH; /* restore original 'allowhook' */
+ L.nny = 0; /* should be zero to be yieldable */
+ L.errfunc = ci.u.c.old_errfunc;
+ return 1; /* continue running the coroutine */
+};
+
+/*
+** Signal an error in the call to 'lua_resume', not in the execution
+** of the coroutine itself. (Such errors should not be handled by any
+** coroutine error handler and should not kill the coroutine.)
+*/
+const resume_error = function(L, msg, narg) {
+ L.top -= narg; /* remove args from the stack */
+ L.stack[L.top++] = new TValue(CT.LUA_TLNGSTR, msg); /* push error message */
+ assert(L.top <= L.ci.top, "stack overflow");
+ return TS.LUA_ERRRUN;
+};
+
+/*
+** Do the work for 'lua_resume' in protected mode. Most of the work
+** depends on the status of the coroutine: initial state, suspended
+** inside a hook, or regularly suspended (optionally with a continuation
+** function), plus erroneous cases: non-suspended coroutine or dead
+** coroutine.
+*/
+const resume = function(L, n) {
+ let firstArg = L.top - n; /* first argument */
+ let ci = L.ci;
+ if (L.status === TS.LUA_OK) { /* starting a coroutine? */
+ if (!luaD_precall(L, firstArg - 1, lua.LUA_MULTRET)) /* Lua function? */
+ lvm.luaV_execute(L); /* call it */
+ } else { /* resuming from previous yield */
+ assert(L.status === TS.LUA_YIELD);
+ L.status = TS.LUA_OK; /* mark that it is running (again) */
+ ci.funcOff = ci.extra;
+ ci.func = L.stack[ci.funcOff];
+
+ if (ci.callstatus & lstate.CIST_LUA) /* yielded inside a hook? */
+ lvm.luaV_execute(L); /* just continue running Lua code */
+ else { /* 'common' yield */
+ if (ci.u.c.k !== null) { /* does it have a continuation function? */
+ n = ci.u.c.k(L, TS.LUA_YIELD, ci.u.c.ctx); /* call continuation */
+ assert(n < (L.top - L.ci.funcOff), "not enough elements in the stack");
+ firstArg = L.top - n; /* yield results come from continuation */
+ }
+
+ luaD_poscall(L, ci, firstArg, n); /* finish 'luaD_precall' */
+ }
+
+ unroll(L, null); /* run continuation */
+ }
+};
+
+const lua_resume = function(L, from, nargs) {
+ let oldnny = L.nny; /* save "number of non-yieldable" calls */
+
+ if (L.status === TS.LUA_OK) { /* may be starting a coroutine */
+ if (L.ci !== L.base_ci) /* not in base level? */
+ return resume_error(L, "cannot resume non-suspended coroutine", nargs);
+ } else if (L.status !== TS.LUA_YIELD)
+ return resume_error(L, "cannot resume dead coroutine", nargs);
+
+ L.nCcalls = from ? from.nCcalls + 1 : 1;
+ if (L.nCcalls >= llimit.LUAI_MAXCCALLS)
+ return resume_error(L, "JS stack overflow", nargs);
+
+ L.nny = 0; /* allow yields */
+
+ assert((L.status === TS.LUA_OK ? nargs + 1: nargs) < (L.top - L.ci.funcOff),
+ "not enough elements in the stack");
+
+ let status = luaD_rawrunprotected(L, resume, nargs);
+ if (status === -1) /* error calling 'lua_resume'? */
+ status = TS.LUA_ERRRUN;
+ else { /* continue running after recoverable errors */
+ while (status > TS.LUA_YIELD && recover(L, status)) {
+ /* unroll continuation */
+ status = luaD_rawrunprotected(L, unroll, status);
+ }
+
+ if (status > TS.LUA_YIELD) { /* unrecoverable error? */
+ L.status = status; /* mark thread as 'dead' */
+ seterrorobj(L, status, L.top); /* push error message */
+ L.ci.top = L.top;
+ } else
+ assert(status === L.status); /* normal end or yield */
+ }
+
+ L.nny = oldnny; /* restore 'nny' */
+ L.nCcalls--;
+ assert(L.nCcalls === (from ? from.nCcalls : 0));
+ return status;
+};
+
+const lua_isyieldable = function(L) {
+ return L.nny === 0;
+};
+
+const lua_yieldk = function(L, nresults, ctx, k) {
+ let ci = L.ci;
+ assert(nresults < (L.top - L.ci.funcOff), "not enough elements in the stack");
+
+ if (L.nny > 0) {
+ if (L !== L.l_G.mainthread)
+ ldebug.luaG_runerror(L, "attempt to yield across a JS-call boundary");
+ else
+ ldebug.luaG_runerror(L, "attempt to yield from outside a coroutine");
+ }
+
+ L.status = TS.LUA_YIELD;
+ ci.extra = ci.funcOff; /* save current 'func' */
+ if (ci.callstatus & lstate.CIST_LUA) /* inside a hook? */
+ assert(k === null, "hooks cannot continue after yielding");
+ else {
+ ci.u.c.k = k;
+ if (k !== null) /* is there a continuation? */
+ ci.u.c.ctx = ctx; /* save context */
+ ci.funcOff = L.top - nresults - 1; /* protect stack below results */
+ ci.func = L.stack[ci.funcOff];
+ luaD_throw(L, TS.LUA_YIELD);
+ }
+
+ assert(ci.callstatus & lstate.CIST_HOOKED); /* must be inside a hook */
+ return 0; /* return to 'luaD_hook' */
+};
+
+const lua_yield = function(L, n) {
+ lua_yieldk(L, n, 0, null);
+};
+
const luaD_pcall = function(L, func, u, old_top, ef) {
let old_ci = L.ci;
// TODO: lu_byte old_allowhooks = L->allowhook;
@@ -321,16 +524,20 @@ const luaD_protectedparser = function(L, data, name) {
return status;
};
-module.exports.nil = nil;
-module.exports.luaD_precall = luaD_precall;
-module.exports.luaD_poscall = luaD_poscall;
-module.exports.moveresults = moveresults;
module.exports.adjust_varargs = adjust_varargs;
-module.exports.tryfuncTM = tryfuncTM;
-module.exports.stackerror = stackerror;
+module.exports.lua_isyieldable = lua_isyieldable;
+module.exports.lua_resume = lua_resume;
+module.exports.lua_yield = lua_yield;
+module.exports.lua_yieldk = lua_yieldk;
module.exports.luaD_call = luaD_call;
module.exports.luaD_callnoyield = luaD_callnoyield;
module.exports.luaD_pcall = luaD_pcall;
-module.exports.luaD_throw = luaD_throw;
+module.exports.luaD_poscall = luaD_poscall;
+module.exports.luaD_precall = luaD_precall;
+module.exports.luaD_protectedparser = luaD_protectedparser;
module.exports.luaD_rawrunprotected = luaD_rawrunprotected;
-module.exports.luaD_protectedparser = luaD_protectedparser; \ No newline at end of file
+module.exports.luaD_throw = luaD_throw;
+module.exports.moveresults = moveresults;
+module.exports.nil = nil;
+module.exports.stackerror = stackerror;
+module.exports.tryfuncTM = tryfuncTM; \ No newline at end of file
diff --git a/src/linit.js b/src/linit.js
index fc2a54d..d52b640 100644
--- a/src/linit.js
+++ b/src/linit.js
@@ -5,10 +5,13 @@ const assert = require('assert');
const lapi = require('./lapi.js');
const lauxlib = require('./lauxlib.js');
+const lualib = require('./lualib.js');
const lbaselib = require('./lbaselib.js');
+const lcorolib = require('./lcorolib.js');
const loadedlibs = {
- "_G": lbaselib.luaopen_base
+ [lualib.LUA_COLIBNAME]: lcorolib.luaopen_coroutine,
+ "_G": lbaselib.luaopen_base
};
const luaL_openlibs = function(L) {
diff --git a/src/lstate.js b/src/lstate.js
index eec9fae..9a9c9e0 100644
--- a/src/lstate.js
+++ b/src/lstate.js
@@ -1,8 +1,10 @@
/*jshint esversion: 6 */
"use strict";
+const assert = require('assert');
+
const lua = require('./lua.js');
-const Table = require('./lobject.js').Table;
+const lobject = require('./lobject.js');
const ldo = require('./ldo.js');
const lapi = require('./lapi.js');
const nil = ldo.nil;
@@ -41,9 +43,10 @@ class CallInfo {
}
-class lua_State {
+class lua_State extends lobject.TValue {
- constructor(cl) {
+ constructor() {
+ super(CT.LUA_TTHREAD, null);
this.base_ci = new CallInfo(); // Will be populated later
this.top = 0;
this.ci = null;
@@ -52,12 +55,12 @@ class lua_State {
this.openupval = [];
this.status = TS.LUA_OK;
this.next = null;
- this.tt = CT.LUA_TTHREAD;
this.twups = [this];
this.errorJmp = null;
- // TODO: hooks
this.nny = 1;
this.errfunc = 0;
+
+ this.value = this;
}
}
@@ -94,10 +97,10 @@ const stack_init = function(L1, L) {
** Create registry table and its predefined values
*/
const init_registry = function(L, g) {
- let registry = new Table();
+ let registry = new lobject.Table();
g.l_registry = registry;
registry.value.set(lua.LUA_RIDX_MAINTHREAD - 1, L);
- registry.value.set(lua.LUA_RIDX_GLOBALS - 1, new Table());
+ registry.value.set(lua.LUA_RIDX_GLOBALS - 1, new lobject.Table());
};
/*
@@ -113,11 +116,44 @@ const f_luaopen = function(L) {
g.version = lapi.lua_version(null);
};
+const preinit_thread = function(L, g) {
+ L.l_G = g;
+ L.stack = [];
+ L.ci = null;
+ L.nci = 0;
+ L.twups = [L]; /* thread has no upvalues */
+ L.errorJmp = null;
+ L.nCcalls = 0;
+ L.hook = null;
+ L.hookmask = 0;
+ L.basehookcount = 0;
+ L.allowhook = 1;
+ L.hookcount = L.basehookcount;
+ L.openupval = [];
+ L.nny = 1;
+ L.status = TS.LUA_OK;
+ L.errfunc = 0;
+};
+
+const lua_newthread = function(L) {
+ let g = L.l_G;
+ let L1 = new lua_State();
+ L.stack[L.top++] = L1;
+ assert(L.top <= L.ci.top, "stack overflow");
+ preinit_thread(L1, g);
+ L1.hookmask = L.hookmask;
+ L1.basehookcount = L.basehookcount;
+ L1.hook = L.hook;
+ L1.hookcount = L1.basehookcount;
+ stack_init(L1, L);
+ return L1;
+};
+
const lua_newstate = function() {
let L = new lua_State();
let g = new global_State(L);
- L.l_G = g;
+ preinit_thread(L, g);
if (luaD_rawrunprotected(L, f_luaopen, null) !== TS.LUA_OK) {
L = null;
@@ -137,4 +173,5 @@ module.exports.CIST_TAIL = (1<<5); /* call was tail called */
module.exports.CIST_HOOKYIELD = (1<<6); /* last hook called yielded */
module.exports.CIST_LEQ = (1<<7); /* using __lt for __le */
module.exports.CIST_FIN = (1<<8); /* call is running a finalizer */
-module.exports.lua_newstate = lua_newstate; \ No newline at end of file
+module.exports.lua_newstate = lua_newstate;
+module.exports.lua_newthread = lua_newthread; \ No newline at end of file
diff --git a/src/lualib.js b/src/lualib.js
index 1eff812..d87cfbc 100644
--- a/src/lualib.js
+++ b/src/lualib.js
@@ -7,5 +7,26 @@ const lua = require('./lua.js');
const LUA_VERSUFFIX = "_" + lua.LUA_VERSION_MAJOR + "_" + lua.LUA_VERSION_MINOR;
+const LUA_COLIBNAME = "coroutine";
+const LUA_TABLIBNAME = "table"
+const LUA_IOLIBNAME = "io"
+const LUA_OSLIBNAME = "os"
+const LUA_STRLIBNAME = "string"
+const LUA_UTF8LIBNAME = "utf8"
+const LUA_BITLIBNAME = "bit32"
+const LUA_MATHLIBNAME = "math"
+const LUA_DBLIBNAME = "debug"
+const LUA_LOADLIBNAME = "package"
-module.exports.LUA_VERSUFFIX = LUA_VERSUFFIX; \ No newline at end of file
+
+module.exports.LUA_BITLIBNAME = LUA_BITLIBNAME;
+module.exports.LUA_COLIBNAME = LUA_COLIBNAME;
+module.exports.LUA_DBLIBNAME = LUA_DBLIBNAME;
+module.exports.LUA_IOLIBNAME = LUA_IOLIBNAME;
+module.exports.LUA_LOADLIBNAME = LUA_LOADLIBNAME;
+module.exports.LUA_MATHLIBNAME = LUA_MATHLIBNAME;
+module.exports.LUA_OSLIBNAME = LUA_OSLIBNAME;
+module.exports.LUA_STRLIBNAME = LUA_STRLIBNAME;
+module.exports.LUA_TABLIBNAME = LUA_TABLIBNAME;
+module.exports.LUA_UTF8LIBNAME = LUA_UTF8LIBNAME;
+module.exports.LUA_VERSUFFIX = LUA_VERSUFFIX; \ No newline at end of file
diff --git a/src/lvm.js b/src/lvm.js
index a31e0c7..15a11e4 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -22,6 +22,65 @@ const ltm = require('./ltm.js');
const ltable = require('./ltable.js');
const ldebug = require('./ldebug.js');
+/*
+** finish execution of an opcode interrupted by an yield
+*/
+const luaV_finishOp = function(L) {
+ let ci = L.ci;
+ let base = ci.u.l.base;
+ let inst = ci.u.l.savedpc[ci.pcOff - 1]; /* interrupted instruction */
+ let op = OC.OpCodes[inst.opcode];
+
+ switch (op) { /* finish its execution */
+ case "OP_ADD": case "OP_SUB": case "OP_MUL": case "OP_DIV": case "OP_IDIV":
+ case "OP_BAND": case "OP_BOR": case "OP_BXOR": case "OP_SHL": case "OP_SHR":
+ case "OP_MOD": case "OP_POW":
+ case "OP_UNM": case "OP_BNOT": case "OP_LEN":
+ case "OP_GETTABUP": case "OP_GETTABLE": case "OP_SELF": {
+ L.stack[base + inst.A] = L.stack[--L.top];
+ break;
+ }
+ case "OP_LE": case "OP_LT": case "OP_EQ": {
+ let res = !L.stack[L.top - 1].l_isfalse();
+ L.top--;
+ if (ci.callstatus & lstate.CIST_LEQ) { /* "<=" using "<" instead? */
+ assert(op === "OP_LE");
+ ci.callstatus ^= lstate.CIST_LEQ; /* clear mark */
+ res = res !== 1 ? 1 : 0; /* negate result */
+ }
+ assert(OC.OpCodes[ci.u.l.savedpc[ci.pcOff]] === "OP_JMP");
+ if (res !== inst.A) /* condition failed? */
+ ci.pcOff++; /* skip jump instruction */
+ break;
+ }
+ case "OP_CONCAT": {
+ let top = L.top - 1; /* top when 'luaT_trybinTM' was called */
+ let b = inst.B; /* first element to concatenate */
+ let total = top - 1 - (base + b); /* yet to concatenate */
+ L.stack[L.top - 2] = L.stack[top]; /* put TM result in proper position */
+ if (total > 1) { /* are there elements to concat? */
+ L.top = top - 1; /* top is one after last element (at top-2) */
+ luaV_concat(L, total); /* concat them (may yield again) */
+ }
+
+ /* move final result to final position */
+ L.stack[ci.u.l.base + inst.A] = L.stack[L.top - 1];
+ L.top = ci.top; /* restore top */
+ break;
+ }
+ case "OP_TFORCALL": {
+ assert(OC.OpCodes[ci.u.l.savedpc[ci.pcOff]] === "OP_TFORLOOP");
+ L.top = ci.top; /* correct top */
+ break;
+ }
+ case "OP_CALL": {
+ if (inst.C - 1 >= 0) /* nresults >= 0? */
+ L.top = ci.top; /* adjust results */
+ break;
+ }
+ }
+};
+
const RA = function(L, base, i) {
return base + i.A;
};
@@ -1013,28 +1072,29 @@ const luaV_finishset = function(L, t, key, val, slot, recur) {
}
-module.exports.RA = RA;
-module.exports.RB = RB;
-module.exports.RC = RC;
-module.exports.RKB = RKB;
-module.exports.RKC = RKC;
-module.exports.luaV_execute = luaV_execute;
module.exports.dojump = dojump;
module.exports.donextjump = donextjump;
-module.exports.luaV_lessequal = luaV_lessequal;
-module.exports.luaV_lessthan = luaV_lessthan;
-module.exports.luaV_equalobj = luaV_equalobj;
module.exports.forlimit = forlimit;
-module.exports.luaV_tointeger = luaV_tointeger;
-module.exports.tonumber = tonumber;
-module.exports.tointeger = tointeger;
-module.exports.LTnum = LTnum;
-module.exports.LEnum = LEnum;
+module.exports.gettable = gettable;
+module.exports.l_strcmp = l_strcmp;
module.exports.LEintfloat = LEintfloat;
+module.exports.LEnum = LEnum;
module.exports.LTintfloat = LTintfloat;
-module.exports.l_strcmp = l_strcmp;
-module.exports.luaV_objlen = luaV_objlen;
+module.exports.LTnum = LTnum;
+module.exports.luaV_concat = luaV_concat;
+module.exports.luaV_equalobj = luaV_equalobj;
+module.exports.luaV_execute = luaV_execute;
+module.exports.luaV_finishOp = luaV_finishOp;
module.exports.luaV_finishset = luaV_finishset;
-module.exports.gettable = gettable;
+module.exports.luaV_lessequal = luaV_lessequal;
+module.exports.luaV_lessthan = luaV_lessthan;
+module.exports.luaV_objlen = luaV_objlen;
+module.exports.luaV_tointeger = luaV_tointeger;
+module.exports.RA = RA;
+module.exports.RB = RB;
+module.exports.RC = RC;
+module.exports.RKB = RKB;
+module.exports.RKC = RKC;
module.exports.settable = settable;
-module.exports.luaV_concat = luaV_concat; \ No newline at end of file
+module.exports.tointeger = tointeger;
+module.exports.tonumber = tonumber; \ No newline at end of file
diff --git a/tests/lcorolib.js b/tests/lcorolib.js
new file mode 100644
index 0000000..6b48912
--- /dev/null
+++ b/tests/lcorolib.js
@@ -0,0 +1,53 @@
+/*jshint esversion: 6 */
+"use strict";
+
+const test = require('tape');
+const beautify = require('js-beautify').js_beautify;
+
+const tests = require("./tests.js");
+const getState = tests.getState;
+const toByteCode = tests.toByteCode;
+
+const VM = require("../src/lvm.js");
+const ldo = require("../src/ldo.js");
+const lapi = require("../src/lapi.js");
+const lauxlib = require("../src/lauxlib.js");
+const lua = require('../src/lua.js');
+const linit = require('../src/linit.js');
+const CT = lua.constant_types;
+
+test('simple coroutine', function (t) {
+ let luaCode = `
+ local co = coroutine.create(function (start)
+ local b = coroutine.yield(start * start);
+ coroutine.yield(b * b)
+ end)
+
+ local success, pow = coroutine.resume(co, 5)
+ success, pow = coroutine.resume(co, pow)
+
+ return pow
+ `, L;
+
+ t.plan(2);
+
+ t.doesNotThrow(function () {
+
+ let bc = toByteCode(luaCode).dataView;
+
+ L = lauxlib.luaL_newstate();
+
+ linit.luaL_openlibs(L);
+
+ lapi.lua_load(L, bc, "test-coroutine");
+
+ lapi.lua_call(L, 0, -1);
+
+ }, "JS Lua program ran without error");
+
+ t.strictEqual(
+ lapi.lua_tonumber(L, -1),
+ 625,
+ "Correct element(s) on the stack"
+ );
+}); \ No newline at end of file