Files
LithosAnanake/v4/tests/test_host_dict.c
T
rajamesandClaude Opus 5.5 bbd1b4047f refactor(v4.0.0): the capsule's inner words keep their state in memory
WORD, the dictionary words and the code generator run underneath
whatever the user has on the stacks, which are ten and nine deep (D-2).
Measured, they used five to seven data cells of their own, leaving a
line about three.  Each now keeps what it works on in its file's
scratch cells and has at most three cells on the data stack; , calls
nothing; and the longest chains of calls are shorter.

The public words of core.v4, input.v4 and dict.v4 get dictionary
headers.  NUMBER is split so the interpreter can have a flag instead of
NODE-ERROR.  The host-node tests share one memory map, host_map.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:41:50 -04:00

432 lines
22 KiB
C

/* test_host_dict.c -- the dictionary, executed on the host node.
*
* DECOMPOSITION.md 5.12 and 5.13: HERE ALIGN ALLOT , C, 2, PAD LATEST, FIND
* and ', and the entry-field words. The definitions are text,
* capsule/dict.v4, on top of core.v4 and input.v4.
*
* v4's dictionary is new: v3's was a C structure and its words handed out
* host pointers. What carries over is what the words mean:
* HERE the next free address , C, 2, append a cell, a byte, a double
* ALLOT reserve space ALIGN round the pointer up to a cell
* FIND ( -- xt | 0 ) the word named next in the input, 0 if there is none
* (v3 and FORTH-79 agree, and so does v4)
* ' FORTH-79: its parameter field address; not found is an error.
* v3's ' returned what FIND returns. For a word that is code the
* two are the same address; for a data word (one made by CREATE,
* VARIABLE or CONSTANT) the parameter field is the cell after its
* code, which is what FORTH-79's n ' NAME ! idiom needs.
* xt as in v3, the one handle the field words take: >LINK LFA LINK>
* >NAME NFA NAME> CFA PFA >BODY
* An address unit is a cell (D-1), so ALLOT counts cells where v3's counted
* bytes, and HERE moves by 1 for a cell where v3's moved by 8.
*
* v3's LATEST pushes HERE, not the latest word (reported, not fixed); here
* it is the newest entry's xt.
*/
#include "v4/text.h"
#include "v4/testcode.h"
#include <stdint.h>
#include <stdio.h>
#include <string.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)
#define CANARY ((v4_cell)0x0C0FFEE5)
#define DICT_W ((v4_cell)4096) /* dictionary space: words 4096 .. 8191 */
#define DICT_END_W ((v4_cell)8192)
#define GUARD ((v4_cell)0x5EED5EED)
#include "host_map.h"
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static v4_text tx;
static unsigned char byte_at(v4_cell baddr)
{
return (unsigned char)(((v4_ucell)n.mem[baddr >> 2] >> (8u * (unsigned)(baddr & 3))) & 0xFFu);
}
static void put_bytes(v4_cell baddr, const void *src, unsigned len)
{
const unsigned char *s = (const unsigned char *)src;
for (unsigned i = 0; i < len; i++) {
v4_cell ba = baddr + (v4_cell)i;
unsigned sh = 8u * (unsigned)(ba & 3);
v4_ucell w = (v4_ucell)n.mem[ba >> 2];
n.mem[ba >> 2] = (v4_cell)((w & ~((v4_ucell)0xFFu << sh)) | ((v4_ucell)s[i] << sh));
}
}
static void set_line(const char *s)
{
unsigned len = (unsigned)strlen(s);
put_bytes(TIB, s, len + 1u);
n.mem[SPAN] = (v4_cell)len;
n.mem[TO_IN] = 0;
}
static v4_cell W(const char *name)
{
v4_cell w = v4_text_word(&tx, name);
if (w < 0) { failures++; printf("FAIL: no word %s\n", name); }
return w;
}
static void fresh(void)
{
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
v4_heat_reset(&h);
n.mem[NODE_ERROR] = 0;
v4_dstack_push(&n.ds, CANARY);
}
static int go(const char *name) { return v4_test_call(&n, &es, &h, W(name), 4000000) > 0; }
static int run(const char *name, unsigned argc, v4_cell a, v4_cell b)
{
fresh();
if (argc > 0) v4_dstack_push(&n.ds, a);
if (argc > 1) v4_dstack_push(&n.ds, b);
return go(name);
}
static v4_cell pop(void) { return v4_dstack_pop(&n.ds); }
static int clean(void) { return pop() == CANARY; }
static int err(void) { return n.mem[NODE_ERROR] != 0; }
/* word ( -- x ) or ( a -- x ): its one result, or a value no test expects */
static v4_cell get(const char *name, unsigned argc, v4_cell a)
{
v4_cell r;
if (!run(name, argc, a, 0)) return (v4_cell)0x0BADBAD;
r = pop();
return clean() ? r : (v4_cell)0x0BADBAD;
}
static void empty_dictionary(void)
{
for (v4_cell i = DICT_W - 1; i <= DICT_END_W; i++) n.mem[i] = GUARD;
n.mem[DP] = DICT_W * 4;
n.mem[LATEST] = 0;
}
/* Make an entry named `name` through WORD and (HEADER); its xt, or 0 if
* none was made. */
static v4_cell define(const char *name)
{
v4_cell was = n.mem[LATEST];
set_line(name);
if (!run("MAKE", 0, 0, 0) || !clean()) return 0;
return n.mem[LATEST] != was ? n.mem[LATEST] : 0;
}
/* FIND on the text `name`. */
static v4_cell find(const char *name)
{
set_line(name);
return get("FIND", 0, 0);
}
/* The counted name of entry xt is `want` (cut to 31), zero-padded to a cell,
* and its fields are in place. */
static int entry_is(v4_cell xt, const char *want, v4_cell link, v4_cell flags)
{
unsigned len = (unsigned)strlen(want), cells, i;
v4_cell nfa;
if (len > 31) len = 31;
cells = (len + 4) / 4;
nfa = xt - 3 - (v4_cell)cells;
if (n.mem[xt - 1] != link || n.mem[xt - 2] != nfa || n.mem[xt - 3] != flags) return 0;
if (byte_at(nfa * 4) != len) return 0;
for (i = 0; i < len; i++) if (byte_at(nfa * 4 + 1 + (v4_cell)i) != (unsigned char)want[i]) return 0;
for (i = len + 1; i < cells * 4; i++) if (byte_at(nfa * 4 + (v4_cell)i) != 0) return 0;
return 1;
}
static uint32_t rng = 0x1234ABCDu;
static uint32_t rnd(void) { rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; return rng; }
/* Stack headroom, as in test_foundation.c. */
typedef void (*setup_fn)(void);
static int fits(const char *name, setup_fn setup, unsigned argc, v4_cell a, unsigned nres, const v4_cell *want,
unsigned dfill, unsigned rfill)
{
unsigned i;
setup();
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
n.mem[NODE_ERROR] = 0;
for (i = 0; i < dfill; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY);
if (argc > 0) v4_dstack_push(&n.ds, a);
for (i = 0; i < rfill; i++) v4_rstack_push(&n.rs, (v4_cell)(0x6B000000 + i));
if (!go(name)) return 0;
for (i = nres; i-- > 0; ) if (pop() != want[i]) return 0;
if (pop() != CANARY) return 0;
for (i = dfill; i-- > 0; ) if (pop() != (v4_cell)(0x5A000000 + i)) return 0;
for (i = rfill; i-- > 0; ) if (v4_rstack_pop(&n.rs) != (v4_cell)(0x6B000000 + i)) return 0;
return !err();
}
static void headroom(const char *name, setup_fn setup, unsigned argc, v4_cell a, unsigned nres, int min_d, int min_r)
{
v4_cell want[2];
unsigned i;
int dh, rh;
setup();
CHECK(run(name, argc, a, 0), "%s runs", name);
for (i = nres; i-- > 0; ) want[i] = pop();
for (dh = 0; dh < V4_DATA_DEPTH; dh++) if (!fits(name, setup, argc, a, nres, want, (unsigned)dh + 1u, 0)) break;
for (rh = 0; rh < V4_RET_DEPTH; rh++) if (!fits(name, setup, argc, a, nres, want, 0, (unsigned)rh + 1u)) break;
printf(" %-8s headroom: data %d below canary, return %d below its return address\n", name, dh, rh);
CHECK(dh >= min_d && rh >= min_r, "%s leaves room", name);
}
static void setup_three(void)
{
empty_dictionary();
(void)define("ALPHA"); (void)define("BETA"); (void)define("GAMMA");
set_line("ALPHA rest");
}
static void setup_new(void) { empty_dictionary(); set_line("NEWWORD rest"); }
int main(void)
{
unsigned i, t;
v4_cell a, b, c, capsule_latest;
printf("v4 host dictionary 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" };
CHECK(host_load(&tx, &n, files, 3), "the capsule assembles");
}
capsule_latest = v4_text_latest(&tx);
CHECK(v4_text_assemble(&tx,
": MAKE ( -- ) 32 WORD jump (HEADER)\n"
": 'PAD PAD ;\n"
": DATA! ( -- ) LATEST (FLAGS) a! @ 4 OR ! ;\n" /* what CREATE will do */
": IMM! ( -- ) LATEST (FLAGS) a! @ 1 OR ! ;\n" /* what IMMEDIATE will do */
"header FIRST-HEADER : ASM-A 1 ;\n"
"header B immediate inline : ASM-B 2 ;\n"
"header ( immediate : ASM-PAREN 3 ;\n"),
"wrappers: %s", v4_text_error(&tx));
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
printf(" code: %ld words\n", (long)v4_text_here(&tx) - 16);
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
n.mem[BASE] = 10;
/* ---- the space: HERE , C, 2, ALIGN ALLOT ---- */
empty_dictionary();
CHECK(get("HERE", 0, 0) == DICT_W, "HERE starts at the dictionary's first cell");
CHECK(run(",", 1, 111, 0) && clean() && !err() && n.mem[DICT_W] == 111 && get("HERE", 0, 0) == DICT_W + 1, ", stores a cell and HERE moves by 1");
CHECK(run(",", 1, -222, 0) && clean() && n.mem[DICT_W + 1] == -222 && get("HERE", 0, 0) == DICT_W + 2, "the next cell");
/* C, packs four to a cell; HERE is the next whole cell; , aligns first */
n.mem[DICT_W + 2] = 0;
for (i = 0; i < 4; i++) {
CHECK(run("C,", 1, (v4_cell)(0x100 + 'a' + i), 0) && clean() && !err(), "C, [%u]", i);
CHECK(byte_at((DICT_W + 2) * 4 + (v4_cell)i) == 'a' + i, "C, stores the low byte at the next byte [%u]", i);
CHECK(get("HERE", 0, 0) == DICT_W + 3, "HERE is the next whole cell after %u bytes", i + 1);
}
CHECK(run("C,", 1, 'e', 0) && clean() && get("HERE", 0, 0) == DICT_W + 4 && n.mem[DP] == (DICT_W + 3) * 4 + 1, "a fifth byte starts another cell");
CHECK(run(",", 1, 333, 0) && clean() && n.mem[DICT_W + 4] == 333 && n.mem[DP] == (DICT_W + 5) * 4, ", after C, goes in the next whole cell");
CHECK(byte_at((DICT_W + 3) * 4) == 'e', "and leaves the byte alone");
CHECK(run("C,", 1, 'f', 0) && clean() && run("ALIGN", 0, 0, 0) && clean() && n.mem[DP] == (DICT_W + 6) * 4, "ALIGN rounds up to a cell");
CHECK(run("ALIGN", 0, 0, 0) && clean() && n.mem[DP] == (DICT_W + 6) * 4, "ALIGN when aligned does nothing");
/* 2, : the low cell first, as v3 (HERE 1 2 2, : the first cell holds 1) */
CHECK(run("2,", 2, 1, 2) && clean() && n.mem[DICT_W + 6] == 1 && n.mem[DICT_W + 7] == 2 && get("HERE", 0, 0) == DICT_W + 8, "v3: 2, stores the low cell first");
/* ALLOT counts cells */
CHECK(run("ALLOT", 1, 5, 0) && clean() && !err() && get("HERE", 0, 0) == DICT_W + 13, "5 ALLOT reserves five cells");
CHECK(run("ALLOT", 1, 0, 0) && clean() && get("HERE", 0, 0) == DICT_W + 13, "0 ALLOT reserves none");
CHECK(run("ALLOT", 1, -3, 0) && clean() && !err() && get("HERE", 0, 0) == DICT_W + 10, "-3 ALLOT gives three back");
CHECK(run("C,", 1, 'x', 0) && run("ALLOT", 1, 2, 0) && clean() && get("HERE", 0, 0) == DICT_W + 13
&& n.mem[DP] == (DICT_W + 13) * 4, "ALLOT aligns first");
CHECK(n.mem[DICT_W - 1] == GUARD, "nothing below the dictionary space written");
/* the ends of the space */
empty_dictionary();
CHECK(run("ALLOT", 1, -1, 0) && clean() && err() && get("HERE", 0, 0) == DICT_W, "ALLOT below the start is refused and sets NODE-ERROR");
CHECK(run("ALLOT", 1, DICT_END_W - DICT_W, 0) && clean() && !err() && get("HERE", 0, 0) == DICT_END_W, "the whole space can be allotted");
CHECK(run(",", 1, 99, 0) && clean() && err() && n.mem[DICT_END_W] == GUARD && get("HERE", 0, 0) == DICT_END_W, ", into a full dictionary stores nothing and sets NODE-ERROR");
CHECK(run("C,", 1, 99, 0) && clean() && err() && n.mem[DICT_END_W] == GUARD, "so does C,");
CHECK(run("ALLOT", 1, 1, 0) && clean() && err() && get("HERE", 0, 0) == DICT_END_W, "and ALLOT");
CHECK(run("ALLOT", 1, -1, 0) && clean() && !err() && run(",", 1, 77, 0) && clean() && !err() && n.mem[DICT_END_W - 1] == 77, "the last cell can be used");
CHECK(get("'PAD", 0, 0) == PAD, "PAD is its buffer's byte address");
/* ---- entries ---- */
empty_dictionary();
CHECK(get("LATEST", 0, 0) == 0, "LATEST is 0 in an empty dictionary");
a = define("ALPHA");
CHECK(a == DICT_W + 2 + 3 && !err(), "an entry's xt follows its name and three cells: %ld", (long)a);
CHECK(entry_is(a, "ALPHA", 0, 0), "the first entry: name, no flags, link 0");
CHECK(get("LATEST", 0, 0) == a && get("HERE", 0, 0) == a, "LATEST is its xt, and its code starts at HERE");
CHECK(run(",", 1, 1001, 0) && clean(), "some code");
b = define("B");
CHECK(b == a + 1 + 1 + 3 && entry_is(b, "B", a, 0), "the second entry links to the first");
(void)run(",", 1, 1002, 0);
c = define("GAMMA-DELTA");
CHECK(c == b + 1 + 3 + 3 && entry_is(c, "GAMMA-DELTA", b, 0), "an eleven-character name takes three cells");
CHECK(get("LATEST", 0, 0) == c, "LATEST is the newest");
/* the field words, all from the xt */
CHECK(get(">LINK", 1, c) == c - 1 && get("LFA", 1, c) == c - 1, ">LINK and LFA are the link's address");
CHECK(get("LINK>", 1, c - 1) == b && get("LINK>", 1, b - 1) == a && get("LINK>", 1, a - 1) == 0, "LINK> follows it, to 0 at the end");
CHECK(get(">NAME", 1, c) == (c - 6) * 4 && get("NFA", 1, c) == (c - 6) * 4, ">NAME and NFA are the name's byte address");
CHECK(byte_at(get(">NAME", 1, b)) == 1 && byte_at(get(">NAME", 1, b) + 1) == 'B', "which is its count byte, then its characters");
CHECK(get("NAME>", 1, get(">NAME", 1, a)) == a && get("NAME>", 1, get(">NAME", 1, b)) == b && get("NAME>", 1, get(">NAME", 1, c)) == c, "NAME> goes back to the xt");
CHECK(get("CFA", 1, c) == c, "CFA is the xt itself, as v3");
CHECK(get("PFA", 1, c) == c && get(">BODY", 1, c) == c, "the parameter field of a code word is its code");
CHECK(run("DATA!", 0, 0, 0) && clean() && n.mem[c - 3] == 4, "(marking the newest a data word)");
CHECK(get("PFA", 1, c) == c + 1 && get(">BODY", 1, c) == c + 1, "the parameter field of a data word is the cell after its code");
CHECK(get("PFA", 1, b) == b, "and the others are unchanged");
{
v4_cell nfa = get(">NAME", 1, c);
CHECK(run("TRAVERSE", 2, nfa, 1) && pop() == nfa + 12 && clean(), "TRAVERSE forward goes past the name's last character");
CHECK(run("TRAVERSE", 2, nfa, 5) && pop() == nfa + 12 && clean(), "for any positive n");
CHECK(run("TRAVERSE", 2, nfa, -1) && pop() == nfa && clean(), "TRAVERSE backward leaves the address, as v3");
CHECK(run("TRAVERSE", 2, nfa, 0) && pop() == nfa && clean(), "and so does 0");
}
/* SMUDGE toggles the newest entry's hidden bit; HIDDEN sets it */
CHECK(run("SMUDGE", 0, 0, 0) && clean() && n.mem[c - 3] == 6, "SMUDGE hides the newest entry, keeping its other flags");
CHECK(run("SMUDGE", 0, 0, 0) && clean() && n.mem[c - 3] == 4, "SMUDGE again shows it");
CHECK(run("HIDDEN", 0, 0, 0) && clean() && n.mem[c - 3] == 6 && run("HIDDEN", 0, 0, 0) && clean() && n.mem[c - 3] == 6, "HIDDEN hides it, once or twice");
CHECK(n.mem[b - 3] == 0 && n.mem[a - 3] == 0, "the others are untouched");
CHECK(run("SMUDGE", 0, 0, 0) && clean() && n.mem[c - 3] == 4, "shown again");
empty_dictionary();
CHECK(run("SMUDGE", 0, 0, 0) && clean() && run("HIDDEN", 0, 0, 0) && clean() && n.mem[DICT_W] == GUARD && n.mem[DICT_W - 1] == GUARD,
"SMUDGE and HIDDEN do nothing in an empty dictionary");
/* names: empty, long, and the edge of the space */
empty_dictionary();
CHECK(define("") == 0 && err() && get("HERE", 0, 0) == DICT_W && n.mem[LATEST] == 0, "an empty name makes nothing and sets NODE-ERROR");
{
char long31[40], long40[48];
memset(long31, 'q', 31); long31[31] = 0;
memset(long40, 'q', 40); long40[40] = 0;
a = define(long31);
CHECK(a == DICT_W + 8 + 3 && entry_is(a, long31, 0, 0), "a 31-character name is kept whole");
b = define(long40);
CHECK(b != 0 && entry_is(b, long31, a, 0) && b - a == 8 + 3, "a 40-character name is cut to 31");
}
empty_dictionary();
n.mem[DP] = (DICT_END_W - 4) * 4;
CHECK(define("ABCD") == 0 && err() && n.mem[LATEST] == 0 && n.mem[DP] == (DICT_END_W - 4) * 4 && n.mem[DICT_END_W - 4] == GUARD,
"an entry that does not fit makes nothing, moves nothing, and sets NODE-ERROR");
n.mem[DP] = (DICT_END_W - 5) * 4;
CHECK(define("ABCD") == DICT_END_W && !err() && n.mem[DICT_END_W] == GUARD, "one that just fits is made");
/* ---- FIND ---- */
empty_dictionary();
CHECK(find("ANYTHING") == 0 && !err(), "FIND in an empty dictionary is 0, and no error");
a = define("ALPHA"); (void)run(",", 1, 1, 0);
b = define("BETA"); (void)run(",", 1, 2, 0);
c = define("GAMMA"); (void)run(",", 1, 3, 0);
CHECK(find("ALPHA") == a && find("BETA") == b && find("GAMMA") == c && !err(), "FIND finds each word");
CHECK(find("DELTA") == 0 && find("ALPH") == 0 && find("ALPHAS") == 0 && find("LPHA") == 0 && !err(), "and not what is not there, nor a part of a name");
CHECK(find("alpha") == 0 && find("Alpha") == 0, "names are case sensitive, as v3");
CHECK(find(" BETA and more") == b && n.mem[TO_IN] == 8, "FIND takes the next word of the input stream and leaves >IN after it");
CHECK(find("") == 0 && find(" ") == 0 && !err(), "at the end of the input it is 0");
{
v4_cell a2 = define("ALPHA");
CHECK(a2 != 0 && a2 != a && find("ALPHA") == a2, "a later word of the same name is the one found");
CHECK(run("SMUDGE", 0, 0, 0) && find("ALPHA") == a, "hidden, the earlier one is found again");
CHECK(run("SMUDGE", 0, 0, 0) && find("ALPHA") == a2, "shown, the later");
CHECK(run("HIDDEN", 0, 0, 0) && find("ALPHA") == a && find("BETA") == b, "HIDDEN hides only the newest");
}
/* 31 characters are significant */
{
char n31[40], n32a[40], n32b[40], n30[40];
empty_dictionary();
memset(n31, 'k', 31); n31[31] = 0;
memcpy(n32a, n31, 31); n32a[31] = 'X'; n32a[32] = 0;
memcpy(n32b, n31, 31); n32b[31] = 'Y'; n32b[32] = 0;
memset(n30, 'k', 30); n30[30] = 0;
a = define(n32a);
CHECK(find(n32a) == a && find(n32b) == a && find(n31) == a, "names that agree in their first 31 characters are the same name");
CHECK(find(n30) == 0, "one that is shorter is not");
n31[30] = 'j';
CHECK(find(n31) == 0, "nor one that differs in the 31st");
}
/* ---- ' ---- */
empty_dictionary();
a = define("CODEWORD"); (void)run(",", 1, 1, 0);
b = define("DATAWORD"); (void)run("DATA!", 0, 0, 0); (void)run(",", 1, 2, 0); (void)run(",", 1, 55, 0);
set_line("CODEWORD x");
CHECK(get("(')", 0, 0) == a && !err() && n.mem[TO_IN] == 9, "' of a code word is its code address, which is what FIND gives");
set_line("DATAWORD");
CHECK(get("(')", 0, 0) == b + 1 && !err() && n.mem[b + 1] == 55, "' of a data word is its parameter field");
set_line("NOSUCHWORD");
CHECK(get("(')", 0, 0) == 0 && err(), "' of a word that is not there is an error");
/* ---- entries the text assembler made ---- */
{
v4_cell xa = W("ASM-A"), xb = W("ASM-B"), xp = W("ASM-PAREN"), made;
empty_dictionary();
n.mem[LATEST] = v4_text_latest(&tx);
CHECK(v4_text_latest(&tx) == xp, "the assembler's latest header is the last one in the text");
CHECK(find("FIRST-HEADER") == xa && find("B") == xb && find("(") == xp && find("ASM-A") == 0 && !err(), "FIND finds the words the assembler gave headers");
CHECK(n.mem[xa - 3] == 0 && n.mem[xb - 3] == 1 + 8 && n.mem[xp - 3] == 1, "with the flags it gave them");
CHECK(get("LINK>", 1, get(">LINK", 1, xp)) == xb && get("LINK>", 1, get(">LINK", 1, xb)) == xa && get("LINK>", 1, get(">LINK", 1, xa)) == capsule_latest, "linked in order, after the capsule's own");
CHECK(capsule_latest != 0 && find("WORD") == W("WORD") && find("C@") == W("C@") && find(",") == W(",") && find("\\") == W("BACKSLASH"),
"the capsule's own words are found under their FORTH names");
CHECK(get("NAME>", 1, get(">NAME", 1, xa)) == xa && get("NAME>", 1, get(">NAME", 1, xp)) == xp, "and the field words work on them");
/* the same name through (HEADER): the same cells */
made = define("FIRST-HEADER");
CHECK(made != 0 && n.mem[made - 3] == n.mem[xa - 3] && made - n.mem[made - 2] == xa - n.mem[xa - 2], "an entry made by (HEADER) has the same shape");
for (i = 0; i < (unsigned)(xa - 3 - n.mem[xa - 2]); i++)
if (n.mem[n.mem[xa - 2] + (v4_cell)i] != n.mem[n.mem[made - 2] + (v4_cell)i]) break;
CHECK(i == (unsigned)(xa - 3 - n.mem[xa - 2]), "and the same name cells");
CHECK(n.mem[made - 1] == xp && find("FIRST-HEADER") == made && find("B") == xb, "new entries link on to the assembler's");
}
/* ---- many words, against a list kept in C ---- */
{
static char names[300][36];
static v4_cell xts[300];
unsigned count = 0;
empty_dictionary();
for (t = 0; t < 300; t++) {
unsigned len = 1 + rnd() % (t % 7 == 0 ? 34 : 9), dup = 0;
char *nm = names[count];
for (i = 0; i < len; i++) nm[i] = (char)("ABCDEFGHab01-+*/!@"[rnd() % 18]);
nm[len] = 0;
xts[count] = define(nm);
CHECK(xts[count] != 0 && !err(), "defining \"%s\"", nm);
(void)run(",", 1, (v4_cell)t, 0);
if (len > 31) nm[31] = 0;
for (i = 0; i < count; i++) if (strcmp(names[i], nm) == 0) dup = 1;
(void)dup;
count++;
}
for (t = 0; t < count; t++) {
v4_cell want = 0;
for (i = 0; i < count; i++) if (strcmp(names[i], names[t]) == 0) want = xts[i]; /* the newest of that name */
CHECK(find(names[t]) == want, "FIND \"%s\"", names[t]);
}
/* walk the links from LATEST: every entry, newest first, then 0 */
{
v4_cell xt = get("LATEST", 0, 0);
for (t = count; t-- > 0; ) {
if (xt != xts[t]) break;
if (get("NAME>", 1, get(">NAME", 1, xt)) != xt) break;
xt = get("LINK>", 1, get(">LINK", 1, xt));
}
CHECK(t == (unsigned)-1 && xt == 0, "the links run through all %u entries, newest first", count);
}
printf(" %u entries in %ld cells\n", count, (long)(get("HERE", 0, 0) - DICT_W));
CHECK(n.mem[DICT_W - 1] == GUARD && n.mem[DICT_END_W] == GUARD, "nothing outside the dictionary space written");
}
/* What they leave their caller (D-2). */
headroom("FIND", setup_three, 0, 0, 1, 3, 2);
headroom("(')", setup_three, 0, 0, 1, 3, 2);
headroom("MAKE", setup_new, 0, 0, 0, 3, 2);
headroom(",", setup_new, 1, 42, 0, 5, 5);
headroom("ALLOT", setup_new, 1, 3, 0, 5, 5);
CHECK(v4_node_guards_intact(&n), "guards intact");
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}