ENGINE step 4 Phase 2 (plan docs/v4.0.0/plans/2026-11-13-engine-step4-phase2-word-accounts.md). Rulings, Captain Bob 2026-11-13: "ID field in the header"; "Per heartbeat tick"; the ACL "All of it in Phase 4". - The word ID, at xt-4 before the flags (ENGINE.md 3b). The system's words are numbered as they are assembled, by a new directive, #id, (and #ids, the count), in the C assembler (text.c) and in the machine ROM's (capsules/v4/rom), which counts in the system's own (NEXT-ID). (HEADER) numbers later words; FORGET gives back the IDs it frees, always the newest; COLD resets to (BOOT-ID); (SEAL) records it. All 313 system headers carry the cell. - The kernel's records (v4/mesh.h): (TELL-DEFINED) and (TELL-FORGOTTEN), requests -19/-20 on port 0, which the kernel puts in (WORD-DEFINED)/(WORD-FORGOTTEN) when it seals a node, and reads the system's own words once. A bit per address marks a word's xt (calls into the ROM or headerless code are not words); at call, the bit test and a count. v4_mesh_charge hands the owner the calls since. - The Stadium: stadium_words_charge -- N calls as N of stadium_word_dispatch's dispatches, shared by calls when the reservoir is short; sk_v4.c charges every node's words at the tick beside its opcodes, and stadium_word_forget before an ID is used again. - Tests first: test_text.c, test_host_dict.c, test_host_quit.c (numbering, FORGET, COLD), test_mesh_boot.c (44 checks), kernel/tests/test_stadium_words.c; qemu_session.py: Hera's words admitted after POST. - amd64: Stadium words: promotions=141 after POST, CONSERVED; POST 539/539; word_count=377; dict_hash 0xa854ba15b437d288 (the new cell); the boot 142 s against Phase 1's 153 s. - v4 test, test-32: all pass; v4-checks: 80 programs under the sanitizers, 0 failures; the hosted builds alike. docs/reference/v4-dictionary.tsv: the two new words; FABRIC-3 XLVIII.24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
396 lines
18 KiB
C
396 lines
18 KiB
C
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
|
|
* Copyright (c) 2023-2026 Robert A. James
|
|
*/
|
|
/* sk_v4.c -- StarForth v4 on bare metal: one host node at a prompt.
|
|
*
|
|
* The node is the golden model of the F18-derived engine (v4/src). It comes
|
|
* up in the kernel's fabric (v4/include/v4/mesh.h; #189 -- until 2026-11-13 as the hosted
|
|
* system still does, by v4_boot_run, v4/include/v4/boot.h):
|
|
* the machine's ROM assembles the system from its source, then the capsules
|
|
* from the directory baked into this kernel are loaded, each checked and its
|
|
* parity line printed, then the prompt.
|
|
* docs/v4.0.0/NUCLEUS.md. This file is all the kernel adds:
|
|
*
|
|
* - what the node prints goes to the kernel's console;
|
|
* - the prompt, and the line: it is read and edited here and handed to the
|
|
* node whole (docs/v4.0.0/ENGINE.md 3.1), and how it ended is said here;
|
|
* - blocks are kept in memory, and are gone at power-off;
|
|
* - the identity gate: with no identity drive the system is headless,
|
|
* and a console exists only while Zuse's drive is attached and checked
|
|
* (sk_v4_identity.c; ENGINE.md 2, ruled 2026-10-20).
|
|
*
|
|
* The kernel brings it up in its own boot, after the Stadium (#189, ENGINE.md step 4,
|
|
* Phase 1): sk_v4_boot(), the kernel's checks after POST, then sk_v4_prompt().
|
|
*
|
|
* Nothing of the v3 VM is started. docs/v4.0.0/DECOMPOSITION.md.
|
|
*/
|
|
#include "starkernel/v4/sk_v4_mint.h"
|
|
#include "starkernel/v4/sk_v4.h"
|
|
#include "starkernel/v4/sk_v4_identity.h"
|
|
#include "starkernel/v4/sk_v4_stop.h"
|
|
#include "starkernel/kbd_input.h"
|
|
#include "starkernel/console.h"
|
|
#include "starkernel/arch.h"
|
|
#include "starkernel/timer.h"
|
|
#include "v4/boot.h"
|
|
#include "v4/fabric.h"
|
|
#include "v4/manage.h"
|
|
#include "v4/mesh.h"
|
|
#include "starkernel/kmalloc.h"
|
|
#include "block_subsystem.h"
|
|
#include "starkernel/capsule.h"
|
|
#include "starkernel/capsule_generated.h"
|
|
#include "starkernel/capsule_sig.h"
|
|
#include "starkernel/capsule_blocks.h"
|
|
#ifdef STARFORTH_ENABLE_VM
|
|
#include "starkernel/vm/stadium.h"
|
|
#include "starkernel/vm/stadium_ops.h"
|
|
#include "starkernel/vm/stadium_words.h"
|
|
#endif
|
|
|
|
#define SK_V4_LINE 1024u /* what the line editor here takes: a line, as the boot takes one (v4/system/boot.c) */
|
|
|
|
/* v3's prompt (repl.c's SK_PROMPT_TEXT): "ok> " in bright cyan, after the
|
|
* console's own "[zuse@Hera] " line prefix. */
|
|
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
|
|
|
|
/* THE KERNEL'S FABRIC (#189, ENGINE step 4 Phase 1, Task 5c; Captain Bob, 2026-11-13: "Build it to
|
|
* proper spec"). The kernel boots the fabric by v4/mesh.h: the nucleus assembled by the machine's
|
|
* ROM, Hera born empty at place 0 and given it, her system and POST -- and every node steps
|
|
* together; there is no scheduler. A node Hera births has its room from the kernel's heap, a Stadium
|
|
* quota under its node key (stadium_node_key) granted from hers, and its opcodes charged as hers
|
|
* are; killed, its room and its quota are given back. */
|
|
#define SK_V4_PLACES 16u
|
|
static v4_mesh sk_v4_mesh;
|
|
#define SK_V4_IDLE_STEPS 4096u /* fabric steps between looks at the keyboard, while a node runs */
|
|
static v4_place sk_v4_places[SK_V4_PLACES];
|
|
static v4_wire_queue sk_v4_queues[4u * SK_V4_PLACES];
|
|
static v4_manage_entry sk_v4_reg[SK_V4_PLACES];
|
|
static v4_blocks sk_v4_blocks[SK_V4_PLACES];
|
|
static v4_device sk_v4_kernel[SK_V4_PLACES];
|
|
static v4_fabric_node sk_v4_nucleus, sk_v4_hera;
|
|
static v4_image sk_v4_image;
|
|
static v4_cell sk_v4_keyed[SK_V4_PLACES]; /* by place: the number its Stadium quota is keyed by; 0: none */
|
|
|
|
/* The words' accounts (#189, Phase 2; v4/mesh.h): each node's records, kept by word ID, and the bit for each
|
|
* address that is a word's xt -- from the kernel's heap at the boot -- and a tick's charge gathered. */
|
|
static uint32_t sk_v4_charge_ids[V4_MESH_WORD_IDS];
|
|
static uint64_t sk_v4_charge_calls[V4_MESH_WORD_IDS];
|
|
static uint32_t sk_v4_charge_n;
|
|
static void sk_v4_charge_one(void *ctx, v4_cell id, v4_uheat_t calls)
|
|
{
|
|
(void)ctx;
|
|
if (sk_v4_charge_n < V4_MESH_WORD_IDS) {
|
|
sk_v4_charge_ids[sk_v4_charge_n] = (uint32_t)id;
|
|
sk_v4_charge_calls[sk_v4_charge_n] = (uint64_t)calls;
|
|
sk_v4_charge_n++;
|
|
}
|
|
}
|
|
/* a word has gone: its ID will be used again, so the Stadium forgets its patron first (v3's coherence) */
|
|
static void sk_v4_forgot(void *self, unsigned place, v4_cell id)
|
|
{
|
|
(void)self;
|
|
#ifdef STARFORTH_ENABLE_VM
|
|
if (place < SK_V4_PLACES && sk_v4_keyed[place] != 0)
|
|
stadium_word_forget(stadium_node_key((uint32_t)sk_v4_keyed[place]), (uint32_t)id);
|
|
#else
|
|
(void)place; (void)id;
|
|
#endif
|
|
}
|
|
|
|
static v4_fabric_node *sk_v4_room(void *self)
|
|
{
|
|
v4_fabric_node *x = (v4_fabric_node *)kmalloc(sizeof *x);
|
|
unsigned char *b = (unsigned char *)x;
|
|
size_t i;
|
|
(void)self;
|
|
if (x) for (i = 0; i < sizeof *x; i++) b[i] = 0;
|
|
return x;
|
|
}
|
|
static void sk_v4_unroom(void *self, v4_fabric_node *x) { (void)self; kfree(x); }
|
|
static void sk_v4_gone(void *self, unsigned place)
|
|
{
|
|
(void)self;
|
|
#ifdef STARFORTH_ENABLE_VM
|
|
if (place < SK_V4_PLACES && sk_v4_keyed[place] != 0) {
|
|
VMUuid key = stadium_node_key((uint32_t)sk_v4_keyed[place]);
|
|
stadium_ops_forget(key);
|
|
(void)stadium_return_quota(key, stadium_node_key(SK_V4_HERA_NODE));
|
|
stadium_words_print_boot_diagnostics(stadium_node_key(SK_V4_HERA_NODE)); /* conservation, after */
|
|
}
|
|
#endif
|
|
if (place < SK_V4_PLACES) sk_v4_keyed[place] = 0;
|
|
}
|
|
/* A node the asker birthed has booted: its parity line, as the boot prints a capsule's. */
|
|
static void sk_v4_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
|
|
{
|
|
static const char hex[] = "0123456789abcdef";
|
|
uint64_t h = v4_image_dict_hash(n, &sk_v4_image);
|
|
char line[64];
|
|
unsigned at = 0, k;
|
|
const char *s;
|
|
(void)self; (void)place;
|
|
for (s = "PARITY:V4_BIRTH node="; *s; s++) line[at++] = *s;
|
|
{ char d[20]; unsigned nd = 0; v4_ucell v = (v4_ucell)number; do { d[nd++] = (char)('0' + v % 10u); v /= 10u; } while (v && nd < 20);
|
|
while (nd) line[at++] = d[--nd]; }
|
|
for (s = " dict_hash=0x"; *s; s++) line[at++] = *s;
|
|
for (k = 16; k-- > 0; ) line[at++] = hex[(h >> (4 * k)) & 0xFu];
|
|
line[at] = 0;
|
|
console_println(line);
|
|
}
|
|
static const char *const sk_v4_capsules[] = { "v4:centre.4th", "v4:hera.4th" };
|
|
|
|
static char sk_v4_text[SK_V4_LINE + 1u];
|
|
|
|
/* POST's block RAM, as the v3 path has (sk_vm_bootstrap.c, sk_post_blk_ram):
|
|
* the node boots and is POSTed with these blocks, 1 to 2047 and nothing
|
|
* above them, and the kernel's real chain is set up afterwards. */
|
|
static uint8_t sk_v4_post_blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE];
|
|
|
|
/* HEAT-OP into the Stadium (#189; Captain Bob: "Per heartbeat tick"): the node's opcode counts are
|
|
* charged to its opcode patrons at the heartbeat's service points -- where its host waits for a key,
|
|
* a tick at a time, and after each line. */
|
|
void sk_v4_charge_opcodes(void)
|
|
{
|
|
#ifdef STARFORTH_ENABLE_VM
|
|
unsigned k;
|
|
uint64_t now = heartbeat_ticks();
|
|
for (k = 0; k < SK_V4_PLACES; k++) {
|
|
v4_fabric_node *x = v4_fabric_node_at(&sk_v4_mesh.f, k);
|
|
v4_cell number;
|
|
if (!x) continue;
|
|
number = x->n.mem[sk_v4_image.me];
|
|
if (number <= 0) continue; /* not told its number yet: charged when it is */
|
|
if (sk_v4_keyed[k] != number) { /* a node first seen: its quota, granted from Hera's */
|
|
VMUuid key = stadium_node_key((uint32_t)number);
|
|
if (stadium_quota_slot_for_vm(key) < 0
|
|
&& stadium_grant_quota(key, stadium_node_key(SK_V4_HERA_NODE)) != 0) continue;
|
|
sk_v4_keyed[k] = number;
|
|
}
|
|
stadium_ops_charge(stadium_node_key((uint32_t)number), x->h.op, now);
|
|
/* and its words, by their calls since (#189, Phase 2: "Per heartbeat tick") */
|
|
sk_v4_charge_n = 0;
|
|
v4_mesh_charge(&sk_v4_mesh, k, sk_v4_charge_one, 0);
|
|
if (sk_v4_charge_n)
|
|
stadium_words_charge(stadium_node_key((uint32_t)number), sk_v4_charge_ids, sk_v4_charge_calls, sk_v4_charge_n, now);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void sk_v4_out(const char *text, unsigned len)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < len; i++) console_putc(text[i]);
|
|
}
|
|
|
|
/* a character for a line that reads the keyboard, when there is one: the
|
|
* serial line or any keyboard (docs/v4.0.0/STICK.md 5.4) */
|
|
static int sk_v4_key(void)
|
|
{
|
|
int c = sk_input_getc();
|
|
return c < 0 ? V4_BOOT_KEY_NONE : c;
|
|
}
|
|
|
|
/* THE KERNEL'S WORDS for the lone node. BYE is v3's (mama_word_bye): a cold
|
|
* restart, and MINT-ZUSE mints the real Zuse onto a second stick
|
|
* (sk_v4_mint.c). v3's BYE reaps Hera's children first; the lone node is started
|
|
* before there is a fleet, so there are none. The rest of the kernel's
|
|
* words come when the node is brought up in the VM's place (ENGINE.md
|
|
* step 4), where v3's own functions serve them. */
|
|
static const char *const sk_v4_words[] = { "BYE", "MINT-ZUSE" };
|
|
|
|
static void sk_v4_serve(v4_node *node, unsigned request)
|
|
{
|
|
(void)node;
|
|
if (request == 1) {
|
|
console_println("BYE: cold restart");
|
|
arch_cold_reset();
|
|
}
|
|
/* MINT-ZUSE: the real Zuse onto a second stick (docs/v4.0.0/STICK.md 6a;
|
|
* plan 2026-10-29-stick-5, Task 4). No POST case: it waits for a stick
|
|
* and a typed YES, as BYE has none. */
|
|
if (request == 2) (void)sk_v4_mint_zuse();
|
|
}
|
|
|
|
/* Read a line from the keyboard: characters are echoed, backspace rubs one
|
|
* out, Enter ends it. Returns its length; or -1 when, with nothing typed,
|
|
* Zuse's session has ended (her drive was pulled): the caller goes headless
|
|
* (not in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs no
|
|
* login: v3's sk_console_readline() the same). While nothing is typed the
|
|
* idle beat runs, as v3's. A line typed past SK_V4_LINE characters is not
|
|
* cut short and run: what is typed after that is not kept, and
|
|
* SK_V4_LINE + 1 is returned, which the boot refuses as too long
|
|
* (V4_BOOT_LINE_TOO_LONG), running nothing. */
|
|
#define SK_V4_CURSOR_BLINK 50u /* ticks between blinks, as v3's (500 ms at 100 Hz) */
|
|
|
|
static int sk_v4_readline(void)
|
|
{
|
|
int len = 0, over = 0;
|
|
int cursor_on = 1;
|
|
uint64_t blink = heartbeat_ticks();
|
|
|
|
console_fb_draw_cursor();
|
|
for (;;) {
|
|
int c = sk_input_getc();
|
|
if (c < 0) {
|
|
uint64_t now = heartbeat_ticks();
|
|
if (now - blink >= SK_V4_CURSOR_BLINK) {
|
|
blink = now;
|
|
cursor_on = !cursor_on;
|
|
if (cursor_on) console_fb_draw_cursor();
|
|
else console_fb_erase_cursor();
|
|
}
|
|
if (len == 0) {
|
|
sk_v4_identity_beat();
|
|
#if !EMERGENCY_CONSOLE_ENABLED
|
|
if (!sk_v4_identity_present()) return -1;
|
|
#endif
|
|
} else {
|
|
heartbeat_service();
|
|
}
|
|
sk_v4_charge_opcodes();
|
|
/* Nothing typed, and a node can run: the fabric steps on between lines, as during them
|
|
* -- every node steps together (JUSTIFICATION.md 4; #189, "Check for the edge cases").
|
|
* A batch, then the keyboard is looked at again. */
|
|
if (v4_mesh_busy(&sk_v4_mesh)) {
|
|
unsigned k;
|
|
for (k = 0; k < SK_V4_IDLE_STEPS; k++) (void)v4_mesh_step(&sk_v4_mesh);
|
|
continue;
|
|
}
|
|
/* Nothing typed: wait for the next interrupt, not in a spin (#151 -- the spin kept a
|
|
* CPU core 100% busy at the prompt, and the demo's host with it). The heartbeat's
|
|
* timer wakes it at 100 Hz, so a key is seen within a tick, as the blink and the
|
|
* identity beat are. */
|
|
arch_halt();
|
|
continue;
|
|
}
|
|
if (c == '\r' || c == '\n') {
|
|
console_fb_erase_cursor(); console_putc('\n');
|
|
return over ? (int)SK_V4_LINE + 1 : len;
|
|
}
|
|
if (c == 8 || c == 127) {
|
|
if (over > 0) { over--; console_putc(8); console_putc(' '); console_putc(8); }
|
|
else if (len > 0) { len--; console_putc(8); console_putc(' '); console_putc(8); }
|
|
} else if (c >= 32 && c < 127) {
|
|
if (len < (int)SK_V4_LINE) sk_v4_text[len++] = (char)c;
|
|
else over++;
|
|
console_putc((char)c);
|
|
}
|
|
cursor_on = 1; /* typing shows a solid cursor, as v3's */
|
|
blink = heartbeat_ticks();
|
|
console_fb_draw_cursor();
|
|
}
|
|
}
|
|
|
|
/* a line to Hera, as a message from the console, and all that follows from it (v4/mesh.h) */
|
|
static int sk_v4_tell(const char *text, unsigned len) { return v4_mesh_tell(&sk_v4_mesh, V4_MESH_HERA, text, len); }
|
|
|
|
void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
|
|
{
|
|
console_println("StarForth v4: Hera, born in the kernel's fabric");
|
|
sk_v4_image = v4_map_image;
|
|
sk_v4_mesh.places = sk_v4_places; sk_v4_mesh.capacity = SK_V4_PLACES;
|
|
sk_v4_mesh.queues = sk_v4_queues; sk_v4_mesh.queue_count = sizeof sk_v4_queues / sizeof sk_v4_queues[0];
|
|
sk_v4_mesh.reg = sk_v4_reg; sk_v4_mesh.blocks = sk_v4_blocks; sk_v4_mesh.kernel = sk_v4_kernel;
|
|
sk_v4_mesh.nucleus = &sk_v4_nucleus; sk_v4_mesh.hera = &sk_v4_hera;
|
|
sk_v4_mesh.room = sk_v4_room; sk_v4_mesh.unroom = sk_v4_unroom;
|
|
sk_v4_mesh.born = 0; sk_v4_mesh.gone = sk_v4_gone; sk_v4_mesh.parity = sk_v4_parity;
|
|
sk_v4_mesh.accounts = (v4_mesh_account *)kmalloc((size_t)SK_V4_PLACES * V4_MESH_WORD_IDS * sizeof(v4_mesh_account));
|
|
sk_v4_mesh.is_word = (unsigned char *)kmalloc((size_t)SK_V4_PLACES * (V4_NODE_WORDS / 8u));
|
|
if (!sk_v4_mesh.accounts || !sk_v4_mesh.is_word) { /* no records kept: the words are not charged */
|
|
if (sk_v4_mesh.accounts) kfree(sk_v4_mesh.accounts);
|
|
if (sk_v4_mesh.is_word) kfree(sk_v4_mesh.is_word);
|
|
sk_v4_mesh.accounts = 0; sk_v4_mesh.is_word = 0;
|
|
console_println("StarForth v4: no room for the words' accounts; words are not charged");
|
|
} else { /* none kept yet: all zero */
|
|
unsigned char *z = (unsigned char *)sk_v4_mesh.accounts;
|
|
size_t i, n = (size_t)SK_V4_PLACES * V4_MESH_WORD_IDS * sizeof(v4_mesh_account);
|
|
for (i = 0; i < n; i++) z[i] = 0;
|
|
for (i = 0; i < (size_t)SK_V4_PLACES * (V4_NODE_WORDS / 8u); i++) sk_v4_mesh.is_word[i] = 0;
|
|
}
|
|
sk_v4_mesh.forgot = sk_v4_forgot;
|
|
sk_v4_mesh.out = sk_v4_out; sk_v4_mesh.key = sk_v4_key;
|
|
sk_v4_mesh.words = sk_v4_words; sk_v4_mesh.word_count = sizeof sk_v4_words / sizeof sk_v4_words[0];
|
|
sk_v4_mesh.serve = sk_v4_serve;
|
|
sk_v4_mesh.capsules = sk_v4_capsules; sk_v4_mesh.capsule_count = sizeof sk_v4_capsules / sizeof sk_v4_capsules[0];
|
|
sk_v4_mesh.im = &sk_v4_image; sk_v4_mesh.self = 0;
|
|
blk_subsys_init(sk_v4_post_blk_ram, sizeof(sk_v4_post_blk_ram));
|
|
if (!v4_mesh_boot(&sk_v4_mesh)) {
|
|
console_println("StarForth v4: not started");
|
|
sk_v4_stop(); /* #177: halts, not a 100% spin */
|
|
}
|
|
if (chain) {
|
|
/* the blocks are other blocks now: what the node has in its two
|
|
* buffers is POST's, and is let go */
|
|
chain();
|
|
if (sk_v4_tell("EMPTY-BUFFERS", 13) != V4_TEXT_COMPLETED) {
|
|
console_println("StarForth v4: the node did not let go of POST's blocks");
|
|
sk_v4_stop(); /* #177: halts, not a 100% spin */
|
|
}
|
|
}
|
|
sk_v4_identity_boot(boot_disk);
|
|
#if EMERGENCY_CONSOLE_ENABLED
|
|
/* a console that needs no login has the screen from the start */
|
|
console_fb_mirror(1);
|
|
console_fb_enable_ttf();
|
|
#endif
|
|
}
|
|
|
|
void sk_v4_prompt(void)
|
|
{
|
|
/* The prompt is the host's: read a line, hand it to the node, say how it
|
|
* ended. */
|
|
for (;;) {
|
|
int len;
|
|
int how;
|
|
int logged_out = 0;
|
|
|
|
sk_v4_charge_opcodes(); /* what the last line retired (#189) */
|
|
|
|
#if !EMERGENCY_CONSOLE_ENABLED
|
|
/* no identity, no console: headless until Zuse's drive is checked.
|
|
* The drives are looked at first, now: one pulled while the last
|
|
* line ran is seen before a prompt is shown (the identity gate's
|
|
* final review, item 1). Not in a build with
|
|
* EMERGENCY_CONSOLE_ENABLED, whose console needs no login (v3's
|
|
* kernel_main.c and repl.c the same; item 3). */
|
|
sk_v4_identity_check_now();
|
|
if (!sk_v4_identity_present()) sk_v4_identity_wait();
|
|
#endif
|
|
console_puts(SK_V4_PROMPT);
|
|
len = sk_v4_readline();
|
|
if (len < 0) continue;
|
|
how = sk_v4_tell(sk_v4_text, (unsigned)len);
|
|
/* The line is waiting for an answer no one here can send. The next
|
|
* line typed breaks the wait (MESH.md 7b.6): it is a message to
|
|
* Hera, the line that was waiting ends in an error, and then the
|
|
* line just typed is done -- all in the one tell, which says how the
|
|
* last ended. */
|
|
while (how == V4_BOOT_LINE_WAITING) {
|
|
len = sk_v4_readline();
|
|
if (len < 0) {
|
|
/* Zuse's session ended while the line waited: its wait is
|
|
* ended now, by an empty line, and not carried into the
|
|
* next session (Captain Bob, 2026-10-26: "end the waiting
|
|
* line at logout"). How it ended is shown. */
|
|
how = sk_v4_tell("", 0u);
|
|
if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
|
|
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
|
|
logged_out = 1;
|
|
break;
|
|
}
|
|
how = sk_v4_tell(sk_v4_text, (unsigned)len);
|
|
if (how == V4_BOOT_LINE_TOO_LONG) { console_puts("Line too long\n ERROR\n"); how = V4_BOOT_LINE_WAITING; continue; }
|
|
}
|
|
if (logged_out) continue;
|
|
if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
|
|
else if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
|
|
else if (how == V4_BOOT_LINE_TOO_LONG) console_puts("Line too long\n ERROR\n");
|
|
else if (how == V4_BOOT_LINE_REFUSED) console_puts("Message refused\n ERROR\n"); /* the node's wire is full: error 19's words */
|
|
else if (how == V4_BOOT_LINE_STOPPED) {
|
|
console_println("StarForth v4: the node stopped on a fault");
|
|
sk_v4_stop(); /* #177: halts, not a 100% spin */
|
|
}
|
|
}
|
|
}
|