feat(v4): mkrom -- the ROM built off the node; it reads tokens
The ROM's source is F18 code in capsules/v4/rom, assembled off the node by v4/tools/mkrom.c with the memory map's constants from v4/tools/mkmap.c (MACHINE.md 2). main.f18 is the entry table payloads call through (2.1); in.f18 reads the console and splits it into tokens exactly as text.c does, the token held packed as a counted string. The ROM's state is a reserved area of the map (host_map.h ROM_*). text.c gains the constant directive, so the off-node assembler and the ROM speak one language. The map also names (NUMBER)'s state, for the next task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
32753f5479
commit
0f82d8c22a
@@ -119,6 +119,23 @@
|
||||
#define MSG (BUF0_W - 48) /* the header of the message being served, 7 cells */
|
||||
#define OUT_W (BUF0_W - 400) /* the output buffer: 256 cells, a character to a cell */
|
||||
#define OUT_CELLS 256
|
||||
/* THE ROM'S STATE (docs/v4.0.0/MACHINE.md 2.1): a reserved area that belongs to the ROM's routines, which payloads
|
||||
* call at run time; it is never cleared after the boot. OUT_W - 144 .. OUT_W - 65, which was free. */
|
||||
#define ROM_TOK (OUT_W - 144) /* the token being read, as a counted string: 16 cells */
|
||||
#define ROM_TOKN (OUT_W - 80) /* how many characters it has */
|
||||
#define ROM_PEEK (OUT_W - 79) /* a character put back, plus one; 0: none */
|
||||
#define ROM_LINE (OUT_W - 78) /* how many new lines have been read */
|
||||
#define ROM_EOF (OUT_W - 77) /* non-zero once EOT has been read: the input has ended */
|
||||
#define ROM_FAILED (OUT_W - 76) /* 0, or the code of the error that stopped the ROM */
|
||||
#define ROM_ERRLINE (OUT_W - 75) /* ROM_LINE when it failed */
|
||||
#define ROM_NBASE (OUT_W - 74) /* (NUMBER): the base, 10 or 16 */
|
||||
#define ROM_NNEG (OUT_W - 73) /* 1 after a leading - */
|
||||
#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 */
|
||||
/* 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 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 */
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* rom_harness.h -- run the ROM's routines on a host node, for tests.
|
||||
*
|
||||
* The ROM (capsules/v4/rom, built by tools/mkrom.c into $(BINDIR)/rom-<w>.c)
|
||||
* is attached to a host node as the machine's ROM. A routine is called
|
||||
* through the ROM's entry table, as a payload calls it (MACHINE.md 2.1): the
|
||||
* table's order is fixed, and is rom_entries[] below. Where a test needs to
|
||||
* know whether P is inside a routine, the ROM's symbols are read from
|
||||
* $(BINDIR)/rom-<w>.sym (V4_ROM_SYM).
|
||||
*/
|
||||
#ifndef V4_TESTS_ROM_HARNESS_H
|
||||
#define V4_TESTS_ROM_HARNESS_H
|
||||
|
||||
#include "v4/testcode.h"
|
||||
#include "host_map.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if V4_CELL_BITS == 64
|
||||
extern const v4_cell v4_rom_64[];
|
||||
extern const unsigned v4_rom_64_words;
|
||||
#define ROM_IMAGE v4_rom_64
|
||||
#define ROM_IMAGE_WORDS v4_rom_64_words
|
||||
#else
|
||||
extern const v4_cell v4_rom_32[];
|
||||
extern const unsigned v4_rom_32_words;
|
||||
#define ROM_IMAGE v4_rom_32
|
||||
#define ROM_IMAGE_WORDS v4_rom_32_words
|
||||
#endif
|
||||
|
||||
/* 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)",
|
||||
"(OP)", "(LIT)", "(BRANCH)", "(BRANCH>)", "(RESOLVE)", "(LABEL)"
|
||||
};
|
||||
#define ROM_ENTRIES (sizeof rom_entries / sizeof rom_entries[0])
|
||||
|
||||
static v4_exec_state rom_es;
|
||||
static v4_heat rom_h;
|
||||
|
||||
/* The address a payload calls for the routine `name`: its entry. */
|
||||
static v4_cell rom_entry(const char *name)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < ROM_ENTRIES; i++) if (strcmp(rom_entries[i], name) == 0) return V4_ROM_BASE + (v4_cell)i;
|
||||
printf("rom_harness: %s is not in the entry table\n", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Call the routine through its entry. 1 if it returned within `max_words`
|
||||
* instruction words; 0 if not, and the node is left where it got to, to be
|
||||
* fed and resumed. */
|
||||
static int rom_call(v4_node *n, const char *name, long max_words)
|
||||
{
|
||||
return v4_test_call(n, &rom_es, &rom_h, rom_entry(name), max_words) >= 0;
|
||||
}
|
||||
|
||||
/* Go on with a call that had not returned. 1 if it now has. */
|
||||
static int rom_resume(v4_node *n, long max_words)
|
||||
{
|
||||
long words = 0;
|
||||
while (n->p != V4_TEST_HALT) {
|
||||
if (words++ >= max_words || n->stopped) return 0;
|
||||
(void)v4_exec_step_word(n, &rom_es, &rom_h);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 1 if P is inside the ROM's word `name`: at or after it, and before the
|
||||
* next word the ROM has. */
|
||||
static int rom_in(const v4_node *n, const char *name)
|
||||
{
|
||||
FILE *f = fopen(V4_ROM_SYM, "r");
|
||||
char sym[80];
|
||||
long addr, start = -1, next = -1;
|
||||
if (!f) { printf("rom_harness: cannot read %s\n", V4_ROM_SYM); return 0; }
|
||||
while (fscanf(f, "%79s %ld", sym, &addr) == 2)
|
||||
if (strcmp(sym, name) == 0) start = addr;
|
||||
rewind(f);
|
||||
while (fscanf(f, "%79s %ld", sym, &addr) == 2)
|
||||
if (start >= 0 && addr > start && (next < 0 || addr < next)) next = addr;
|
||||
fclose(f);
|
||||
if (start < 0) return 0;
|
||||
if (next < 0) next = (long)V4_ROM_BASE + (long)ROM_IMAGE_WORDS;
|
||||
return (long)n->p >= start && (long)n->p < next;
|
||||
}
|
||||
|
||||
/* Reset `n`, attach the ROM and the console's receive registers. */
|
||||
static void rom_boot(v4_node *n)
|
||||
{
|
||||
(void)host_load; /* host_map.h's loader is not used here */
|
||||
(void)host_branch_slots;
|
||||
(void)rom_call; /* not every test uses every helper */
|
||||
(void)rom_resume;
|
||||
(void)rom_in;
|
||||
v4_node_reset(n);
|
||||
v4_exec_reset(&rom_es);
|
||||
v4_heat_reset(&rom_h);
|
||||
v4_node_rom_attach(n, ROM_IMAGE, ROM_IMAGE_WORDS);
|
||||
v4_node_console_input_attach(n, CONSOLE_RX, CONSOLE_ST);
|
||||
}
|
||||
|
||||
#endif /* V4_TESTS_ROM_HARNESS_H */
|
||||
@@ -0,0 +1,117 @@
|
||||
/* test_host_rom_in.c -- the ROM's input: characters from the console, and tokens.
|
||||
*
|
||||
* docs/v4.0.0/plans/2026-10-15-machine-step1-rom.md Task 2. The ROM reads
|
||||
* the text it assembles from CONSOLE-RX, waiting on CONSOLE-STATUS, and
|
||||
* splits it into tokens exactly as v4/src/text.c's next_token does: blanks
|
||||
* are space, tab, CR and LF; `\` skips to the end of the line and `(` to the
|
||||
* next `)`; a token is at most 63 characters. The end of the input is EOT
|
||||
* (4), which the loader sends after the last blob.
|
||||
*
|
||||
* Routines are called through the ROM's entry table, as a payload calls them
|
||||
* (MACHINE.md 2.1).
|
||||
*/
|
||||
#include "rom_harness.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)
|
||||
|
||||
static v4_node n;
|
||||
|
||||
/* the token the ROM has read, as a C string. (TOK) holds it as a counted
|
||||
* string: the count byte, then the characters, four to a cell, low byte
|
||||
* first, zero-padded -- the form `",` lays down and (FIND) compares. */
|
||||
static unsigned tok_byte(unsigned b)
|
||||
{
|
||||
return (unsigned)(((v4_ucell)n.mem[ROM_TOK + (v4_cell)(b / 4u)] >> (8u * (b % 4u))) & 0xFFu);
|
||||
}
|
||||
static const char *tok(void)
|
||||
{
|
||||
static char s[80];
|
||||
v4_cell len = n.mem[ROM_TOKN], i;
|
||||
if (len < 0 || len > 63) len = 0;
|
||||
for (i = 0; i < len; i++) s[i] = (char)tok_byte((unsigned)i + 1u);
|
||||
s[len] = 0;
|
||||
return s;
|
||||
}
|
||||
/* the token's padding is zero and its count byte is its length */
|
||||
static int tok_packed(void)
|
||||
{
|
||||
unsigned b, len = (unsigned)n.mem[ROM_TOKN];
|
||||
if (tok_byte(0) != len) return 0;
|
||||
for (b = len + 1u; b < 64u; b++) if (tok_byte(b) != 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* call (TOKEN); its length, or -1 if it did not return */
|
||||
static v4_cell token(void)
|
||||
{
|
||||
if (!rom_call(&n, "(TOKEN)", 100000)) return -1;
|
||||
return v4_dstack_pop(&n.ds);
|
||||
}
|
||||
|
||||
static void feed(const char *s) { v4_node_console_feed(&n, s, (unsigned)strlen(s)); }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
rom_boot(&n);
|
||||
|
||||
/* (KEY) waits for a character, then takes it */
|
||||
feed("Q");
|
||||
CHECK(rom_call(&n, "(KEY)", 1000) && v4_dstack_pop(&n.ds) == 'Q', "(KEY) takes a character");
|
||||
CHECK(!rom_call(&n, "(KEY)", 1000), "with none pending it waits");
|
||||
CHECK(rom_in(&n, "(KEY)"), "inside (KEY): P %ld", (long)n.p);
|
||||
|
||||
/* tokens, comments, lines */
|
||||
rom_boot(&n);
|
||||
feed(" dup \\ c\n ( x\n y ) over\t; \x04");
|
||||
CHECK(token() == 3 && strcmp(tok(), "dup") == 0, "first token: dup, got '%s'", tok());
|
||||
CHECK(tok_packed(), "packed as a counted string, zero-padded");
|
||||
CHECK(n.mem[ROM_LINE] == 0, "no new line passed yet: %ld", (long)n.mem[ROM_LINE]);
|
||||
CHECK(token() == 4 && strcmp(tok(), "over") == 0, "comments skipped: over, got '%s'", tok());
|
||||
CHECK(n.mem[ROM_LINE] == 2, "two new lines passed, one in each comment: %ld", (long)n.mem[ROM_LINE]);
|
||||
CHECK(token() == 1 && strcmp(tok(), ";") == 0, "then ;, got '%s'", tok());
|
||||
CHECK(tok_packed(), "a shorter token after a longer one leaves no trace of it");
|
||||
CHECK(token() == 0, "then the end");
|
||||
CHECK(token() == 0, "and the end stays the end");
|
||||
CHECK(n.mem[ROM_FAILED] == 0, "nothing failed");
|
||||
|
||||
/* the input running dry is not the end: (TOKEN) waits (Review Focus 5) */
|
||||
rom_boot(&n);
|
||||
CHECK(!rom_call(&n, "(TOKEN)", 10000), "no input: (TOKEN) waits");
|
||||
CHECK(rom_in(&n, "(KEY)"), "waiting in (KEY)");
|
||||
feed("ab");
|
||||
CHECK(!rom_resume(&n, 10000), "a token not yet ended: still waiting");
|
||||
feed("c\x04");
|
||||
CHECK(rom_resume(&n, 10000) && v4_dstack_pop(&n.ds) == 3 && strcmp(tok(), "abc") == 0, "then it ends: abc, got '%s'", tok());
|
||||
|
||||
/* a token ended by EOT, with no blank after it */
|
||||
rom_boot(&n);
|
||||
feed("x\x04");
|
||||
CHECK(token() == 1 && strcmp(tok(), "x") == 0, "x before EOT");
|
||||
CHECK(token() == 0, "then the end");
|
||||
|
||||
/* "(" and "\" are comments only as whole tokens */
|
||||
rom_boot(&n);
|
||||
feed("(x \\y )\x04");
|
||||
CHECK(token() == 2 && strcmp(tok(), "(x") == 0, "(x is a token, got '%s'", tok());
|
||||
CHECK(token() == 2 && strcmp(tok(), "\\y") == 0, "\\y is a token, got '%s'", tok());
|
||||
CHECK(token() == 1 && strcmp(tok(), ")") == 0, ") is a token, got '%s'", tok());
|
||||
|
||||
/* 63 characters is a token; 64 is "name too long" */
|
||||
rom_boot(&n);
|
||||
feed("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \x04");
|
||||
CHECK(token() == 63 && n.mem[ROM_FAILED] == 0 && tok_packed(), "63 characters are a token");
|
||||
rom_boot(&n);
|
||||
feed("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \x04");
|
||||
CHECK(token() == 0 && n.mem[ROM_FAILED] == ROM_ERR_TOO_LONG, "64 are name too long: failed %ld", (long)n.mem[ROM_FAILED]);
|
||||
|
||||
/* a comment not closed */
|
||||
rom_boot(&n);
|
||||
feed("\n( never closed\x04");
|
||||
CHECK(token() == 0 && n.mem[ROM_FAILED] == ROM_ERR_COMMENT, "( with no ): failed %ld", (long)n.mem[ROM_FAILED]);
|
||||
CHECK(n.mem[ROM_ERRLINE] == 1, "on the second line (0-based 1): %ld", (long)n.mem[ROM_ERRLINE]);
|
||||
|
||||
printf("test_host_rom_in: %d checks, %d failures\n", checks, failures);
|
||||
return failures != 0;
|
||||
}
|
||||
@@ -489,6 +489,16 @@ int main(void)
|
||||
CHECK(v4_text_word(&tx, "FAR") > (v4_cell)(V4_NODE_WORDS - 200u), "FAR is far: %ld", (long)v4_text_word(&tx, "FAR"));
|
||||
}
|
||||
|
||||
/* ---- `constant NAME VALUE`, the directive form of v4_text_constant (MACHINE.md, the ROM's language) ---- */
|
||||
CHECK(ok("constant SEVEN 7 constant MINUS -2 constant BIG $7F : T SEVEN MINUS + BIG + ;") && result("T", 0, 0, 0) == 132,
|
||||
"constant defines a literal: %s", v4_text_error(&tx));
|
||||
CHECK(ok("constant K 3 : T K+2 ;") && result("T", 0, 0, 0) == 5, "CONST+N works with a directive constant");
|
||||
CHECK(refused("constant K 3 constant K 4", "already defined", 1), "a constant twice is refused");
|
||||
CHECK(refused("constant dup 3", "cannot be used as a name", 1), "an opcode is not a constant's name");
|
||||
CHECK(refused("constant K", "constant without a value", 1), "constant needs a value");
|
||||
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");
|
||||
|
||||
CHECK(v4_node_guards_intact(&na) && v4_node_guards_intact(&nb), "guards intact");
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
|
||||
Reference in New Issue
Block a user