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>
118 lines
5.0 KiB
C
118 lines
5.0 KiB
C
/* 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;
|
|
}
|