lua-users home
lua-l archive

2nd version: try..catch for Lua, now "finally" supported

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


As suggested by Mark Hamburg, I've updated my try..catch patch, now
"finally" clause is supported, with some bug-fix.
Syntax:
 try
 ... (block) ...
 end
 Simply ignore errors raised in block, and
 try
 ... (block) ...
 catch err do
 ... (handler block) ...
 end
will catch error object raised in block to err, which is a variable
local to handler block. you can give err another name you like, of
course.
then finally, the "finally" :-)
 try
 ... (block) ...
 finally
 ...(cleanup block) ...
 end
will always execute the "cleanup block", if error occurs, this will
raise error again after cleanup block executed. NOTE if 'return' or
'break' exists in "cleanup block", it will not raise the error object
but just do what "return" and "break" do.
Here's simple testing code for "finally":
----------------------------------------------------------------------
function doit()
 for i=1,2 do
 print ("i=", i)
 try
 try
 print "do1"
 finally
 print "finally do1"
 end
 error "cannot doit"
 print "wont be here"
 finally
 try
 print "do2"
 finally
 print "finally do2"
 -- break
 end
 print "wont be here 2"
 end
 end
 print "break out"
end
try
 print "lets go"
 doit()
catch err do
 print ("err: ", err)
end
----------------------------------------------------------------------
try uncomment the "break" statement and see what will happen.
!IMPORTANT!
1) this patch is only tested on Windows and MSVC, though it should be
platform independent.
2) may not be compatible with C++ exceptions currently. I use
setjmp-longjmp here.
3) I do not have a thorough understanding on lua VM yet, so some code
might be buggy, bulky or stupid, I will refine it later. Again,
comments are always welcome.
Hu
diff -urN src2/ldo.c src/ldo.c
--- src2/ldo.c	2008年01月19日 06:31:22.000000000 +0800
+++ src/ldo.c	2008年01月27日 10:33:19.500630400 +0800
@@ -40,14 +40,6 @@
 */
 
 
