feat(v4.0.0): a node is handed a line -- ENGINE.md step 1
A v4 node no longer reads its own command line or prints a prompt. Its host puts a line of text in the node's input buffer and starts it at (LINE); the node interprets it and stops at (IDLE), leaving in (LINE-STATUS) how it ended: completed, an error, or QUIT. The host says " ok" or " ERROR" and prompts, as the kernel's REPL does for a v3 VM. A line may be 1024 characters, a block, as v3's. Ruled 2026-10-05 (V3-PARITY.md 1b); design ENGINE.md 3.1. - quit.v4: (REPL), the node's prompt loop, is gone; (LINE) (IDLE) (DONE) - image.h/.c: v4_line_begin, v4_line_done, v4_line_status; the node is idle at switch-on - boot.c: v4_boot_line, the one loop the hosted binary, the kernel and the capsule loader hand a line with; the code that took " ok" and the prompt back out of the node's output is gone - hosted.c, sk_v4.c: the prompt and the line editing are the host's - test_host_quit.c: the tests are the node's host; two tests of the old 80-character prompt line now test a whole line, 1024 and 1025 characters Verified: make -C v4 test passes at both widths; hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST (550 of 550) with the same hashes as hosted, and three lines typed at each bare-metal prompt through the serial port are answered correctly (logs/20261005-180922, -181152, -181541). Still the lone node: kernel_main.c starts it before the fleet tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab7a9bf06f
commit
01c447f9ab
+3
-1
@@ -51,7 +51,7 @@
|
||||
#define CFS_CELLS 32
|
||||
#define WBUF_W (TOP - 170) /* WORD's buffer: 65 cells = 260 bytes */
|
||||
#define WBUF_CELLS 65
|
||||
#define TIB_W (TOP - 440) /* the text input buffer: 260 cells = 1040 bytes */
|
||||
#define TIB_W (TOP - 440) /* the text input buffer: 260 cells = 1040 bytes; a line is at most 1024 */
|
||||
#define PAD_W (TOP - 470) /* PAD: 21 cells = 84 bytes */
|
||||
#define SBUF_W (TOP - 540) /* 64 cells for the tests' own strings */
|
||||
#define HBUF_W (SVARS - 16) /* the hold buffer: 16 cells = 64 bytes */
|
||||
@@ -67,6 +67,7 @@
|
||||
#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 LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
|
||||
#define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */
|
||||
#define BUF0_W (BVARS - 2 * 256) /* the two block buffers, 256 cells each */
|
||||
@@ -142,6 +143,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
|
||||
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, "BLOCK-NUMBER", STORAGE_REG);
|
||||
v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1);
|
||||
v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2);
|
||||
|
||||
+98
-26
@@ -41,6 +41,7 @@
|
||||
* 3 ROLL is ROT. v3's count from zero.
|
||||
*/
|
||||
#include "v4/text.h"
|
||||
#include "v4/image.h"
|
||||
#include "v4/testcode.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -57,7 +58,8 @@ static v4_node n;
|
||||
static v4_exec_state es;
|
||||
static v4_heat h;
|
||||
static v4_text tx;
|
||||
static v4_cell w_quit, w_key, w_key_end, w_fault, capsule_latest;
|
||||
static v4_cell w_key, w_key_end, w_fault, capsule_latest;
|
||||
static v4_image img; /* where a line is handed to the node: v4/include/v4/image.h */
|
||||
static char out[V4_CONSOLE_CAP + 1];
|
||||
|
||||
/* The storage device's blocks: zeros at every switch-on. */
|
||||
@@ -71,19 +73,32 @@ static void put_block(unsigned num, const char *text)
|
||||
}
|
||||
static long last_steps;
|
||||
|
||||
/* Run until the node has taken all its input and has been inside KEY, waiting,
|
||||
* for 64 instruction words; or until `max` words. True if it is waiting. */
|
||||
static int run_until_waiting(long max)
|
||||
/* THE HOST. A node is handed a line and prints no prompt (ENGINE.md 3.1);
|
||||
* these tests are its host, and do what tools/hosted.c does: take what is
|
||||
* typed a line at a time, hand each line over, and say " ok" or " ERROR" and
|
||||
* the next prompt. What is typed after a line is there for a word in it
|
||||
* that reads the keyboard, and what such a word does not take is the next
|
||||
* line. */
|
||||
static char typed[8192]; /* typed and not yet taken */
|
||||
static unsigned typed_len;
|
||||
static int line_open; /* a line has been handed over and has not ended */
|
||||
|
||||
/* Run until the line ends, or until it has been inside KEY with nothing to
|
||||
* read for 64 instruction words; or until `max` words. 1: ended, 2: waiting
|
||||
* for a character, 0: neither. */
|
||||
static int run_line(long max)
|
||||
{
|
||||
long steps = 0;
|
||||
unsigned idle = 0;
|
||||
while (steps < max && idle < 64) {
|
||||
if (v4_line_done(&n, &img)) { last_steps = steps; return 1; }
|
||||
(void)v4_exec_step_word(&n, &es, &h);
|
||||
steps++;
|
||||
if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
|
||||
}
|
||||
last_steps = steps;
|
||||
return idle >= 64;
|
||||
if (v4_line_done(&n, &img)) return 1;
|
||||
return idle >= 64 ? 2 : 0;
|
||||
}
|
||||
|
||||
/* A node just switched on: an empty dictionary above the capsule's words,
|
||||
@@ -121,8 +136,10 @@ static void boot_with(unsigned depth)
|
||||
v4_heat_reset(&h);
|
||||
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
|
||||
v4_dstack_push(&n.ds, CANARY);
|
||||
n.p = w_quit;
|
||||
(void)run_until_waiting(100000);
|
||||
n.mem[LINE_STATUS] = V4_LINE_COMPLETED;
|
||||
n.p = img.idle; /* idle, waiting to be handed a line */
|
||||
typed_len = 0;
|
||||
line_open = 0;
|
||||
}
|
||||
static void boot(void) { boot_with(0); }
|
||||
/* The same with nothing at all on the data stack. */
|
||||
@@ -141,16 +158,52 @@ static int canary_under_expect(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Feed `input` and return what the node prints until it waits again. */
|
||||
/* Type `input` and return what the console then shows: what each line
|
||||
* printed, the host's " ok" or " ERROR" after it, and the next prompt. A
|
||||
* line that has not been ended shows nothing; nor does one that is waiting
|
||||
* for a character. */
|
||||
static const char *say(const char *input)
|
||||
{
|
||||
unsigned len = (unsigned)strlen(input);
|
||||
v4_node_console_attach(&n, CONSOLE_TX); /* empties the capture */
|
||||
if (v4_node_console_feed(&n, input, len) != len) return "(input queue full)";
|
||||
if (!run_until_waiting(40000000)) return "(still running)";
|
||||
if (n.console_dropped) return "(too much output)";
|
||||
memcpy(out, n.console, n.console_len);
|
||||
out[n.console_len] = 0;
|
||||
unsigned ilen = (unsigned)strlen(input), olen = 0;
|
||||
|
||||
if (typed_len + ilen > sizeof typed) return "(input queue full)";
|
||||
memcpy(typed + typed_len, input, ilen);
|
||||
typed_len += ilen;
|
||||
out[0] = 0;
|
||||
for (;;) {
|
||||
const char *tail;
|
||||
unsigned took;
|
||||
int ended;
|
||||
|
||||
if (!line_open) {
|
||||
char *nl = (char *)memchr(typed, '\n', typed_len);
|
||||
unsigned len;
|
||||
if (!nl) break;
|
||||
len = (unsigned)(nl - typed);
|
||||
if (!v4_line_begin(&n, &img, typed, len)) return "(line too long)";
|
||||
memmove(typed, nl + 1, typed_len - len - 1);
|
||||
typed_len -= len + 1;
|
||||
line_open = 1;
|
||||
}
|
||||
v4_node_console_attach(&n, CONSOLE_TX); /* empties the capture */
|
||||
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST); /* and the keyboard */
|
||||
if (v4_node_console_feed(&n, typed, typed_len) != typed_len) return "(input queue full)";
|
||||
ended = run_line(40000000);
|
||||
took = n.input_pos; /* what a word in the line read */
|
||||
memmove(typed, typed + took, typed_len - took);
|
||||
typed_len -= took;
|
||||
if (n.console_dropped || olen + n.console_len + 16 > sizeof out) return "(too much output)";
|
||||
memcpy(out + olen, n.console, n.console_len);
|
||||
olen += n.console_len;
|
||||
out[olen] = 0;
|
||||
if (ended == 0) return "(still running)";
|
||||
if (ended == 2) break;
|
||||
line_open = 0;
|
||||
tail = v4_line_status(&n, &img) == V4_LINE_COMPLETED ? " ok\nok> "
|
||||
: v4_line_status(&n, &img) == V4_LINE_ERROR ? " ERROR\nok> " : "ok> ";
|
||||
memcpy(out + olen, tail, strlen(tail) + 1);
|
||||
olen += (unsigned)strlen(tail);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
/* Feed a file of FORTH source to the prompt, a line at a time, as if typed.
|
||||
@@ -614,7 +667,12 @@ int main(void)
|
||||
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
|
||||
printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16);
|
||||
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
|
||||
w_quit = v4_text_word(&tx, "QUIT");
|
||||
img.line = v4_text_word(&tx, "(LINE)");
|
||||
img.idle = v4_text_word(&tx, "(IDLE)");
|
||||
img.line_status = LINE_STATUS;
|
||||
img.tib = TIB;
|
||||
img.tib_bytes = 1025u;
|
||||
img.span = SPAN;
|
||||
w_key = v4_text_word(&tx, "KEY");
|
||||
w_key_end = v4_text_word(&tx, "CR"); /* the word after KEY in core.v4 */
|
||||
w_fault = v4_text_word(&tx, "(FAULTS)");
|
||||
@@ -623,10 +681,11 @@ int main(void)
|
||||
|
||||
/* ---- switch-on ---- */
|
||||
boot();
|
||||
CHECK(n.console_len == 5 && memcmp(n.console, "\nok> ", 5) == 0, "QUIT starts a new line and prompts");
|
||||
CHECK(n.input_pos == n.input_len, "then waits for a line");
|
||||
CHECK(run_until_waiting(5000) && last_steps == 64 && n.console_len == 5, "and goes on waiting, printing nothing");
|
||||
CHECK(v4_line_done(&n, &img) && n.console_len == 0, "the node is idle and has printed nothing: the prompt is its host's");
|
||||
CHECK(run_line(5000) == 1 && last_steps == 0, "and goes on waiting to be handed a line");
|
||||
CHECK(canary_under_expect(), "with the data stack as it found it");
|
||||
(void)v4_exec_step_word(&n, &es, &h);
|
||||
CHECK(v4_line_done(&n, &img), "it stays idle however long it is run");
|
||||
|
||||
/* ---- the sessions ---- */
|
||||
for (i = 0; i < NSCRIPT; i++) {
|
||||
@@ -713,16 +772,15 @@ int main(void)
|
||||
snprintf(want, sizeof want, "<><%.*s\n ok\nok> ", (int)i, "abcdefghijkl");
|
||||
CHECK(is(say(line), want), "ABORT\" with %u characters, false then true", i);
|
||||
}
|
||||
/* the longest: 76 characters fit an 80-character line after ." and a space.
|
||||
* EXPECT stops at the 80th character, so that line's new-line is still to
|
||||
* come and is read as an empty line (FORTH-79's EXPECT; see input.v4). */
|
||||
/* 76 characters after ." and a space make a line of 80. That was the most
|
||||
* the node's own QUERY would take; a line the host hands over is whole. */
|
||||
{
|
||||
char line[128], want[128], text[80];
|
||||
for (i = 0; i < 76; i++) text[i] = (char)('#' + i); /* no " in it */
|
||||
text[76] = 0;
|
||||
boot();
|
||||
snprintf(line, sizeof line, ".\" %s\"\n", text);
|
||||
snprintf(want, sizeof want, "%s ok\nok> ok\nok> ", text);
|
||||
snprintf(want, sizeof want, "%s ok\nok> ", text);
|
||||
CHECK(is(say(line), want), "a string that fills an 80-character line");
|
||||
boot();
|
||||
snprintf(line, sizeof line, ".\" %s\"\n", text + 1);
|
||||
@@ -730,16 +788,30 @@ int main(void)
|
||||
CHECK(is(say(line), want), "and one that fills a 79-character line");
|
||||
}
|
||||
|
||||
/* ---- QUERY takes 80 characters; what is over is the next line ---- */
|
||||
/* ---- a line is handed over whole, up to 1024 characters: a block, as v3 ---- */
|
||||
boot();
|
||||
{
|
||||
char line[128];
|
||||
static char line[1100];
|
||||
memset(line, ' ', 100);
|
||||
memcpy(line, "65 EMIT", 7);
|
||||
memcpy(line + 70, "66 EMIT", 7);
|
||||
memcpy(line + 80, "67 EMIT 68 EMIT", 15);
|
||||
line[100] = '\n'; line[101] = 0;
|
||||
CHECK(is(say(line), "AB ok\nok> CD ok\nok> "), "a line of 100 characters is read as 80 and 20");
|
||||
CHECK(is(say(line), "ABCD ok\nok> "), "a line of 100 characters is one line");
|
||||
|
||||
boot();
|
||||
memset(line, ' ', 1024);
|
||||
memcpy(line, "65 EMIT", 7);
|
||||
memcpy(line + 1017, "66 EMIT", 7);
|
||||
line[1024] = '\n'; line[1025] = 0;
|
||||
CHECK(is(say(line), "AB ok\nok> "), "a line of 1024 characters is interpreted to its end");
|
||||
|
||||
boot();
|
||||
memset(line, ' ', 1025);
|
||||
memcpy(line, "65 EMIT", 7);
|
||||
line[1025] = '\n'; line[1026] = 0;
|
||||
CHECK(is(say(line), "(line too long)"), "one of 1025 is not taken: the node's input buffer holds a block");
|
||||
CHECK(v4_line_done(&n, &img) && n.console_len == 0, "and nothing of it was run");
|
||||
}
|
||||
|
||||
/* ---- ABORT and QUIT from deep in a programme, again and again ---- */
|
||||
|
||||
Reference in New Issue
Block a user