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>
432 lines
22 KiB
C
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;
|
|
}
|