-/* chain list of long jump buffers */
-struct lua_longjmp {
- struct lua_longjmp *previous;
- luai_jmpbuf b;
- volatile int status; /* error code */
-};
-
-
 void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
 switch (errcode) {
 case LUA_ERRMEM: {
@@ -109,7 +101,8 @@
 
 
 int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
- struct lua_longjmp lj;
+ struct lua_longjmp lj;
+ lj.type = JMPTYPE_LONGJMP;
 lj.status = 0;
 lj.previous = L->errorJmp; /* chain new error handler */
 L->errorJmp = &lj;
diff -urN src2/ldo.h src/ldo.h
--- src2/ldo.h	2007年12月27日 21:02:26.000000000 +0800
+++ src/ldo.h	2008年01月31日 01:25:27.063960000 +0800
@@ -11,6 +11,29 @@
 #include "lobject.h"
 #include "lstate.h"
 #include "lzio.h"
+
+#include <setjmp.h>
+
+#define JMPTYPE_LONGJMP 0
+#define JMPTYPE_TRY 1
+
+/* chain list of long jump buffers */
+struct lua_longjmp {
+ struct lua_longjmp *previous;
+ luai_jmpbuf b;
+ volatile int status; /* error code */
+
+ int type; /* JMPTYPE_* */
+ const Instruction *dest;
+ ptrdiff_t errobj; /* holds error object */
+ int opcode;
+ ptrdiff_t old_ci;
+ ptrdiff_t old_errfunc;
+ int old_top;
+ int old_nexeccalls;
+ unsigned short oldnCcalls;
+ lu_byte old_allowhooks;
+};
 
 
 #define luaD_checkstack(L,n)	\
diff -urN src2/llex.c src/llex.c
--- src2/llex.c	2007年12月27日 21:02:26.000000000 +0800
+++ src/llex.c	2008年01月30日 09:37:51.007926400 +0800
@@ -35,10 +35,10 @@
 
 /* ORDER RESERVED */
 const char *const luaX_tokens [] = {
- "and", "break", "do", "else", "elseif",
- "end", "false", "for", "function", "if",
+ "and", "break", "catch", "do", "else", "elseif",
+ "end", "false", "finally", "for", "function", "if",
 "in", "local", "nil", "not", "or", "repeat",
- "return", "then", "true", "until", "while",
+ "return", "then", "true", "try", "until", "while",
 "..", "...", "==", ">=", "<=", "~=",
 "<number>", "<name>", "<string>", "<eof>",
 NULL
diff -urN src2/llex.h src/llex.h
--- src2/llex.h	2007年12月27日 21:02:26.000000000 +0800
+++ src/llex.h	2008年01月30日 09:37:37.628688000 +0800
@@ -23,10 +23,10 @@
 */
 enum RESERVED {
 /* terminal symbols denoted by reserved words */
- TK_AND = FIRST_RESERVED, TK_BREAK,
- TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
+ TK_AND = FIRST_RESERVED, TK_BREAK, TK_CATCH,
+ TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FINALLY, TK_FOR, TK_FUNCTION,
 TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
- TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
+ TK_RETURN, TK_THEN, TK_TRUE, TK_TRY, TK_UNTIL, TK_WHILE,
 /* other terminal symbols */
 TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER,
 TK_NAME, TK_STRING, TK_EOS
diff -urN src2/lopcodes.c src/lopcodes.c
--- src2/lopcodes.c	2007年12月27日 21:02:26.000000000 +0800
+++ src/lopcodes.c	2008年01月30日 20:51:03.330273600 +0800
@@ -36,7 +36,13 @@
 "NOT",
 "LEN",
 "CONCAT",
- "JMP",
+ "JMP",
+ "TRY",
+ "TRYCATCH",
+ "TRYFINALLY",
+ "EXITTRY",
+ "EXITFIN",
+ "RETFIN",
 "EQ",
 "LT",
 "LE",
@@ -83,6 +89,12 @@
 ,opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_LEN */
 ,opmode(0, 1, OpArgR, OpArgR, iABC)		/* OP_CONCAT */
 ,opmode(0, 0, OpArgR, OpArgN, iAsBx)		/* OP_JMP */
+ ,opmode(0, 0, OpArgR, OpArgN, iAsBx)		/* OP_TRY */
+ ,opmode(0, 1, OpArgR, OpArgN, iAsBx)		/* OP_TRYCATCH */
+ ,opmode(0, 0, OpArgR, OpArgN, iAsBx)		/* OP_TRYFIN */
+ ,opmode(0, 0, OpArgN, OpArgN, iABC)		/* OP_EXITTRY */
+ ,opmode(0, 0, OpArgN, OpArgN, iABC)		/* OP_EXITFIN */
+ ,opmode(0, 0, OpArgN, OpArgN, iABC)		/* OP_RETFIN */
 ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_EQ */
 ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_LT */
 ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_LE */
diff -urN src2/lopcodes.h src/lopcodes.h
--- src2/lopcodes.h	2007年12月27日 21:02:26.000000000 +0800
+++ src/lopcodes.h	2008年01月30日 20:50:18.646020800 +0800
@@ -181,7 +181,13 @@
 OP_CONCAT,/*	A B C	R(A) := R(B).. ... ..R(C)			*/
 
 OP_JMP,/*	sBx	pc+=sBx					*/
-
+OP_TRY,/* 	sBx	pc+=sBx					*/
+OP_TRYCATCH, /* A sBx R(A) := errorobj */
+OP_TRYFIN, /* A sBx R(A) := errorobj */
+OP_EXITTRY,
+OP_EXITFIN,
+OP_RETFIN, /* A B C R(A) := errorobj */
+
 OP_EQ,/*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
 OP_LT,/*	A B C	if ((RK(B) < RK(C)) ~= A) then pc++ 		*/
 OP_LE,/*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++ 		*/
diff -urN src2/lparser.c src/lparser.c
--- src2/lparser.c	2007年12月28日 23:32:24.000000000 +0800
+++ src/lparser.c	2008年01月31日 00:13:56.504438400 +0800
@@ -42,7 +42,7 @@
 int breaklist; /* list of jumps out of this loop */
 lu_byte nactvar; /* # active locals outside the breakable structure */
 lu_byte upval; /* true if some variable in the block is an upvalue */
- lu_byte isbreakable; /* true if `block' is a loop */
+ lu_byte isbreakable; /* 0: normal block, 1: loop, 2: try, 3: finally */
 } BlockCnt;
 
 
@@ -300,7 +300,7 @@
 if (bl->upval)
 luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0);
 /* a block either controls scope or breaks (never both) */
- lua_assert(!bl->isbreakable || !bl->upval);
+ lua_assert(bl->isbreakable != 1|| !bl->upval);
 lua_assert(bl->nactvar == fs->nactvar);
 fs->freereg = fs->nactvar; /* free registers */
 luaK_patchtohere(fs, bl->breaklist);
@@ -871,7 +871,8 @@
 static int block_follow (int token) {
 switch (token) {
 case TK_ELSE: case TK_ELSEIF: case TK_END:
- case TK_UNTIL: case TK_EOS:
+ case TK_UNTIL: case TK_EOS:
+ case TK_CATCH: case TK_FINALLY:
 return 1;
 default: return 0;
 }
@@ -976,7 +977,11 @@
 FuncState *fs = ls->fs;
 BlockCnt *bl = fs->bl;
 int upval = 0;
- while (bl && !bl->isbreakable) {
+ while (bl && bl->isbreakable != 1) {
+ if (bl->isbreakable == 2)
+ luaK_codeABC(fs, OP_EXITTRY, 0, 0, 0);
+ else if (bl->isbreakable == 3)
+ luaK_codeABC(fs, OP_EXITFIN, 0, 0, 0);
 upval |= bl->upval;
 bl = bl->previous;
 }
@@ -1161,6 +1166,76 @@
 check_match(ls, TK_END, TK_IF, line);
 }
 
+
+static void trystat (LexState *ls, int line) {
+ /* trystat -> TRY block CATCH err DO block END */
+ FuncState *fs = ls->fs;
+ BlockCnt bl;
+ int escapelist = NO_JUMP;
+ int pc;
+
+ luaX_next(ls);
+ pc = luaK_codeAsBx(fs, OP_TRY, 0, NO_JUMP);
+
+ enterblock(fs, &bl, 2); /* try block */
+ block(ls);
+ leaveblock(fs);
+
+ if (ls->t.token == TK_CATCH) {
+ TString *varname;
+ int base;
+
+ luaK_codeABC(fs, OP_EXITTRY, 0, 0, 0);
+ luaK_concat(fs, &escapelist, luaK_jump(fs));
+ SET_OPCODE(fs->f->code[pc], OP_TRYCATCH); /* change it to TRYCATCH */
+ luaK_patchtohere(fs, pc);
+
+ // local err
+ enterblock(fs, &bl, 0);
+ luaX_next(ls); /* skip `catch' */
+ varname = str_checkname(ls); /* first variable name */
+
+ // do
+ checknext(ls, TK_DO);
+ base = fs->freereg;
+ new_localvar(ls, varname, 0);
+ adjustlocalvars(ls, 1); /* control variables */
+ luaK_reserveregs(fs, 1);
+ SETARG_A(fs->f->code[pc], base);
+ block(ls);
+ leaveblock(fs);
+
+ } else if (ls->t.token == TK_FINALLY) {
+ int base;
+ luaK_codeABC(fs, OP_EXITTRY, 0, 0, 0);
+ luaK_concat(fs, &escapelist, luaK_jump(fs));
+ SET_OPCODE(fs->f->code[pc], OP_TRYFIN); /* change it to TRYFIN */
+ luaK_patchtohere(fs, pc);
+
+ enterblock(fs, &bl, 3); /* finally block */
+ luaX_next(ls); /* skip 'finally' */
+
+ base = fs->freereg;
+ new_localvarliteral(ls, "(error obj)", 0);
+ adjustlocalvars(ls, 1); /* control variables */
+ luaK_reserveregs(fs, 1);
+ SETARG_A(fs->f->code[pc], base);
+
+ block(ls);
+
+ luaK_codeABC(fs, OP_RETFIN, base, 0, 0); /* OP_ENDFIN jump to the return point */
+ leaveblock(fs);
+
+ } else {
+ luaK_codeABC(fs, OP_EXITTRY, 0, 0, 0);
+ luaK_concat(fs, &escapelist, pc);
+ }
+
+ luaK_patchtohere(fs, escapelist);
+
+ check_match(ls, TK_END, TK_TRY, line);
+}
+
 
 static void localfunc (LexState *ls) {
 expdesc v, b;
@@ -1238,6 +1313,7 @@
 static void retstat (LexState *ls) {
 /* stat -> RETURN explist */
 FuncState *fs = ls->fs;
+ BlockCnt *bl = fs->bl;
 expdesc e;
 int first, nret; /* registers with returned values */
 luaX_next(ls); /* skip RETURN */
@@ -1263,6 +1339,15 @@
 lua_assert(nret == fs->freereg - first);
 }
 }
+ }
+
+ /* before return, we should exit all try-catch blocks */
+ while (bl) {
+ if (bl->isbreakable == 2)
+ luaK_codeABC(fs, OP_EXITTRY, 0, 0, 0);
+ else if (bl->isbreakable == 3)
+ luaK_codeABC(fs, OP_EXITFIN, 0, 0, 0);
+ bl = bl->previous;
 }
 luaK_ret(fs, first, nret);
 }
@@ -1296,6 +1381,10 @@
 case TK_FUNCTION: {
 funcstat(ls, line); /* stat -> funcstat */
 return 0;
+ }
+ case TK_TRY: {
+ trystat(ls, line);
+ return 0;
 }
 case TK_LOCAL: { /* stat -> localstat */
 luaX_next(ls); /* skip LOCAL */
diff -urN src2/lstate.c src/lstate.c
--- src2/lstate.c	2008年01月03日 23:20:40.000000000 +0800
+++ src/lstate.c	2008年01月30日 21:14:30.824150400 +0800
@@ -54,13 +54,32 @@
 L1->ci->func = L1->top;
 setnilvalue(L1->top++); /* `function' entry for this `ci' */
 L1->base = L1->ci->base = L1->top;
- L1->ci->top = L1->top + LUA_MINSTACK;
+ L1->ci->top = L1->top + LUA_MINSTACK;
+ L1->fstack = NULL;
 }
 
 
 static void freestack (lua_State *L, lua_State *L1) {
+ struct lua_longjmp *pj, *pprev, *pnext;
 luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo);
- luaM_freearray(L, L1->stack, L1->stacksize, TValue);
+ luaM_freearray(L, L1->stack, L1->stacksize, TValue);
+
+ /* free try-catch info */
+ pj = L->errorJmp;
+ pnext = NULL;
+ while (pj) {
+ pprev = pj->previous;
+ if (pj->type == JMPTYPE_TRY) {
+ if (pnext == NULL)
+ L->errorJmp = pprev;
+ else
+ pnext->previous = pprev;
+ luaM_free(L, pj);
+ }
+ else
+ pnext = pj;
+ pj = pprev;
+ }
 }
 
 
