diff options
| -rw-r--r-- | lparser.c | 84 | ||||
| -rw-r--r-- | testes/code.lua | 20 | ||||
| -rw-r--r-- | testes/goto.lua | 16 |
3 files changed, 100 insertions, 20 deletions
| @@ -113,7 +113,7 @@ static void checknext (LexState *ls, int c) { | |||
| 113 | 113 | ||
| 114 | 114 | ||
| 115 | static void check_match (LexState *ls, int what, int who, int where) { | 115 | static void check_match (LexState *ls, int what, int who, int where) { |
| 116 | if (!testnext(ls, what)) { | 116 | if (unlikely(!testnext(ls, what))) { |
| 117 | if (where == ls->linenumber) | 117 | if (where == ls->linenumber) |
| 118 | error_expected(ls, what); | 118 | error_expected(ls, what); |
| 119 | else { | 119 | else { |
| @@ -350,7 +350,7 @@ static void solvegoto (LexState *ls, int g, Labeldesc *label) { | |||
| 350 | Labellist *gl = &ls->dyd->gt; /* list of goto's */ | 350 | Labellist *gl = &ls->dyd->gt; /* list of goto's */ |
| 351 | Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ | 351 | Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ |
| 352 | lua_assert(eqstr(gt->name, label->name)); | 352 | lua_assert(eqstr(gt->name, label->name)); |
| 353 | if (gt->nactvar < label->nactvar) /* enter some scope? */ | 353 | if (unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */ |
| 354 | jumpscopeerror(ls, gt); | 354 | jumpscopeerror(ls, gt); |
| 355 | luaK_patchlist(ls->fs, gt->pc, label->pc); | 355 | luaK_patchlist(ls->fs, gt->pc, label->pc); |
| 356 | for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ | 356 | for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ |
| @@ -393,6 +393,11 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, | |||
| 393 | } | 393 | } |
| 394 | 394 | ||
| 395 | 395 | ||
| 396 | static int newgotoentry (LexState *ls, TString *name, int line, int pc) { | ||
| 397 | return newlabelentry(ls, &ls->dyd->gt, name, line, pc); | ||
| 398 | } | ||
| 399 | |||
| 400 | |||
| 396 | /* | 401 | /* |
| 397 | ** Solves forward jumps. Check whether new label 'lb' matches any | 402 | ** Solves forward jumps. Check whether new label 'lb' matches any |
| 398 | ** pending gotos in current block and solves them. Return true | 403 | ** pending gotos in current block and solves them. Return true |
| @@ -471,8 +476,15 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { | |||
| 471 | ** generates an error for an undefined 'goto'. | 476 | ** generates an error for an undefined 'goto'. |
| 472 | */ | 477 | */ |
| 473 | static l_noret undefgoto (LexState *ls, Labeldesc *gt) { | 478 | static l_noret undefgoto (LexState *ls, Labeldesc *gt) { |
| 474 | const char *msg = "no visible label '%s' for <goto> at line %d"; | 479 | const char *msg; |
| 475 | msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); | 480 | if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) { |
| 481 | msg = "break outside loop at line %d"; | ||
| 482 | msg = luaO_pushfstring(ls->L, msg, gt->line); | ||
| 483 | } | ||
| 484 | else { | ||
| 485 | msg = "no visible label '%s' for <goto> at line %d"; | ||
| 486 | msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); | ||
| 487 | } | ||
| 476 | luaK_semerror(ls, msg); | 488 | luaK_semerror(ls, msg); |
| 477 | } | 489 | } |
| 478 | 490 | ||
| @@ -1212,7 +1224,7 @@ static void gotostat (LexState *ls) { | |||
| 1212 | Labeldesc *lb = findlabel(ls, name); | 1224 | Labeldesc *lb = findlabel(ls, name); |
| 1213 | if (lb == NULL) /* no label? */ | 1225 | if (lb == NULL) /* no label? */ |
| 1214 | /* forward jump; will be resolved when the label is declared */ | 1226 | /* forward jump; will be resolved when the label is declared */ |
| 1215 | newlabelentry(ls, &ls->dyd->gt, name, line, luaK_jump(fs)); | 1227 | newgotoentry(ls, name, line, luaK_jump(fs)); |
| 1216 | else { /* found a label */ | 1228 | else { /* found a label */ |
| 1217 | /* backward jump; will be resolved here */ | 1229 | /* backward jump; will be resolved here */ |
| 1218 | if (fs->nactvar > lb->nactvar) /* leaving the scope of some variable? */ | 1230 | if (fs->nactvar > lb->nactvar) /* leaving the scope of some variable? */ |
| @@ -1226,15 +1238,10 @@ static void gotostat (LexState *ls) { | |||
| 1226 | /* | 1238 | /* |
| 1227 | ** Break statement. Semantically equivalent to "goto break". | 1239 | ** Break statement. Semantically equivalent to "goto break". |
| 1228 | */ | 1240 | */ |
| 1229 | static void breakstat (LexState *ls, int pc) { | 1241 | static void breakstat (LexState *ls) { |
| 1230 | FuncState *fs = ls->fs; | ||
| 1231 | int line = ls->linenumber; | 1242 | int line = ls->linenumber; |
| 1232 | BlockCnt *bl = fs->bl; | ||
| 1233 | luaX_next(ls); /* skip break */ | 1243 | luaX_next(ls); /* skip break */ |
| 1234 | newlabelentry(ls, &ls->dyd->gt, luaS_newliteral(ls->L, "break"), line, pc); | 1244 | newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs)); |
| 1235 | while (bl && !bl->isloop) { bl = bl->previous; } | ||
| 1236 | if (!bl) | ||
| 1237 | luaX_syntaxerror(ls, "no loop to break"); | ||
| 1238 | } | 1245 | } |
| 1239 | 1246 | ||
| 1240 | 1247 | ||
| @@ -1243,7 +1250,7 @@ static void breakstat (LexState *ls, int pc) { | |||
| 1243 | */ | 1250 | */ |
| 1244 | static void checkrepeated (LexState *ls, TString *name) { | 1251 | static void checkrepeated (LexState *ls, TString *name) { |
| 1245 | Labeldesc *lb = findlabel(ls, name); | 1252 | Labeldesc *lb = findlabel(ls, name); |
| 1246 | if (lb != NULL) { /* already defined? */ | 1253 | if (unlikely(lb != NULL)) { /* already defined? */ |
| 1247 | const char *msg = "label '%s' already defined on line %d"; | 1254 | const char *msg = "label '%s' already defined on line %d"; |
| 1248 | msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); | 1255 | msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); |
| 1249 | luaK_semerror(ls, msg); /* error */ | 1256 | luaK_semerror(ls, msg); /* error */ |
| @@ -1332,7 +1339,7 @@ static void fixforjump (FuncState *fs, int pc, int dest, int back) { | |||
| 1332 | int offset = dest - (pc + 1); | 1339 | int offset = dest - (pc + 1); |
| 1333 | if (back) | 1340 | if (back) |
| 1334 | offset = -offset; | 1341 | offset = -offset; |
| 1335 | if (offset > MAXARG_Bx) | 1342 | if (unlikely(offset > MAXARG_Bx)) |
| 1336 | luaX_syntaxerror(fs->ls, "control structure too long"); | 1343 | luaX_syntaxerror(fs->ls, "control structure too long"); |
| 1337 | SETARG_Bx(*jmp, offset); | 1344 | SETARG_Bx(*jmp, offset); |
| 1338 | } | 1345 | } |
| @@ -1439,28 +1446,67 @@ static void forstat (LexState *ls, int line) { | |||
| 1439 | } | 1446 | } |
| 1440 | 1447 | ||
| 1441 | 1448 | ||
| 1449 | /* | ||
| 1450 | ** Check whether next instruction is a single jump (a 'break', a 'goto' | ||
| 1451 | ** to a forward label, or a 'goto' to a backward label with no variable | ||
| 1452 | ** to close). If so, set the name of the 'label' it is jumping to | ||
| 1453 | ** ("break" for a 'break') or to where it is jumping to ('target') and | ||
| 1454 | ** return true. If not a single jump, leave input unchanged, to be | ||
| 1455 | ** handled as a regular statement. | ||
| 1456 | */ | ||
| 1457 | static int issinglejump (LexState *ls, TString **label, int *target) { | ||
| 1458 | if (testnext(ls, TK_BREAK)) { /* a break? */ | ||
| 1459 | *label = luaS_newliteral(ls->L, "break"); | ||
| 1460 | return 1; | ||
| 1461 | } | ||
| 1462 | else if (ls->t.token != TK_GOTO || luaX_lookahead(ls) != TK_NAME) | ||
| 1463 | return 0; /* not a valid goto */ | ||
| 1464 | else { | ||
| 1465 | TString *lname = ls->lookahead.seminfo.ts; /* label's id */ | ||
| 1466 | Labeldesc *lb = findlabel(ls, lname); | ||
| 1467 | if (lb) { /* a backward jump? */ | ||
| 1468 | if (ls->fs->nactvar > lb->nactvar) /* needs to close variables? */ | ||
| 1469 | return 0; /* not a single jump; cannot optimize */ | ||
| 1470 | *target = lb->pc; | ||
| 1471 | } | ||
| 1472 | else /* jump forward */ | ||
| 1473 | *label = lname; | ||
| 1474 | luaX_next(ls); /* skip goto */ | ||
| 1475 | luaX_next(ls); /* skip name */ | ||
| 1476 | return 1; | ||
| 1477 | } | ||
| 1478 | } | ||
| 1479 | |||
| 1480 | |||
| 1442 | static void test_then_block (LexState *ls, int *escapelist) { | 1481 | static void test_then_block (LexState *ls, int *escapelist) { |
| 1443 | /* test_then_block -> [IF | ELSEIF] cond THEN block */ | 1482 | /* test_then_block -> [IF | ELSEIF] cond THEN block */ |
| 1444 | BlockCnt bl; | 1483 | BlockCnt bl; |
| 1484 | int line; | ||
| 1445 | FuncState *fs = ls->fs; | 1485 | FuncState *fs = ls->fs; |
| 1486 | TString *jlb = NULL; | ||
| 1487 | int target = NO_JUMP; | ||
| 1446 | expdesc v; | 1488 | expdesc v; |
| 1447 | int jf; /* instruction to skip 'then' code (if condition is false) */ | 1489 | int jf; /* instruction to skip 'then' code (if condition is false) */ |
| 1448 | luaX_next(ls); /* skip IF or ELSEIF */ | 1490 | luaX_next(ls); /* skip IF or ELSEIF */ |
| 1449 | expr(ls, &v); /* read condition */ | 1491 | expr(ls, &v); /* read condition */ |
| 1450 | checknext(ls, TK_THEN); | 1492 | checknext(ls, TK_THEN); |
| 1451 | if (ls->t.token == TK_BREAK) { | 1493 | line = ls->linenumber; |
| 1494 | if (issinglejump(ls, &jlb, &target)) { /* 'if x then goto' ? */ | ||
| 1452 | luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ | 1495 | luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ |
| 1453 | enterblock(fs, &bl, 0); /* must enter block before 'goto' */ | 1496 | enterblock(fs, &bl, 0); /* must enter block before 'goto' */ |
| 1454 | breakstat(ls, v.t); /* handle break */ | 1497 | if (jlb != NULL) /* forward jump? */ |
| 1498 | newgotoentry(ls, jlb, line, v.t); /* will be resolved later */ | ||
| 1499 | else /* backward jump */ | ||
| 1500 | luaK_patchlist(fs, v.t, target); /* jump directly to 'target' */ | ||
| 1455 | while (testnext(ls, ';')) {} /* skip semicolons */ | 1501 | while (testnext(ls, ';')) {} /* skip semicolons */ |
| 1456 | if (block_follow(ls, 0)) { /* 'break' is the entire block? */ | 1502 | if (block_follow(ls, 0)) { /* jump is the entire block? */ |
| 1457 | leaveblock(fs); | 1503 | leaveblock(fs); |
| 1458 | return; /* and that is it */ | 1504 | return; /* and that is it */ |
| 1459 | } | 1505 | } |
| 1460 | else /* must skip over 'then' part if condition is false */ | 1506 | else /* must skip over 'then' part if condition is false */ |
| 1461 | jf = luaK_jump(fs); | 1507 | jf = luaK_jump(fs); |
| 1462 | } | 1508 | } |
| 1463 | else { /* regular case (not goto/break) */ | 1509 | else { /* regular case (not a jump) */ |
| 1464 | luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ | 1510 | luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ |
| 1465 | enterblock(fs, &bl, 0); | 1511 | enterblock(fs, &bl, 0); |
| 1466 | jf = v.f; | 1512 | jf = v.f; |
| @@ -1671,7 +1717,7 @@ static void statement (LexState *ls) { | |||
| 1671 | break; | 1717 | break; |
| 1672 | } | 1718 | } |
| 1673 | case TK_BREAK: { /* stat -> breakstat */ | 1719 | case TK_BREAK: { /* stat -> breakstat */ |
| 1674 | breakstat(ls, luaK_jump(ls->fs)); | 1720 | breakstat(ls); |
| 1675 | break; | 1721 | break; |
| 1676 | } | 1722 | } |
| 1677 | case TK_GOTO: { /* stat -> 'goto' NAME */ | 1723 | case TK_GOTO: { /* stat -> 'goto' NAME */ |
diff --git a/testes/code.lua b/testes/code.lua index 9b3f2b68..4d44fa6a 100644 --- a/testes/code.lua +++ b/testes/code.lua | |||
| @@ -339,8 +339,26 @@ check(function (a, b) | |||
| 339 | 339 | ||
| 340 | checkequal( | 340 | checkequal( |
| 341 | function (a) while a < 10 do a = a + 1 end end, | 341 | function (a) while a < 10 do a = a + 1 end end, |
| 342 | function (a) while true do if not(a < 10) then break end; a = a + 1; end end | 342 | function (a) |
| 343 | ::loop:: | ||
| 344 | if not (a < 10) then goto exit end | ||
| 345 | a = a + 1 | ||
| 346 | goto loop | ||
| 347 | ::exit:: | ||
| 348 | end | ||
| 343 | ) | 349 | ) |
| 344 | 350 | ||
| 351 | checkequal( | ||
| 352 | function (a) repeat local x = a + 1; a = x until a > 0 end, | ||
| 353 | function (a) | ||
| 354 | ::loop:: do | ||
| 355 | local x = a + 1 | ||
| 356 | a = x | ||
| 357 | end | ||
| 358 | if not (a > 0) then goto loop end | ||
| 359 | end | ||
| 360 | ) | ||
| 361 | |||
| 362 | |||
| 345 | print 'OK' | 363 | print 'OK' |
| 346 | 364 | ||
diff --git a/testes/goto.lua b/testes/goto.lua index 85038d02..92f048fb 100644 --- a/testes/goto.lua +++ b/testes/goto.lua | |||
| @@ -249,6 +249,22 @@ assert(testG(2) == "2") | |||
| 249 | assert(testG(3) == "3") | 249 | assert(testG(3) == "3") |
| 250 | assert(testG(4) == 5) | 250 | assert(testG(4) == 5) |
| 251 | assert(testG(5) == 10) | 251 | assert(testG(5) == 10) |
| 252 | |||
| 253 | do | ||
| 254 | -- if x back goto out of scope of upvalue | ||
| 255 | local X | ||
| 256 | goto L1 | ||
| 257 | |||
| 258 | ::L2:: goto L3 | ||
| 259 | |||
| 260 | ::L1:: do | ||
| 261 | local scoped a = function () X = true end | ||
| 262 | assert(X == nil) | ||
| 263 | if a then goto L2 end -- jumping back out of scope of 'a' | ||
| 264 | end | ||
| 265 | |||
| 266 | ::L3:: assert(X == true) -- checks that 'a' was correctly closed | ||
| 267 | end | ||
| 252 | -------------------------------------------------------------------------------- | 268 | -------------------------------------------------------------------------------- |
| 253 | 269 | ||
| 254 | 270 | ||
