lua-users home
lua-l archive

[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;
+      }
     }
   }
 }