diff -urN src2/lstate.h src/lstate.h
--- src2/lstate.h	2008年01月03日 23:20:40.000000000 +0800
+++ src/lstate.h	2008年01月30日 21:13:20.993739200 +0800
@@ -123,7 +123,8 @@
 GCObject *openupval; /* list of open upvalues in this stack */
 GCObject *gclist;
 struct lua_longjmp *errorJmp; /* current error recover point */
- ptrdiff_t errfunc; /* current error handling function (stack index) */
+ ptrdiff_t errfunc; /* current error handling function (stack index) */
+ struct lua_longjmp *fstack; /* stack for "finally" ret */
 };
 
 
diff -urN src2/luaconf.h src/luaconf.h
--- src2/luaconf.h	2008年01月19日 01:07:48.000000000 +0800
+++ src/luaconf.h	2008年01月27日 10:31:25.827176000 +0800
@@ -603,14 +603,7 @@
 ** compiling as C++ code, with _longjmp/_setjmp when asked to use them,
 ** and with longjmp/setjmp otherwise.
 */
-#if defined(__cplusplus)
-/* C++ exceptions */
-#define LUAI_THROW(L,c)	throw(c)
-#define LUAI_TRY(L,c,a)	try { a } catch(...) \
-	{ if ((c)->status == 0) (c)->status = -1; }
-#define luai_jmpbuf	int /* dummy variable */
-
-#elif defined(LUA_USE_ULONGJMP)
+#if defined(LUA_USE_ULONGJMP)
 /* in Unix, try _longjmp/_setjmp (more efficient) */
 #define LUAI_THROW(L,c)	_longjmp((c)->b, 1)
 #define LUAI_TRY(L,c,a)	if (_setjmp((c)->b) == 0) { a }
