[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: pcall() and coroutine.yield()
- From: Michael Roth <mroth@...>
- Date: Tue, 19 Oct 2004 12:33:27 +0200
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hello,
Mike Pall wrote:
| Yes, this is the sad state of affairs. I have been bitten by this and it
| forced me to restructure my code in awkward ways. Being able to mix
pcall()
| and coroutines would *really* help.
Nice to know that I'm not alone with these problems... ;-\
| I have thought about a similar solution, too. But there are really three
| different issues here:
After thinking a little bit more about this, I skeched roughly following
framework:
| 1. The C stack frame that gets added when you execute lua_pcall() from
C code.
| 2. Where to store the setjmp() jmp_buf.
| 3. Which C frame to return to with longjmp().
I think we should cut the problem in two pieces:
1.) error/pcall in native lua code during the interpreter loop.
2.) error/pcall in C code, aka library code.
| The first problem can be solved with your idea. It is sort of a special
| tail-call from C. In fact I would implement a generic C tail-call
facility:
|
| push function; push args; return lua_tailcall(L, nargs);
Yes, same idea here. But I would suggest a total of three new C api
functions:
a.) lua_tailcall() exactly as you suggested above.
b.) lua_chaincall()
c.) lua_tailerror()
Description of lua_chaincall() and lua_chainerror() follows:
lua_chaincall():
First, you push a "chain function". This is a native lua function, a C
function or a closure, whatever you like.
Second, you push the function to be called and its arguments.
Third, you return from your C function with lua_chaincall():
lua_pushcfunction(L, foo);
lua_pushcfunction(L, bar);
lua_push_some_args(L, ...);
return lua_chaincall(L, nargs);
This closes the current C callframe and calls function bar. After
function bar() returns, function foo gets called. Foo receives as first
argument a boolean whether bar raised an error or not (like first value
returned from pcall). The remaining arguments foo receives are the error
object or the return values of foo.
Essentialy, lua_chaincall() work similar like pcall().
lua_chainerror():
Very simple. Is like lua_tailcall() but expects an error object on the
stack. It differs from lua_tailcall() in that way, that the lua core
goes in "error-mode" and winds up the stack to the last lua_chaincall():
lua_pushstring(L, "some error occured.");
return lua_tailerror();
With the two functions lua_chaincall() and lua_tailerror() it is easily
possible to mix library code written in C with lua native code and still
be able to yield from a coroutine because library code could be written
in such a way that the code always calls other functions throught
lua_tailcall() or lua_chaincall().
For practice, most library code will possibly use a closure as the
chaining function, so that the library function know the state with
which it gets called. Example:
int luaopen_foobar(lua_State * L)
{
lua_pushstring(L, "foobar");
lua_pushinteger(L, 0);
lua_pushcclosure(L, l_foobar, 1);
lua_settable(L, LUA_GLOBALSINDEX);
return 0;
}
int l_foobar(lua_State * L)
{
switch(lua_tointeger(L, lua_upvalueindex(1)))
{
case 0: /* first call of foobar() */
do_stuff();
lua_pushinteger(L, 1);
lua_pushcclosure(L, l_foobar, 1);
lua_push_some_other_function(L, ...);
lua_push_some_args(L, ...)
return lua_chaincall(L, nargs);
case 1: /* return from previous chaincall() */
if (lua_toboolean(L, 1))
{
/* no error returned from chaincall() */
continue_with_something();
}
else
{
/* error from chaincall() */
cleanup_in_someway();
~ lua_pushvalue(L, 2); /* push error object */
return lua_tailerror(); /* propagate error to caller */
}
default:
lua_pushstring(L, "invalid state.");
return lua_tailerror();
}
}
| But I think a "side-call" would be even more useful (tell me, if there
| is a better name for it).
I don't know if my lua_chaincall() does the same as your side-call(),
but I think at least they are similarly.
| For the second and third problem above, I may have an interesting
solution.
| I have not tried to code this, since it requires quite a bit of
restructuring
| of some Lua core parts:
So, no comes the tricky part. The "old" lua_pcall() and lua_error() api
functions.
In the long term, I think, they should be avoided. But in the meantime,
I think, there should be a workarround. Of course, if a library is using
lua_pcall(), inside the called function you can't use lua_yield().
Ok, my ideas are roughly (in mixed C and pseudo code):
int luaopen_pcall(lua_State * L)
{
lua_pushcfunction(L, pcall_trampoline);
lua_rawseti(L, LUA_GLOBALSINDEX, PCALL_TRAMPOLINE_INDEX);
return 0;
}
static int pcall_trampoline(lua_State * L)
{
lua_pushcfunction(L, pcall_helper);
lua_insert(L, 1);
return lua_chaincall(L, lua_gettop(L) - 2);
}
static int pcall_helper(lua_State * L)
{
return lua_gettop(L);
}
int lua_pcall(lua_State * L)
{
if (setjmp() == 0)
{
store_jmpbuf_in_someway(L);
lua_rawgeti(l, LUA_GLOBALSINDEX, PCALL_TRAMPOLINE_INDEX);
lua_insert(L, 1);
lua_call(L, lua_gettop(L) - 1, LUA_MULTRET);
return lua_gettop(L);
}
else
{
/* return from longjmp() */
/* someone called the old lua_error() */
push_boolean(L, 0);
push_error_object_in_someway(L)
return 2;
}
}
int lua_error(lua_State * L)
{
jmp_buf jmp = fetch_latest_jmpbuf_in_someway(L);
store_error_object_in_someway(L);
longjmp(jmp);
}
I think, this should somehow work. I left out a small detail: What to
do, if library code calls lua_error(), but there was never a call to
lua_pcall() before?
Solution: The first time, the lua interpreter calls a C function, it
have to store a last resort jmp_buf. It could do this lazy. As long as
the interpreter doesn't leave its domain, nobody could call lua_error().
So, that's it. What do you think folks? Have I made any errors in this
concept? Suggestions? Better solutions?
cu
Michael
-----BEGIN PGP SIGNATURE-----
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org
iD8DBQFBdO12SIrOxc3jOmoRAq7EAJ96aCWJ7/QSUyms+eMGKcPgxGYeFACfcCiY
IJOTCTA+fQ1CijimEdEwRAQ=
=ziy7
-----END PGP SIGNATURE-----