- Text after start is refused, as text.c refuses it: before it jumps, the ROM reads on to the end of the source; blanks, comments and the ends of blobs may follow, a token is ERR-AFTER (26), "text after start". - A token too long where a name, a target or a value is wanted is "name too long", as text.c says: (MISSING) reports "missing" only when no error came first. `", keeps "missing", which is text.c's word for both. - text.c's "address out of range" names the address, not the value. - The system's source files are one list, v4/sys_files.h: boot.c, the harness and the nucleus test are made from it. - The ROM's error codes and words are one list, v4/rom_errors.h: boot.c's messages, host_map.h's ROM_ERR_* and mkmap's ERR-* are made from it. - node.h says what v4_node_store does with a ROM address (nothing, as for any address outside RAM, D-14); test_host_rom_range pins it. ROM: 2095 words, rom_hash 0x06d0dcb7a0cb41a1. dict_hash unchanged, 0xe5a57f3eb72ff94a. make -C v4 test test-32 hosted-check pass. QEMU: amd64, aarch64, riscv64 to ok>, POST 538/538, POST: PASSED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
290 lines
15 KiB
C
290 lines
15 KiB
C
/* test_host_rom_asm.c -- the ROM assembles text exactly as v4/src/text.c does.
|
|
*
|
|
* Plan Tasks 6 and 7. The ROM is started at its entry (12) and fed a text
|
|
* through the console, ending with `start`; text.c assembles the same text.
|
|
* Where text.c accepts it, the ROM must lay down the same words and jump to
|
|
* the word start names; where text.c refuses it, the ROM must stop at
|
|
* (ROM-FAILED) with the same class of error, on the same line. The texts
|
|
* are test_text.c's own (rom_asm_cases.h), test_text.c's four real
|
|
* definitions, and the ROM's own directives: set, here, store, start.
|
|
*/
|
|
#include "rom_harness.h"
|
|
#include "v4/text.h"
|
|
#include "rom_asm_cases.h"
|
|
#include "v4/rom_errors.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 VARS ((v4_cell)(V4_NODE_WORDS - 16u)) /* test_text.c's VAR */
|
|
|
|
static v4_node a, b;
|
|
static v4_text tx;
|
|
static char in[1u << 16];
|
|
|
|
/* the ROM error code for one of text.c's messages */
|
|
static v4_cell code_of(const char *m)
|
|
{
|
|
static const struct { const char *s; v4_cell c; } map[] = {
|
|
{ "label already defined", ROM_ERR_LABEL_DUP }, { "& needs", ROM_ERR_AMP },
|
|
{ "macros nested too deep", ROM_ERR_DEEP }, { "macro without", ROM_ERR_MACRO },
|
|
{ "endmacro without", ROM_ERR_MACRO }, { "nested too deep", ROM_ERR_FOR },
|
|
{ "NEXT without", ROM_ERR_FOR }, { "FOR without", ROM_ERR_FOR },
|
|
{ "already defined", ROM_ERR_DEFINED }, { "cannot be used as a name", ROM_ERR_BAD_NAME },
|
|
{ "does not fit a cell", ROM_ERR_BIG }, { "does not fit", ROM_ERR_FIT },
|
|
{ "branch cannot reach", ROM_ERR_FIT }, { "name too long", ROM_ERR_TOO_LONG },
|
|
{ "comment not closed", ROM_ERR_COMMENT }, { "not allowed in a macro", ROM_ERR_IN_MACRO },
|
|
{ "too many labels", ROM_ERR_LABELS }, { "must fit one instruction word", ROM_ERR_UNEXT },
|
|
{ "not something to branch to", ROM_ERR_TARGET }, { "never defined", ROM_ERR_UNDEFINED },
|
|
{ "write the number", ROM_ERR_FETCH_P }, { "too many unresolved", ROM_ERR_REFS },
|
|
{ "not a value", ROM_ERR_VALUE }, { "not a number", ROM_ERR_VALUE },
|
|
{ "too many names", ROM_ERR_NAMES }, { "out of name space", ROM_ERR_NAMES },
|
|
{ "address out of range", ROM_ERR_RANGE }, { "text after start", ROM_ERR_AFTER }, { "without", ROM_ERR_MISSING },
|
|
};
|
|
unsigned i;
|
|
for (i = 0; i < sizeof map / sizeof map[0]; i++) if (strstr(m, map[i].s)) return map[i].c;
|
|
return -1;
|
|
}
|
|
|
|
static unsigned line_of(const char *m)
|
|
{
|
|
unsigned l = 0;
|
|
return sscanf(m, "line %u:", &l) == 1 ? l : 0;
|
|
}
|
|
|
|
/* Run the ROM on `text` (EOT is added). 1: it started -- P left the ROM for
|
|
* RAM; 0: it stopped at (ROM-FAILED); -1: neither within the steps allowed. */
|
|
static int rom_run(const char *text)
|
|
{
|
|
static long failed_lo = -1, failed_hi = -1;
|
|
size_t len = strlen(text), fed = 0;
|
|
long steps = 0;
|
|
if (failed_lo < 0) { /* where (ROM-FAILED) is: looked up once */
|
|
FILE *f = fopen(V4_ROM_SYM, "r");
|
|
char sym[80]; long ad, lo = -1, hi = -1;
|
|
while (f && fscanf(f, "%79s %ld", sym, &ad) == 2) if (strcmp(sym, "(ROM-FAILED)") == 0) lo = ad;
|
|
if (f) rewind(f);
|
|
while (f && fscanf(f, "%79s %ld", sym, &ad) == 2) if (ad > lo && (hi < 0 || ad < hi)) hi = ad;
|
|
if (f) fclose(f);
|
|
failed_lo = lo; failed_hi = hi < 0 ? (long)V4_ROM_BASE + (long)ROM_IMAGE_WORDS : hi;
|
|
}
|
|
rom_boot(&b);
|
|
b.p = V4_ROM_BASE + 12;
|
|
while (steps++ < 50000000L) {
|
|
if (fed <= len) fed += v4_node_console_feed(&b, text + fed, (unsigned)(len + 1 - fed));
|
|
if (b.p < V4_ROM_BASE) return 1;
|
|
if ((long)b.p >= failed_lo && (long)b.p < failed_hi) return 0;
|
|
(void)v4_exec_step_word(&b, &rom_es, &rom_h);
|
|
if (b.stopped) return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* text.c and the ROM on the same text, with a word to start at after it */
|
|
static void same(const char *text)
|
|
{
|
|
static char ta[1u << 15];
|
|
int okA, rb;
|
|
v4_cell w, hereA, endA;
|
|
|
|
snprintf(ta, sizeof ta, "constant VAR %ld\nconstant N-1 %d\n%s\n: ZZEND ;\n", (long)VARS, V4_CELL_BITS - 1, text);
|
|
v4_node_reset(&a);
|
|
v4_text_begin(&tx, &a, ROM_ORIGIN);
|
|
okA = v4_text_assemble(&tx, ta) && v4_text_finish(&tx);
|
|
snprintf(in, sizeof in, "%s", ta); /* start on ZZEND's line: the ROM sees as many lines as text.c */
|
|
in[strlen(in) - 1] = 0;
|
|
strcat(in, " start ZZEND\n\x04");
|
|
rb = rom_run(in);
|
|
if (okA) {
|
|
hereA = v4_text_here(&tx);
|
|
endA = v4_text_word(&tx, "ZZEND");
|
|
CHECK(rb == 1, "[%s] text.c accepts; the ROM %s (code %ld line %ld)", text,
|
|
rb == 0 ? "refused" : "did not finish", (long)b.mem[ROM_FAILED], (long)b.mem[ROM_ERRLINE] + 1);
|
|
if (rb != 1) return;
|
|
CHECK(b.p == endA, "[%s] the ROM started at ZZEND: P %ld, text.c %ld", text, (long)b.p, (long)endA);
|
|
CHECK(b.mem[ROM_HERE] == hereA, "[%s] here: ROM %ld, text.c %ld", text, (long)b.mem[ROM_HERE], (long)hereA);
|
|
for (w = ROM_ORIGIN; w < hereA; w++) if (a.mem[w] != b.mem[w]) break;
|
|
CHECK(w == hereA, "[%s] word %ld differs: text.c %lx, ROM %lx", text, (long)w,
|
|
(unsigned long)a.mem[w], (unsigned long)b.mem[w]);
|
|
} else {
|
|
const char *m = v4_text_error(&tx);
|
|
v4_cell want = code_of(m);
|
|
unsigned l = line_of(m);
|
|
CHECK(rb == 0, "[%s] text.c refuses (%s); the ROM %s", text, m, rb == 1 ? "accepted" : "did not finish");
|
|
if (rb != 0) return;
|
|
CHECK(b.mem[ROM_FAILED] == want, "[%s] text.c: %s; ROM code %ld, want %ld", text, m, (long)b.mem[ROM_FAILED], (long)want);
|
|
if (l > 2) CHECK((unsigned)b.mem[ROM_ERRLINE] + 1u == l, "[%s] line: text.c %u, ROM %ld", text, l, (long)b.mem[ROM_ERRLINE] + 1);
|
|
}
|
|
}
|
|
|
|
|
|
/* text.c on each blob as a file of its own, as the host tests and text.c
|
|
* read the system; the ROM on the same blobs as the boot feeds them, each
|
|
* followed by a new line and V4_ROM_BLOB_END (Review Focus 1: a blob that
|
|
* ends mid-comment, mid-macro or mid-word). */
|
|
static void same_blobs(const char *const *blobs, unsigned count)
|
|
{
|
|
unsigned i;
|
|
int okA = 1, rb;
|
|
v4_cell w, hereA;
|
|
|
|
v4_node_reset(&a);
|
|
v4_text_begin(&tx, &a, ROM_ORIGIN);
|
|
for (i = 0; i < count && okA; i++) okA = v4_text_assemble(&tx, blobs[i]);
|
|
okA = okA && v4_text_assemble(&tx, ": ZZEND ;") && v4_text_finish(&tx);
|
|
in[0] = 0;
|
|
for (i = 0; i < count; i++) {
|
|
size_t l = strlen(in);
|
|
snprintf(in + l, sizeof in - l, "%s\n%c", blobs[i], (char)V4_ROM_BLOB_END);
|
|
}
|
|
strcat(in, ": ZZEND ;\nstart ZZEND\n\x04");
|
|
rb = rom_run(in);
|
|
if (okA) {
|
|
hereA = v4_text_here(&tx);
|
|
CHECK(rb == 1, "[%s | %s] text.c accepts; the ROM %s (code %ld)", blobs[0], blobs[count - 1],
|
|
rb == 0 ? "refused" : "did not finish", (long)b.mem[ROM_FAILED]);
|
|
if (rb != 1) return;
|
|
CHECK(b.p == v4_text_word(&tx, "ZZEND"), "[%s | %s] the ROM started at ZZEND", blobs[0], blobs[count - 1]);
|
|
for (w = ROM_ORIGIN; w < hereA; w++) if (a.mem[w] != b.mem[w]) break;
|
|
CHECK(w == hereA, "[%s | %s] word %ld differs", blobs[0], blobs[count - 1], (long)w);
|
|
} else {
|
|
const char *m = v4_text_error(&tx);
|
|
CHECK(rb == 0, "[%s | %s] text.c refuses (%s); the ROM %s", blobs[0], blobs[count - 1], m,
|
|
rb == 1 ? "accepted" : "did not finish");
|
|
if (rb != 0) return;
|
|
CHECK(b.mem[ROM_FAILED] == code_of(m), "[%s | %s] text.c: %s; ROM code %ld, want %ld", blobs[0], blobs[count - 1], m,
|
|
(long)b.mem[ROM_FAILED], (long)code_of(m));
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sizeof rom_asm_cases / sizeof rom_asm_cases[0]; i++) same(rom_asm_cases[i]);
|
|
|
|
/* test_text.c's four real definitions, and more of the notation together */
|
|
same("macro SWAP over push push drop pop pop endmacro\n"
|
|
": SWAP' ( a b -- b a ) SWAP ;\n"
|
|
": UM/MOD ( ulo uhi ud -- urem uquot )\n"
|
|
" a! N-1 FOR\n"
|
|
" -if L0\n"
|
|
" 2* over -if L1 drop 1 + jump L2\n"
|
|
" L1: drop L2: push 2* pop\n"
|
|
" jump SUB\n"
|
|
" L0:\n"
|
|
" 2* over -if L3 drop 1 + jump L4\n"
|
|
" L3: drop L4: push 2* pop\n"
|
|
" dup a xor -if L5\n"
|
|
" drop -if NOSUB jump SUB\n"
|
|
" L5: drop dup inv a + inv -if L6\n"
|
|
" drop jump NOSUB\n"
|
|
" L6: push drop pop jump SETBIT\n"
|
|
" SUB: inv a + inv\n"
|
|
" SETBIT: push 1 + pop\n"
|
|
" NOSUB:\n"
|
|
" NEXT\n"
|
|
" SWAP ;\n"
|
|
": C@ ( baddr -- c )\n"
|
|
" dup 2/ 2/ a! 3 and\n"
|
|
" if K0 -1 + if K1 -1 + if K2\n"
|
|
" drop @ 23 FOR 2/ UNEXT 255 and ;\n"
|
|
" K2: drop @ 15 FOR 2/ UNEXT 255 and ;\n"
|
|
" K1: drop @ 7 FOR 2/ UNEXT 255 and ;\n"
|
|
" K0: drop @ 255 and ;\n"
|
|
": C! ( c baddr -- )\n"
|
|
" dup 2/ 2/ a! 3 and push 255 and pop\n"
|
|
" if K0 -1 + if K1 -1 + if K2\n"
|
|
" drop 23 FOR 2* UNEXT @ 4278190080 inv and + ! ;\n"
|
|
" K2: drop 15 FOR 2* UNEXT @ -16711681 and + ! ;\n"
|
|
" K1: drop 7 FOR 2* UNEXT @ -65281 and + ! ;\n"
|
|
" K0: drop @ -256 and + ! ;\n"
|
|
": ROUNDTRIP ( c baddr -- c' ) dup push C! pop jump C@\n");
|
|
same(": A B C ; : B jump D ; : C if E D E: ; : D ; macro TWO dup dup endmacro macro FOUR TWO TWO endmacro\n"
|
|
": F FOUR &A VAR+3 \\ a comment\n ( and another ) ;\n#, 5 \", ( #, &F \", abcdefgh");
|
|
|
|
/* the ROM's own directives */
|
|
CHECK(rom_run("set S 5 set S 9 : T S ;\nstore 7000 S store 7001 here\n#, here\n: Z ;\nstart Z\n\x04") == 1,
|
|
"set, here and store assemble: code %ld", (long)b.mem[ROM_FAILED]);
|
|
CHECK(b.mem[7000] == 9 && b.mem[7001] == 18 && b.mem[18] == 18, "set takes its last value, here is the next word: %ld %ld %ld",
|
|
(long)b.mem[7000], (long)b.mem[7001], (long)b.mem[18]);
|
|
CHECK(rom_run(": T ;\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_NO_START, "no start: refused");
|
|
CHECK(rom_run(": T ;\nconstant K 1\nstart K\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_START, "start of a constant: refused");
|
|
CHECK(rom_run(": T ;\nstart\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_MISSING, "start alone: refused");
|
|
CHECK(rom_run(": T here ;\n: Z ;\nstart Z\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_TARGET, "here alone is not code");
|
|
CHECK(rom_run("store 9000\n: Z ;\nstart Z\n\x04") == 0, "store needs two values");
|
|
|
|
/* a blob split over two feeds, and a forward call resolved in the second (Review Focus 1) */
|
|
CHECK(rom_run(": A B ;\n: B 7 ;\n: Z ;\nstart Z\n\x04") == 1 && (b.mem[ROM_ORIGIN] & 0xFFFFFFFF) != 0, "a forward call across words");
|
|
|
|
/* the tables are cleared at start (Review Focus 4) */
|
|
rom_run(": A B ;\n: B 7 ;\nmacro M dup endmacro\n: Z M ;\nstart Z\n\x04");
|
|
for (i = 0; i < (unsigned)(DICT_END_W - DICT_W); i++) if (b.mem[DICT_W + (v4_cell)i] != 0) break;
|
|
CHECK(i == (unsigned)(DICT_END_W - DICT_W), "the symbol table is cleared: cell %u is not", i);
|
|
for (i = 0; i < 1024u; i++) if (b.mem[BUF0_W + (v4_cell)i] != 0) break;
|
|
CHECK(i == 1024u, "the block window is cleared: cell %u is not", i);
|
|
for (i = 0; i < 260u; i++) if (b.mem[TIB_W + (v4_cell)i] != 0) break;
|
|
CHECK(i == 260u, "the text input buffer is cleared: cell %u is not", i);
|
|
|
|
/* blobs: each ends as a file ends for text.c (Review Focus 1) */
|
|
{
|
|
static const char *const comment[] = { ": A ;\n( a comment left open", ": B 1 ; )" };
|
|
static const char *const macro[] = { "macro M 1", "endmacro : A M ;" };
|
|
static const char *const nfor[] = { ": A 3 FOR", "NEXT ;" };
|
|
static const char *const label[] = { ": A L1: 1", "jump L1 ;" };
|
|
static const char *const bsl[] = { ": A ;\n\\ a comment with no new line", ": B A ;" };
|
|
static const char *const word[] = { ": A 1", "2 ;" };
|
|
static const char *const closed[] = { "( closed ) : A ;", ": B A ;" };
|
|
same_blobs(comment, 2); same_blobs(macro, 2); same_blobs(nfor, 2); same_blobs(label, 2);
|
|
same_blobs(bsl, 2); same_blobs(word, 2); same_blobs(closed, 2);
|
|
}
|
|
|
|
/* store outside RAM: refused with an error, as text.c refuses it, not a fault */
|
|
{
|
|
static char t[80];
|
|
same("store -1 5");
|
|
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS); same(t);
|
|
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS - 1); same(t);
|
|
}
|
|
|
|
/* a token too long where a name or a value is wanted: "name too long", as
|
|
* text.c says, not "missing" -- the first error stands */
|
|
{
|
|
static const char *const forms[] = { "constant %s 5", "constant K %s", "set %s 5", "set S %s", ": %s",
|
|
"store %s 5", "store 300 %s", "#, %s", "jump %s", "if %s", "\", %s" };
|
|
static char t[160], long_tok[65];
|
|
memset(long_tok, 'X', 64); long_tok[64] = 0;
|
|
for (i = 0; i < sizeof forms / sizeof forms[0]; i++) { snprintf(t, sizeof t, forms[i], long_tok); same(t); }
|
|
}
|
|
|
|
/* text after start: refused, as text.c refuses it; blanks, comments and
|
|
* the blob's end after it are not text */
|
|
CHECK(rom_run(": Z ;\nstart Z\n1\n\x04") == 0 && b.mem[ROM_FAILED] == ROM_ERR_AFTER, "text after start: refused (code %ld)",
|
|
(long)b.mem[ROM_FAILED]);
|
|
CHECK(rom_run(": Z ;\nstart Z \\ a comment\n( and another )\n\x1c\x04") == 1, "comments and a blob's end after start: started (code %ld)",
|
|
(long)b.mem[ROM_FAILED]);
|
|
{
|
|
v4_node_reset(&a);
|
|
v4_text_begin(&tx, &a, ROM_ORIGIN);
|
|
CHECK(!v4_text_assemble(&tx, ": Z ;\nstart Z\n1\n") && code_of(v4_text_error(&tx)) == ROM_ERR_AFTER,
|
|
"text.c refuses text after start too: %s", v4_text_error(&tx));
|
|
}
|
|
|
|
/* the ROM's error messages, as the boot prints them: one list
|
|
* (v4/rom_errors.h), its codes 1, 2, 3 ... in order */
|
|
{
|
|
static const struct { int code; const char *text; } list[] = {
|
|
#define E(name, code, text) { code, text },
|
|
V4_ROM_ERRORS(E)
|
|
#undef E
|
|
};
|
|
for (i = 0; i < sizeof list / sizeof list[0]; i++) if (list[i].code != (int)i + 1) break;
|
|
CHECK(i == sizeof list / sizeof list[0], "the ROM's error codes run 1, 2, 3 ... : entry %u is %d", i, list[i].code);
|
|
CHECK(ROM_ERR_RANGE == 25 && strcmp(list[ROM_ERR_RANGE - 1].text, "address out of range") == 0, "ROM_ERR_RANGE's message");
|
|
CHECK(strcmp(list[ROM_ERR_AFTER - 1].text, "text after start") == 0, "ROM_ERR_AFTER's message");
|
|
}
|
|
|
|
printf("test_host_rom_asm: %d checks, %d failures\n", checks, failures);
|
|
return failures != 0;
|
|
}
|