feat(v4): the ROM's symbol table

(FIND) ( -- rec | 0 ), (DEFINE) ( value kind -- ) and (OPCODE) ( -- op | -1 ),
entries 3-5.  Names are refused as text.c's define refuses them, compared
case by case with text.c; a set variable may be set again.  256 hash heads,
records growing up and names growing down in dictionary space, used only
while the ROM assembles.  text.c gains the data directives #, and ", (the
ROM's language; its mnemonic and reserved-word tables are data); the
nucleus image is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-15 11:54:15 -04:00
co-authored by Claude Opus 5.5
parent 080811dbd2
commit 07911dcf91
9 changed files with 404 additions and 13 deletions
+23
View File
@@ -133,9 +133,32 @@
#define ROM_NI (OUT_W - 72) /* the next character of the token */
#define ROM_NU (OUT_W - 71) /* the value so far, unsigned */
#define ROM_NBIG (OUT_W - 70) /* 1 once it has not fitted a cell */
#define ROM_RECN (OUT_W - 128) /* (DEFINE): cells of symbol records used */
#define ROM_POOLN (OUT_W - 127) /* cells of the name pool used */
#define ROM_REC (OUT_W - 126) /* (FIND): the record being looked at */
#define ROM_CNT (OUT_W - 125) /* (DEFINE): cells in the token's name */
#define ROM_PTR (OUT_W - 124) /* (SAME?), (DEFINE): where in a name */
#define ROM_I (OUT_W - 123) /* where in the token */
#define ROM_WI (OUT_W - 122) /* (WALK): which entry of a table */
/* THE ROM'S SYMBOL TABLE, used only while it assembles, in dictionary space (cleared at `start`): 256 hash heads,
* then records growing up -- link, name, kind, value -- and names, as counted strings, growing down from the top. */
#define ROM_SYMH DICT_W
#define ROM_SYMREC (DICT_W + 256)
#define ROM_POOLTOP DICT_END_W
#define ROM_REC_LINK 0
#define ROM_REC_NAME 1
#define ROM_REC_KIND 2
#define ROM_REC_VALUE 3
#define ROM_KIND_WORD 1
#define ROM_KIND_CONST 2
#define ROM_KIND_MACRO 3
#define ROM_KIND_SET 4
/* the ROM's error codes */
#define ROM_ERR_TOO_LONG 1 /* a token longer than 63 characters: "name too long" */
#define ROM_ERR_COMMENT 2 /* a ( with no ) before the end: "comment not closed" */
#define ROM_ERR_BAD_NAME 3 /* an opcode, a reserved word, a label, a number or &...: "cannot be used as a name" */
#define ROM_ERR_DEFINED 4 /* "already defined" */
#define ROM_ERR_NAMES 5 /* the symbol table is full: "too many names" */
#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */
#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */
#define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
+1 -1
View File
@@ -29,7 +29,7 @@ extern const unsigned v4_rom_32_words;
/* The entry table, in its fixed order: entry i is at V4_ROM_BASE + i. */
static const char *const rom_entries[] = {
"(KEY)", "(TOKEN)", "(NUMBER)", "(FIND)", "(DEFINE)",
"(KEY)", "(TOKEN)", "(NUMBER)", "(FIND)", "(DEFINE)", "(OPCODE)",
"(OP)", "(LIT)", "(BRANCH)", "(BRANCH>)", "(RESOLVE)", "(LABEL)"
};
#define ROM_ENTRIES (sizeof rom_entries / sizeof rom_entries[0])
+140
View File
@@ -0,0 +1,140 @@
/* test_host_rom_sym.c -- the ROM's symbol table.
*
* Plan Task 4. (DEFINE) ( value kind -- ) gives the token (TOKEN) read a
* value and a kind; (FIND) ( -- rec | 0 ) finds it, newest first; (OPCODE)
* ( -- op | -1 ) says which of the 32 mnemonics it is. A name is refused as
* v4/src/text.c's define refuses one -- an opcode, a reserved word, a label
* (NAME:), a number, &... -- and every such case is also given to text.c,
* the oracle. A set variable (kind 4) may be set again; nothing else may be
* defined twice. The table is in dictionary space: records growing up,
* names growing down, and "too many names" when they meet.
*/
#include "rom_harness.h"
#include "v4/text.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_node n, oracle_node;
static v4_text tx;
static void tokenize(const char *name)
{
char in[80];
snprintf(in, sizeof in, "%s ", name);
v4_node_console_feed(&n, in, (unsigned)strlen(in));
if (!rom_call(&n, "(TOKEN)", 100000)) printf(" (TOKEN) did not return for %s\n", name);
(void)v4_dstack_pop(&n.ds);
}
/* (DEFINE) the name; the error code it raised (0: none), cleared again */
static v4_cell define(const char *name, v4_cell value, v4_cell kind)
{
v4_cell err;
tokenize(name);
v4_dstack_push(&n.ds, value);
v4_dstack_push(&n.ds, kind);
if (!rom_call(&n, "(DEFINE)", 1000000)) return -99;
err = n.mem[ROM_FAILED];
n.mem[ROM_FAILED] = 0;
if (n.ds.depth != 0) return -98; /* (DEFINE) leaves nothing, refused or not */
return err;
}
static v4_cell find(const char *name)
{
tokenize(name);
if (!rom_call(&n, "(FIND)", 1000000)) return -99;
return v4_dstack_pop(&n.ds);
}
static v4_cell opcode(const char *name)
{
tokenize(name);
if (!rom_call(&n, "(OPCODE)", 1000000)) return -99;
return v4_dstack_pop(&n.ds);
}
/* text.c's verdict on NAME as a name: 0 accepted, ROM_ERR_BAD_NAME refused */
static v4_cell oracle(const char *name)
{
char src[120];
v4_node_reset(&oracle_node);
v4_text_begin(&tx, &oracle_node, 16);
snprintf(src, sizeof src, "constant %s 1", name);
if (v4_text_assemble(&tx, src)) return 0;
return strstr(v4_text_error(&tx), "cannot be used as a name") ? ROM_ERR_BAD_NAME : -1;
}
int main(void)
{
static const char *const mnemonic[32] = {
";", "ex", "jump", "call", "unext", "next", "if", "-if", "@p", "@+", "@b", "@", "!p", "!+", "!b", "!",
"+*", "2*", "2/", "inv", "+", "and", "xor", "drop", "dup", "pop", "over", "a", "nop", "push", "b!", "a!"
};
static const char *const names[] = {
"dup", "a!", ";", "-if", ":", "macro", "endmacro", "FOR", "NEXT", "UNEXT", "constant", "#,", "\",",
"x:", "ab:", "12", "$ff", "-1", "99999999999999999999999", "&y", "&",
"a1", "abc", "(x", "x:y", "DUP", "Dup", "+1", "-", "$", "LONGERNAME", ":x"
};
unsigned i, count;
v4_cell rec;
char nm[16];
rom_boot(&n);
/* the 32 mnemonics, and things that are not one */
for (i = 0; i < 32; i++) CHECK(opcode(mnemonic[i]) == (v4_cell)i, "%s is opcode %u", mnemonic[i], i);
CHECK(opcode("DUP") == -1 && opcode("dupe") == -1 && opcode("du") == -1 && opcode("@pp") == -1, "not mnemonics");
/* define and find */
CHECK(define("A", 5, ROM_KIND_CONST) == 0, "A defined");
CHECK(define("B", 100, ROM_KIND_WORD) == 0, "B defined");
CHECK(define("Cee", 7, ROM_KIND_MACRO) == 0, "Cee defined");
rec = find("A");
CHECK(rec > 0 && n.mem[rec + ROM_REC_KIND] == ROM_KIND_CONST && n.mem[rec + ROM_REC_VALUE] == 5, "A found: rec %ld", (long)rec);
rec = find("B");
CHECK(rec > 0 && n.mem[rec + ROM_REC_KIND] == ROM_KIND_WORD && n.mem[rec + ROM_REC_VALUE] == 100, "B found");
rec = find("Cee");
CHECK(rec > 0 && n.mem[rec + ROM_REC_KIND] == ROM_KIND_MACRO && n.mem[rec + ROM_REC_VALUE] == 7, "Cee found");
CHECK(find("C") == 0 && find("Ce") == 0 && find("Ceee") == 0 && find("a") == 0, "near misses are not found");
/* twice */
CHECK(define("A", 6, ROM_KIND_CONST) == ROM_ERR_DEFINED, "A twice: already defined");
CHECK(define("A", 6, ROM_KIND_SET) == ROM_ERR_DEFINED, "a set variable over a constant: already defined");
CHECK(define("V", 1, ROM_KIND_SET) == 0 && define("V", 2, ROM_KIND_SET) == 0, "a set variable may be set again");
rec = find("V");
CHECK(rec > 0 && n.mem[rec + ROM_REC_VALUE] == 2, "and has its new value");
CHECK(define("V", 3, ROM_KIND_CONST) == ROM_ERR_DEFINED, "but not defined as something else");
/* names refused as text.c refuses them */
for (i = 0; i < sizeof names / sizeof names[0]; i++) {
v4_cell o = oracle(names[i]), r = define(names[i], 1, ROM_KIND_CONST);
CHECK(o == r, "%s: text.c %ld, ROM %ld", names[i], (long)o, (long)r);
}
/* and the ROM's own directives, which text.c does not have */
CHECK(define("set", 1, ROM_KIND_CONST) == ROM_ERR_BAD_NAME && define("here", 1, ROM_KIND_CONST) == ROM_ERR_BAD_NAME
&& define("store", 1, ROM_KIND_CONST) == ROM_ERR_BAD_NAME && define("start", 1, ROM_KIND_CONST) == ROM_ERR_BAD_NAME,
"set here store start are reserved");
/* capacity: fill the table, then find every name with its value */
rom_boot(&n);
for (count = 0; count < 5000; count++) {
v4_cell err;
snprintf(nm, sizeof nm, "S%04u", count);
err = define(nm, (v4_cell)count * 3, ROM_KIND_WORD);
if (err == ROM_ERR_NAMES) break;
if (err != 0) { CHECK(0, "%s: error %ld", nm, (long)err); break; }
}
CHECK(count >= 800, "at least 800 five-character names fit: %u", count);
CHECK(count < 5000, "and the table does fill");
for (i = 0; i < count; i++) {
snprintf(nm, sizeof nm, "S%04u", i);
rec = find(nm);
if (!(rec > 0 && n.mem[rec + ROM_REC_VALUE] == (v4_cell)i * 3)) { CHECK(0, "%s not found with its value", nm); break; }
}
CHECK(i == count, "every one of them is found");
printf("test_host_rom_sym: %d checks, %d failures\n", checks, failures);
return failures != 0;
}
+22
View File
@@ -499,6 +499,28 @@ int main(void)
CHECK(refused("constant K x", "not a number", 1), "a constant's value is a number");
CHECK(refused(": constant ;", "cannot be used as a name", 1), "constant is reserved");
/* ---- `#, VALUE` and `", TOKEN`, data in the text (the ROM's language) ---- */
{
v4_ucell ab = 2u | ((v4_ucell)'a' << 8) | ((v4_ucell)'b' << 16), paren = 1u | ((v4_ucell)'(' << 8);
v4_ucell five_l = 5u | ((v4_ucell)'a' << 8) | ((v4_ucell)'b' << 16) | ((v4_ucell)'c' << 24);
v4_ucell five_h = (v4_ucell)'d' | ((v4_ucell)'e' << 8);
CHECK(ok("constant K 9 : T ; #, 5 #, -1 #, K #, K+2 #, &T \", ab \", ( \", abcde"),
"data assembles: %s", v4_text_error(&tx));
CHECK(nb.mem[ORIGIN + 1] == 5 && nb.mem[ORIGIN + 2] == -1 && nb.mem[ORIGIN + 3] == 9 && nb.mem[ORIGIN + 4] == 11
&& nb.mem[ORIGIN + 5] == ORIGIN, "#, lays a number, a constant, CONST+N and &NAME");
CHECK((v4_ucell)nb.mem[ORIGIN + 6] == ab && (v4_ucell)nb.mem[ORIGIN + 7] == paren,
"\", lays a counted string, padded to a whole cell; ( is a name here");
CHECK((v4_ucell)nb.mem[ORIGIN + 8] == five_l && (v4_ucell)nb.mem[ORIGIN + 9] == five_h && v4_text_here(&tx) == ORIGIN + 10,
"a longer one runs on into the next cell");
CHECK(ok(": T dup #, 7 ;") && nb.mem[ORIGIN + 1] == 7 && v4_text_word(&tx, "T") == ORIGIN,
"#, inside a word ends the instruction word first");
CHECK(refused("#, x", "not a value", 1), "#, needs a value");
CHECK(refused("#,", "#, without a value", 1), "#, at the end");
CHECK(refused("\",", "\", without a string", 1), "\", at the end");
CHECK(refused(": #, ;", "cannot be used as a name", 1) && refused(": \", ;", "cannot be used as a name", 1),
"#, and \", are reserved");
}
CHECK(v4_node_guards_intact(&na) && v4_node_guards_intact(&nb), "guards intact");
printf(" %d checks, %d failures\n", checks, failures);