aboutsummaryrefslogtreecommitdiff
path: root/src/compiler/stmt.zc
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/stmt.zc')
-rw-r--r--src/compiler/stmt.zc100
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);