feat(v4.0.0): every error is raised and ends the line (D-18)
Ruled 2026-10-04: guard all errors. The errors that set NODE-ERROR and let the line run on now stop it at once, with a message. - A store of a non-zero code to NODE-ERROR is a trap, a sixth kind of fault: nothing after it executes, the return stack is emptied, and the data stack is left as the word left it. Not attached, NODE-ERROR is plain memory, as the tests below the prompt use it. - The capsule's words store a code where they stored -1, and the prompt's (RAISED) prints its message: Negative count, Not a number, Number too long, Not a character, Dictionary full, Name missing, Control structure mismatch, Control structures too deep. - ' and COMPILE and [COMPILE] of a word that is not there say UNKNOWN WORD: 'xxx', as the interpreter does. - tests: the trap in test_exec.c; every message from the prompt, with the rest of the line not run and the stack kept, in test_host_quit.c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
644bfc0a25
commit
dc7e37ba5c
@@ -421,6 +421,48 @@ static void test_stack_faults(void)
|
||||
CHECK(n.ds.t == 55, "detached, the address is memory");
|
||||
}
|
||||
|
||||
/* D-18: NODE-ERROR as a trap. A store of a non-zero code is a fault of its
|
||||
* own kind: the code is written, the return stack is emptied, the data stack
|
||||
* is left, and P is the sixth word of the table. */
|
||||
static void test_raised_errors(void)
|
||||
{
|
||||
static const unsigned store[] = { V4_OP_STORE_A, V4_OP_STORE_B, V4_OP_STORE_INC };
|
||||
unsigned k;
|
||||
for (k = 0; k < 3; k++) {
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
v4_node_error_attach(&n, 700);
|
||||
dpush(11); dpush(22); dpush(5); rpush(1); rpush(2);
|
||||
n.a = 700; n.b = 700;
|
||||
n.mem[0] = word6(store[k], V4_OP_DROP, V4_OP_DROP, NOP, NOP, NOP); n.p = 0;
|
||||
CHECK(v4_exec_step_word(&n, &es, &h) == 1, "the store retires and nothing after it runs");
|
||||
CHECK(n.faults == 1 && n.fault_kind == V4_FAULT_RAISED && n.fault_addr == 5 && n.p == HANDLER + (v4_cell)V4_FAULT_RAISED,
|
||||
"a non-zero store to NODE-ERROR raises: kind %u, code %lld", n.fault_kind, (long long)n.fault_addr);
|
||||
CHECK(n.mem[700] == 5, "the code is in NODE-ERROR");
|
||||
CHECK(n.rs.depth == 0 && n.ds.depth == 2 && n.ds.t == 22 && n.ds.s == 11, "the return stack is emptied and the data stack left");
|
||||
/* zero clears it and is no fault */
|
||||
dpush(0); n.a = 700; v4_exec_op(&n, &es, &h, V4_OP_STORE_A, 0, 0);
|
||||
CHECK(n.faults == 1 && n.mem[700] == 0, "a store of zero is an ordinary store");
|
||||
dpush(-1); n.a = 699; v4_exec_op(&n, &es, &h, V4_OP_STORE_A, 0, 0);
|
||||
CHECK(n.faults == 1 && n.mem[699] == -1, "and so is a store next to it");
|
||||
}
|
||||
/* detached: ordinary memory */
|
||||
fresh(); v4_node_fault_attach(&n, HANDLER);
|
||||
CHECK(n.error_reg == -1, "a fresh node has no error trap");
|
||||
dpush(7); n.a = 700; v4_exec_op(&n, &es, &h, V4_OP_STORE_A, 0, 0);
|
||||
CHECK(n.faults == 0 && n.mem[700] == 7, "not attached, NODE-ERROR is memory and the word goes on");
|
||||
/* with no table the node stops */
|
||||
fresh(); v4_node_error_attach(&n, 700);
|
||||
dpush(7); n.a = 700;
|
||||
CHECK(run1(V4_OP_STORE_A) == 1 && n.stopped && n.mem[700] == 7, "with no handler a raised error stops the node");
|
||||
v4_node_reset(&n);
|
||||
CHECK(n.error_reg == -1, "reset detaches the trap");
|
||||
/* the table must be six words inside memory */
|
||||
v4_node_fault_attach(&n, (v4_cell)(V4_NODE_WORDS - V4_FAULT_KINDS));
|
||||
CHECK(n.fault_vector == (v4_cell)(V4_NODE_WORDS - V4_FAULT_KINDS), "a table that ends with memory is accepted");
|
||||
v4_node_fault_attach(&n, (v4_cell)(V4_NODE_WORDS - V4_FAULT_KINDS + 1u));
|
||||
CHECK(n.fault_vector == -1, "one that runs past it is not");
|
||||
}
|
||||
|
||||
/* D-14: an address outside node memory is a fault. The opcode does nothing,
|
||||
* the rest of its word is not executed, and P becomes the handler; with no
|
||||
* handler the node stops. */
|
||||
@@ -542,6 +584,7 @@ int main(void)
|
||||
test_iword_in_wide_cell();
|
||||
#endif
|
||||
test_stack_faults();
|
||||
test_raised_errors();
|
||||
test_address_faults();
|
||||
test_soak();
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
* v3 goes on compiling the lines that follow into it.
|
||||
* - ABORT" at the prompt stops the line when its flag is true. v3 prints
|
||||
* the text and carries on. Compiled into a word, v3's crashes (SIGSEGV).
|
||||
* - control words that do not match give ERROR alone; v3 names the word.
|
||||
* - every error has a message (D-18), but v4's says what was wrong, not
|
||||
* which word: "Control structure mismatch" where v3 says
|
||||
* "LOOP: missing DO".
|
||||
* - QUERY takes 80 characters (FORTH-79); v3's line is 255.
|
||||
* - an address outside memory says so (D-14); v3 gives ERROR alone.
|
||||
* - M/MOD by zero says so (D-15); v3 gives ERROR alone.
|
||||
@@ -82,6 +84,7 @@ static void boot_with(unsigned depth)
|
||||
v4_node_console_attach(&n, CONSOLE_TX);
|
||||
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST);
|
||||
v4_node_fault_attach(&n, w_fault);
|
||||
v4_node_error_attach(&n, NODE_ERROR);
|
||||
v4_dstack_reset(&n.ds);
|
||||
v4_rstack_reset(&n.rs);
|
||||
v4_exec_reset(&es);
|
||||
@@ -223,14 +226,28 @@ static const transcript script[] = {
|
||||
{ ": Z3 5 0 DO 9 3 I - / DROP LOOP 65 EMIT ; Z3\n66 EMIT\n", "/: Division by zero\n ERROR\nok> B ok\nok> ", 0 },
|
||||
{ ": BAD 1 NOSUCH 2 ;\nBAD\n65 EMIT\n", "UNKNOWN WORD: 'NOSUCH'\n ERROR\nok> UNKNOWN WORD: 'BAD'\n ERROR\nok> A ok\nok> ", 0 },
|
||||
{ "1 ABORT\" now\" 65 EMIT\n66 EMIT\n", "now\n ok\nok> B ok\nok> ", 0 },
|
||||
{ ": B1 IF LOOP ;\n65 EMIT\n", " ERROR\nok> A ok\nok> ", 0 },
|
||||
{ ": B1 IF LOOP ;\n65 EMIT\n", "Control structure mismatch\n ERROR\nok> A ok\nok> ", 0 },
|
||||
{ ": A2 0 ABORT\" boom\" 67 EMIT ; A2\n", "C ok\nok> ", 0 },
|
||||
{ ": A1 ABORT\" boom\" 65 EMIT ; 1 A1 66 EMIT\n0 A1\n", "boom\n ok\nok> A ok\nok> ", 0 },
|
||||
{ "1 2 3 QUIT 68 EMIT\n+ + 48 + EMIT\n", "\nok> 6 ok\nok> ", 0 },
|
||||
{ ": Q1 QUIT ; : Q2 65 EMIT Q1 66 EMIT ; : Q3 Q2 67 EMIT ; Q3 68 EMIT\n69 EMIT\n", "A\nok> E ok\nok> ", 0 },
|
||||
{ ": HALF 1 2 [ QUIT\n65 EMIT\nHALF\n", "\nok> A ok\nok> UNKNOWN WORD: 'HALF'\n ERROR\nok> ", 0 },
|
||||
{ ": HALF 1 2 [ ABORT\n65 EMIT\n", " ok\nok> A ok\nok> ", 0 },
|
||||
{ "PAD PAD -1 CMOVE 65 EMIT\n66 EMIT\n", "A ERROR\nok> B ok\nok> ", 0 },
|
||||
/* every error stops the line at once, says what it was, and leaves the data stack (D-18) */
|
||||
{ "1 2 3 PAD PAD -1 CMOVE 65 EMIT\n66 EMIT .S\n", "Negative count\n ERROR\nok> B<3> 1 2 3 \n ok\nok> ", 0 },
|
||||
{ "7 PAD -5 TYPE 65 EMIT\n.S\n", "Negative count\n ERROR\nok> <1> 7 \n ok\nok> ", 0 },
|
||||
/* NUMBER takes a counted string: "-12" and "-1x", built at PAD */
|
||||
{ "3 PAD C! 45 PAD 1+ C! 49 PAD 2+ C! 50 PAD 3 + C! 0 PAD 4 + C! PAD NUMBER D.\n", "-12 ok\nok> ", 0 },
|
||||
{ "3 PAD C! 45 PAD 1+ C! 49 PAD 2+ C! 120 PAD 3 + C! 7 PAD NUMBER 65 EMIT\n.S\n", "Not a number\n ERROR\nok> <3> 7 0 0 \n ok\nok> ", 0 },
|
||||
{ "7 <# 300 HOLD 65 EMIT\n.S\n", "Not a character\n ERROR\nok> <1> 7 \n ok\nok> ", 0 },
|
||||
{ ": HH <# 70 0 DO 65 HOLD LOOP ; HH 66 EMIT\n67 EMIT\n", "Number too long\n ERROR\nok> C ok\nok> ", 0 },
|
||||
{ "7 : \n.S\n", "Name missing\n ERROR\nok> <1> 7 \n ok\nok> ", 0 },
|
||||
{ "VARIABLE\nCREATE\n5 CONSTANT\n", "Name missing\n ERROR\nok> Name missing\n ERROR\nok> Name missing\n ERROR\nok> ", 0 },
|
||||
{ "7 ' NOSUCH 65 EMIT\n.S\n", "UNKNOWN WORD: 'NOSUCH'\n ERROR\nok> <1> 7 \n ok\nok> ", 0 },
|
||||
{ ": X1 COMPILE NOSUCH ;\n: X2 [COMPILE] NOPE ;\n65 EMIT\n", "UNKNOWN WORD: 'NOSUCH'\n ERROR\nok> UNKNOWN WORD: 'NOPE'\n ERROR\nok> A ok\nok> ", 0 },
|
||||
{ ": X3 THEN ;\n: X4 BEGIN IF UNTIL ;\n: X5 DO REPEAT ;\n", "Control structure mismatch\n ERROR\nok> Control structure mismatch\n ERROR\nok> "
|
||||
"Control structure mismatch\n ERROR\nok> ", 0 },
|
||||
{ ": X6 IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF 65 EMIT ;\n66 EMIT\n", "Control structures too deep\n ERROR\nok> B ok\nok> ", 0 },
|
||||
{ ".\" no closing quote\n65 EMIT\n", "no closing quote ok\nok> A ok\nok> ", 0 },
|
||||
{ ".\" \"\n", " ok\nok> ", 0 },
|
||||
{ ": T ABORT\" \" ; 1 T\n", "\n ok\nok> ", 0 },
|
||||
@@ -303,8 +320,8 @@ int main(void)
|
||||
CHECK(is(say("65 EMIT\n"), "A ok\nok> ") && is(say("H1\n"), "UNKNOWN WORD: 'H1'\n ERROR\nok> "), "and the definition is gone");
|
||||
CHECK(is(say(": H2 1 IF IA\n"), " ok\nok> ") && n.mem[STATE] == 0 && n.mem[CFP] == CFS_W, "ABORT while compiling stops compiling");
|
||||
CHECK(is(say("66 EMIT\n"), "B ok\nok> ") && is(say("H2\n"), "UNKNOWN WORD: 'H2'\n ERROR\nok> "), "and the definition is gone");
|
||||
CHECK(is(say(": H3 1 IF [ PAD PAD -1 CMOVE ]\n"), " ERROR\nok> ") && n.mem[STATE] == 0 && n.mem[CFP] == CFS_W,
|
||||
"a word that sets NODE-ERROR while a definition is open ends it");
|
||||
CHECK(is(say(": H3 1 IF [ PAD PAD -1 CMOVE ]\n"), "Negative count\n ERROR\nok> ") && n.mem[STATE] == 0 && n.mem[CFP] == CFS_W,
|
||||
"a word that raises an error while a definition is open ends it");
|
||||
CHECK(is(say("67 EMIT\n"), "C ok\nok> ") && is(say("H3\n"), "UNKNOWN WORD: 'H3'\n ERROR\nok> "), "and the definition is gone");
|
||||
CHECK(is(say(": H4 68 EMIT ; H4\n"), "D ok\nok> "), "the next definition compiles and runs");
|
||||
|
||||
@@ -527,8 +544,9 @@ int main(void)
|
||||
CHECK(is(say("-1 U.\n"), "18446744073709551615 ok\nok> "), "the largest number (as v3)");
|
||||
CHECK(is(say("-9223372036854775808 . 9223372036854775807 .\n"), "-9223372036854775808 9223372036854775807 ok\nok> "), "the smallest and largest signed");
|
||||
CHECK(is(say("HEX -1 U. DECIMAL\n"), "FFFFFFFFFFFFFFFF ok\nok> "), "in hex");
|
||||
/* 64 binary digits are one more than the hold buffer's 63 (D-13): the last is dropped and the line is an error */
|
||||
CHECK(is(say("2 BASE ! -1 U. DECIMAL\n"), "111111111111111111111111111111111111111111111111111111111111111 ERROR\nok> "), "in binary, 63 of its 64 digits and ERROR");
|
||||
/* 64 binary digits are one more than the hold buffer's 63 (D-13): an error, and nothing is printed */
|
||||
CHECK(is(say("2 BASE ! -1 U. DECIMAL\n"), "Number too long\n ERROR\nok> "), "in binary, its 64 digits are one too many");
|
||||
CHECK(is(say("DECIMAL\n"), " ok\nok> "), "(back to decimal)");
|
||||
CHECK(is(say("9223372036854775807 2 BASE ! U. DECIMAL\n"), "111111111111111111111111111111111111111111111111111111111111111 ok\nok> "), "a 63-digit number prints whole");
|
||||
#endif
|
||||
/* .S works on the stack it is printing, so it needs some of it free */
|
||||
@@ -553,9 +571,19 @@ int main(void)
|
||||
CHECK(most + 8u >= V4_DATA_DEPTH, ".S works with all but eight cells of the stack in use");
|
||||
}
|
||||
|
||||
/* ---- the fault table: five words, each a jump to its handler ---- */
|
||||
/* ---- a full dictionary ---- */
|
||||
boot_bare();
|
||||
n.mem[DP] = (DICT_END_W - 2) * 4;
|
||||
CHECK(is(say(": FULL 1 2 3 4 5 6 7 8 9 ; 65 EMIT\n"), "Dictionary full\n ERROR\nok> ") && n.mem[STATE] == 0, "a definition that does not fit");
|
||||
CHECK(is(say("FULL\n"), "UNKNOWN WORD: 'FULL'\n ERROR\nok> "), "is not defined");
|
||||
n.mem[DP] = (DICT_END_W - 1) * 4;
|
||||
CHECK(is(say("1 , 65 EMIT\n"), "A ok\nok> ") && is(say("2 , 66 EMIT\n"), "Dictionary full\n ERROR\nok> "), "the last cell can be used; the one after it cannot");
|
||||
CHECK(is(say("5 ALLOT 67 EMIT\n"), "Dictionary full\n ERROR\nok> ") && is(say("-99999 ALLOT\n"), "Dictionary full\n ERROR\nok> "), "nor can ALLOT go past either end");
|
||||
CHECK(is(say("68 EMIT .S\n"), "D<0> \n ok\nok> "), "and the prompt goes on");
|
||||
|
||||
/* ---- the fault table: six words, each a jump to its handler ---- */
|
||||
{
|
||||
static const char *const handler[] = { "(FAULT)", "(D-OVER)", "(D-UNDER)", "(R-OVER)", "(R-UNDER)" };
|
||||
static const char *const handler[] = { "(FAULT)", "(D-OVER)", "(D-UNDER)", "(R-OVER)", "(R-UNDER)", "(RAISED)" };
|
||||
for (i = 0; i < V4_FAULT_KINDS; i++) {
|
||||
v4_iword w = (v4_iword)((v4_ucell)n.mem[w_fault + (v4_cell)i] & 0xFFFFFFFFu);
|
||||
CHECK(v4_iword_op(w, 0) == V4_OP_JUMP && v4_iword_branch(w_fault + (v4_cell)i + 1, w, 0) == v4_text_word(&tx, handler[i]),
|
||||
|
||||
Reference in New Issue
Block a user