diff -urN src2/lvm.c src/lvm.c
--- src2/lvm.c	2007年12月28日 23:32:24.000000000 +0800
+++ src/lvm.c	2008年01月31日 01:26:10.055779200 +0800
@@ -368,13 +368,61 @@
 Protect(Arith(L, ra, rb, rc, tm)); \
 }
 
-
+
+static void releasetry(lua_State *L) {
+ struct lua_longjmp *pj = L->errorJmp;
+ if (pj->type == JMPTYPE_TRY) {
+ L->errfunc = pj->old_errfunc;
+ L->errorJmp = pj->previous;
+ luaM_free(L, pj);
+ }
+}
+
+static void restoretry(lua_State *L) {
+ struct lua_longjmp *pj = L->errorJmp;
+
+ StkId oldtop = restorestack(L, pj->old_top);
+ luaF_close(L, oldtop); /* close eventual pending closures */
+
+ L->nCcalls = pj->oldnCcalls;
+ L->ci = restoreci(L, pj->old_ci);
+ L->base = L->ci->base;
+ L->allowhook = pj->old_allowhooks;
+
+ if (pj->opcode != OP_TRY)
+ luaD_seterrorobj(L, pj->status, L->base + pj->errobj);
+ L->top = oldtop;
+
+ if (L->size_ci > LUAI_MAXCALLS) { /* there was an overflow? */
+ int inuse = cast_int(L->ci - L->base_ci);
+ if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */
+ luaD_reallocCI(L, LUAI_MAXCALLS);
+ }
+
+ if (pj->opcode == OP_TRYFIN) {
+ L->errorJmp = pj->previous;
+ pj->previous = L->fstack;
+ L->fstack = pj;
+ }
+ else
+ releasetry(L);
+}
+
+static void releasefin(lua_State *L) {
+ if (L->fstack) {
+ struct lua_longjmp *pj = L->fstack;
+ L->fstack = pj->previous;
+ luaM_free(L, pj);
+ }
+}
 
 void luaV_execute (lua_State *L, int nexeccalls) {
 LClosure *cl;
 StkId base;
 TValue *k;
- const Instruction *pc;
+ const Instruction *pc;
+ struct lua_longjmp *pj;
+
 reentry: /* entry point */
 lua_assert(isLua(L->ci));
 pc = L->savedpc;
@@ -756,7 +804,69 @@
 }
 }
 continue;
