feat(v4.0.0): POST is the kernel's -- the boot feeds the cases, and nothing of the harness is on the node

The boot runs POST with the runner (v4/system/post.c) after the capsules:
it feeds the 538 cases to the node and judges them from outside.  A
case's printing no longer reaches the console.  What the cases define
stays in the dictionary, as in v3; the system is sealed after POST and
the boot prints PARITY:V4_SYSTEM word_count=N dict_hash=..., as v3 prints
its parity after POST.

Gone: capsules/v4/post79.4th and its blocks 7000 up; the harness words;
(CATCH) and (EMIT-HOOK), with what EMIT and the prompt loop did for them.

The generator runs v3 on the lines as the kernel sends them, without the
capsule's "T| ".  One expected result follows from that: >IN.initial
prints 6, not 9.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=411, dict_hash
0x6fb1d09418b189ee on all six: logs/20261007-105118, -105335, -105651.
T{ is unknown at the prompt; RS1 prints 42 42 before and after COLD.  A
scratch build with one expectation changed names the case and ends
PARITY:FAIL, POST: FAILED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 10:59:02 -04:00
co-authored by Claude Opus 5.5
parent 2bf958ce3a
commit 4bdb210a99
20 changed files with 659 additions and 1981 deletions
+2 -2
View File
@@ -139,7 +139,7 @@ CAPSULE_DIR_C := $(BINDIR)/capsule_generated.c
# the kernel's capsule code: find, check the hash, verify the signature,
# split a payload into blocks
SYSTEM_SRCS := $(HERE)/system/boot.c \
SYSTEM_SRCS := $(HERE)/system/boot.c $(HERE)/system/post.c $(HERE)/system/post_cases.c \
$(addprefix $(ROOT)/kernel/src/capsule/,capsule_find.c capsule_validate.c capsule_sig.c capsule_blocks.c) \
$(ROOT)/kernel/src/hash/xxhash64.c $(ROOT)/kernel/src/crypto/x509_ed25519.c $(CRYPTO_SRCS)
# The kernel's sources are held to the kernel's warnings, not this model's.
@@ -169,7 +169,7 @@ $(foreach i,$(HOSTED_ISAS),$(eval $(call HOSTED_RULE,$(i))))
# `make post` boots the amd64 system alone, which is the quick check after a
# change to the vocabulary: nucleus, forth79.4th, POST, prompt. `make
# post79` writes the POST capsule, ../capsules/v4/post79.4th, again from
# post79` writes POST's cases, system/post_cases.c, again from
# v3's cases and tools/post79_rules.py (tools/mkpost.py); it needs the
# hosted v3 binary.
.PHONY: post post79
+14 -10
View File
@@ -159,21 +159,25 @@ a time with POST after each, is the next step (`NUCLEUS.md` section 8).
## POST
`capsules/v4/post79.4th`: 538 cases in blocks 7000 up, covering 126 of the
130 words of the FORTH-79 Required Word Set. 443 are v3's cases with what
the hosted v3 binary did as the expected result. The other 107, and the 27
v3 cases left out, are listed with reasons in `docs/v4.0.0/POST79.md`.
POST is the kernel's, as in v3 (`MESH.md` step 6b, `NUCLEUS.md` 6.3). The
kernel holds the cases, `v4/system/post_cases.c`: 538 of them, covering 126
of the 130 words of the FORTH-79 Required Word Set. 431 are v3's cases with
what the hosted v3 binary did as the expected result; every other case,
and the v3 cases left out, are listed with reasons in
`docs/v4.0.0/POST79.md`.
- `make -C v4 post` boots the amd64 system: the quick check after a change.
- `make -C v4 hosted-check` boots all three hosted ISAs and compares them.
- `make -C v4 post79` writes the capsule again (`tools/mkpost.py`,
- `make -C v4 post79` writes the cases again (`tools/mkpost.py`,
`tools/post79_rules.py`); it needs the hosted v3 binary.
POST is FORTH: a harness of FORTH-79 words and two nucleus hooks, `(CATCH)`
and `(EMIT-HOOK)`. It forgets itself when it has finished. The boot passes
only on seeing POST's tally line with `fail=0` (`v4/system/boot.c`). A colon
definition broken on purpose (`U/MOD` without its last `SWAP`) fails five
cases and the boot stops with `POST: FAILED`.
The runner, `v4/system/post.c`, feeds each case to the node a line at a
time, keeps what the node prints, reads its data stack from outside, and
judges: an error exactly if one is expected, and otherwise the stack and
every character printed. Nothing of POST is in the node's dictionary. What
the cases define stays there, as in v3, and the system is sealed after
POST. The boot then prints `PARITY:V4_SYSTEM word_count=N dict_hash=0x...`,
and passes only if no case failed (`v4/system/boot.c`).
Not tested: `KEY`, `EXPECT`, `QUERY` (the keyboard) and `QUIT` (it returns
without ` ok`).
+4 -10
View File
@@ -251,18 +251,12 @@ header CMOVE
(OUT) (OUT^) a! ! ;
NONE: drop ;
\ EMIT puts the character with what has been printed, unless (EMIT-HOOK)
\ holds the xt of a word: then that word is given it instead ( c -- ), and
\ returns to EMIT's caller. Everything the node prints goes through EMIT,
\ so a hook sees all of it. The hook is taken off when the text being
\ interpreted ends (quit.v4), so that nothing can leave a node silent.
\ POST uses it to compare what a case prints (docs/v4.0.0/NUCLEUS.md 6.3).
\ A is kept: the words that print have always been free to use it across
\ an EMIT. As v3, only the low 8 bits of the character are printed.
\ EMIT puts the character with what has been printed. Everything the node
\ prints goes through EMIT. A is kept: the words that print have always
\ been free to use it across an EMIT. As v3, only the low 8 bits of the
\ character are printed.
header EMIT
: EMIT ( c -- )
(EMIT-HOOK) b! @b if NONE push ;
NONE: drop
255 and a push push \ A, and then the character, to the return stack
(OUT^) b! @b a! pop !+ a !b \ the character goes where (OUT^) points, which moves on
a (OUT)+256 xor if FULL drop pop a! ;
+3 -16
View File
@@ -48,18 +48,10 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
\ structures. A definition that was under way stays hidden.
: (RESET) 0 STATE a! ! (CG-RESET) CFBASE (CFP) a! ! ;
\ CATCHING A LINE'S ERROR. While the variable (CATCH) is not zero, a line
\ that ends in an error -- a fault, or a code in NODE-ERROR -- does not say
\ ERROR: (CATCH) is set to -1 and the line is reported as completed. Whatever message the
\ error printed has gone wherever EMIT was sending it. POST uses this to
\ go on to its next case, and to check the cases that must fail
\ (docs/v4.0.0/NUCLEUS.md 6.3). (EMIT-HOOK), core.v4, is set to 0 here at
\ the end of every line, caught or not.
\
\ ( s -- ) the text is done with. What it printed is sent, then a message
\ saying how it ended -- 0 QUIT, 1 completed, 2 an error -- and the node waits
\ for the next. (DONE) is entered with the same number and decides whether
\ an error was one to catch; it is always jumped to, never called, by
\ for the next. (DONE) is entered with the same number and, for an error,
\ stops compiling first; it is always jumped to, never called, by
\ something that has emptied the return stack (or by a fault, which empties
\ it). (LINE-STATUS) keeps how the last text ended.
: (FINISH) ( s -- )
@@ -70,11 +62,8 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
jump (IDLE)
: (DONE)
(EMIT-HOOK) b! 0 !b \ what is printed is printed again
if GO -1 + if OK
drop (RESET)
(CATCH) b! @b if LOUD drop -1 !b 1 jump (DONE) \ caught: the text completed
LOUD: drop 2 jump (FINISH)
drop (RESET) 2 jump (FINISH)
OK: drop 1 jump (FINISH)
GO: drop 0 jump (FINISH)
@@ -247,13 +236,11 @@ header (SEAL)
\ further, the message for the code is printed (-1: the word has printed
\ its own) and the line ends with ERROR. The codes are listed in core.v4.
header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ;
header (CATCH) inline : (CATCH)' (CATCH) ;
header (ME) inline : (ME)' (ME) ;
header (CONSOLE) inline : (CONSOLE)' (CONSOLE) ;
header (LOST) inline : (LOST)' (LOST) ;
header (WORD-DEFINED) inline : (WORD-DEFINED)' (WORD-DEFINED) ;
header (WORD-FORGOTTEN) inline : (WORD-FORGOTTEN)' (WORD-FORGOTTEN) ;
header (EMIT-HOOK) inline : (EMIT-HOOK)' (EMIT-HOOK) ;
header QUIT
: QUIT R-CLEAR (RESET) CR 0 jump (DONE)
+51 -50
View File
@@ -4,6 +4,7 @@
* It is not part of the engine (v4/src): it needs the capsule directory,
* which only a whole system has.
*/
#include "v4/post.h"
#include "v4/blocks.h"
#include "v4/boot.h"
#include "v4/message.h"
@@ -13,18 +14,18 @@
#include "starkernel/capsule_blocks.h"
/* The capsules, in the order they are loaded. */
/* POST is part of the boot: v4:post79.4th is loaded after the vocabulary it
* tests and must end with a clean tally. -DV4_POST_AT_BOOT=0 leaves it
* out, for work on a vocabulary that does not pass yet. */
static const char *const boot_capsules[] = {
"v4:forth79.4th",
};
/* POST is part of the boot, and is the kernel's (v4/include/v4/post.h;
* docs/v4.0.0/NUCLEUS.md 6.3): when the capsules are in, the kernel feeds
* its cases to the node and judges them, and the boot passes only if none
* failed. -DV4_POST_AT_BOOT=0 leaves it out, for work on a vocabulary
* that does not pass yet. */
#ifndef V4_POST_AT_BOOT
#define V4_POST_AT_BOOT 1
#endif
static const char *const boot_capsules[] = {
"v4:forth79.4th",
#if V4_POST_AT_BOOT
"v4:post79.4th",
#endif
};
#define LINE_MAX 1025u /* a line is at most 1024 characters, a block */
#define STEP_LIMIT 4000000000ULL /* instruction words one line may take */
@@ -77,37 +78,6 @@ static uint32_t word_count(const v4_node *n, const v4_image *im)
/* ---- handing the node a line ---------------------------------------------- */
/* POST's verdict. The POST capsule ends by printing one line,
* PARITY:V4_POST tests=N pass=N fail=N
* and the boot passes only if it has seen that line with fail=0. That no
* line was refused is not enough: a POST that was broken half way would
* refuse nothing. */
static char post_line[96];
static unsigned post_len;
static int post_seen, post_clean;
static int text_at(const char *line, unsigned len, unsigned at, const char *want)
{
unsigned i;
for (i = 0; want[i]; i++)
if (at + i >= len || line[at + i] != want[i]) return 0;
return 1;
}
static void post_watch(char c)
{
static const char clean[] = " fail=0";
if (c != '\n') {
if (post_len < sizeof post_line) post_line[post_len++] = c;
return;
}
if (text_at(post_line, post_len, 0, "PARITY:V4_POST ")) {
post_seen = 1;
post_clean = post_len >= sizeof clean - 1 && text_at(post_line, post_len, post_len - (unsigned)(sizeof clean - 1), clean);
}
post_len = 0;
}
/* The boot is the node's neighbour on two of its ports. On port 0 it is
* the kernel: the node writes the number of a request there (ENGINE.md
* 3.3) -- for a block (blocks.h), or for one of the host's words. On port
@@ -120,7 +90,10 @@ static void post_watch(char c)
/* What the kernel keeps of the node's block window (blocks.h). */
static v4_blocks boot_blocks;
int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
/* Hand the node a line and run it to its end. What it prints goes to the
* console; or, if `keep` is given, into `keep`, at most `cap` characters,
* and none of it is shown: *printed is then how many it printed in all. */
static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep, unsigned cap, unsigned *printed)
{
static v4_message out, in; /* one line at a time: the boot is not re-entered */
v4_node *n = b->n;
@@ -155,8 +128,8 @@ int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
if (v4_message_type(&in) == V4_MSG_OUTPUT) {
for (i = 0; i < chars; i++) {
char c = v4_message_char(&in, i);
post_watch(c);
b->out(&c, 1);
if (!keep) b->out(&c, 1);
else { if (*printed < cap) keep[*printed] = c; (*printed)++; }
}
} else if (v4_message_type(&in) == V4_MSG_DONE) {
return (int)in.word[V4_MSG_HEADER];
@@ -185,6 +158,27 @@ int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
}
}
int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
{
return run_line(b, text, len, 0, 0u, 0);
}
#if V4_POST_AT_BOOT
/* POST's host (post.h): a line is handed to the node as any line is, and
* what the node prints is kept for the runner and not shown. */
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
{
int how;
*len = 0;
how = run_line((const v4_boot *)self, text, text_len, out, cap, len);
return (how == V4_TEXT_QUIT || how == V4_TEXT_COMPLETED || how == V4_TEXT_ERROR) ? how : -1;
}
static void post_say(void *self, const char *text, unsigned len)
{
((const v4_boot *)self)->out(text, len);
}
#endif
/* ---- one capsule --------------------------------------------------------- */
static void say_where(const v4_boot *b, const char *name, uint32_t block, uint32_t line)
@@ -345,7 +339,6 @@ int v4_boot_run(const v4_boot *b)
{
unsigned i;
post_len = 0; post_seen = 0; post_clean = 0;
/* a node is born with nothing in it, and is sent the nucleus */
if (!v4_image_born(b->n, b->es, b->h, b->im)) {
say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
@@ -375,16 +368,24 @@ int v4_boot_run(const v4_boot *b)
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
#if V4_POST_AT_BOOT
if (!post_seen || !post_clean) {
say(b, post_seen ? "V4: POST reported failures\n" : "V4: POST did not report\n");
say(b, "PARITY:FAIL\nPOST: FAILED\n");
return 0;
{
v4_post_host host;
v4_post_tally tally;
host.self = (void *)b; host.n = b->n; host.line = post_line; host.say = post_say;
if (!v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally)) {
say(b, "V4: POST reported failures\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
}
#endif
/* The kernel's words and the capsules are the system: COLD comes back
* to here, and FORGET will not go below it. */
/* The kernel's words, the capsules and what POST's cases defined are
* the system, as in v3: COLD comes back to here, and FORGET will not go
* below it. */
v4_image_seal(b->n, b->im);
say(b, "PARITY:V4_SYSTEM word_count="); say_dec(b, word_count(b->n, b->im));
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
say(b, "\n");
#if V4_POST_AT_BOOT
say(b, "PARITY:OK\nPOST: PASSED\n");
+1 -1
View File
@@ -877,7 +877,7 @@ const v4_post_case v4_post_cases[] = {
{ "-TRAILING.empty", l318, 1u, V4_POST_STACK, 0, 0u, "\012", 1u },
{ "CMOVE.basic", l319, 2u, V4_POST_STACK, 0, 0u, "65 66 \012", 7u },
{ "CMOVE.empty", l320, 1u, V4_POST_STACK, 0, 0u, "", 0u },
{ ">IN.initial", l321, 1u, V4_POST_STACK, 0, 0u, "9 \012", 3u },
{ ">IN.initial", l321, 1u, V4_POST_STACK, 0, 0u, "6 \012", 3u },
{ ">IN.advances", l322, 1u, V4_POST_STACK, 0, 0u, "0 \012", 3u },
{ "..positive", l323, 1u, V4_POST_STACK, 0, 0u, "42 \012", 4u },
{ "..negative", l324, 1u, V4_POST_STACK, 0, 0u, "-42 \012", 5u },
-4
View File
@@ -65,8 +65,6 @@
#define SRC (BVARS + 8)
#define SRC_HOOK (BVARS + 9)
#define ACL_HOOK (BUF0_W - 6) /* the xt of the access control recheck word, or 0 */
#define EMIT_HOOK (BUF0_W - 7) /* the xt of a word EMIT gives its character to, or 0: the console */
#define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */
#define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */
#define PORT (BUF0_W - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */
#define CONSOLE (BUF0_W - 16) /* the node what this one prints is sent to; 0: whoever sent the text being served */
@@ -175,8 +173,6 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
v4_text_constant(tx, "BLK", BLK);
v4_text_constant(tx, "(SRC)", SRC);
v4_text_constant(tx, "(SRC-HOOK)", SRC_HOOK);
v4_text_constant(tx, "(EMIT-HOOK)", EMIT_HOOK);
v4_text_constant(tx, "(CATCH)", CATCH);
v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS);
v4_text_constant(tx, "(PORT)", PORT);
v4_text_constant(tx, "(ME)", ME);
+6 -1
View File
@@ -1277,6 +1277,11 @@ int main(void)
post_lost = 0;
}
/* ---- nothing of POST is on the node (NUCLEUS.md 6.3) ---- */
boot();
CHECK(is(say("(CATCH)\n"), "UNKNOWN WORD: '(CATCH)'\n ERROR\nok> ") && is(say("(EMIT-HOOK)\n"), "UNKNOWN WORD: '(EMIT-HOOK)'\n ERROR\nok> ")
&& is(say("T{\n"), "UNKNOWN WORD: 'T{'\n ERROR\nok> "), "the nucleus has neither of the variables the POST capsule needed, nor its words");
/* ---- storage that refuses a write (MESH.md 8.2) ---- */
boot();
CHECK(is(say(REFUSING_FIRST " BLOCK DROP UPDATE SAVE-BUFFERS 65 EMIT\n"), "Storage refused\n ERROR\nok> "), "a write storage will not take says so, and the prompt returns");
@@ -1450,7 +1455,7 @@ int main(void)
boot_bare();
CHECK(load_source("tools.fth"), "tools.fth compiles, every line of it");
CHECK(is(say(": SQ DUP * ; SEE SQ\n"), ": SQ\n dup call * \n ; \n ok\nok> "), "a definition: an in-line word, a call by name, the return");
CHECK(is(say("SEE DUP\n"), ": DUP\n dup ; \n ok\nok> ") && is(say("SEE EMIT\n"), out) && strncmp(out, ": EMIT\n @p ", 12) == 0 && strstr(out, " b! @b \n if ") && strstr(out, " pop a! ; \n"),
CHECK(is(say("SEE DUP\n"), ": DUP\n dup ; \n ok\nok> ") && is(say("SEE EMIT\n"), out) && strncmp(out, ": EMIT\n @p 255 and ", 20) == 0 && strstr(out, " pop a! ; \n"),
"the capsule's own words; a literal's value follows its @p");
CHECK(is(say("VARIABLE V 7 V ! SEE V\n"), ": V\n data: 7 \n ok\nok> ") && is(say("5 CONSTANT K SEE K\n"), ": K\n data: 5 \n ok\nok> "), "a variable and a constant show what they hold");
CHECK(is(say(": IM 1 ; IMMEDIATE SEE IM\n"), ": IM\n @p 1 ; \nIMMEDIATE\n ok\nok> "), "an immediate word says so");
+38 -4
View File
@@ -31,6 +31,7 @@
#include "v4/manage.h"
#include "v4/blocks.h"
#include "v4/message.h"
#include "v4/post.h"
#include "v4/text.h"
#include "block_subsystem.h"
#include <stdio.h>
@@ -154,7 +155,6 @@ static int host_open(void *self, const char *name, unsigned len)
(void)self;
if (len == 17 && memcmp(name, "v4:nucleus-64.f18", 17) == 0) { hera_reads.is_f18 = 1; hera_reads.cells_at = 0; return 1; }
if (len == 14 && memcmp(name, "v4:forth79.4th", 14) == 0) return read_file(&hera_reads, "forth79.4th");
if (len == 13 && memcmp(name, "v4:post79.4th", 13) == 0) return read_file(&hera_reads, "post79.4th");
return 0;
}
static int host_cell(void *self, v4_cell *w)
@@ -277,6 +277,30 @@ static const char *tell(v4_cell node, const char *text)
}
static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
/* POST's host, for the node numbered 10 (v4/include/v4/post.h): a line is
* told to it from the console, and what it prints is handed to the runner. */
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
{
static char line[V4_MSG_MAX_CHARS + 1];
const char *r;
(void)self;
*len = 0;
if (text_len > V4_MSG_MAX_CHARS) return -1;
memcpy(line, text, text_len); line[text_len] = 0;
r = tell(10, line);
if (ended != 1 || ended_from != 10) return -1;
*len = printed_len;
memcpy(out, r, printed_len < cap ? printed_len : cap);
return (int)ended_how;
}
static char post_said[2048];
static unsigned post_said_len;
static void post_say(void *self, const char *text, unsigned len)
{
(void)self;
if (post_said_len + len < sizeof post_said) { memcpy(post_said + post_said_len, text, len); post_said_len += len; post_said[post_said_len] = 0; }
}
/* Every line of a capsule of FORTH blocks, told to a node from the console:
* how many lines did not complete. */
static unsigned load(v4_cell node, const char *file)
@@ -376,7 +400,16 @@ int main(void)
}
CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "it asks where it is, and is answered");
CHECK(load(10, "forth79.4th") == 0, "it takes in FORTH-79");
CHECK(load(10, "post79.4th") == 0 && post_passed[10] == 1 && post_other == 0, "and passes POST");
{
v4_post_host host;
v4_post_tally tally;
host.self = 0; host.n = node_numbered(10); host.line = post_line; host.say = post_say;
CHECK(v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally) && tally.tests == 538 && tally.pass == 538 && tally.fail == 0
&& strstr(post_said, "PARITY:V4_POST tests=538 pass=538 fail=0\n") != NULL && strstr(post_said, "POST FAIL") == NULL,
"and passes POST, which the kernel runs on her: %u of %u, %u failed; \"%s\"", tally.pass, tally.tests, tally.fail, post_said);
}
CHECK(strstr(tell(10, "T{"), "UNKNOWN WORD") != NULL, "nothing of POST's harness is on her: \"%s\"", printed);
CHECK(strcmp(tell(10, "RS1"), "42 42 \n") == 0, "what POST's cases defined is: \"%s\"", printed);
CHECK(load(10, "hera.4th") == 0, "and its own capsule, which makes it Hera");
CHECK(told(10, "(SEAL)"), "that is the system it comes back to");
@@ -387,9 +420,10 @@ int main(void)
CHECK(ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "Hera births four nodes and joins them: \"%s\"", r);
}
CHECK(born_count == 4 && refused == 0, "four nodes were asked for: %u", born_count);
CHECK(post_passed[10] == 1 && post_passed[11] == 0 && post_passed[12] == 0 && post_passed[13] == 0 && post_passed[14] == 0 && post_other == 0,
"a node she births is not POSTed: POST is the kernel's, once, on Hera: %u; %u %u %u %u, %u failed",
CHECK(post_passed[10] == 0 && post_passed[11] == 0 && post_passed[12] == 0 && post_passed[13] == 0 && post_passed[14] == 0 && post_other == 0,
"a node she births is not POSTed, and no node prints a POST tally: POST is the kernel's, once, on Hera: %u %u %u %u %u, %u failed",
post_passed[10], post_passed[11], post_passed[12], post_passed[13], post_passed[14], post_other);
CHECK(strstr(tell(10, "COLD"), "Cold Start") != NULL && strcmp(tell(10, "RS1"), "42 42 \n") == 0, "COLD on Hera comes back to a system that has what POST's cases defined: \"%s\"", printed);
CHECK(parity_count == 4 && parity_number[0] == 11 && parity_number[1] == 12 && parity_number[2] == 13 && parity_number[3] == 14, "the parity of each was recorded: %u", parity_count);
CHECK(parity_hash[0] == parity_hash[1] && parity_hash[1] == parity_hash[2] && parity_hash[2] == parity_hash[3],
"and their dictionary hashes are the same: %016llx %016llx %016llx %016llx",
+6 -2
View File
@@ -240,10 +240,14 @@ ANSI = re.compile(r"\x1b\[[0-9;]*m")
def run_v3(v3, cases):
"""Runs the cases in one session. Returns a list, one entry per case:
None if v3 did not get through it, else (error, stack, output)."""
script = [": T| ; IMMEDIATE", ": TCLR BEGIN DEPTH WHILE DROP REPEAT ;"]
# The lines are run as the kernel sends them to the node: without the
# "T| " each began with when they were lines of a capsule. A case that
# looks at where it is in its line (>IN) depends on it.
script = [": TCLR BEGIN DEPTH WHILE DROP REPEAT ;"]
for n, case in enumerate(cases):
script.append('TCLR DECIMAL FORTH DEFINITIONS ." [[B%d]]"' % n)
script.extend(case["lines"])
assert all(line.startswith("T| ") for line in case["lines"]), case
script.extend(line[3:] for line in case["lines"])
script.append('." [[E%d]]" BASE @ DECIMAL .S BASE ! ." [[S%d]]"' % (n, n))
script.append("BYE")
done = subprocess.run([v3, "--log-none"], input=("\n".join(script) + "\n").encode("latin-1"),
+3 -1
View File
@@ -112,7 +112,9 @@ STANDARD = {
"TYPE.basic_string": dict(text="72 PAD C! 105 PAD 1+ C! PAD 2 TYPE CR", out="Hi\n", why=D1 + "; and " + RS),
"TYPE.numbers": dict(text="49 PAD C! 50 PAD 1+ C! PAD 2 TYPE CR", out="12\n", why=D1 + "; and " + RS),
">IN.initial": dict(out="9 \n", why="FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address"),
# 6: the line is ">IN @ . CR" and >IN is read when "@" has been taken with the blank after it. It was 9
# while the cases were a capsule, whose lines each began "T| " (until MESH.md step 6b).
">IN.initial": dict(out="6 \n", why="FORTH-79: >IN is the offset into the input, 6 here; v3 prints an address"),
"WORD.empty_input": None, "WORD.space_delim": None, "WORD.newline_delim": None, "WORD.tab_delim": None,
"WORD.comma_delim": None, "WORD.skip_leading": None, "WORD.single_char": None, "WORD.long_word": None,