The final review of step 6e, by a fresh reviewer told to break it, and what was ruled: fix them all, test first. Each has a test that failed before the mend. - Two texts from one node could be begun in the wrong order when room for their answers came between the node's look at the first and at the second. Texts whose answers go by the same port are begun in the order they came: (D-MASK), quit.v4. 102 of 700 timings were wrong before. - A word written with PORT! to a node that speaks whole messages, or to a console's port, blocked its writer for ever and cut off every node beyond it. It is error 18 now, whether that node is asleep, running or stuck: fabric.c, node.c. - A node asleep for room on one wire is woken when another of its wires is cut. - A line there was no room for on the lone node's wire was reported as a fault and ended the hosted program. It is Message refused, and the prompt comes back: V4_BOOT_LINE_REFUSED. hosted-check has the case; no bare-metal boot has typed it. - Ruled: text behind held output for the same port is refused though it would fit. MESH.md 7d.4 said otherwise and is corrected. - The removal test also cuts and re-makes a wire, and has an exchange in which output is held at a full wire: 26,814 disturbances in all. - MESH.md and v4/README.md no longer say that a stuck node holds up no other node, that only printing waits, or that no node is left waiting on one that is gone; 7d.6 lists the limits and what the products do that they did not before. make -C v4 test and sanitize at both widths, hosted-check, lint. Three bare-metal boots: logs/20261013-194827 (amd64), -195131 (aarch64), -195518 (riscv64). POST 538 of 538, word_count=317, dict_hash=0xe5a57f3eb72ff94a on all six; the three sessions are the same character for character. The boots were built with the working tree's uncommitted change to kernel/Makefile that adds the ISA to the kernel's version line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1152 lines
82 KiB
C
1152 lines
82 KiB
C
/* test_host_mesh.c -- StarForth nodes that talk: a console wired to one
|
|
* node, and two more in a row behind it. docs/v4.0.0/MESH.md sections 6
|
|
* and 7, steps 3 and 4.
|
|
*
|
|
* console --1 [10] 2--2 [11] 3--2 [12]
|
|
*
|
|
* Each node is the nucleus (v4/capsule/ *.v4) and nothing else. They start
|
|
* alike, numbered 0 and knowing no way anywhere; everything they come to
|
|
* know they are told by text sent from the console, as whoever wires a node
|
|
* tells it. The console is a device on a port and speaks whole messages
|
|
* (v4/include/v4/message.h): it puts them on the wire to its node and
|
|
* takes them off the wire from it, through the fabric (MESH.md 7d.2).
|
|
*
|
|
* Each scene after the first is played on a row made afresh, row(), so
|
|
* that none depends on what an earlier one left.
|
|
*/
|
|
#include "v4/image.h"
|
|
#include "v4/fabric.h"
|
|
#include "v4/message.h"
|
|
#include "v4/text.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "host_map.h"
|
|
|
|
static int failures = 0, checks = 0;
|
|
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
|
|
|
|
static v4_text tx;
|
|
static v4_node nucleus; /* one node with the nucleus in it: what every node here starts as */
|
|
static v4_cell w_idle, w_fault;
|
|
static v4_fabric_node pool[3];
|
|
static v4_place places[3];
|
|
static v4_fabric f;
|
|
#define QUEUES 12u /* two for each wire there may be: the console's, 10-11, 11-12, 10-12, and two wires to spare */
|
|
static v4_wire_queue queues[QUEUES];
|
|
|
|
/* ---- the console: a device on a port ---- */
|
|
#define CONSOLE_ID 1
|
|
static const v4_device console = { 0, 0, 0 }; /* it takes and gives no words: only whole messages */
|
|
/* a kernel on a node's port 0: it takes the words written to it, and no messages (MESH.md 7d.5) */
|
|
static unsigned kernel_words;
|
|
static int kernel_take(void *self, v4_cell value) { (void)self; (void)value; kernel_words++; return 1; }
|
|
static const v4_device kernel_dev = { kernel_take, 0, 0 };
|
|
static unsigned hera, mid, far; /* where the three nodes are: 10, 11, 12 */
|
|
static char printed[32768]; /* what has come back since the console last sent */
|
|
static unsigned printed_len;
|
|
static v4_cell printed_from; /* which node the last of it came from */
|
|
static int ended; /* how many messages of how text ended have come, */
|
|
static v4_cell ended_from, ended_how; /* and the last of them */
|
|
static unsigned ended_at; /* and how much had been printed when it came */
|
|
static unsigned stale_out; /* output messages whose last word had anything in it past the last character */
|
|
static int nacks; /* how many NACKs have come to the console, */
|
|
static v4_cell nack_from, nack_about; /* and the last of them: who refused, and the node the refused message was for */
|
|
|
|
/* Everything the node at the console has put on the console's wire is taken, in order. How many messages. */
|
|
static unsigned console_takes(void)
|
|
{
|
|
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
|
|
unsigned took = 0, i;
|
|
while (v4_fabric_device_take(&f, hera, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
|
|
took++;
|
|
if (hd[2] == V4_MSG_OUTPUT) {
|
|
for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
|
|
printed[printed_len] = 0;
|
|
printed_from = hd[1];
|
|
if (hd[6] % 4 != 0 && ((v4_ucell)text[hd[6] / 4] >> (8u * (unsigned)(hd[6] % 4))) != 0) stale_out++;
|
|
} else if (hd[2] == V4_MSG_DONE) {
|
|
ended++; ended_from = hd[1]; ended_how = text[0]; ended_at = printed_len;
|
|
} else if (hd[2] == V4_MSG_NACK) {
|
|
nacks++; nack_from = hd[1]; nack_about = text[0];
|
|
}
|
|
}
|
|
return took;
|
|
}
|
|
|
|
static int is_empty(const char *s) { return s[0] == 0; }
|
|
|
|
/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */
|
|
static int asleep(unsigned id)
|
|
{
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
|
|
return x && x->n.doing && x->n.do_op == 8;
|
|
}
|
|
/* 1 if it is asleep waiting for room on a wire (operation 9). */
|
|
static int waits_for_room(unsigned id)
|
|
{
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
|
|
return x && x->n.doing && x->n.do_op == 9;
|
|
}
|
|
/* the queue of messages going from a node's port, and coming to it */
|
|
static const v4_wire_queue *going_from(unsigned id, unsigned port) { return places[id].wire[port].tx; }
|
|
static const v4_wire_queue *coming_to(unsigned id, unsigned port) { return places[id].wire[port].rx; }
|
|
|
|
static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */
|
|
static unsigned stuck; /* bit k: the node at place k is known to be going round for ever, and is not waited for */
|
|
static void (*watch)(void); /* called after every step, if set */
|
|
static unsigned long steps_run;
|
|
static unsigned between_data, between_ret; /* the most of each stack any node has used while doing nothing that is a text's (QUIET), */
|
|
static int was_quiet[3], clean[3]; /* after a text that left nothing on the data stack */
|
|
|
|
static void show_stuck(const char *text)
|
|
{
|
|
unsigned k, q;
|
|
if (!getenv("V4_MESH_STUCK")) return;
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n;
|
|
printf(" stuck after \"%.40s\": node %ld p=%ld doing=%d op=%ld how=%ld wire=%u asking=%d d=%u r=%u await=%ld keep=%ld lost=%ld refused=%ld fault=%u@%ld err=%ld\n", text, (long)n->mem[ME], (long)n->p, n->doing, (long)n->do_op, (long)n->how, n->wire,
|
|
n->asking, (unsigned)n->ds.depth, (unsigned)n->rs.depth, (long)n->mem[AWAIT_FROM], (long)n->mem[KEEP], (long)n->mem[LOST], (long)n->mem[REFUSED], n->fault_kind, (long)n->fault_addr, (long)n->mem[NODE_ERROR]);
|
|
for (q = 0; q < V4_PORTS; q++) if (places[k].wire[q].rx) printf(" port %u: %u cells coming, %u going\n", q, places[k].wire[q].rx->used, places[k].wire[q].tx->used); }
|
|
}
|
|
/* Let everything that follows from what has been put happen: until nothing can, or -- with a node known to be
|
|
* stuck -- until every other node has been asleep, and the console has been sent nothing, for a while. */
|
|
static int run(void)
|
|
{
|
|
unsigned steps = 0, quiet = 0, k;
|
|
while (steps < step_cap) {
|
|
unsigned did = v4_fabric_step(&f), took = console_takes(), awake = 0, running = 0;
|
|
steps++; steps_run++;
|
|
if (watch) watch();
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) {
|
|
int quiet_now = pool[k].n.mem[QUIET] == 1;
|
|
if (quiet_now && !was_quiet[k]) clean[k] = pool[k].n.ds.depth <= 1; /* its text has ended: did it leave anything? (how it ended may still be there) */
|
|
was_quiet[k] = quiet_now;
|
|
if (!quiet_now || !clean[k]) continue;
|
|
if (pool[k].n.ds.depth > between_data) between_data = (unsigned)pool[k].n.ds.depth;
|
|
if (pool[k].n.rs.depth > between_ret) between_ret = (unsigned)pool[k].n.rs.depth;
|
|
}
|
|
/* a step in which a node only faulted counts nothing done (exec.h): it goes on from its handler at the next */
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !pool[k].n.doing && !pool[k].n.asking && !pool[k].n.stopped && !(pool[k].n.reading && !pool[k].n.given)) running = 1;
|
|
if (did == 0 && took == 0 && !running) return 1;
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !(stuck & 1u << k) && !(pool[k].n.doing && (pool[k].n.do_op == 8 || pool[k].n.do_op == 9))) awake = 1;
|
|
quiet = (stuck && !awake && took == 0) ? quiet + 1 : 0;
|
|
if (quiet >= 400000) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
/* Let time pass until `when` says so; 0 if it never does. */
|
|
static int run_until(int (*when)(void), unsigned cap)
|
|
{
|
|
unsigned steps;
|
|
for (steps = 0; steps < cap; steps++) {
|
|
if (when()) return 1;
|
|
(void)v4_fabric_step(&f); (void)console_takes(); steps_run++;
|
|
if (watch) watch();
|
|
}
|
|
return when();
|
|
}
|
|
/* The console sends text to a node: the message is put on the wire to the node the console is on. */
|
|
static int put_text(v4_cell node, const char *text)
|
|
{
|
|
static v4_message m;
|
|
if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return 0;
|
|
return v4_fabric_device_put(&f, hera, 1, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE;
|
|
}
|
|
static void forget(void)
|
|
{
|
|
printed_len = 0; printed[0] = 0; printed_from = -1;
|
|
ended = 0; ended_from = -1; ended_how = -1; ended_at = 0;
|
|
}
|
|
/* Send text to a node and let everything that follows from it happen. */
|
|
static const char *tell(v4_cell node, const char *text)
|
|
{
|
|
forget();
|
|
if (!put_text(node, text)) return "(not put)";
|
|
if (!run()) { show_stuck(text); return "(still running)"; }
|
|
return printed;
|
|
}
|
|
|
|
/* A StarForth node in the fabric: born, then given the nucleus and the
|
|
* registers the nucleus expects, waiting for a message. */
|
|
static unsigned starforth_node(unsigned k)
|
|
{
|
|
int id = v4_fabric_add(&f, &pool[k], PORT);
|
|
v4_node *n = &pool[k].n;
|
|
memcpy(n->mem, nucleus.mem, sizeof n->mem);
|
|
v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);
|
|
v4_node_error_attach(n, NODE_ERROR);
|
|
v4_node_fault_attach(n, w_fault);
|
|
n->p = w_idle;
|
|
return (unsigned)id;
|
|
}
|
|
|
|
/* Three nodes that know nothing, in a row, the console on the first. */
|
|
static int fresh(void)
|
|
{
|
|
unsigned i;
|
|
int ok;
|
|
v4_fabric_init(&f, places, 3);
|
|
v4_fabric_queues(&f, queues, QUEUES);
|
|
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
|
|
hera = starforth_node(0);
|
|
mid = starforth_node(1);
|
|
far = starforth_node(2);
|
|
ok = v4_fabric_wire_device(&f, hera, 1, &console) && v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2);
|
|
stuck = 0; watch = 0; nacks = 0; step_cap = 20000000;
|
|
was_quiet[0] = was_quiet[1] = was_quiet[2] = 0; clean[0] = clean[1] = clean[2] = 0;
|
|
forget();
|
|
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
|
|
return ok && asleep(hera) && asleep(mid) && asleep(far);
|
|
}
|
|
static int told(v4_cell node, const char *text) { return is_empty(tell(node, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
|
|
/* The same, told who they are, the way to each other, and who is on each port:
|
|
* console --1 [10] 2--2 [11] 3--2 [12] */
|
|
static int row(void)
|
|
{
|
|
return fresh()
|
|
&& told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")
|
|
&& told(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")
|
|
&& told(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")
|
|
&& told(10, "0 NO-ROUTE 11 2 ROUTE 12 2 ROUTE 11 2 NEIGHBOUR") && told(11, "0 NO-ROUTE 12 3 ROUTE 10 2 NEIGHBOUR 12 3 NEIGHBOUR") && told(12, "11 2 NEIGHBOUR");
|
|
}
|
|
/* And with the far node wired straight to the one at the console as well, port 3 to port 3; node 10 reaches
|
|
* 12 by that wire, and 12 reaches everything by it but 11. */
|
|
static int triangle(void)
|
|
{
|
|
return row() && v4_fabric_wire(&f, hera, 3, far, 3)
|
|
&& told(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR") && told(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR");
|
|
}
|
|
static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; }
|
|
|
|
/* The whole of the mesh as it stands, kept and put back: so that one moment can be gone on from many times. */
|
|
static v4_fabric_node kept_pool[3];
|
|
static v4_place kept_places[3];
|
|
static v4_wire_queue kept_queues[QUEUES];
|
|
static v4_fabric kept_f;
|
|
static void keep(void) { memcpy(kept_pool, pool, sizeof pool); memcpy(kept_places, places, sizeof places); memcpy(kept_queues, queues, sizeof queues); kept_f = f; }
|
|
static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places, kept_places, sizeof places); memcpy(queues, kept_queues, sizeof queues); f = kept_f; forget(); }
|
|
|
|
/* what a scene waits for */
|
|
static int far_has_one(void) { return coming_to(far, 2) && coming_to(far, 2)->used > 0; }
|
|
static int far_has_nack(void) { return far_has_one() && coming_to(far, 2)->cell[(coming_to(far, 2)->head + 2u) % V4_WIRE_CELLS] == V4_MSG_NACK; }
|
|
static int far_awaits_and_has_one(void) { return pool[far].n.mem[AWAIT_FROM] == 11 && far_has_one(); }
|
|
static v4_cell mid_for; /* whom the message waited for at node 11's wire from node 12 is for */
|
|
static int mid_has_one(void) { return coming_to(mid, 3) && coming_to(mid, 3)->used > 0 && coming_to(mid, 3)->cell[coming_to(mid, 3)->head] == mid_for; }
|
|
static v4_cell far_lost_wanted;
|
|
static int far_has_lost(void) { return pool[far].n.mem[LOST] >= far_lost_wanted && asleep(far); }
|
|
static int mid_holds(void) { return pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0; }
|
|
static unsigned saw_mid_hold;
|
|
static void watch_mid_holds(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0) saw_mid_hold++; }
|
|
|
|
/* what a scene watches for while it runs */
|
|
static unsigned saw_room_wait, saw_full;
|
|
static void watch_far_waits(void)
|
|
{
|
|
if (waits_for_room(far)) { saw_room_wait++; if (going_from(far, 3) && going_from(far, 3)->used == V4_WIRE_CELLS - 8u) saw_full++; }
|
|
}
|
|
|
|
/* ---- a node removed at every step of an exchange (MESH.md 7d.7, acceptance 6) ---- */
|
|
/* 1 if the node is doing nothing and waits for nothing but a message: asleep, and blocked at no port. */
|
|
static int at_rest(unsigned id)
|
|
{
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
|
|
return x && asleep(id) && !x->n.asking && !x->n.reading && !x->n.stopped;
|
|
}
|
|
/* 1 if what came back ends with the answer to `1 2 + .`, asked by the word Q that each node was given; a wait
|
|
* the removal left is first ended by the line, and says so. */
|
|
#define ASK ": Q 1 2 + . ;"
|
|
static int answers(v4_cell node, unsigned id)
|
|
{
|
|
const char *got = tell(node, "Q");
|
|
size_t len = strlen(got);
|
|
return len >= 2 && strcmp(got + len - 2, "3 ") == 0 && ended >= 1 && ended_from == node && at_rest(id);
|
|
}
|
|
/* AN EXCHANGE, DISTURBED AT EVERY STEP. The exchange that start() begins is played from the mesh as kept:
|
|
* once whole, to see how many steps it takes, and then once for every one of those steps with the mesh
|
|
* disturbed at that step --
|
|
* 1 node 11 removed, and node 12 then wired to node 10, as whoever removed it would, so that it can be asked;
|
|
* 2 node 12 removed;
|
|
* 3 the wire between nodes 11 and 12 cut and made again at once: what waited on it is let go.
|
|
* After each, every node left must come to rest, asleep and blocked at no port, and answer. With `moments`
|
|
* only the steps at which something the fabric keeps had changed are tried -- what is on a wire, or what a
|
|
* node asks of one: for an exchange too long to disturb at every step. A removal between two such steps
|
|
* finds the mesh as the one before it did.
|
|
* How many steps the exchange takes; how many disturbances were tried; how many went wrong, and the first. */
|
|
static v4_fabric_node now_pool[3];
|
|
static v4_place now_places[3];
|
|
static v4_wire_queue now_queues[QUEUES];
|
|
static v4_fabric now_f;
|
|
static v4_cell start_node;
|
|
static const char *start_text;
|
|
static int start_plain(void) { return put_text(start_node, start_text); }
|
|
static int hera_busy(void) { return pool[hera].n.mem[QUIET] == 0; }
|
|
/* Text for node 12 that prints, with node 10 made busy and the wire from node 11 to node 10 full but for 7
|
|
* cells: what node 12 prints, and how its text ended, are held by node 11, asleep for room (MESH.md 7d.4). */
|
|
static int start_held(void)
|
|
{
|
|
static v4_cell big[V4_WIRE_HEADER + V4_MSG_MAX_CHARS / 4u];
|
|
v4_wire_queue *q = places[mid].wire[2].tx;
|
|
unsigned k;
|
|
int ok = put_text(12, "A") && put_text(10, "SLOW") && run_until(hera_busy, 5000); /* node 10 has passed the one on and begun the other */
|
|
memset(big, 0, sizeof big);
|
|
big[0] = CONSOLE_ID; big[1] = 12; big[2] = V4_MSG_OUTPUT; big[3] = 16; big[6] = 1024;
|
|
for (k = 0; k < 3; k++) ok = ok && v4_wire_put(q, big, big + V4_WIRE_HEADER) == V4_WIRE_DONE;
|
|
big[6] = 884;
|
|
return ok && v4_wire_put(q, big, big + V4_WIRE_HEADER) == V4_WIRE_DONE && V4_WIRE_CELLS - q->used == 7;
|
|
}
|
|
/* what the fabric keeps, in a number: what every wire holds and what every node asks of one */
|
|
static unsigned long fabric_mark(void)
|
|
{
|
|
unsigned long h = 1469598103UL;
|
|
unsigned k;
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) h = (h ^ (unsigned long)(pool[k].n.doing * 16 + (int)pool[k].n.do_op)) * 16777619UL;
|
|
for (k = 0; k < QUEUES; k++) h = ((h ^ queues[k].used) * 16777619UL ^ queues[k].head) * 16777619UL;
|
|
return h;
|
|
}
|
|
static unsigned disturbed_at_every_step(int (*start)(void), unsigned how, int moments, unsigned *tried, unsigned *wrong, unsigned *first_wrong)
|
|
{
|
|
unsigned long before, mark, last = 0;
|
|
unsigned whole, s;
|
|
*tried = 0; *wrong = 0; *first_wrong = 0;
|
|
put_back();
|
|
before = steps_run;
|
|
saw_mid_hold = 0; watch = watch_mid_holds;
|
|
if (!start() || !run()) { watch = 0; *wrong = 1; return 0; }
|
|
watch = 0;
|
|
whole = (unsigned)(steps_run - before);
|
|
put_back();
|
|
if (!start()) { *wrong = 1; return 0; }
|
|
for (s = 0; s <= whole; s++) {
|
|
mark = fabric_mark();
|
|
if (!moments || s == 0 || mark != last) {
|
|
int ok;
|
|
memcpy(now_pool, pool, sizeof pool); memcpy(now_places, places, sizeof places); memcpy(now_queues, queues, sizeof queues); now_f = f; /* the exchange, s steps on */
|
|
if (how == 1) ok = v4_fabric_remove(&f, mid) == &pool[mid] && run() && at_rest(hera) && at_rest(far)
|
|
&& v4_fabric_wire(&f, hera, 2, far, 2) && answers(12, far) && answers(10, hera);
|
|
else if (how == 2) ok = v4_fabric_remove(&f, far) == &pool[far] && run() && at_rest(hera) && at_rest(mid) && answers(11, mid) && answers(10, hera);
|
|
else ok = v4_fabric_unwire(&f, mid, 3) && v4_fabric_wire(&f, mid, 3, far, 2) && run() && at_rest(hera) && at_rest(mid) && at_rest(far)
|
|
&& answers(12, far) && answers(11, mid) && answers(10, hera);
|
|
(*tried)++;
|
|
if (!ok) { if (!*wrong) *first_wrong = s; (*wrong)++; }
|
|
memcpy(pool, now_pool, sizeof pool); memcpy(places, now_places, sizeof places); memcpy(queues, now_queues, sizeof queues); f = now_f;
|
|
}
|
|
last = mark;
|
|
(void)v4_fabric_step(&f); (void)console_takes();
|
|
}
|
|
return whole;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned i;
|
|
|
|
printf("v4 mesh tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
|
|
{
|
|
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
|
|
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
|
|
CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles");
|
|
}
|
|
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
|
|
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
|
|
w_idle = v4_text_word(&tx, "(IDLE)");
|
|
w_fault = v4_text_word(&tx, "(FAULTS)");
|
|
nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */
|
|
nucleus.mem[LATEST] = v4_text_latest(&tx);
|
|
nucleus.mem[CFP] = CFS_W;
|
|
nucleus.mem[BASE] = 10;
|
|
nucleus.mem[FENCE] = DICT_W;
|
|
nucleus.mem[BOOT_CELLS] = nucleus.mem[DP];
|
|
nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST];
|
|
nucleus.mem[LOG_LEVEL] = 2;
|
|
nucleus.mem[CONTEXT] = LATEST;
|
|
nucleus.mem[CURRENT] = LATEST;
|
|
nucleus.mem[SRC] = TIB;
|
|
nucleus.mem[LINE_STATUS] = 1;
|
|
HOST_MESSAGES_EMPTY(&nucleus);
|
|
|
|
CHECK(fresh(), "the console is wired to one node's port 1, the three nodes are in a row, and all three sleep");
|
|
|
|
/* ---- they are told who they are and the way to each other, by text ---- */
|
|
CHECK(is_empty(tell(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")) && ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10,
|
|
"the node at the console takes text sent to 0, and is 10 from then on: %ld", (long)ended_from);
|
|
CHECK(is_empty(tell(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")) && ended == 1 && ended_from == 11 && ended_how == V4_TEXT_COMPLETED,
|
|
"text sent to 0 now passes through 10 to the next, which is 11 from then on: %ld", (long)ended_from);
|
|
CHECK(is_empty(tell(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED,
|
|
"and then through both to the last, 12: %ld", (long)ended_from);
|
|
CHECK(is_empty(tell(10, "11 2 ROUTE 12 2 ROUTE")) && ended_from == 10 && is_empty(tell(11, "12 3 ROUTE")) && ended_from == 11,
|
|
"each is told the way to the others");
|
|
|
|
/* ---- they talk ---- */
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10 && ended_how == V4_TEXT_COMPLETED, "text for the node at the console is done there");
|
|
CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && printed_from == 11 && ended_from == 11, "text for the next node is done there, and what it prints comes back");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED,
|
|
"text for the last passes through two nodes, and what it prints comes back through two: \"%s\" from %ld", printed, (long)printed_from);
|
|
CHECK(strcmp(tell(12, "NOSUCH"), "UNKNOWN WORD: 'NOSUCH'\n") == 0 && ended_from == 12 && ended_how == V4_TEXT_ERROR, "an error there is reported from there");
|
|
CHECK(strcmp(tell(12, ": SQ DUP * ;"), "") == 0 && strcmp(tell(12, "9 SQ ."), "81 ") == 0 && strstr(tell(11, "9 SQ ."), "UNKNOWN WORD") != NULL,
|
|
"each node has its own dictionary: a word defined on one is not on another");
|
|
{
|
|
const char *all = tell(12, "WORDS");
|
|
CHECK(strlen(all) > 1000 && printed_from == 12 && ended == 1 && ended_at == printed_len, "more than a message holds comes back in several, and then how it ended: %u characters", (unsigned)strlen(all));
|
|
}
|
|
{
|
|
static char line[1100];
|
|
memset(line, ' ', 1024);
|
|
memcpy(line, "66 EMIT", 7);
|
|
memcpy(line + 1017, "67 EMIT", 7);
|
|
line[1024] = 0;
|
|
CHECK(strcmp(tell(12, line), "BC") == 0, "a message as long as a message may be is passed on whole: 1024 characters");
|
|
}
|
|
|
|
/* ---- one node sends to another ---- */
|
|
CHECK(strcmp(tell(10, ": TELL S\" 65 EMIT 12 . \" 12 SEND ; TELL"), "A12 ") == 0 && printed_from == 12,
|
|
"text one node SENDs another is done there, and what that prints goes to its console: \"%s\" from %ld", printed, (long)printed_from);
|
|
CHECK(strstr(tell(10, ": NOWAY S\" 1\" 99 SEND ; NOWAY"), "Argument out of range") != NULL && ended_how == V4_TEXT_ERROR,
|
|
"SEND to a node there is no way to is an error");
|
|
CHECK(strstr(tell(10, ": LONG PAD 1025 12 SEND ; LONG"), "Argument out of range") != NULL && strstr(tell(10, ": SHORT PAD -1 12 SEND ; SHORT"), "Argument out of range") != NULL
|
|
&& strcmp(tell(10, "DEPTH ."), "0 ") == 0, "and so is more text than a message carries, or less than none; nothing is left on the stack");
|
|
CHECK(strstr(tell(10, ": P5 S\" 1\" 0 5 SEND-ON ; P5"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR, "SEND-ON by a port with no one on it is an error that says so");
|
|
|
|
/* ---- no way ---- */
|
|
nacks = 0;
|
|
i = (unsigned)pool[hera].n.mem[LOST];
|
|
CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to is not done by anyone");
|
|
CHECK(nacks == 1 && nack_from == 10 && nack_about == 99, "the console, which sent it, is told it was refused, by the node that could not pass it on: %d from %ld about %ld",
|
|
nacks, (long)nack_from, (long)nack_about);
|
|
CHECK(pool[hera].n.mem[LOST] == (v4_cell)i + 1, "and that node has counted it");
|
|
CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing");
|
|
i = (unsigned)pool[hera].n.mem[LOST];
|
|
CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i && coming_to(hera, 2)->used == 0,
|
|
"an answer that comes to a node that is not waiting for it is let go, and is not counted: a SEND that is not waited on is ordinary");
|
|
|
|
/* ---- waiting ---- */
|
|
{
|
|
v4_uheat_t c0 = clock_of(0), c1 = clock_of(1), c2 = clock_of(2);
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far), "with nothing to do, all three sleep");
|
|
CHECK(clock_of(0) == c0 && clock_of(1) == c1 && clock_of(2) == c2, "and execute nothing");
|
|
}
|
|
|
|
/* ---- the ways are forgotten and told again: the wiring has changed ---- */
|
|
CHECK(v4_fabric_wire(&f, hera, 3, far, 3), "the far node is wired straight to the one at the console as well");
|
|
CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 3 ROUTE")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE")), "and both are told");
|
|
{
|
|
v4_uheat_t before = clock_of(1);
|
|
CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && clock_of(1) == before, "text for it no longer passes through the middle node, which executes nothing");
|
|
}
|
|
|
|
/* ---- two neighbours with something for each other at the same moment ----
|
|
* Each puts its message on the wire to the other, whole, and neither waits for the other (MESH.md 7d.2).
|
|
* Each node is told who is on each of its ports: it is how it knows its centre. */
|
|
CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 2 ROUTE")) && is_empty(tell(12, "NO-ROUTES 2 DEFAULT-ROUTE")), "the row of three again");
|
|
CHECK(is_empty(tell(10, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")) && is_empty(tell(11, "10 2 NEIGHBOUR 12 3 NEIGHBOUR")) && is_empty(tell(12, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")),
|
|
"each is told who is on the other end of its ports");
|
|
CHECK(strstr(tell(10, "11 8 NEIGHBOUR"), "Argument out of range") != NULL && strstr(tell(10, "11 -1 NEIGHBOUR"), "Argument out of range") != NULL,
|
|
"a port a node has not got is an error");
|
|
/* the far node sends the middle one text, and then word of how its own
|
|
* text ended, which goes by the middle one; the middle one does the
|
|
* text and answers the far one: the two messages meet on one wire */
|
|
CHECK(strcmp(tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI"), "H") == 0 && printed_from == 11 && ended == 1 && ended_from == 12,
|
|
"from the far end to the middle, with the answers crossing: \"%s\" from %ld", printed, (long)printed_from);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three are asleep again");
|
|
|
|
/* every node sends every other a number of messages, all at once: each
|
|
* counts what it is sent */
|
|
#define COUNTING "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT VARIABLE TRIED : BURST WHO ! 0 DO 1 TRIED +! S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;"
|
|
CHECK(is_empty(tell(10, COUNTING)) && is_empty(tell(11, COUNTING)) && is_empty(tell(12, COUNTING)), "each node is given a word to count with and one to send with");
|
|
{
|
|
const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO");
|
|
CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far), "all three come to rest");
|
|
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0,
|
|
"every one of them arrived, and none was let go: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ----
|
|
* 800 messages among three nodes, all at once. A SEND the wire has no room for is refused at once: the
|
|
* word that sends ends there, error 19, and the refusal is counted. A message a middle node has no room
|
|
* to pass on is let go there, counted, and its sender told. Nothing waits, and it comes to rest. */
|
|
{
|
|
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v, let_go, orders = 0;
|
|
unsigned k, q, left = 0;
|
|
static const v4_cell who[3] = { 10, 11, 12 };
|
|
const char *r;
|
|
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(11, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(12, "0 GOT ! 0 SENT ! 0 TRIED !")), "the counts begin again");
|
|
for (k = 0; k < 3; k++) { lost -= pool[k].n.mem[LOST]; refused -= pool[k].n.mem[REFUSED]; }
|
|
r = tell(10, ": GO2 S\" 200 12 BURST 200 10 BURST\" 11 SEND S\" 200 10 BURST 200 11 BURST\" 12 SEND 200 12 BURST 200 11 BURST ; GO2");
|
|
CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends");
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three come to rest, asleep");
|
|
for (k = 0; k < 3; k++) for (q = 0; q < V4_PORTS; q++) if (coming_to(k, q)) left += coming_to(k, q)->used;
|
|
CHECK(left == 0, "with nothing left on any wire: %u cells", left);
|
|
for (k = 0; k < 3; k++) {
|
|
v = strtol(tell(who[k], "GOT @ ."), NULL, 10); got += v;
|
|
CHECK(v >= 0 && v <= 400, "node %ld did %ld of the messages for it", (long)who[k], v);
|
|
sent += strtol(tell(who[k], "SENT @ ."), NULL, 10);
|
|
v = strtol(tell(who[k], "TRIED @ ."), NULL, 10); tried += v;
|
|
if (v == 0) orders++; /* it never began to send: the text that told it to was itself let go on the way */
|
|
lost += pool[k].n.mem[LOST];
|
|
refused += pool[k].n.mem[REFUSED];
|
|
}
|
|
let_go = sent - got;
|
|
printf(" the flood: %ld sends tried: %ld refused at once, %ld let go on the way, %ld done; %ld of the two texts that set a node sending let go; (LOST) %ld, (REFUSED) %ld\n",
|
|
tried, tried - sent, let_go, got, orders, lost, refused);
|
|
/* THE ACCOUNT IS EXACT. Every send tried was refused at once (counted once, in (REFUSED), and the word
|
|
* that sent ended there); or got on a wire and was done; or got on a wire and was let go on the way by
|
|
* a node with no room to pass it on -- counted there in (LOST), and once more where its NACK ended: in
|
|
* (REFUSED) at the node that sent it, or in (LOST) if the NACK could not go either. That is so too of the
|
|
* text that sets a node sending, which is one more message on those wires. Nothing else is
|
|
* counted: an answer waits for room and is not let go, and one that nobody waits for is not a loss. */
|
|
CHECK(tried > sent && got > 0 && let_go >= 0, "there was not room for all of them: some were refused at once");
|
|
CHECK(lost + refused == (tried - sent) + 2 * (let_go + orders), "every message is accounted for, exactly: (LOST) + (REFUSED) is %ld; %ld refused at once and twice %ld let go is %ld",
|
|
lost + refused, tried - sent, let_go + orders, (tried - sent) + 2 * (let_go + orders));
|
|
CHECK(let_go > 0, "and some were let go on the way, so that is not an account of nothing");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
|
|
}
|
|
|
|
/* ---- acceptance 2 and 3: REFUSED AT ONCE, and a stuck node holds up no one ---- */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
v4_cell r0;
|
|
char text[64];
|
|
CHECK(told(10, ": F 0 DO S\" HIT\" 11 SEND LOOP ;"), "node 10 is given a word that sends node 11 short texts");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
CHECK(ended == 0 && !asleep(mid) && coming_to(mid, 2)->used == 0, "node 11 is stuck in a loop that never ends; it took the text that stuck it");
|
|
r0 = pool[hera].n.mem[REFUSED];
|
|
snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u));
|
|
CHECK(is_empty(tell(10, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED && pool[hera].n.mem[REFUSED] == r0 && going_from(hera, 2)->used == V4_WIRE_CELLS,
|
|
"as many as the wire holds are taken, and none is refused: the wire is full, %u cells", going_from(hera, 2)->used);
|
|
for (i = 1; i <= 5; i++) {
|
|
const char *r = tell(10, "1 F");
|
|
CHECK(strstr(r, "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[REFUSED] == r0 + (v4_cell)i,
|
|
"one more is refused at once, and counted: the line ends, having waited for nothing: \"%s\", %ld", r, (long)(pool[hera].n.mem[REFUSED] - r0));
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers");
|
|
}
|
|
CHECK(asleep(hera) && asleep(far) && pool[hera].n.mem[LOST] == 0, "nothing waits: nodes 10 and 12 are asleep, and nothing was let go");
|
|
|
|
/* text for node 12, beyond the stuck one */
|
|
{
|
|
static char line[1100];
|
|
v4_cell lost = pool[hera].n.mem[LOST];
|
|
memset(line, ' ', 1024); memcpy(line, "1 DROP", 6); line[1024] = 0;
|
|
nacks = 0;
|
|
(void)tell(12, line);
|
|
CHECK(nacks == 1 && nack_from == 10 && nack_about == 12 && pool[hera].n.mem[LOST] == lost + 1 && ended == 0,
|
|
"text for node 12, beyond the stuck node, is refused at node 10, whose wire to 11 is full, and the console is sent the NACK: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "node 10 answers, and sleeps");
|
|
}
|
|
}
|
|
|
|
/* ---- a refusal owed to a stuck node ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
{
|
|
v4_cell lost = pool[far].n.mem[LOST];
|
|
v4_uheat_t c;
|
|
CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE") && told(11, "55 3 ROUTE"), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C1 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C1");
|
|
CHECK(ended == 0 && pool[far].n.mem[LOST] == lost + 1, "node 11 sends 55 a message by node 12 and is then stuck in a loop: node 12 has let the message go");
|
|
CHECK(going_from(far, 2)->used == 8 && coming_to(far, 2)->used == 0, "its NACK is on the wire to node 11, which does not take it: %u cells", going_from(far, 2)->used);
|
|
c = clock_of(far);
|
|
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
|
|
CHECK(asleep(far) && clock_of(far) == c, "node 12 is asleep, not going round: it owes nothing");
|
|
/* nothing but the text goes on the wire: not what lies after it in the node's memory */
|
|
(void)tell(12, ": S5 S\" ABCDEXYZ\" DROP 5 11 SEND ; S5");
|
|
CHECK(coming_to(mid, 3)->used == 8 + 9 && coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS] == 'E',
|
|
"five characters sent of eight: the last word on the wire holds the fifth and nothing else: %lx", (unsigned long)coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS]);
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed);
|
|
}
|
|
|
|
/* the same with the wire to the stuck node full: the NACK cannot go, and is counted */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
v4_cell lost;
|
|
v4_uheat_t c;
|
|
char text[64];
|
|
CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE : F 0 DO S\" HIT\" 11 SEND LOOP ;") && told(11, "55 3 ROUTE"), "node 12 has no way for 55, and a word that sends node 11 short texts");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C2 400000 0 DO LOOP S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2");
|
|
snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u));
|
|
CHECK(told(12, text) && going_from(far, 2)->used == V4_WIRE_CELLS, "node 11 is busy; node 12 fills the wire to it");
|
|
lost = pool[far].n.mem[LOST];
|
|
far_lost_wanted = lost + 1;
|
|
CHECK(run_until(far_has_lost, 30000000) && run() && pool[far].n.mem[LOST] == lost + 2 && going_from(far, 2)->used == V4_WIRE_CELLS && coming_to(far, 2)->used == 0,
|
|
"node 11 then sends 55 a message by node 12 and sticks: node 12 lets it go, and the NACK it owes has no room on the wire: both are counted: %ld", (long)(pool[far].n.mem[LOST] - lost));
|
|
c = clock_of(far);
|
|
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
|
|
CHECK(asleep(far) && clock_of(far) == c && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 12 is asleep, owing nothing, and it and node 10 answer");
|
|
}
|
|
|
|
/* ---- THE KERNEL ON A NODE'S PORT TAKES NO MESSAGES (MESH.md 7d.5, ruled 2026-10-08) ---- */
|
|
CHECK(triangle() && v4_fabric_wire_kernel(&f, far, 0, &kernel_dev), "the same made afresh, with a kernel on node 12's port 0");
|
|
{
|
|
kernel_words = 0;
|
|
CHECK(strcmp(tell(12, "7 0 PORT! 1 2 + ."), "3 ") == 0 && kernel_words == 1, "a word written to the kernel's port is taken, as ever");
|
|
CHECK(strstr(tell(12, ": S0 S\" 1 DROP\" 0 0 SEND-ON ; S0"), "No one on that port") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && kernel_words == 1,
|
|
"a message sent by that port is refused at once: \"%s\"", printed);
|
|
CHECK(told(12, "1 0 ROUTE"), "node 12 is told the way to its console is the kernel's port");
|
|
CHECK(is_empty(tell(12, "65 EMIT")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_ERROR && asleep(far),
|
|
"a text that prints cannot send what it printed: it ends in an error, said by the wire the text came on, and node 12 sleeps");
|
|
CHECK(is_empty(tell(12, ": P 3000 0 DO I . LOOP ; P")) && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(far) && pool[far].n.ds.depth == 0,
|
|
"one that prints more than the buffer holds ends in error 18 and does not wait: %ld", (long)pool[far].n.mem[NODE_ERROR]);
|
|
CHECK(told(12, "1 NO-ROUTE") && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && kernel_words == 1, "told to forget that way, it prints again; the kernel was sent nothing");
|
|
}
|
|
|
|
/* ---- acceptance 5: PRINTING IS NOT LOST ---- */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
static char want[32768];
|
|
size_t at = 0;
|
|
for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i);
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while");
|
|
forget();
|
|
saw_room_wait = 0; watch = watch_far_waits;
|
|
CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(),
|
|
"node 12 prints them while node 10, by which they go to the console, is busy with one line after another");
|
|
watch = 0;
|
|
CHECK(saw_room_wait > 0, "the wire to node 10 filled, and node 12 slept for room on it");
|
|
CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want));
|
|
CHECK(ended == 5 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0 && pool[far].n.mem[LINE_STATUS] == 1, "and then how the text ended; nothing was let go");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on");
|
|
}
|
|
|
|
/* ---- printing held by a stuck node (MESH.md 7d.6) ---- */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
v4_cell lost, refused;
|
|
v4_uheat_t c;
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE") && told(10, "77 3 ROUTE : X S\" 1 DROP\" 77 SEND ;"),
|
|
"node 12's way to the console is by node 11; node 10 is told the way to 77 is by node 12");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
(void)tell(12, "P");
|
|
CHECK(ended == 0 && waits_for_room(far) && going_from(far, 2)->used > V4_WIRE_CELLS - 8u - 71u && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u,
|
|
"node 12 prints until the wire to the stuck node is full but for the room kept for its answer, and sleeps for room: %u cells", going_from(far, 2)->used);
|
|
c = clock_of(far);
|
|
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
|
|
CHECK(waits_for_room(far) && clock_of(far) == c, "executing nothing");
|
|
lost = pool[far].n.mem[LOST]; refused = pool[hera].n.mem[REFUSED];
|
|
/* its way for 77 is the full wire: while one more fits beside the room kept it is passed on, and
|
|
* after that it is refused and node 10 told. It is never kept. */
|
|
for (i = 0; i < 12 && pool[far].n.mem[LOST] == lost; i++) {
|
|
unsigned before = going_from(far, 2)->used;
|
|
CHECK(is_empty(tell(10, "X")) && ended == 1 && ended_from == 10, "node 10 sends a message for node 77 by node 12");
|
|
CHECK(coming_to(far, 3)->used == 0 && waits_for_room(far), "node 12, woken, does not keep it, and sleeps for room again");
|
|
CHECK((going_from(far, 2)->used == before + 9u && pool[far].n.mem[LOST] == lost) || (going_from(far, 2)->used == before && pool[far].n.mem[LOST] == lost + 1),
|
|
"it is passed on, or it is refused: %u cells then, %u now", before, going_from(far, 2)->used);
|
|
}
|
|
CHECK(pool[far].n.mem[LOST] == lost + 1 && pool[hera].n.mem[REFUSED] == refused + 1 && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u,
|
|
"when one more would take the room kept for the answer it is refused, and node 10 told: the 8 cells are still there (%u used)", going_from(far, 2)->used);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile");
|
|
lost = pool[far].n.mem[LOST];
|
|
CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed");
|
|
stuck = 0;
|
|
CHECK(run() && asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[OUT_PTR] == OUT_W,
|
|
"node 12's text ends in error 18, No one on that port, and it sleeps: error %ld, ended %ld", (long)pool[far].n.mem[NODE_ERROR], (long)pool[far].n.mem[LINE_STATUS]);
|
|
CHECK(pool[far].n.mem[LOST] == lost + 2, "the error's message and the word of how the text ended had no one to go to, and are counted: %ld", (long)(pool[far].n.mem[LOST] - lost));
|
|
(void)tell(12, "NO-ROUTES 3 DEFAULT-ROUTE");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && asleep(far) && asleep(hera), "told another way to the console, it goes on: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- NOTHING BREAKS A WAIT FOR ROOM TO PRINT (MESH.md 7d.4, ruled 2026-10-08) ----
|
|
* A line typed ahead while a text prints more than the wire holds does not cut the printing short. */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
static char want[32768];
|
|
size_t at = 0;
|
|
for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i);
|
|
snprintf(want + at, sizeof want - at, "56 ");
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while");
|
|
forget();
|
|
saw_room_wait = 0; watch = watch_far_waits;
|
|
CHECK(put_text(10, "SLOW") && put_text(12, "P") && put_text(12, "7 8 * .") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(),
|
|
"node 12 is typed two lines, the first of which prints more than the wire holds, while node 10 is busy");
|
|
watch = 0;
|
|
CHECK(saw_room_wait > 0 && strstr(printed, "Interrupted") == NULL, "node 12 slept for room to print with the second line waiting on its wire, and was not interrupted");
|
|
CHECK(strcmp(printed, want) == 0 && ended == 5 && pool[far].n.mem[LOST] == 0, "the first text printed everything, and then the second line was done: %u characters of %u, %d ended", printed_len, (unsigned)strlen(want), ended);
|
|
}
|
|
/* and with a stuck node on the way, where the room never comes, text for the node is left on its wire */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE"), "node 12's way to the console is by node 11 again");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
(void)tell(12, "P");
|
|
CHECK(ended == 0 && waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0, "node 11 is stuck, and node 12 sleeps for room to print");
|
|
(void)tell(10, ": T12 S\" 1 DROP\" 12 SEND ; T12");
|
|
(void)tell(12, "65 EMIT");
|
|
CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && pool[far].n.mem[QUIET] == 0 && coming_to(far, 3)->used == 18,
|
|
"text for node 12, from another node and from its console, is left on the wire: its text is not ended, and it sleeps on: error %ld, %u cells",
|
|
(long)pool[far].n.mem[NODE_ERROR], coming_to(far, 3)->used);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile");
|
|
}
|
|
|
|
/* ---- PRINTING THROUGH A MIDDLE NODE IS NOT LOST (ruled 2026-10-08) ----
|
|
* In the row, what node 12 prints goes by node 11 to node 10, which is busy. The wire from 11 to 10
|
|
* fills; node 11 does not let go what it has no room to pass on: it leaves it on the wire from 12, which
|
|
* fills in its turn, and node 12 waits. */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
static char want[32768];
|
|
size_t at = 0;
|
|
for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i);
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while");
|
|
forget();
|
|
saw_room_wait = 0; saw_mid_hold = 0; watch = watch_mid_holds;
|
|
CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(),
|
|
"node 12 prints them while node 10 is busy with one line after another");
|
|
watch = 0;
|
|
CHECK(saw_mid_hold > 0, "node 11 left output on the wire it came by, for want of room, and slept for room");
|
|
CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want));
|
|
CHECK(ended == 5 && ended_at <= printed_len && pool[far].n.mem[LINE_STATUS] == 1 && pool[hera].n.mem[LOST] == 0 && pool[mid].n.mem[LOST] == 0 && pool[far].n.mem[LOST] == 0 && pool[far].n.mem[REFUSED] == 0,
|
|
"and then how the text ended; nothing was let go anywhere: %ld %ld %ld", (long)pool[hera].n.mem[LOST], (long)pool[mid].n.mem[LOST], (long)pool[far].n.mem[LOST]);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[mid].n.mem[H_PORT] == -1, "all three sleep, node 11 for a message and not for room");
|
|
|
|
/* the middle node removed while it holds output: node 12's text ends in an error; node 10 goes on */
|
|
CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "the same again");
|
|
forget();
|
|
CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000),
|
|
"node 11 is holding output, asleep for room");
|
|
CHECK(v4_fabric_remove(&f, mid) == &pool[1] && run(), "node 11 is removed, and the two that are left come to rest");
|
|
CHECK(asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[KEEP] == -1, "node 12's text has ended in error 18, No one on that port, and it sleeps");
|
|
CHECK(pool[far].n.ds.depth == 0, "with nothing left on its data stack of what the word that was printing had there: %u", (unsigned)pool[far].n.ds.depth);
|
|
CHECK(strncmp(printed, want, printed_len) == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "what had arrived was in order, and node 10 goes on");
|
|
|
|
/* the node that prints removed while the middle node holds its output: what is on the wires arrives */
|
|
CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "and again");
|
|
forget();
|
|
CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), "node 11 is holding output again");
|
|
CHECK(v4_fabric_remove(&f, far) == &pool[2] && run(), "node 12 is removed, and the two that are left come to rest");
|
|
CHECK(asleep(mid) && asleep(hera) && pool[mid].n.mem[H_PORT] == -1 && coming_to(hera, 2)->used == 0, "node 11 sleeps, holding nothing; nothing is left on the wire to node 10");
|
|
CHECK(printed_len > 0 && strncmp(printed, want, printed_len) == 0 && ended >= 4, "what node 12 had printed before it went arrived, in order: %u characters", printed_len);
|
|
CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and nodes 10 and 11 go on");
|
|
}
|
|
|
|
/* ---- A TAKE THAT DOES NOT TAKE (fix round 1, F1) ----
|
|
* The wire is cut at every step from a message's coming to its being taken. If it was still on the wire
|
|
* when the wire was cut it was not taken, and nothing may be done for it: not the text before it run
|
|
* again, not a refusal counted, not a wait ended with an old answer, not a way forgotten. */
|
|
{
|
|
unsigned k, cut_before = 0, cut_after = 0, wrong = 0;
|
|
CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire");
|
|
keep();
|
|
for (k = 0; k < 200; k++) { /* text, taken by (IDLE) */
|
|
int there;
|
|
v4_cell lost;
|
|
put_back();
|
|
lost = pool[far].n.mem[LOST];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = far_has_one();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(far) || !v4_fabric_wire(&f, mid, 3, far, 2)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; }
|
|
if (strcmp(tell(12, "N @ ."), there ? "1 " : "11 ") != 0 || pool[far].n.mem[LOST] != lost + (there ? 0 : 1)) {
|
|
if (!wrong) printf(" cut %u steps after the text came, it %s on the wire: N is %s, %ld counted lost\n", k, there ? "still" : "no longer", printed, (long)(pool[far].n.mem[LOST] - lost));
|
|
wrong++;
|
|
}
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "text: cut before it was taken (%u times) nothing was done; cut after (%u), the text was done once, and its answer counted: %u wrong", cut_before, cut_after, wrong);
|
|
|
|
cut_before = cut_after = wrong = 0;
|
|
CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000), "a NACK has come to node 12's wire");
|
|
keep();
|
|
for (k = 0; k < 200; k++) { /* a NACK, taken by (IDLE) */
|
|
int there;
|
|
v4_cell refused;
|
|
put_back();
|
|
refused = pool[far].n.mem[REFUSED];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = far_has_nack();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(far)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; }
|
|
if (pool[far].n.mem[REFUSED] != refused + (there ? 0 : 1)) wrong++;
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a NACK: cut before it was taken (%u times) no refusal was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
|
|
|
|
cut_before = cut_after = wrong = 0;
|
|
CHECK(row() && told(12, ": Q S\" 1 DROP\" 11 SEND 11 AWAIT 7 + . ;") && put_text(12, "Q") && run_until(far_awaits_and_has_one, 1000000), "an answer has come to the wire of node 12, which waits for it");
|
|
keep();
|
|
for (k = 0; k < 200; k++) { /* an answer, taken by AWAIT */
|
|
int there;
|
|
put_back();
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = far_has_one();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(far)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; }
|
|
/* not taken: it waits on. Taken: the wait ended with the answer, 1, and "8 " was printed -- to a wire with no one on it. */
|
|
if (there ? !(pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[QUIET] == 0) : !(pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[QUIET] == 1)) wrong++;
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 5 && cut_after > 0, "an answer: cut before it was taken (%u times) the wait went on; cut after (%u), it ended: %u wrong", cut_before, cut_after, wrong);
|
|
|
|
cut_before = cut_after = wrong = 0;
|
|
CHECK(row() && told(12, "55 2 ROUTE 44 2 ROUTE") && told(11, "55 12 GONE") && told(12, "55 2 ROUTE") && told(11, ": G44 44 12 GONE ;") && pool[far].n.mem[ROUTE_COUNT] == 2
|
|
&& put_text(11, "G44") && run_until(far_has_one, 1000000), "a GONE from its centre has come to node 12's wire");
|
|
keep();
|
|
for (k = 0; k < 200; k++) { /* a GONE, taken by (IDLE): node 11 is node 12's centre */
|
|
int there;
|
|
v4_cell ways;
|
|
put_back();
|
|
ways = pool[far].n.mem[ROUTE_COUNT];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = far_has_one();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(far)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; }
|
|
if (pool[far].n.mem[ROUTE_COUNT] != ways - (there ? 0 : 1)) wrong++;
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a GONE: cut before it was taken (%u times) no way was forgotten; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
|
|
}
|
|
|
|
/* ---- ONLY WHAT WAS LOOKED AT IS TAKEN (fix round 2, NP1; MESH.md 7d.3) ----
|
|
* Between the look and the take the wire is cut, made again, and a different message put on it. */
|
|
{
|
|
unsigned k, tried = 0, wrong = 0, hung = 0;
|
|
static v4_message big;
|
|
static char blanks[1025];
|
|
/* text was looked at; a NACK is there now. The NACK must not be done as text: it is a refusal, and counted as one. */
|
|
CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire");
|
|
keep();
|
|
for (k = 0; k < 200; k++) {
|
|
static const v4_cell hd[V4_WIRE_HEADER] = { 12, 11, V4_MSG_NACK, 16, 0, 0, 4 }, t[1] = { 'T' | ('2' << 8) };
|
|
v4_cell refused;
|
|
put_back();
|
|
refused = pool[far].n.mem[REFUSED];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
if (!far_has_one()) break;
|
|
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, hd, t) != V4_WIRE_DONE) { wrong += 1000; break; }
|
|
tried++;
|
|
if (!run()) { hung++; continue; }
|
|
if (strcmp(tell(12, "N @ ."), "1 ") != 0 || pool[far].n.mem[REFUSED] != refused + 1 || pool[far].n.mem[ME] != 12) wrong++;
|
|
}
|
|
CHECK(tried > 100 && wrong == 0 && hung == 0, "text looked at, and a NACK there when it comes to take: at each of %u steps the NACK was counted as a refusal and nothing was done as text: %u wrong, %u hung", tried, wrong, hung);
|
|
|
|
/* a NACK was looked at; a text of 1,024 characters is there now. Its 256 cells must not be taken where one was looked for. */
|
|
memset(blanks, ' ', 1024);
|
|
CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000) && v4_message_text(&big, 12, 11, V4_MSG_TEXT, blanks, 1024), "a NACK has come to node 12's wire");
|
|
keep();
|
|
tried = wrong = hung = 0;
|
|
for (k = 0; k < 200; k++) {
|
|
static v4_cell before[V4_NODE_WORDS];
|
|
v4_cell at;
|
|
unsigned changed = 0;
|
|
put_back();
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
if (!far_has_nack()) break;
|
|
memcpy(before, pool[far].n.mem, sizeof before);
|
|
if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, big.word, big.word + V4_MSG_HEADER) != V4_WIRE_DONE) { wrong += 1000; break; }
|
|
tried++;
|
|
if (!run()) { hung++; continue; }
|
|
/* the cells after (ONE), as far as the variables go, are as they were: the text went to TIB, where text goes */
|
|
for (at = ONE + 1; at < BUF0_W; at++) if (at != LINE_STATUS && at != QUIET && at != DONE_PORT && at != KEEP && at != REPLY && !(at >= MSG && at < MSG + 7) && at != PRINT_TO && at != PRINT_PORT
|
|
&& !(at >= S_ON && at <= S_HAVE) && at != DEFER && pool[far].n.mem[at] != before[at]) changed++;
|
|
if (changed || pool[far].n.mem[ME] != 12 || strcmp(tell(12, "1 2 + ."), "3 ") != 0) wrong++;
|
|
}
|
|
CHECK(tried > 100 && wrong == 0 && hung == 0, "a NACK looked at, and a text of 1,024 characters there when it comes to take: at each of %u steps nothing was overwritten, and node 12 answers: %u wrong, %u hung", tried, wrong, hung);
|
|
}
|
|
|
|
/* ---- A DROP THAT DOES NOT DROP (fix round 2, NP3) ----
|
|
* The wire is cut between the look and the drop: the cut let the message go, and nothing is counted or told for it. */
|
|
{
|
|
unsigned k, cut_before = 0, cut_after = 0, wrong = 0;
|
|
/* (REFUSE): node 11 has no way for what node 12 sends 99 */
|
|
mid_for = 99;
|
|
CHECK(row() && told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE") && told(12, ": S99 S\" 1 DROP\" 99 SEND ;") && put_text(12, "S99") && run_until(mid_has_one, 1000000), "a message node 11 must refuse has come to its wire");
|
|
keep();
|
|
for (k = 0; k < 320; k++) {
|
|
int there;
|
|
v4_cell lost;
|
|
put_back();
|
|
lost = pool[mid].n.mem[LOST];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = mid_has_one();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(mid)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else cut_after++;
|
|
/* cut after the drop the message is counted; and its NACK too, if the cut came before that was put */
|
|
if (there ? pool[mid].n.mem[LOST] != lost : !(pool[mid].n.mem[LOST] == lost + 1 || pool[mid].n.mem[LOST] == lost + 2)) wrong++;
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a refusal: cut before the drop (%u times) nothing was counted; cut after (%u), the message, and its NACK if it could not go: %u wrong", cut_before, cut_after, wrong);
|
|
|
|
/* (IDLE): what node 12 prints comes to node 11, which is not a console: it is let go, and counted */
|
|
cut_before = cut_after = wrong = 0;
|
|
mid_for = 11;
|
|
CHECK(row() && told(12, "11 (CONSOLE) !") && put_text(12, "65 EMIT") && run_until(mid_has_one, 1000000), "what a node printed has come to the wire of a node that is no console");
|
|
keep();
|
|
for (k = 0; k < 320; k++) {
|
|
int there;
|
|
v4_cell lost;
|
|
put_back();
|
|
lost = pool[mid].n.mem[LOST];
|
|
for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); }
|
|
there = mid_has_one();
|
|
(void)v4_fabric_unwire(&f, mid, 3);
|
|
if (!run() || !asleep(mid)) { wrong += 1000; break; }
|
|
if (there) cut_before++; else cut_after++;
|
|
if (pool[mid].n.mem[LOST] != lost + (there ? 0 : 1)) wrong++;
|
|
}
|
|
CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "what should not have come: cut before the drop (%u times) nothing was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong);
|
|
}
|
|
|
|
/* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ----
|
|
* Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to
|
|
* node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text
|
|
* that prints. Node 12 begins it -- there is room for its answer -- and its printing waits for room, since
|
|
* it may not take the 8 cells kept; when node 10 takes what is on the wire, the printing goes and then
|
|
* the answer, which node 10 is waiting for. */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
CHECK(told(11, "1 3 ROUTE : F1 0 DO S\" HIT\" 1 SEND LOOP ;") && told(10, ": WAIT 60000 0 DO LOOP ;")
|
|
&& told(10, ": G S\" 127 F1\" 11 SEND WAIT S\" 65 EMIT\" 12 SEND WAIT WAIT 12 AWAIT . ;"), "nodes 11 and 10 are given their words");
|
|
saw_room_wait = 0; saw_full = 0; watch = watch_far_waits;
|
|
(void)tell(10, "G");
|
|
watch = 0;
|
|
CHECK(saw_full > 0, "node 12 slept for room to print with the wire to node 10 holding exactly the 8 cells kept for its answer (%u, %u)", saw_room_wait, saw_full);
|
|
CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0,
|
|
"what it printed arrived, and then how the text ended, which node 10 waited for: the answer was not refused: \"%s\"", printed);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[far].n.mem[KEEP] == -1, "all three sleep, and node 12 keeps no room");
|
|
}
|
|
|
|
/* ---- Review Focus 2 and 3: LEFT FOR ITSELF ----
|
|
* Node 10 waits for node 12's answer. On the one wire, from node 11, there then come: text for node 10
|
|
* itself; a message for the console; and node 12's answer. */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
v4_uheat_t c;
|
|
CHECK(told(12, ": LL 400000 0 DO LOOP ;") && told(11, ": S1 S\" 66 EMIT\" 10 SEND ;"), "node 12 has a word that takes a long while, node 11 one that sends node 10 text");
|
|
forget();
|
|
stuck = 1u << far; /* it is busy, not stuck: it is not waited for */
|
|
CHECK(put_text(10, ": Q S\" LL\" 12 SEND 12 AWAIT . ; Q") && run() && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera) && ended == 0,
|
|
"node 10 sends node 12 text that takes a long while, and waits for its answer, asleep");
|
|
CHECK(is_empty(tell(11, "S1")) && ended == 1 && ended_from == 11, "node 11 sends node 10 text, and then word of how its own text ended, for the console: that is passed on by node 10");
|
|
CHECK(coming_to(hera, 2)->used == 9 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "the text for node 10 itself is left on the wire, in front, and node 10 waits on, asleep: %u cells", coming_to(hera, 2)->used);
|
|
c = clock_of(hera);
|
|
for (i = 0; i < 2000; i++) (void)v4_fabric_step(&f);
|
|
CHECK(clock_of(hera) == c && asleep(hera), "with a message for itself on a wire it executes nothing until another comes: it does not go round");
|
|
stuck = 0;
|
|
forget();
|
|
CHECK(run() && strcmp(printed, "1 B") == 0 && ended == 1 && ended_at == 2 && pool[hera].n.mem[AWAIT_FROM] == 0,
|
|
"node 12's answer comes behind it and ends the wait; and then the text that was left is done: \"%s\"", printed);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far) && coming_to(hera, 2)->used == 0, "all three sleep, with nothing left on the wire");
|
|
}
|
|
|
|
/* ---- acceptance 8: ORDER ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
CHECK(told(11, "12 2 ROUTE") && told(12, ": SLOW 30000 0 DO LOOP ;"), "node 11 is told the way to node 12 is by node 10; node 12 is given a word that takes a while");
|
|
CHECK(strcmp(tell(11, ": TRIO S\" SLOW 68 EMIT\" 12 SEND S\" 69 EMIT\" 12 SEND S\" 70 EMIT\" 12 SEND ; TRIO"), "DEF") == 0,
|
|
"it sends node 12 three texts, the first slow to do: the other two wait on the wire, and they are done in the order they were sent: \"%s\"", printed);
|
|
CHECK(strcmp(tell(12, ": TWO 65 EMIT 66 EMIT NOSUCHWORD ; 67 EMIT"), "UNKNOWN WORD: 'NOSUCHWORD'\n") == 0 && ended == 1 && ended_at == printed_len,
|
|
"what a text prints, and then how it ended, reach the console in that order");
|
|
|
|
/* ---- AWAIT's endings (MESH.md 7b) ---- */
|
|
CHECK(row(), "a row made afresh");
|
|
/* refused: a NACK about the node waited for */
|
|
{
|
|
v4_cell refused = pool[far].n.mem[REFUSED], lost = pool[hera].n.mem[LOST];
|
|
CHECK(is_empty(tell(12, ": N77 S\" 1 DROP\" 77 SEND ; N77")) && ended_how == V4_TEXT_COMPLETED, "node 12 sends text to node 77: it has a way for everything, toward 11");
|
|
CHECK(pool[hera].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "node 10, two away, has no way to 77 and counts the message; node 12 is told, and counts the refusal");
|
|
CHECK(strstr(tell(12, ": W77 S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && ended_from == 12,
|
|
"a node that sends to 77 and waits for its answer: the wait ends, with an error that says the message was refused: \"%s\"", printed);
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[REFUSED] == refused + 1, "and it goes on to its next line, waiting for no one");
|
|
/* the refusal came before the wait began: it is on the wire, and is found there */
|
|
CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[far].n.mem[AWAIT_FROM] == 0 && asleep(far),
|
|
"node 12 sends to 77, is busy a while, and then waits: the refusal is on its wire already, and the wait ends at once: \"%s\"", printed);
|
|
/* a NACK about another node does not end it, and is counted */
|
|
refused = pool[far].n.mem[REFUSED];
|
|
(void)tell(12, ": QO S\" 1 DROP\" 77 SEND 11 AWAIT . ; QO");
|
|
CHECK(ended == 0 && pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[REFUSED] == refused + 1 && asleep(far), "waiting on node 11, it is told its message for 77 was refused: that is counted, and it waits on");
|
|
}
|
|
/* gone: a GONE from its centre, and not from another */
|
|
{
|
|
v4_cell ways = pool[far].n.mem[ROUTE_COUNT];
|
|
CHECK(told(11, "44 3 ROUTE"), "node 11 is given a way");
|
|
ways = pool[mid].n.mem[ROUTE_COUNT];
|
|
CHECK(is_empty(tell(11, "44 AWAIT .")) && ended == 0 && pool[mid].n.mem[AWAIT_FROM] == 44, "node 11 waits for an answer from a node 44");
|
|
CHECK(is_empty(tell(10, ": G12 44 11 GONE ;")) && told(10, "12 2 ROUTE"), "(node 10 is given a word)");
|
|
/* node 12 is still waiting on 11: a line from the console ends that */
|
|
CHECK(strstr(tell(12, "1 DROP"), "Interrupted") != NULL, "(node 12's wait is ended from the console)");
|
|
CHECK(told(12, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 44 && pool[mid].n.mem[ROUTE_COUNT] == ways,
|
|
"node 12 tells it that 44 is gone: node 12 is not its centre, and it goes on waiting, its ways as they were");
|
|
CHECK(strstr(tell(10, "G12"), "Node gone") != NULL && printed_from == 11 && pool[mid].n.mem[AWAIT_FROM] == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways - 1,
|
|
"node 10, its centre, tells it: the wait ends with an error that says so, and the way is forgotten: \"%s\"", printed);
|
|
CHECK(asleep(mid) && coming_to(mid, 3)->used == 0 && coming_to(mid, 2)->used == 0, "it sleeps; the GONE it did not believe was not kept");
|
|
(void)tell(11, "33 AWAIT .");
|
|
CHECK(ended == 0 && told(10, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 33 && asleep(mid), "a GONE from its centre about another node does not end a wait");
|
|
}
|
|
/* a GONE that says it is from the centre but comes by another port is not believed (MESH.md 7b.4) */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
{
|
|
v4_cell ways;
|
|
CHECK(told(11, "44 3 ROUTE") && told(12, ": FORGE 10 (ME) ! 44 11 GONE 12 (ME) ! ;"), "node 11 has a way to 44; node 12 has a word that sends a GONE saying it is from node 10");
|
|
ways = pool[mid].n.mem[ROUTE_COUNT];
|
|
CHECK(told(12, "FORGE") && asleep(mid) && coming_to(mid, 3)->used == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways,
|
|
"it comes to node 11 by the wire from node 12, not by its centre's: it is not believed, and the way stays: %ld ways of %ld", (long)pool[mid].n.mem[ROUTE_COUNT], (long)ways);
|
|
CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, "from node 10, by its centre's port, it is");
|
|
}
|
|
/* interrupted: text from the console */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
const char *r;
|
|
CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "node 10 waits for an answer from 12, which is not going to send one");
|
|
CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && ended == 1 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera),
|
|
"text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and what comes back, and goes on waiting");
|
|
r = tell(10, "65 EMIT");
|
|
CHECK(strcmp(r, "Interrupted\nA") == 0 && ended == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so, and is then done: \"%s\"", r);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on as before");
|
|
CHECK(strstr(tell(10, "5 NO-ROUTE"), "ERROR") == NULL && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "forgetting the way to a node there was no way to changes nothing");
|
|
}
|
|
|
|
/* ---- an answer refused on its way back; a message that would go round for ever ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
{
|
|
v4_cell refused, l10, l11, r12;
|
|
/* a message that would go round for ever is refused after 16 nodes have passed it on */
|
|
refused = pool[hera].n.mem[REFUSED]; l10 = pool[hera].n.mem[LOST]; l11 = pool[mid].n.mem[LOST];
|
|
CHECK(told(10, "66 2 ROUTE"), "node 10 is told the way to 66 is by node 11, whose way for everything it has not been told of is back by node 10");
|
|
CHECK(is_empty(tell(10, ": H66 S\" 1 DROP\" 66 SEND ; H66")) && ended == 1 && ended_how == V4_TEXT_COMPLETED, "node 10 sends 66 a message: it comes to rest");
|
|
CHECK(pool[hera].n.mem[LOST] + pool[mid].n.mem[LOST] == l10 + l11 + 1 && pool[hera].n.mem[REFUSED] == refused + 1,
|
|
"one of the two let it go when it had been passed on as often as a message may be, and node 10 was told it was refused");
|
|
nacks = 0;
|
|
CHECK(is_empty(tell(66, "1 DROP")) && nacks == 1 && nack_about == 66, "the same from the console, which sends it with no count: it too is let go, and the console told");
|
|
CHECK(strstr(tell(10, ": W66 S\" 1 DROP\" 66 SEND 66 AWAIT . ; W66"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[AWAIT_FROM] == 0,
|
|
"and waiting for 66's answer, node 10 is the one that lets its own message go when it comes back the last time: its wait ends, the message refused: \"%s\"", printed);
|
|
CHECK(told(10, "66 NO-ROUTE") && asleep(hera) && asleep(mid) && asleep(far), "the way is forgotten, and all three are at rest");
|
|
|
|
/* An answer that cannot be passed on: the NACK is for the node the answer was for, not the node that
|
|
* answered. Here it cannot go either -- it has the answer's own way to go -- and whoever waits is
|
|
* not told: MESH.md 7d.6. That wait is ended from the console. */
|
|
r12 = pool[far].n.mem[REFUSED]; l11 = pool[mid].n.mem[LOST];
|
|
CHECK(told(12, "10 2 ROUTE") && told(11, "10 4 ROUTE"), "node 12 is told the way to node 10 is by node 11, which is told it is by a port with no one on it");
|
|
(void)tell(10, ": RA S\" 1 DROP\" 12 SEND 12 AWAIT . ; RA");
|
|
CHECK(ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && pool[mid].n.mem[LOST] == l11 + 2,
|
|
"node 10 sends node 12 text and waits; the answer comes back by node 11, which cannot pass it on, nor the NACK for it: both are counted: %ld", (long)(pool[mid].n.mem[LOST] - l11));
|
|
CHECK(pool[far].n.mem[REFUSED] == r12 && asleep(far), "node 12, which answered, is told nothing: the NACK was not for it");
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "Interrupted\n3 ") == 0 && asleep(hera), "node 10's wait is ended from the console: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- the stacks, between texts (MESH.md 7c.6, 7d.4) ----
|
|
* After a text that left nothing on the data stack, what a node's stacks hold while it does nothing that
|
|
* is a text's is what the nucleus uses: passing on, refusing, taking in, and sending what a text printed and
|
|
* how it ended. It is looked at once an instruction word, so it is the least that was used; that 28
|
|
* values may wait is measured in test_host_quit.c and, for passing on and refusing, in test_host_depth.c.
|
|
* The return stack is empty when a text ends; (FINISH)'s wait for room goes seven deep on it. */
|
|
CHECK(stale_out == 0, "no message of what a node printed carried anything past its last character: %u did", stale_out);
|
|
printf(" between texts the nucleus used at most %u cells of the data stack and %u entries of the return stack\n", between_data, between_ret);
|
|
CHECK(between_data <= 4 && between_ret <= 7, "between texts: four cells of the data stack, and seven return entries");
|
|
|
|
|
|
/* ---- A WORD WRITTEN TO A PORT, PORT!, HOLDS NO ONE (found by the final review of step 6e) ----
|
|
* A node's neighbours speak whole messages and are asleep at their wires; so is its console. A word
|
|
* written to one of them will never be read. The line that wrote it ends in an error and the node goes
|
|
* on; before, it was blocked for ever, and every node beyond it was cut off. */
|
|
CHECK(row(), "a row made afresh");
|
|
step_cap = 2000000;
|
|
CHECK(strstr(tell(10, "5 2 PORT! 65 EMIT"), "No one on that port") != NULL && strchr(printed, 'A') == NULL && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(hera),
|
|
"node 10 writes a word to node 11's port: its line ends No one on that port: \"%s\"", printed);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "and all three answer");
|
|
CHECK(strstr(tell(10, "5 1 PORT!"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR && strcmp(tell(10, "1 2 + ."), "3 ") == 0,
|
|
"the same for a word written to the console's port: \"%s\"", printed);
|
|
CHECK(strstr(tell(11, "5 3 PORT!"), "No one on that port") != NULL && ended_from == 11 && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(11, "20 22 + ."), "42 ") == 0,
|
|
"and for node 11 writing to node 12's: node 12 is not cut off");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
CHECK(ended == 0 && !asleep(mid), "node 11 is stuck in a loop: it does not sleep");
|
|
CHECK(strstr(tell(10, "5 2 PORT!"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR && strcmp(tell(10, "1 2 + ."), "3 ") == 0,
|
|
"a word node 10 writes to it is refused all the same, and node 10 can be typed to: \"%s\"", printed);
|
|
stuck = 0;
|
|
step_cap = 20000000;
|
|
|
|
/* ---- TWO TEXTS FROM ONE NODE ARE BEGUN IN THE ORDER THEY CAME (acceptance 8; found by the final review) ----
|
|
* Node 12 has two texts from node 10 on its wire, and the wire their answers go by has 7 cells free:
|
|
* neither can be begun. Node 11, which was not taking, then takes a message off that wire -- at every
|
|
* step in turn, so that the room comes before node 12 looks at the first text, between its look at the
|
|
* first and at the second, and after both. Whenever it comes, the first is begun first. */
|
|
{
|
|
static v4_cell big[V4_WIRE_HEADER + V4_MSG_MAX_CHARS / 4u], hd[V4_WIRE_HEADER];
|
|
static v4_message m;
|
|
unsigned k, wrong = 0, first_wrong = 0, undone = 0;
|
|
v4_wire_queue *out, *in;
|
|
CHECK(row() && told(12, ": X1 65 EMIT ; : X2 66 EMIT ;"), "a row made afresh; node 12 has two words that print");
|
|
v4_fabric_sleep(&f, mid); /* node 11 takes nothing for now */
|
|
out = places[far].wire[2].tx; in = places[far].wire[2].rx;
|
|
memset(big, 0, sizeof big);
|
|
big[0] = 1; big[1] = 12; big[2] = V4_MSG_OUTPUT; big[3] = 16;
|
|
big[6] = 1024; for (i = 0; i < 3; i++) (void)v4_wire_put(out, big, big + V4_WIRE_HEADER);
|
|
big[6] = 884; (void)v4_wire_put(out, big, big + V4_WIRE_HEADER);
|
|
CHECK(V4_WIRE_CELLS - out->used == 7, "the wire from node 12 to node 11 has 7 cells free: %u", V4_WIRE_CELLS - out->used);
|
|
CHECK(v4_message_text(&m, 12, 10, V4_MSG_TEXT, "X1", 2) && (m.word[3] = 16, m.word[5] = 1, v4_wire_put(in, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE)
|
|
&& v4_message_text(&m, 12, 10, V4_MSG_TEXT, "X2", 2) && (m.word[3] = 16, m.word[5] = 2, v4_wire_put(in, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE),
|
|
"two texts from node 10 are on the wire to node 12, numbered 1 and 2");
|
|
keep();
|
|
for (k = 0; k < 700; k++) {
|
|
unsigned began = 0, seq[2] = { 0, 0 };
|
|
put_back();
|
|
for (i = 0; i < 100000 && began < 2; i++) {
|
|
const v4_node *n = &pool[far].n;
|
|
int kept = n->mem[KEEP] >= 0;
|
|
if (i == k) { v4_wire_first(out); (void)v4_wire_look(out, hd); (void)v4_wire_drop(out); }
|
|
(void)v4_fabric_step(&f); (void)console_takes();
|
|
if (!kept && n->mem[KEEP] >= 0) seq[began++] = (unsigned)n->mem[MSG + 5];
|
|
}
|
|
if (began < 2) undone++;
|
|
else if (seq[0] != 1 || seq[1] != 2) { if (!wrong) first_wrong = k; wrong++; }
|
|
}
|
|
CHECK(undone == 0 && wrong == 0, "whenever the room came, both texts were done, the first first: %u not done, %u in the wrong order, the first when it came at step %u", undone, wrong, first_wrong);
|
|
}
|
|
|
|
/* ---- A NODE REMOVED, OR A WIRE CUT, AT EVERY STEP (MESH.md 7d.7, acceptance 6) ----
|
|
* Three exchanges, each played once whole and then once for every step it takes with node 11 removed at
|
|
* that step, again with node 12 removed, and again with the wire between them cut and made again: the
|
|
* nodes left come to rest, blocked at nothing, and answer.
|
|
* What is typed is a word defined beforehand, and so is what a node left is asked (Q, which is
|
|
* `1 2 + .`): a line of several words is mostly the dictionary searched, thousands of steps in which the
|
|
* mesh does nothing, and every one of them would be a removal played to the end.
|
|
* WHAT "ANSWERS" MEANS HERE. A node that was waiting for an answer from a node now removed, or for one
|
|
* that was on a wire now cut, is still waiting when the mesh comes to rest: removing a node tells no one
|
|
* (Hera's KILL does: test_host_unit.c). Its wait is ended by the line it is then typed, "Interrupted",
|
|
* and it answers. */
|
|
{
|
|
static const struct { const char *what, *set10, *set11, *set12; v4_cell node; const char *text; } ex[3] = {
|
|
{ "text from the console for node 12, what it prints and how it ended", "1 DROP", "1 DROP", ": A 7 8 * . ;", 12, "A" },
|
|
{ "node 11 SENDs node 12 text and waits for its answer", "1 DROP", ": T S\" Q\" 12 SEND 12 AWAIT . ;", "1 DROP", 11, "T" },
|
|
{ "a message node 12 has no way for, and its NACK to node 10", "55 2 ROUTE : N S\" 1 DROP\" 55 SEND ;", "55 3 ROUTE", "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 11 2 ROUTE", 10, "N" }
|
|
};
|
|
static const char *const how_name[4] = { "", "node 11 removed", "node 12 removed", "the wire between nodes 11 and 12 cut and made again" };
|
|
unsigned e, g, whole, tried, wrong, first, all = 0;
|
|
step_cap = 2000000;
|
|
for (e = 0; e < 3; e++) {
|
|
CHECK(row() && told(10, ex[e].set10) && told(11, ex[e].set11) && told(12, ex[e].set12) && told(10, ASK) && told(11, ASK) && told(12, ASK), "a row made afresh for: %s", ex[e].what);
|
|
step_cap = 2000000;
|
|
keep();
|
|
start_node = ex[e].node; start_text = ex[e].text;
|
|
for (g = 1; g <= 3; g++) {
|
|
whole = disturbed_at_every_step(start_plain, g, 0, &tried, &wrong, &first);
|
|
all += tried;
|
|
CHECK(whole > 0 && wrong == 0, "%s, in %u steps: with %s at each of them the rest come to rest and answer: %u did not, the first at step %u",
|
|
ex[e].what, whole, how_name[g], wrong, first);
|
|
}
|
|
}
|
|
put_back();
|
|
CHECK(is_empty(tell(10, "N")) && pool[hera].n.mem[REFUSED] == 1 && pool[far].n.mem[LOST] == 1, "the last of them, whole: node 12 let the message go and node 10 was told it was refused");
|
|
|
|
/* and with what node 12 prints held at node 11, which sleeps for room: at every step at which
|
|
* anything on a wire, or what a node asks of one, has changed */
|
|
CHECK(row() && told(12, ": A 7 8 * . ;") && told(10, ": SLOW 3000 0 DO LOOP ;") && told(10, ASK) && told(11, ASK) && told(12, ASK), "a row made afresh for output held at a full wire");
|
|
step_cap = 2000000;
|
|
keep();
|
|
for (g = 1; g <= 3; g++) {
|
|
whole = disturbed_at_every_step(start_held, g, 1, &tried, &wrong, &first);
|
|
all += tried;
|
|
CHECK(whole > 0 && saw_mid_hold > 0, "text for node 12 that prints, node 10 busy and the wire to it full: node 11 held what was printed, asleep for room, for %u of %u steps", saw_mid_hold, whole);
|
|
CHECK(tried > 20 && wrong == 0, "with %s at each of the %u steps at which a wire or a node's asking changed, the rest come to rest and answer: %u did not, the first at step %u",
|
|
how_name[g], tried, wrong, first);
|
|
}
|
|
printf(" a node removed or a wire cut: %u times in all\n", all);
|
|
step_cap = 20000000;
|
|
}
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures != 0;
|
|
}
|