diff options
Diffstat (limited to 'src/compiler/stmt.zc')
| -rw-r--r-- | src/compiler/stmt.zc | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc index a2ad0d1..21d5801 100644 --- a/src/compiler/stmt.zc +++ b/src/compiler/stmt.zc @@ -391,6 +391,8 @@ int kw_break() { if (lsp <= loopbase) { serr("break outside a loop or a switch"); return 0; } f = R_LOOP + (lsp - 1) * 8; if (f[4] != bxdepth) { serr("break can't leave a block expression"); return 0; } + if (dfr_on && dfr_ctx == ctxsp && lsp <= dfr_lsp) { serr("break can't leave a deferred statement"); return 0; } + defer_run(f[2] + 1, 0); exit_vars(f[2] + 1, 0); if (live) exit_rec(hole()); else hole(); op(O_JMP); @@ -406,6 +408,8 @@ int kw_continue() { if (j < loopbase) { serr("continue outside a loop"); return 0; } f = R_LOOP + j * 8; if (f[4] != bxdepth) { serr("continue can't leave a block expression"); return 0; } + if (dfr_on && dfr_ctx == ctxsp && j < dfr_lsp) { serr("continue can't leave a deferred statement"); return 0; } + defer_run(f[2] + 1, 0); backedge(f[3]); exit_vars(f[2] + 1, 0); if (f[0] == -2) { f[7] = fixnew(hole(), f[7]); op(O_JMP); } // do/while: the condition isn't placed yet @@ -413,6 +417,100 @@ int kw_continue() { live = 0; return 0; } +// ---- defer ---- + +// defer expr; or defer { statements } registers code that runs when the +// enclosing block ends. nothing is compiled at the defer: its tokens are kept +// on the dfr stack (DFR_W words per entry: block depth, token list, whether +// the analyzer was on, whether it failed, locals at the defer) and compiled +// again, inline, at every exit that leaves the block: the end of the block +// (block_close, and body for a function), return, break and continue. so the +// deferred code is evaluated when it runs and sees the variables as they are +// then, and the analyzer sees its frees and moves at the exit, where they +// happen. a defer a branch never reached is not on the stack at an exit on +// another path, because the stack only holds the defers read so far in the +// blocks still open. the entries of an exit run newest first. +word .u defer_tokens() { + int n = 0, depth = 0, blk = is_op(s_lb), semi, bad = 0; word q; + while (1) { + if (tk == T_EOF) { bad = 1; break; } + semi = 0; + if (tk == T_OP) { + if (tv == s_lb || tv == s_lp || tv == s_lk) depth++; + else if (tv == s_rb || tv == s_rp || tv == s_rk) depth--; + else if (tv == s_semi && depth == 0) semi = 1; + } + if (depth < 0) { bad = 1; break; } + reserve_R_QBUF((n + 1) * 3); + R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tpack(); n++; + next(); + if (semi) break; + if (blk && depth == 0) { accept(s_semi); break; } + } + if (bad) { die("missing ; after the deferred statement"); n = 0; } + q = rt.new(n * 3 + 1); + q[0] = n; + rt.copy_w(q + 1, R_QBUF, n * 3); + return u*q; +} +int kw_defer() { + int k; word q; + stmtonly(); + if (!infn) { serr("defer outside a function"); return 0; } + if (ndfr >= DFR_MAX) { die("too many pending defers in one function"); return 0; } + next(); + q = defer_tokens(); + k = ndfr * DFR_W; + dfr[k] = fdepth; dfr[k + 1] = d*q; dfr[k + 2] = fchk; dfr[k + 3] = 0; dfr[k + 4] = nloc; + ndfr++; + return 0; +} +// forget the deferred statements from entry base up +int defer_drop(int base) { + word q; + while (ndfr > base) { ndfr--; q = dfr[ndfr * DFR_W + 1]; dfr[ndfr * DFR_W + 1] = 0; rt.free(q); } + return 0; +} +// compile one deferred statement here. the locals declared after the defer are +// hidden while it is compiled, so its names mean what they meant where it was +// written (a name declared later in the block, or one that an inner block +// declares again, is not seen). +int defer_one(int e) { + word q = dfr[e * DFR_W + 1]; int j, k, mark = dfr[e * DFR_W + 4], top = nloc, e0 = nerr; + int ofchk = fchk, oon = dfr_on, olsp = dfr_lsp, octx = dfr_ctx, n0 = ndfr; + j = nloc; + while (j > mark) { j--; k = local_key(lname[j]); lheads[k] = lprev[j]; } + fchk = dfr[e * DFR_W + 2]; dfr_on = 1; dfr_lsp = lsp; dfr_ctx = ctxsp; + src_push(1, q, 0, 0); next(); stmt(); + if (tk != T_EOF) serr("unexpected tokens after the deferred statement"); + src_pop(); + defer_drop(n0); + if (nloc > top) local_drop(top); + j = mark; + while (j < top) { k = local_key(lname[j]); lheads[k] = j; j++; } + fchk = ofchk; dfr_on = oon; dfr_lsp = olsp; dfr_ctx = octx; + if (nerr != e0) dfr[e * DFR_W + 3] = 1; + return 0; +} +// compile the deferred statements registered at block depth dep or deeper in +// this function, newest first, here. pop is for the end of a block, which +// forgets them; an early exit (return, break, continue) leaves them, the +// block's normal end still runs them on the other path. nothing is emitted +// where control can't arrive, and a statement that failed once is not +// compiled again, so its error is reported once. +int defer_run(int dep, int pop) { + int i = ndfr; + if (live) { + while (i > dfr_base && dfr[(i - 1) * DFR_W] >= dep) { + i--; + if (!dfr[i * DFR_W + 3]) defer_one(i); + } + } + if (pop) { + while (ndfr > dfr_base && dfr[(ndfr - 1) * DFR_W] >= dep) defer_drop(ndfr - 1); + } + return 0; +} // switch (e) { case 1, 2: ... case 'x': ... default: ... } // cases don't fall through: each one ends where the next begins, and break // leaves the switch early. case labels are constants. @@ -540,6 +638,7 @@ int kw_return() { next(); if (!infn) serr("return outside a function"); if (bxdepth) serr("return can't leave a block expression"); + if (dfr_on && dfr_ctx == ctxsp) { serr("return can't be used in a deferred statement"); return resync(); } if (is_op(s_semi)) { lit(0); c = C_PLAIN; if_ret(0, 1); } else { c = expr(); rval(); @@ -560,6 +659,7 @@ int kw_return() { expect(s_semi); retcheck(c); borrow_cleanup(); + defer_run(1, 0); // the value is on the stack; the deferred code is stack neutral exit_vars(1, 0); epichain = fixnew(here, epichain); lit(rt.MW - 1); op(O_JMP); |