- }
+ }
+ case OP_TRY:
+ case OP_TRYCATCH:
+ case OP_TRYFIN: {
+ pj = luaM_malloc(L, sizeof(struct lua_longjmp));
+ pj->type = JMPTYPE_TRY;
+ pj->status = 0;
+ pj->dest = pc + GETARG_sBx(i);
+ pj->previous = L->errorJmp;
+ pj->errobj = ra - base;
+ pj->opcode = GET_OPCODE(i);
+
+ pj->oldnCcalls = L->nCcalls;
+ pj->old_ci = saveci(L, L->ci);
+ pj->old_allowhooks = L->allowhook;
+ pj->old_errfunc = L->errfunc;
+ pj->old_nexeccalls = nexeccalls;
+ pj->old_top = savestack(L, L->top);
+ L->errorJmp = pj;
+ L->errfunc = 0;
+
+ if (setjmp(pj->b)) {
+ pc = L->errorJmp->dest;
+ nexeccalls = L->errorJmp->old_nexeccalls;
+ restoretry(L);
+ L->savedpc = pc;
+ goto reentry;
+ }
+
+ continue;
+ }
+ case OP_EXITTRY: {
+ if (L->errorJmp) {
+ struct lua_longjmp *pj = L->errorJmp;
+ if (pj->opcode == OP_TRYFIN) { /* move it to fstack, and jump to 'finally' clause */
+ const Instruction *tmpc = pc;
+ setnilvalue(L->base + pj->errobj); /* clear internal error object */
+ L->errorJmp = pj->previous;
+ pc = pj->dest;
+ pj->dest = tmpc; /* save pc for RETFIN */
+ pj->previous = L->fstack;
+ L->fstack = pj;
+ } else
+ releasetry(L);
+ }
+ continue;
+ }
+ case OP_EXITFIN: {
+ releasefin(L);
+ continue;
+ }
+ case OP_RETFIN: {
+ if (!ttisnil(ra)) { /* raise error again */
+ int status = L->fstack->status;
+ releasefin(L);
+ L->top = ra + 1;
+ luaD_throw(L, status);
+ } else {
+ pc = L->fstack->dest;
+ releasefin(L);
+ }
+ continue;
+ }
 }
 }
 }

AltStyle によって変換されたページ (->オリジナル) /