---
source: src/main.rs
assertion_line: 136
expression: compiled
input_file: test-data/lua5.4-tests/coroutine.lua
---
print("testing coroutines");
local debug = require('debug')
local f
local main, ismain = coroutine.running()
assert(type(main) == "thread" && ismain);
assert(!coroutine.resume(main));
assert(!coroutine.isyieldable(main) && !coroutine.isyieldable());
assert(!pcall(coroutine.yield));
assert(!pcall(coroutine.resume, 0));
assert(!pcall(coroutine.status, 0));
local fn eqtab(t1, t2) {
assert(#t1 == #t2);
for i = 1, #t1 {
local v = t1[(i)]
assert(t2[(i)] == v);
}
}
_G.x = nil
global fn foo(a, ...) {
local x, y = coroutine.running()
assert(x == f && y == false);
assert(coroutine.resume(f) == false);
assert(coroutine.status(f) == "running");
local arg = {
...
}
assert(coroutine.isyieldable(x));
for i = 1, #arg {
_G.x = {
coroutine.yield(table.unpack(arg[(i)]))
}
}
return table.unpack(a)
}
f = coroutine.create(foo)
assert(coroutine.isyieldable(f));
assert(type(f) == "thread" && coroutine.status(f) == "suspended");
assert(string.find(tostring(f), "thread"));
local s, a, b, c, d
s, a, b, c, d = coroutine.resume(f, {
1,
2,
3
}, {}, {
1
}, {
'a',
'b',
'c'
})
assert(coroutine.isyieldable(f));
assert(s && a == nil && coroutine.status(f) == "suspended");
s, a, b, c, d = coroutine.resume(f)
eqtab(_G.x, {});
assert(s && a == 1 && b == nil);
assert(coroutine.isyieldable(f));
s, a, b, c, d = coroutine.resume(f, 1, 2, 3)
eqtab(_G.x, {
1,
2,
3
});
assert(s && a == 'a' && b == 'b' && c == 'c' && d == nil);
s, a, b, c, d = coroutine.resume(f, "xuxu")
eqtab(_G.x, {
"xuxu"
});
assert(s && a == 1 && b == 2 && c == 3 && d == nil);
assert(coroutine.status(f) == "dead");
s, a = coroutine.resume(f, "xuxu")
assert(!s && string.find(a, "dead") && coroutine.status(f) == "dead");
local fn foo(i) {
return coroutine.yield(i)
}
f = coroutine.wrap(fn () {
for i = 1, 10 {
assert(foo(i) == _G.x);
}
return 'a'
})
for i = 1, 10 {
_G.x = i
assert(f(i) == i);
}
_G.x = 'xuxu'
assert(f('xuxu') == 'a');
global fn pf(n, i) {
coroutine.yield(n);
pf(n * i, i + 1);
}
f = coroutine.wrap(pf)
local s = 1
for i = 1, 10 {
assert(f(1, 1) == s);
s = s * i
}
global fn gen(n) {
return coroutine.wrap(fn () {
for i = 2, n {
coroutine.yield(i);
}
})
}
global fn filter(p, g) {
return coroutine.wrap(fn () {
while 1 {
local n = g()
if n == nil {
return
}
if math.fmod(n, p) != 0 {
coroutine.yield(n);
}
}
})
}
local x = gen(80)
local a = {}
while 1 {
local n = x()
if n == nil {
break
}
table.insert(a, n);
x = filter(n, x)
}
assert(#a == 22 && a[(#a)] == 79);
x, a = nil
print("to-be-closed variables in coroutines");
local fn func2close(f) {
return setmetatable({}, {
__close = f
})
}
{
local co = coroutine.create(print)
assert(coroutine.resume(co, "testing 'coroutine.close'"));
assert(coroutine.status(co) == "dead");
local st, msg = coroutine.close(co)
assert(st && msg == nil);
st, msg = coroutine.close(co)
assert(st && msg == nil);
local st, msg = pcall(coroutine.close, coroutine.running())
assert(!st && string.find(msg, "running"));
local main = coroutine.running()
(coroutine.wrap(fn () {
local st, msg = pcall(coroutine.close, main)
assert(!st && string.find(msg, "normal"));
}))();
{
local co
co = coroutine.create(fn () {
local x = func2close(fn () {
coroutine.close(co);
})
coroutine.yield(20);
getmetatable(x).__close(x);
})
local st, msg = coroutine.resume(co)
assert(st && msg == 20);
st, msg = coroutine.close(co)
assert(!st && string.find(msg, "running coroutine"));
}
local X
local co = coroutine.create(error)
local st, msg = coroutine.resume(co, 100)
assert(!st && msg == 100);
st, msg = coroutine.close(co)
assert(!st && msg == 100);
st, msg = coroutine.close(co)
assert(st && msg == nil);
co = coroutine.create(fn () {
local x = func2close(fn (self, err) {
assert(err == nil);
X = false
})
X = true
coroutine.yield();
getmetatable(x).__close(x);
})
coroutine.resume(co);
assert(X);
assert(coroutine.close(co));
assert(!X && coroutine.status(co) == "dead");
local x = 0
co = coroutine.create(fn () {
local y = func2close(fn (self, err) {
assert(err == 111);
x = 200
error(200);
})
local x = func2close(fn (self, err) {
assert(err == nil);
error(111);
})
coroutine.yield();
getmetatable(x).__close(x);
getmetatable(y).__close(y);
})
coroutine.resume(co);
assert(x == 0);
local st, msg = coroutine.close(co)
assert(st == false && coroutine.status(co) == "dead" && msg == 200);
assert(x == 200);
st, msg = coroutine.close(co)
assert(st && msg == nil);
}
{
local X = false
local Y = false
global fn foo() {
local x = func2close(fn (self, err) {
Y = debug.getinfo(2)
X = err
})
error(43);
getmetatable(x).__close(x);
}
co = coroutine.create(fn () {
return pcall(foo)
})
local st1, st2, err = coroutine.resume(co)
assert(st1 && !st2 && err == 43);
assert(X == 43 && Y.what == "C");
local track = {}
local fn h(o) {
local hv = o
return 1
getmetatable(hv).__close(hv);
}
local fn foo() {
local x = func2close(fn (_, msg) {
track[(#track + 1)] = msg || false
error(20);
})
local y = func2close(fn (_, msg) {
track[(#track + 1)] = msg || false
return 1000
})
local z = func2close(fn (_, msg) {
track[(#track + 1)] = msg || false
error(10);
})
coroutine.yield(1);
h(func2close(fn (_, msg) {
track[(#track + 1)] = msg || false
error(2);
}));
getmetatable(z).__close(z);
getmetatable(y).__close(y);
getmetatable(x).__close(x);
}
local co = coroutine.create(pcall)
local st, res = coroutine.resume(co, foo)
assert(st && res == 1);
local st, res1, res2 = coroutine.resume(co)
assert(coroutine.status(co) == "dead");
assert(st && !res1 && res2 == 20);
assert(track[(1)] == false && track[(2)] == 2 && track[(3)] == 10 && track[(4)] == 10);
}
co = coroutine.wrap(fn () {
assert(!pcall(table.sort, {
1,
2,
3
}, coroutine.yield));
assert(coroutine.isyieldable());
coroutine.yield(20);
return 30
})
assert(co() == 20);
assert(co() == 30);
local f = fn (s, i) {
return coroutine.yield(i)
}
local f1 = coroutine.wrap(fn () {
return xpcall(pcall, fn (...) {
return ...
}, fn () {
local s = 0
{
local _internal_expr_2, _internal_stop_2, _internal_acc_2 = f, nil, 1;
while true {
local i = _internal_expr_2(_internal_stop_2, _internal_acc_2);
_internal_acc_2 = i;
if _internal_acc_2 == nil { break; }
pcall(fn () {
s = s + i
});
}
}
error({
s
});
})
})
f1();
for i = 1, 10 {
assert(f1(i) == i);
}
local r1, r2, v = f1(nil)
assert(r1 && !r2 && v[(1)] == (10 + 1) * 10 / 2);
global fn f(a, b) {
a = coroutine.yield(a)
error({
a + b
});
}
global fn g(x) {
return x[(1)] * 2
}
co = coroutine.wrap(fn () {
coroutine.yield(xpcall(f, g, 10, 20));
})
assert(co() == 10);
r, msg = co(100)
assert(!r && msg == 240);
{
local fn f(c) {
assert(!coroutine.isyieldable());
return c .. c
}
local co = coroutine.wrap(fn (c) {
assert(coroutine.isyieldable());
local s = string.gsub("a", ".", f)
return s
})
assert(co() == "aa");
}
{
local X
local co = coroutine.create(fn () {
coroutine.yield(10);
return 20
})
local trace = {}
local fn dotrace(event) {
trace[(#trace + 1)] = event
}
debug.sethook(co, dotrace, "clr");
loop {
} until !coroutine.resume(co)
local correcttrace = {
"call",
"line",
"call",
"return",
"line",
"return"
}
assert(#trace == #correcttrace);
for k, v with pairs(trace) {
assert(v == correcttrace[(k)]);
}
}
global fn foo() {
assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1);
assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined);
coroutine.yield(3);
error(foo);
}
global fn goo() {
foo();
}
x = coroutine.wrap(goo)
assert(x() == 3);
local a, b = pcall(x)
assert(!a && b == foo);
x = coroutine.create(goo)
a, b = coroutine.resume(x)
assert(a && b == 3);
a, b = coroutine.resume(x)
assert(!a && b == foo && coroutine.status(x) == "dead");
a, b = coroutine.resume(x)
assert(!a && string.find(b, "dead") && coroutine.status(x) == "dead");
global fn all(a, n, k) {
if k == 0 {
coroutine.yield(a);
} else {
for i = 1, n {
a[(k)] = i
all(a, n, k - 1);
}
}
}
local a = 0
for t with coroutine.wrap(fn () {
all({}, 5, 4);
}) {
a = a + 1
}
assert(a == 5 ^ 4);
local C = {}
setmetatable(C, {
__mode = "kv"
});
local x = coroutine.wrap(fn () {
local a = 10
local fn f() {
a = a + 10
return a
}
while true {
a = a + 1
coroutine.yield(f);
}
})
C[(1)] = x
local f = x()
assert(f() == 21 && x()() == 32 && x() == f);
x = nil
collectgarbage();
assert(C[(1)] == undef);
assert(f() == 43 && f() == 53);
global fn co_func(current_co) {
assert(coroutine.running() == current_co);
assert(coroutine.resume(current_co) == false);
coroutine.yield(10, 20);
assert(coroutine.resume(current_co) == false);
coroutine.yield(23);
return 10
}
local co = coroutine.create(co_func)
local a, b, c = coroutine.resume(co, co)
assert(a == true && b == 10 && c == 20);
a, b = coroutine.resume(co, co)
assert(a == true && b == 23);
a, b = coroutine.resume(co, co)
assert(a == true && b == 10);
assert(coroutine.resume(co, co) == false);
assert(coroutine.resume(co, co) == false);
{
local A = coroutine.running()
local B = coroutine.create(fn () {
return coroutine.resume(A)
})
local st, res = coroutine.resume(B)
assert(st == true && res == false);
local X = false
A = coroutine.wrap(fn () {
local _ = func2close(fn () {
X = true
})
return pcall(A, 1)
getmetatable(_).__close(_);
})
st, res = A()
assert(!st && string.find(res, "non%-suspended") && X == true);
}
{
local co
co = coroutine.wrap(fn () {
local x = func2close(fn () {
return pcall(co)
})
error(111);
getmetatable(x).__close(x);
})
local st, errobj = pcall(co)
assert(!st && errobj == 111);
st, errobj = pcall(co)
assert(!st && string.find(errobj, "dead coroutine"));
}
local co1, co2
co1 = coroutine.create(fn () {
return co2()
})
co2 = coroutine.wrap(fn () {
assert(coroutine.status(co1) == 'normal');
assert(!coroutine.resume(co1));
coroutine.yield(3);
})
a, b = coroutine.resume(co1)
assert(a && b == 3);
assert(coroutine.status(co1) == 'dead');
a = fn (a) {
coroutine.wrap(a)(a);
}
assert(!pcall(a, a));
a = nil
local x = coroutine.create(fn () {
local a = 10
_G.f = fn () {
a = a + 1
return a
}
error('x');
})
assert(!coroutine.resume(x));
assert(!coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1));
assert(_G.f() == 11);
assert(_G.f() == 12);
if !T {
(Message || print)('\n >>> testC not active: skipping coroutine API tests <<<\n');
} else {
print("testing yields inside hooks");
local turn
global fn fact(t, x) {
assert(turn == t);
if x == 0 {
return 1
} else {
return x * fact(t, x - 1)
}
}
local A, B = 0, 0
local x = coroutine.create(fn () {
T.sethook("yield 0", "", 2);
A = fact("A", 6)
})
local y = coroutine.create(fn () {
T.sethook("yield 0", "", 3);
B = fact("B", 7)
})
while A == 0 || B == 0 {
if A == 0 {
turn = "A"
assert(T.resume(x));
}
if B == 0 {
turn = "B"
assert(T.resume(y));
}
debug.traceback(x);
debug.traceback(y);
}
assert(B /_ A == 7);
{
local done
local fn test(n) {
done = false
return coroutine.wrap(fn () {
local a = {}
for i = 1, n {
a[(i)] = i
}
T.sethook("pushint 10; yield 0", "", 1);
local a1 = {
table.unpack(a)
}
assert(#a1 == n);
for i = 1, n {
assert(a[(i)] == i);
}
done = true
})
}
local co = test(0)
while !done {
co(30);
}
co = test(1)
while !done {
co(20, 10);
}
co = test(3)
while !done {
co();
}
co = test(100)
while !done {
co();
}
}
local line = debug.getinfo(1, "l").currentline + 2
local fn foo() {
local x = 10
x = x + 10
_G.XX = x
}
local co = coroutine.wrap(fn () {
T.sethook("setglobal X; yield 0", "l", 0);
foo();
return 10
})
_G.XX = nil
_G.X = nil
co();
assert(_G.X == line);
_G.X = nil
co();
assert(_G.X == line + 1);
_G.X = nil
co();
assert(_G.X == line + 2 && _G.XX == nil);
_G.X = nil
co();
assert(_G.X == line + 3 && _G.XX == 20);
assert(co() == 10);
co = coroutine.wrap(fn () {
T.sethook("yield 0", "", 1);
foo();
return 10
})
_G.XX = nil
local c = 0
loop {
c = c + 1
local a = co()
} until a == 10
assert(_G.XX == 20 && c >= 5);
co = coroutine.wrap(fn () {
T.sethook("yield 0", "", 2);
foo();
return 10
})
_G.XX = nil
local c = 0
loop {
c = c + 1
local a = co()
} until a == 10
assert(_G.XX == 20 && c >= 5);
_G.X = nil
_G.XX = nil
{
c = coroutine.create(fn (a, ...) {
T.sethook("yield 0", "l");
assert(a == 10);
return ...
})
assert(coroutine.resume(c, 1, 2, 3));
local n, v = debug.getlocal(c, 0, 1)
assert(n == "a" && v == 1);
assert(debug.setlocal(c, 0, 1, 10));
local t = debug.getinfo(c, 0)
assert(t.currentline == t.linedefined + 1);
assert(!debug.getinfo(c, 1));
assert(coroutine.resume(c));
v = {
coroutine.resume(c)
}
assert(v[(1)] == true && v[(2)] == 2 && v[(3)] == 3 && v[(4)] == undef);
assert(!coroutine.resume(c));
}
{
local c = coroutine.create(fn () {
T.testC("yield 1", 10, 20);
})
local a, b = coroutine.resume(c)
assert(a && b == 20);
assert(debug.getinfo(c, 0).linedefined == -1);
a, b = debug.getlocal(c, 0, 2)
assert(b == 10);
}
print("testing coroutine API");
assert(T.testC(`
newthread # create thread
pushvalue 2 # push body
pushstring 'a a a' # push argument
xmove 0 3 2 # move values to new thread
resume -1, 1 # call it first time
pushstatus
xmove 3 0 0 # move results back to stack
setglobal X # result
setglobal Y # status
pushvalue 2 # push body (to call it again)
pushstring 'b b b'
xmove 0 3 2
resume -1, 1 # call it again
pushstatus
xmove 3 0 0
return 1 # return result
`, fn (...) {
return ...
}) == 'b b b');
assert(X == 'a a a' && Y == 'OK');
C = coroutine.create(fn () {
return T.testC(`
pushnum 10;
pushnum 20;
resume -3 2;
pushstatus
gettop;
return 3`, C)
})
local a, b, c, d = coroutine.resume(C)
assert(a == true && string.find(b, "non%-suspended") && c == "ERRRUN" && d == 4);
a, b, c, d = T.testC(`
rawgeti R 1 # get main thread
pushnum 10;
pushnum 20;
resume -3 2;
pushstatus
gettop;
return 4`)
assert(a == coroutine.running() && string.find(b, "non%-suspended") && c == "ERRRUN" && d == 4);
local state = T.newstate()
assert(T.testC(state, "newthread; isyieldable -1; remove 1; return 1"));
assert(!T.testC(state, "rawgeti R 1; isyieldable -1; remove 1; return 1"));
T.testC(state, "settop 0");
T.loadlib(state);
assert(T.doremote(state, `
coroutine = require'coroutine';
X = function (x) coroutine.yield(x, 'BB'); return 'CC' end;
return 'ok'`));
t = table.pack(T.testC(state, `
rawgeti R 1 # get main thread
pushstring 'XX'
getglobal X # get function for body
pushstring AA # arg
resume 1 1 # 'resume' shadows previous stack!
gettop
setglobal T # top
setglobal B # second yielded value
setglobal A # fist yielded value
rawgeti R 1 # get main thread
pushnum 5 # arg (noise)
resume 1 1 # after coroutine ends, previous stack is back
pushstatus
return *
`))
assert(t.n == 4 && t[(2)] == 'XX' && t[(3)] == 'CC' && t[(4)] == 'OK');
assert(T.doremote(state, "return T") == '2');
assert(T.doremote(state, "return A") == 'AA');
assert(T.doremote(state, "return B") == 'BB');
T.closestate(state);
print('+');
}
_X = coroutine.wrap(fn () {
local a = 10
local x = fn () {
a = a + 1
}
coroutine.yield();
})
_X();
if !_soft {
local j = 2 ^ 9
local lim = 1000000
local t = {
lim - 10,
lim - 5,
lim - 1,
lim,
lim + 1
}
for i = 1, #t {
local j = t[(i)]
co = coroutine.create(fn () {
local t = {}
for i = 1, j {
t[(i)] = i
}
return table.unpack(t)
})
local r, msg = coroutine.resume(co)
assert(!r);
}
co = nil
}
assert(coroutine.running() == main);
print("+");
print("testing yields inside metamethods");
local fn val(x) {
if type(x) == "table" {
return x.x
} else {
return x
}
}
local mt = {
__eq = fn (a, b) {
coroutine.yield(nil, "eq");
return val(a) == val(b)
},
__lt = fn (a, b) {
coroutine.yield(nil, "lt");
return val(a) < val(b)
},
__le = fn (a, b) {
coroutine.yield(nil, "le");
return a - b <= 0
},
__add = fn (a, b) {
coroutine.yield(nil, "add");
return val(a) + val(b)
},
__sub = fn (a, b) {
coroutine.yield(nil, "sub");
return val(a) - val(b)
},
__mul = fn (a, b) {
coroutine.yield(nil, "mul");
return val(a) * val(b)
},
__div = fn (a, b) {
coroutine.yield(nil, "div");
return val(a) / val(b)
},
__idiv = fn (a, b) {
coroutine.yield(nil, "idiv");
return val(a) /_ val(b)
},
__pow = fn (a, b) {
coroutine.yield(nil, "pow");
return val(a) ^ val(b)
},
__mod = fn (a, b) {
coroutine.yield(nil, "mod");
return val(a) % val(b)
},
__unm = fn (a, b) {
coroutine.yield(nil, "unm");
return -val(a)
},
__bnot = fn (a, b) {
coroutine.yield(nil, "bnot");
return ~val(a)
},
__shl = fn (a, b) {
coroutine.yield(nil, "shl");
return val(a) << val(b)
},
__shr = fn (a, b) {
coroutine.yield(nil, "shr");
return val(a) >> val(b)
},
__band = fn (a, b) {
coroutine.yield(nil, "band");
return val(a) & val(b)
},
__bor = fn (a, b) {
coroutine.yield(nil, "bor");
return val(a) | val(b)
},
__bxor = fn (a, b) {
coroutine.yield(nil, "bxor");
return val(a) ^^ val(b)
},
__concat = fn (a, b) {
coroutine.yield(nil, "concat");
return val(a) .. val(b)
},
__index = fn (t, k) {
coroutine.yield(nil, "idx");
return t.k[(k)]
},
__newindex = fn (t, k, v) {
coroutine.yield(nil, "nidx");
t.k[(k)] = v
}
}
local fn new(x) {
return setmetatable({
x = x,
k = {}
}, mt)
}
local a = new(10)
local b = new(12)
local c = new("hello")
local fn run(f, t) {
local i = 1
local c = coroutine.wrap(f)
while true {
local res, stat = c()
if res {
assert(t[(i)] == undef);
return res, t
}
assert(stat == t[(i)]);
i = i + 1
}
}
assert(run(fn () {
if (a >= b) {
return '>='
} else {
return '<'
}
}, {
"le",
"sub"
}) == "<");
assert(run(fn () {
if (a <= b) {
return '<='
} else {
return '>'
}
}, {
"le",
"sub"
}) == "<=");
assert(run(fn () {
if (a == b) {
return '=='
} else {
return '~='
}
}, {
"eq"
}) == "~=");
assert(run(fn () {
return a & b + a
}, {
"add",
"band"
}) == 2);
assert(run(fn () {
return 1 + a
}, {
"add"
}) == 11);
assert(run(fn () {
return a - 25
}, {
"sub"
}) == -15);
assert(run(fn () {
return 2 * a
}, {
"mul"
}) == 20);
assert(run(fn () {
return a ^ 2
}, {
"pow"
}) == 100);
assert(run(fn () {
return a / 2
}, {
"div"
}) == 5);
assert(run(fn () {
return a % 6
}, {
"mod"
}) == 4);
assert(run(fn () {
return a /_ 3
}, {
"idiv"
}) == 3);
assert(run(fn () {
return a + b
}, {
"add"
}) == 22);
assert(run(fn () {
return a - b
}, {
"sub"
}) == -2);
assert(run(fn () {
return a * b
}, {
"mul"
}) == 120);
assert(run(fn () {
return a ^ b
}, {
"pow"
}) == 10 ^ 12);
assert(run(fn () {
return a / b
}, {
"div"
}) == 10 / 12);
assert(run(fn () {
return a % b
}, {
"mod"
}) == 10);
assert(run(fn () {
return a /_ b
}, {
"idiv"
}) == 0);
local a1000 = new(1000)
assert(run(fn () {
return a1000 + 1000
}, {
"add"
}) == 2000);
assert(run(fn () {
return a1000 - 25000
}, {
"sub"
}) == -24000);
assert(run(fn () {
return 2000 * a
}, {
"mul"
}) == 20000);
assert(run(fn () {
return a1000 / 1000
}, {
"div"
}) == 1);
assert(run(fn () {
return a1000 % 600
}, {
"mod"
}) == 400);
assert(run(fn () {
return a1000 /_ 500
}, {
"idiv"
}) == 2);
assert(run(fn () {
return a % b
}, {
"mod"
}) == 10);
assert(run(fn () {
return ~a & b
}, {
"bnot",
"band"
}) == ~10 & 12);
assert(run(fn () {
return a | b
}, {
"bor"
}) == 10 | 12);
assert(run(fn () {
return a ^^ b
}, {
"bxor"
}) == 10 ^^ 12);
assert(run(fn () {
return a << b
}, {
"shl"
}) == 10 << 12);
assert(run(fn () {
return a >> b
}, {
"shr"
}) == 10 >> 12);
assert(run(fn () {
return 10 & b
}, {
"band"
}) == 10 & 12);
assert(run(fn () {
return a | 2
}, {
"bor"
}) == 10 | 2);
assert(run(fn () {
return a ^^ 2
}, {
"bxor"
}) == 10 ^^ 2);
assert(run(fn () {
return a >> 2
}, {
"shr"
}) == 10 >> 2);
assert(run(fn () {
return 1 >> a
}, {
"shr"
}) == 1 >> 10);
assert(run(fn () {
return a << 2
}, {
"shl"
}) == 10 << 2);
assert(run(fn () {
return 1 << a
}, {
"shl"
}) == 1 << 10);
assert(run(fn () {
return 2 ^^ a
}, {
"bxor"
}) == 2 ^^ 10);
assert(run(fn () {
return a .. b
}, {
"concat"
}) == "1012");
assert(run(fn () {
return a .. b .. c .. a
}, {
"concat",
"concat",
"concat"
}) == "1012hello10");
assert(run(fn () {
return "a" .. "b" .. a .. "c" .. c .. b .. "x"
}, {
"concat",
"concat",
"concat"
}) == "ab10chello12x");
{
local mt1 = {
__le = fn (a, b) {
coroutine.yield(10);
return (val(a) <= val(b))
},
__lt = fn (a, b) {
coroutine.yield(10);
return val(a) < val(b)
}
}
local mt2 = {
__lt = mt1.__lt,
__le = mt1.__le
}
local fn run(f) {
local co = coroutine.wrap(f)
local res
loop {
res = co()
} until res != 10
return res
}
local fn test() {
local a1 = setmetatable({
x = 1
}, mt1)
local a2 = setmetatable({
x = 2
}, mt2)
assert(a1 < a2);
assert(a1 <= a2);
assert(1 < a2);
assert(1 <= a2);
assert(2 > a1);
assert(2 >= a2);
return true
}
run(test);
}
assert(run(fn () {
a.BB = print
return a.BB
}, {
"nidx",
"idx"
}) == print);
{
local _ENV = _ENV
f = fn () {
AAA = BBB + 1
return AAA
}
}
g = new(10)
g.k.BBB = 10
debug.setupvalue(f, 1, g);
assert(run(f, {
"idx",
"nidx",
"idx"
}) == 11);
assert(g.k.AAA == 11);
print("+");
print("testing yields inside 'for' iterators");
local f = fn (s, i) {
if i % 2 == 0 {
coroutine.yield(nil, "for");
}
if i < s {
return i + 1
}
}
assert(run(fn () {
local s = 0
{
local _internal_expr_1, _internal_stop_1, _internal_acc_1 = f, 4, 0;
while true {
local i = _internal_expr_1(_internal_stop_1, _internal_acc_1);
_internal_acc_1 = i;
if _internal_acc_1 == nil { break; }
s = s + i
}
}
return s
}, {
"for",
"for",
"for"
}) == 10);
if T == nil {
(Message || print)('\n >>> testC not active: skipping coroutine API tests <<<\n');
print("OK");
return
}
print('testing coroutine API');
local fn apico(...) {
local x = {
...
}
return coroutine.wrap(fn () {
return T.testC(table.unpack(x))
})
}
local a = {
apico(`
pushstring errorcode
pcallk 1 0 2;
invalid command (should not arrive here)
`, `return *`, "stackmark", error)()
}
assert(#a == 4 && a[(3)] == "stackmark" && a[(4)] == "errorcode" && _G.status == "ERRRUN" && _G.ctx == 2);
local co = apico("pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;", coroutine.yield, "getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command", "getglobal status; getglobal ctx; return *")
assert(co() == 10);
assert(co(20, 30) == 'a');
a = {
co()
}
assert(#a == 10 && a[(2)] == coroutine.yield && a[(5)] == 20 && a[(6)] == 30 && a[(7)] == "YIELD" && a[(8)] == 3 && a[(9)] == "YIELD" && a[(10)] == 4);
assert(!pcall(co));
f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;")
co = coroutine.wrap(fn () {
assert(f() == 23);
assert(f() == 23);
return 10
})
assert(co(23, 16) == 5);
assert(co(23, 16) == 5);
assert(co(23, 16) == 10);
f = T.makeCfunc(`
pushnum 102
yieldk 1 U2
cannot be here!
`, ` # continuation
pushvalue U3 # accessing upvalues inside a continuation
pushvalue U4
return *
`, 23, "huu")
x = coroutine.wrap(f)
assert(x() == 102);
eqtab({
x()
}, {
23,
"huu"
});
f = T.makeCfunc([[pushstring 'a'; pushnum 102; yield 2; ]])
a, b, c, d = T.testC(`newthread; pushvalue 2; xmove 0 3 1; resume 3 0;
pushstatus; xmove 3 0 0; resume 3 0; pushstatus;
return 4; `, f)
assert(a == 'YIELD' && b == 'a' && c == 102 && d == 'OK');
local count = 3
f = T.makeCfunc(`
remove 1; # remove argument
pushvalue U3; # get selection function
call 0 1; # call it (result is 'f' or 'yield')
pushstring hello # single argument for selected function
pushupvalueindex 2; # index of continuation program
callk 1 -1 .; # call selected function
errorerror # should never arrive here
`, `
# continuation program
pushnum 34 # return value
return * # return all results
`, fn () {
count = count - 1
if count == 0 {
return coroutine.yield
} else {
return f
}
})
co = coroutine.wrap(fn () {
return f(nil)
})
assert(co() == "hello");
a = {
co()
}
assert(#a == 3 && a[(1)] == a[(2)] && a[(2)] == a[(3)] && a[(3)] == 34);
co = coroutine.wrap(fn (...) {
return T.testC(` # initial function
yieldk 1 2
cannot be here!
`, ` # 1st continuation
yieldk 0 3
cannot be here!
`, ` # 2nd continuation
yieldk 0 4
cannot be here!
`, ` # 3th continuation
pushvalue 6 # function which is last arg. to 'testC' here
pushnum 10; pushnum 20;
pcall 2 0 0 # call should throw an error and return to next line
pop 1 # remove error message
pushvalue 6
getglobal status; getglobal ctx
pcallk 2 2 5 # call should throw an error and jump to continuation
cannot be here!
`, ` # 4th (and last) continuation
return *
`, fn (a, b) {
x = a
y = b
error("errmsg");
}, ...)
})
local a = {
co(3, 4, 6)
}
assert(a[(1)] == 6 && a[(2)] == undef);
a = {
co()
}
assert(a[(1)] == undef && _G.status == "YIELD" && _G.ctx == 2);
a = {
co()
}
assert(a[(1)] == undef && _G.status == "YIELD" && _G.ctx == 3);
a = {
co(7, 8)
}
assert(type(a[(1)]) == 'string' && type(a[(2)]) == 'string' && type(a[(3)]) == 'string' && type(a[(4)]) == 'string' && type(a[(5)]) == 'string' && type(a[(6)]) == 'function');
assert(a[(7)] == 3 && a[(8)] == 4);
assert(a[(9)] == 7 && a[(10)] == 8);
assert(a[(11)]::find("errmsg") && #a == 11);
assert(x == "YIELD" && y == 4);
assert(!pcall(co));
local co = coroutine.wrap(fn () {
local a = {
pcall(pcall, pcall, pcall, pcall, pcall, pcall, pcall, error, "hi")
}
return pcall(assert, table.unpack(a))
})
local a = {
co()
}
assert(a[(10)] == "hi");
print('OK');