/* 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; }