From e178a76c36775c4afc025830cdb5a86b5e22f9f5 Mon Sep 17 00:00:00 2001 From: rajames Date: Sun, 4 Oct 2026 08:47:29 -0400 Subject: [PATCH] fix(v4.0.0): LEAVE follows FORTH-79 LEAVE sets the limit equal to the index: the rest of the body runs with the index unchanged and the loop ends at the next LOOP or +LOOP. v3's left the loop at once, which is FORTH-83's. Co-Authored-By: Claude Opus 5.5 --- docs/v4.0.0/DECOMPOSITION.md | 2 +- v4/tests/test_loops.c | 50 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 89170bf4..d63bc0f2 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -726,7 +726,7 @@ The storage service belongs to the Artemis role, now a device node. | `(?DO)` | IN | `over over xor if SKIP drop (DO) … body and loop end … jump PAST SKIP: drop drop drop PAST:` — skips the loop when index = limit, as v3. Executed on the golden model (2026-10-03). | | `(LOOP)` | IN | See below. Adds 1 and goes round again while index < limit, signed, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends, and sequences recorded from the v3 binary. | | `(+LOOP)` | IN | See below. `( n -- )`: adds `n` and goes round again while index < limit for `n >= 0`, while index >= limit for `n < 0`, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends with 11 steps, and sequences recorded from the v3 binary. Clobbers `A`. | -| `(LEAVE)` | IN | `pop drop pop drop jump PAST` — discards the limit and index and leaves the loop at once. In a single loop this is what v3 does. v3 left the two on its return stack (index set to limit) and jumped past the loop, so a `LEAVE` in an inner loop made the outer `LOOP` count the inner loop's leftovers and stop: `: T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ;` prints `0 1 10` in v3 and `0 1 0 0 1 1 0 1 2` here. Executed on the golden model (2026-10-03) in `LOOP` and `+LOOP`, including sequences recorded from the v3 binary. | +| `(LEAVE)` | IN | `pop pop drop dup push push` — FORTH-79 (ruled 2026-10-04): the limit is set equal to the index, so the loop ends at the next `LOOP` or `+LOOP`; the index is unchanged and the rest of the body still runs. v3's `LEAVE` left the loop at once (FORTH-83's behaviour) and, having set the index to the limit, left both on its return stack, so a `LEAVE` in an inner loop made the outer `LOOP` count the inner loop's leftovers and stop: `: T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ;` prints `0 1 10` in v3 and `0 1 0 0 1 1 0 1 2` here. Executed on the golden model (2026-10-04) in `LOOP` and `+LOOP`, up, down and with a step of 0. | | `I` | IN | `pop dup push` — executed on the golden model (2026-10-03). | | `J` | IN | `pop pop pop dup push a! push push a` — the outer index waits in `A` while the inner pair goes back, so `J` needs no return entry beyond the two loops' own (with two in-line `SWAP`s it needed one more). Executed on the golden model (2026-10-03), including a sequence recorded from the v3 binary. Clobbers `A`. | | `UNLOOP` | IN | `pop drop pop drop` — executed on the golden model (2026-10-03) as `UNLOOP EXIT`, including a sequence recorded from the v3 binary. | diff --git a/v4/tests/test_loops.c b/v4/tests/test_loops.c index eb9ca784..45aa47ce 100644 --- a/v4/tests/test_loops.c +++ b/v4/tests/test_loops.c @@ -13,18 +13,21 @@ * LOOP adds 1 and goes round again while index < limit, signed * +LOOP ( n -- ) adds n and goes round again while index < limit for * n >= 0, while index >= limit for n < 0 - * LEAVE leaves the loop at once + * LEAVE FORTH-79 (ruled 2026-10-04): sets the limit equal to the + * index, so the loop ends at the next LOOP or +LOOP; the index is + * unchanged and the rest of the body still runs. v3's LEAVE left + * the loop at once, which is FORTH-83's. * I J the index of this loop, of the loop outside it * UNLOOP discards the loop's limit and index, for EXIT * So `0 5 DO .. LOOP` runs once in v3 and here; 5.18's first (LOOP), which * tested for index = limit, would have gone round the whole number circle. * - * Where v4 parts from v3: v3's LEAVE set the index to the limit and jumped - * past the loop without discarding the two, which is invisible in a single - * loop but breaks an outer one: in v3 + * v3's LEAVE also left the loop's limit and index on its return stack, which + * is invisible in a single loop but breaks an outer one: in v3 * : T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ; * prints "0 1 10" and stops, the outer LOOP having counted the inner loop's - * leftovers. Here LEAVE discards them, and the outer loop carries on. + * leftovers. Here the inner LOOP discards them as it ends, and the outer + * loop carries on. * * The expected sequences marked "v3:" were recorded from the v3 binary on * 2026-10-03, as in test_numout.c. @@ -109,8 +112,9 @@ static v4_cell t_do, t_qdo, t_ploop, t_pqdo, t_nest, t_leave, t_pleave, t_nestle exit_ = FWD(MINUS_IF); O(DROP); O(PUSH); O(PUSH); JUMP(body); \ HERE_(exit_); O(DROP); O(DROP); O(DROP); } while (0) -/* (LEAVE) pop drop pop drop jump PAST the caller resolves PAST after the loop */ -#define LEAVE_(past) do { O(RPOP); O(DROP); O(RPOP); O(DROP); (past) = FWD(JUMP); } while (0) +/* (LEAVE) pop pop drop dup push push R: index index + * The limit becomes the index; nothing is jumped over. */ +#define LEAVE_() do { O(RPOP); O(RPOP); O(DROP); O(DUP); O(PUSH); O(PUSH); } while (0) /* I pop dup push * J pop pop pop dup push a! push push a @@ -132,7 +136,7 @@ static v4_cell t_do, t_qdo, t_ploop, t_pqdo, t_nest, t_leave, t_pleave, t_nestle static void build(void) { - v4_asm_ref a, b, skip, past; + v4_asm_ref a, b, skip, past; /* past: the jump over a skipped ?DO loop */ v4_cell body, body2; /* ( limit start -- ) DO I rec LOOP */ @@ -167,11 +171,10 @@ static void build(void) DO_(); body = v4_asm_label(&as); I_(); REC_(); I_(); VGET(KV); O(XOR); a = FWD(IF); O(DROP); b = FWD(JUMP); - HERE_(a); O(DROP); LEAVE_(past); + HERE_(a); O(DROP); LEAVE_(); HERE_(b); I_(); REC_(); LOOP_(body); - HERE_(past); LIT(77); VSET(LAST); O(SEMI); /* ( limit start -- ) DO I rec I KV @ = IF LEAVE THEN I rec STEP @ +LOOP 77 LAST ! */ @@ -179,11 +182,10 @@ static void build(void) DO_(); body = v4_asm_label(&as); I_(); REC_(); I_(); VGET(KV); O(XOR); a = FWD(IF); O(DROP); b = FWD(JUMP); - HERE_(a); O(DROP); LEAVE_(past); + HERE_(a); O(DROP); LEAVE_(); HERE_(b); I_(); REC_(); VGET(STEP); PLOOP_(body); - HERE_(past); LIT(77); VSET(LAST); O(SEMI); /* ( -- ) 3 0 DO 10 0 DO I rec I 1 = IF LEAVE THEN LOOP I rec LOOP 77 LAST ! */ @@ -194,10 +196,9 @@ static void build(void) body2 = v4_asm_label(&as); I_(); REC_(); I_(); LIT(1); O(XOR); a = FWD(IF); O(DROP); b = FWD(JUMP); - HERE_(a); O(DROP); LEAVE_(past); + HERE_(a); O(DROP); LEAVE_(); HERE_(b); LOOP_(body2); - HERE_(past); I_(); REC_(); LOOP_(body); LIT(77); VSET(LAST); O(SEMI); @@ -376,14 +377,17 @@ int main(void) /* : T 3 0 DO 12 10 DO I . J . LOOP LOOP ; 10 0 11 0 10 1 11 1 10 2 11 2 */ CHECK(call(t_nest, 0, 0, 2, 3, 12) && SAW(10, 0, 11, 0, 10, 1, 11, 1, 10, 2, 11, 2), "v3: nested loops, I and J"); - /* : T 10 0 DO I . I 3 = IF LEAVE THEN I . LOOP ; 0 0 1 1 2 2 3 - * : T 20 0 DO I . I 6 = IF LEAVE THEN I . 3 +LOOP 77 . ; 0 0 3 3 6 77 - * : T 0 20 DO I . I 11 = IF LEAVE THEN I . -3 +LOOP 77 . ; 20 20 17 17 14 14 11 77 */ - CHECK(call(t_leave, 0, 3, 2, 10, 0) && SAW(0, 0, 1, 1, 2, 2, 3) && n.mem[LAST] == 77, "v3: LEAVE in LOOP"); - CHECK(call(t_pleave, 3, 6, 2, 12, 0) && SAW(0, 0, 3, 3, 6) && n.mem[LAST] == 77, "LEAVE in +LOOP, limit below 16"); - CHECK(call(t_pleave, 3, 6, 2, 20, 0) && SAW(0, 0, 3, 3, 6) && n.mem[LAST] == 77, "v3: LEAVE in +LOOP, up"); - CHECK(call(t_pleave, -3, 11, 2, 0, 20) && SAW(20, 20, 17, 17, 14, 14, 11) && n.mem[LAST] == 77, - "v3: LEAVE in +LOOP, down"); + /* FORTH-79 LEAVE: the body runs on to the loop's end with I unchanged. + * (v3, leaving at once, printed one number fewer in each of these.) + * : T 10 0 DO I . I 3 = IF LEAVE THEN I . LOOP ; 0 0 1 1 2 2 3 3 + * : T 20 0 DO I . I 6 = IF LEAVE THEN I . 3 +LOOP 77 . ; 0 0 3 3 6 6 77 + * : T 0 20 DO I . I 11 = IF LEAVE THEN I . -3 +LOOP 77 . ; 20 20 17 17 14 14 11 11 77 */ + CHECK(call(t_leave, 0, 3, 2, 10, 0) && SAW(0, 0, 1, 1, 2, 2, 3, 3) && n.mem[LAST] == 77, "LEAVE in LOOP"); + CHECK(call(t_pleave, 3, 6, 2, 12, 0) && SAW(0, 0, 3, 3, 6, 6) && n.mem[LAST] == 77, "LEAVE in +LOOP, limit below 16"); + CHECK(call(t_pleave, 3, 6, 2, 20, 0) && SAW(0, 0, 3, 3, 6, 6) && n.mem[LAST] == 77, "LEAVE in +LOOP, up"); + CHECK(call(t_pleave, -3, 11, 2, 0, 20) && SAW(20, 20, 17, 17, 14, 14, 11, 11) && n.mem[LAST] == 77, + "LEAVE in +LOOP, down"); + CHECK(call(t_pleave, 0, 4, 2, 9, 4) && SAW(4, 4) && n.mem[LAST] == 77, "LEAVE ends a 0 +LOOP"); /* : T 10 0 DO I 3 = IF UNLOOP EXIT THEN I . LOOP 99 . ; 0 1 2 */ CHECK(call(t_unloop, 0, 3, 2, 10, 0) && SAW(0, 1, 2) && n.mem[LAST] == 0, "v3: UNLOOP EXIT"); @@ -441,7 +445,7 @@ int main(void) /* LEAVE and UNLOOP EXIT at every index of a short loop */ for (i = 0; i < 12; i++) { unsigned m; - for (m = 0, j = 0; j < 12; j++) { want[m++] = (v4_cell)j; if (j == i) break; want[m++] = (v4_cell)j; } + for (m = 0, j = 0; j <= i; j++) { want[m++] = (v4_cell)j; want[m++] = (v4_cell)j; } CHECK(call(t_leave, 0, (v4_cell)i, 2, 12, 0) && saw(want, m) && n.mem[LAST] == 77, "LEAVE at %u", i); for (m = 0, j = 0; j < i; j++) want[m++] = (v4_cell)j; CHECK(call(t_unloop, 0, (v4_cell)i, 2, 12, 0) && saw(want, m) && n.mem[LAST] == 0, "UNLOOP EXIT at %u", i);