/* test_strings.c -- the byte-string words, executed. * * DECOMPOSITION.md 5.3 and 5.9: FILL ERASE MOVE, COUNT CMOVE CMOVE> BLANK * -TRAILING COMPARE SEARCH SCAN SKIP. All are loops over C@ and C!, which * are assembled again here as they are in test_foundation.c, on a node of * its own. * * Behaviour follows v3 (v3/src/word_source/string_words.c, memory_words.c): * COUNT ( baddr -- baddr+1 c ) * CMOVE ( src dst u -- ) low byte first * CMOVE> ( src dst u -- ) high byte first * MOVE ( addr1 addr2 n -- ) FORTH-79 (ruled 2026-10-04): n cells * from addr1 to addr2, the cell at addr1 * first; nothing for n <= 0. The addresses * are word addresses (D-1). v3's MOVE moved * bytes, either way round, like memmove. * FILL ( baddr u c -- ) the low byte of c * BLANK ERASE ( baddr u -- ) FILL with 32, with 0 * -TRAILING ( baddr u -- baddr u' ) without its trailing spaces * COMPARE ( a1 u1 a2 u2 -- n ) -1, 0 or 1: the first differing * byte (unsigned), else the shorter is less * SEARCH ( a1 u1 a2 u2 -- a3 u3 f ) the first place string 2 occurs in * string 1: the rest of string 1 from there * and -1, or string 1 and 0; an empty * string 2 is found at the start * SCAN ( baddr u c -- baddr' u' ) from the first byte equal to c * SKIP ( baddr u c -- baddr' u' ) past the leading bytes equal to c * A negative count reads as 0 in -TRAILING, COMPARE, SEARCH, SCAN and SKIP, * as in v3. In CMOVE, CMOVE>, MOVE, FILL, BLANK and ERASE it does nothing * and sets NODE-ERROR, where v3 raised its error flag (or, in FILL, read it * as a huge unsigned count). * * Where v4 parts from v3: v3's -TRAILING, COMPARE, SEARCH, SCAN, SKIP and * BLANK took a string whose first byte equalled its length to be a counted * string and stepped over that byte. 5.9 does not keep that for COMPARE and * SEARCH; it is not kept for the others either. Addresses are not * range-checked (open, node.h). * * Five transcripts of the real v3 binary are recorded below as expected * results, taken on 2026-10-03 as in test_numout.c. */ #include "v4/asm.h" #include "v4/testcode.h" #include #include #include 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 CANARY ((v4_cell)0x0C0FFEE5) /* The memory map is open (D-4); the test chooses the addresses. */ #define NODE_ERROR ((v4_cell)(V4_NODE_WORDS - 2u)) #define SV ((v4_cell)(V4_NODE_WORDS - 12u)) /* (S): COMPARE's and SEARCH's lengths and addresses, 4 cells */ #define BUFW ((v4_cell)(V4_NODE_WORDS - 96u)) /* 64 cells = 256 bytes of text */ #define BUF ((v4_cell)(BUFW * 4)) #define BUFLEN 256 static v4_node n; static v4_exec_state es; static v4_heat h; static v4_asm as; static v4_cell w_cfetch, w_cstore, w_count, w_cmove, w_cmoveup, w_move, w_fill, w_blank, w_erase, w_trailing, w_compare, w_search, w_scan, w_skip; #define O(name) v4_asm_op(&as, V4_OP_##name) #define LIT(v) v4_asm_lit(&as, (v4_cell)(v)) #define CALL(w) v4_asm_branch(&as, V4_OP_CALL, (w)) #define JUMP(w) v4_asm_branch(&as, V4_OP_JUMP, (w)) #define FWD(op) v4_asm_branch_fwd(&as, V4_OP_##op) #define HERE_(r) v4_asm_resolve(&as, (r), v4_asm_label(&as)) #define VGET(a) do { LIT(a); O(BANG_B); O(FETCH_B); } while (0) #define VSET(a) do { LIT(a); O(BANG_B); O(STORE_B); } while (0) #define SWAP_INLINE() do { O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); } while (0) #define NROT_INLINE() do { SWAP_INLINE(); O(PUSH); SWAP_INLINE(); O(RPOP); } while (0) #define SUB_INLINE() do { O(PUSH); O(INV); O(RPOP); O(ADD); O(INV); } while (0) #define ERROR_EXIT() do { LIT(NODE_ERROR); O(BANG_B); LIT(-1); O(STORE_B); O(SEMI); } while (0) static void build(void) { v4_asm_ref a, b, c; v4_cell l, l2; /* C@ and C!, call-free, as in test_foundation.c. */ w_cfetch = v4_asm_label(&as); { v4_asm_ref f0, f1, f2; O(DUP); O(TWO_SLASH); O(TWO_SLASH); O(BANG_A); LIT(3); O(AND); f0 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); f1 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); f2 = v4_asm_branch_fwd(&as, V4_OP_IF); O(DROP); O(FETCH_A); LIT(23); O(PUSH); (void)v4_asm_label(&as); O(TWO_SLASH); O(UNEXT); LIT(255); O(AND); O(SEMI); v4_asm_resolve(&as, f2, v4_asm_label(&as)); O(DROP); O(FETCH_A); LIT(15); O(PUSH); (void)v4_asm_label(&as); O(TWO_SLASH); O(UNEXT); LIT(255); O(AND); O(SEMI); v4_asm_resolve(&as, f1, v4_asm_label(&as)); O(DROP); O(FETCH_A); LIT(7); O(PUSH); (void)v4_asm_label(&as); O(TWO_SLASH); O(UNEXT); LIT(255); O(AND); O(SEMI); v4_asm_resolve(&as, f0, v4_asm_label(&as)); O(DROP); O(FETCH_A); LIT(255); O(AND); O(SEMI); } { v4_asm_ref k0, k1, k2; w_cstore = v4_asm_label(&as); O(DUP); O(TWO_SLASH); O(TWO_SLASH); O(BANG_A); LIT(3); O(AND); O(PUSH); LIT(255); O(AND); O(RPOP); k0 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); k1 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); k2 = v4_asm_branch_fwd(&as, V4_OP_IF); O(DROP); LIT(23); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT((v4_cell)(v4_ucell)0xFF000000u); O(INV); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k2, v4_asm_label(&as)); O(DROP); LIT(15); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT(-16711681); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k1, v4_asm_label(&as)); O(DROP); LIT(7); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT(-65281); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k0, v4_asm_label(&as)); O(DROP); O(FETCH_A); LIT(-256); O(AND); O(ADD); O(STORE_A); O(SEMI); } /* : COUNT ( baddr -- baddr+1 c ) dup C@ push 1 + pop ; */ w_count = v4_asm_label(&as); O(DUP); CALL(w_cfetch); O(PUSH); LIT(1); O(ADD); O(RPOP); O(SEMI); /* : CMOVE ( src dst u -- ) * -if OK drop drop drop NODE-ERROR b! -1 !b ; u < 0 * OK: if DONE push src dst R: u * over C@ over C! 1 + push 1 + pop pop -1 + jump OK * DONE: drop drop drop ; */ w_cmove = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); ERROR_EXIT(); HERE_(a); l = v4_asm_label(&as); b = FWD(IF); O(PUSH); O(OVER); CALL(w_cfetch); O(OVER); CALL(w_cstore); LIT(1); O(ADD); O(PUSH); LIT(1); O(ADD); O(RPOP); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(DROP); O(DROP); O(DROP); O(SEMI); /* : CMOVE> ( src dst u -- ) * -if OK drop drop drop NODE-ERROR b! -1 !b ; u < 0 * OK: if DONE -1 + push src dst R: i = u - 1 * over pop dup push + C@ src dst c * over pop dup push + C! src dst * pop jump OK * DONE: drop drop drop ; */ w_cmoveup = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); ERROR_EXIT(); HERE_(a); l = v4_asm_label(&as); b = FWD(IF); LIT(-1); O(ADD); O(PUSH); O(OVER); O(RPOP); O(DUP); O(PUSH); O(ADD); CALL(w_cfetch); O(OVER); O(RPOP); O(DUP); O(PUSH); O(ADD); CALL(w_cstore); O(RPOP); JUMP(l); HERE_(b); O(DROP); O(DROP); O(DROP); O(SEMI); /* : MOVE ( addr1 addr2 n -- ) FORTH-79: cells, low address first * -if OK drop drop drop ; n < 0 * OK: if DONE push addr1 addr2 R: n * over a! @ over a! ! 1 + push 1 + pop pop -1 + jump OK * DONE: drop drop drop ; */ w_move = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); O(SEMI); HERE_(a); l = v4_asm_label(&as); b = FWD(IF); O(PUSH); O(OVER); O(BANG_A); O(FETCH_A); O(OVER); O(BANG_A); O(STORE_A); LIT(1); O(ADD); O(PUSH); LIT(1); O(ADD); O(RPOP); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(DROP); O(DROP); O(DROP); O(SEMI); /* : FILL ( baddr u c -- ) * -ROT c baddr u * -if OK drop drop drop NODE-ERROR b! -1 !b ; u < 0 * OK: if DONE push over over C! 1 + pop -1 + jump OK * DONE: drop drop drop ; */ w_fill = v4_asm_label(&as); NROT_INLINE(); a = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); ERROR_EXIT(); HERE_(a); l = v4_asm_label(&as); b = FWD(IF); O(PUSH); O(OVER); O(OVER); CALL(w_cstore); LIT(1); O(ADD); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(DROP); O(DROP); O(DROP); O(SEMI); /* : BLANK ( baddr u -- ) 32 jump FILL : ERASE ( baddr u -- ) 0 jump FILL */ w_blank = v4_asm_label(&as); LIT(32); JUMP(w_fill); w_erase = v4_asm_label(&as); LIT(0); JUMP(w_fill); /* : -TRAILING ( baddr u -- baddr u' ) * -if L drop 0 ; u < 0 * L: if DONE over over + -1 + C@ -32 + if SP drop ; not a space * SP: drop -1 + jump L * DONE: ; */ w_trailing = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); LIT(0); O(SEMI); HERE_(a); l = v4_asm_label(&as); b = FWD(IF); O(OVER); O(OVER); O(ADD); LIT(-1); O(ADD); CALL(w_cfetch); LIT(-32); O(ADD); c = FWD(IF); O(DROP); O(SEMI); HERE_(c); O(DROP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(SEMI); /* : COMPARE ( a1 u1 a2 u2 -- n ) * -if A drop 0 A: (S) 1 + b! !b a1 u1 a2 * SWAP -if B drop 0 B: dup (S) b! !b a1 a2 u1 * (S) 1 + b! @b a1 a2 u1 u2 * over over - -if GE drop drop jump M the smaller * GE: drop push drop pop * M: a1 a2 m * L: if EQ push a1 a2 R: m * over C@ over C@ - if SAME c1 - c2 * -if GT drop drop drop pop drop -1 ; * GT: drop drop drop pop drop 1 ; * SAME: drop 1 + push 1 + pop pop -1 + jump L * EQ: drop drop drop (S) b! @b (S) 1 + b! @b - u1 - u2 * -if NN drop -1 ; * NN: if ZZ drop 1 ; * ZZ: ; * SWAP and the subtractions are in line. The two clamped lengths wait * in the variable (S). */ w_compare = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); VSET(SV + 1); SWAP_INLINE(); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); O(DUP); VSET(SV); VGET(SV + 1); O(OVER); O(OVER); SUB_INLINE(); a = FWD(MINUS_IF); O(DROP); O(DROP); b = FWD(JUMP); HERE_(a); O(DROP); O(PUSH); O(DROP); O(RPOP); HERE_(b); l = v4_asm_label(&as); a = FWD(IF); O(PUSH); O(OVER); CALL(w_cfetch); O(OVER); CALL(w_cfetch); SUB_INLINE(); b = FWD(IF); c = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); O(RPOP); O(DROP); LIT(-1); O(SEMI); HERE_(c); O(DROP); O(DROP); O(DROP); O(RPOP); O(DROP); LIT(1); O(SEMI); HERE_(b); O(DROP); LIT(1); O(ADD); O(PUSH); LIT(1); O(ADD); O(RPOP); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(a); O(DROP); O(DROP); O(DROP); VGET(SV); VGET(SV + 1); SUB_INLINE(); a = FWD(MINUS_IF); O(DROP); LIT(-1); O(SEMI); HERE_(a); b = FWD(IF); O(DROP); LIT(1); O(SEMI); HERE_(b); O(SEMI); /* : SEARCH ( a1 u1 a2 u2 -- a3 u3 flag ) * -if A drop 0 A: (S) 1 + b! !b (S) b! !b a1 u1 (S): a2 u2 * -if B drop 0 B: dup (S) 3 + b! !b over (S) 2 + b! !b a u (S)+2: a1 u1 * L: dup (S) 1 + b! @b - -if TRY u - u2 * drop drop drop (S) 2 + b! @b (S) 3 + b! @b 0 ; not found * TRY: drop over (S) b! @b (S) 1 + b! @b a u p q k * I: if MATCH push a u p q R: k * over C@ over C@ xor if SAME * drop drop drop pop drop push 1 + pop -1 + jump L next place * SAME: drop 1 + push 1 + pop pop -1 + jump I * MATCH: drop drop drop -1 ; */ w_search = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); VSET(SV + 1); VSET(SV); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); O(DUP); VSET(SV + 3); O(OVER); VSET(SV + 2); l = v4_asm_label(&as); O(DUP); VGET(SV + 1); SUB_INLINE(); a = FWD(MINUS_IF); O(DROP); O(DROP); O(DROP); VGET(SV + 2); VGET(SV + 3); LIT(0); O(SEMI); HERE_(a); O(DROP); O(OVER); VGET(SV); VGET(SV + 1); l2 = v4_asm_label(&as); a = FWD(IF); O(PUSH); O(OVER); CALL(w_cfetch); O(OVER); CALL(w_cfetch); O(XOR); b = FWD(IF); O(DROP); O(DROP); O(DROP); O(RPOP); O(DROP); O(PUSH); LIT(1); O(ADD); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(DROP); LIT(1); O(ADD); O(PUSH); LIT(1); O(ADD); O(RPOP); O(RPOP); LIT(-1); O(ADD); JUMP(l2); HERE_(a); O(DROP); O(DROP); O(DROP); LIT(-1); O(SEMI); /* : SCAN ( baddr u c -- baddr' u' ) * 255 and push -if L drop 0 baddr u R: c * L: if DONE over C@ pop dup push xor if FOUND * drop push 1 + pop -1 + jump L * FOUND: drop * DONE: pop drop ; */ w_scan = v4_asm_label(&as); LIT(255); O(AND); O(PUSH); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); l = v4_asm_label(&as); a = FWD(IF); O(OVER); CALL(w_cfetch); O(RPOP); O(DUP); O(PUSH); O(XOR); b = FWD(IF); O(DROP); O(PUSH); LIT(1); O(ADD); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(b); O(DROP); HERE_(a); O(RPOP); O(DROP); O(SEMI); /* : SKIP ( baddr u c -- baddr' u' ) * 255 and push -if L drop 0 baddr u R: c * L: if DONE over C@ pop dup push xor if SAME drop jump DONE * SAME: drop push 1 + pop -1 + jump L * DONE: pop drop ; */ w_skip = v4_asm_label(&as); LIT(255); O(AND); O(PUSH); a = FWD(MINUS_IF); O(DROP); LIT(0); HERE_(a); l = v4_asm_label(&as); a = FWD(IF); O(OVER); CALL(w_cfetch); O(RPOP); O(DUP); O(PUSH); O(XOR); b = FWD(IF); O(DROP); c = FWD(JUMP); HERE_(b); O(DROP); O(PUSH); LIT(1); O(ADD); O(RPOP); LIT(-1); O(ADD); JUMP(l); HERE_(a); HERE_(c); O(RPOP); O(DROP); O(SEMI); } /* ---- running ---------------------------------------------------------- */ /* The test's own copy of the 256 text bytes; the node's are compared with * it after every call. */ static unsigned char img[BUFLEN]; static unsigned char node_byte(v4_cell baddr) { return (unsigned char)(((v4_ucell)n.mem[baddr >> 2] >> (8u * (unsigned)(baddr & 3))) & 0xFFu); } static void load_img(void) { unsigned i; for (i = 0; i < BUFLEN / 4; i++) { v4_ucell w = (v4_ucell)img[4 * i] | ((v4_ucell)img[4 * i + 1] << 8) | ((v4_ucell)img[4 * i + 2] << 16) | ((v4_ucell)img[4 * i + 3] << 24); n.mem[BUFW + (v4_cell)i] = (v4_cell)w; } } static int img_matches(void) { unsigned i; for (i = 0; i < BUFLEN; i++) if (node_byte(BUF + (v4_cell)i) != img[i]) return 0; for (i = 0; i < BUFLEN / 4; i++) /* nothing above byte 3 of a cell either */ if (((v4_ucell)n.mem[BUFW + (v4_cell)i] >> 16 >> 16) != 0) return 0; return 1; } /* Runs `word` on argc arguments over the current img; true when it returns. * The results are then popped with res(). */ static int call(v4_cell word, unsigned argc, v4_cell a, v4_cell b, v4_cell c, v4_cell d) { v4_cell args[4]; unsigned i; args[0] = a; args[1] = b; args[2] = c; args[3] = d; load_img(); v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&es); v4_heat_reset(&h); v4_node_store(&n, NODE_ERROR, 0); v4_dstack_push(&n.ds, CANARY); for (i = 0; i < argc; i++) v4_dstack_push(&n.ds, args[i]); return v4_test_call(&n, &es, &h, word, 4000000) > 0; } static v4_cell res(void) { return v4_dstack_pop(&n.ds); } static int clean(void) { return v4_dstack_pop(&n.ds) == CANARY; } static int err(void) { return v4_node_load(&n, NODE_ERROR) != 0; } /* Stack headroom, as in test_foundation.c: the results and the bytes must * be what a run with empty stacks gave. */ static int fits(v4_cell word, unsigned argc, const v4_cell *args, unsigned nres, const v4_cell *want, const unsigned char *img0, const unsigned char *img1, unsigned dfill, unsigned rfill) { unsigned i; memcpy(img, img0, BUFLEN); load_img(); v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&es); for (i = 0; i < dfill; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i)); v4_dstack_push(&n.ds, CANARY); for (i = 0; i < argc; i++) v4_dstack_push(&n.ds, args[i]); for (i = 0; i < rfill; i++) v4_rstack_push(&n.rs, (v4_cell)(0x6B000000 + i)); if (v4_test_call(&n, &es, &h, word, 4000000) <= 0) return 0; for (i = nres; i-- > 0; ) if (v4_dstack_pop(&n.ds) != want[i]) return 0; if (v4_dstack_pop(&n.ds) != CANARY) return 0; for (i = dfill; i-- > 0; ) if (v4_dstack_pop(&n.ds) != (v4_cell)(0x5A000000 + i)) return 0; for (i = rfill; i-- > 0; ) if (v4_rstack_pop(&n.rs) != (v4_cell)(0x6B000000 + i)) return 0; memcpy(img, img1, BUFLEN); return img_matches(); } static void headroom(const char *name, v4_cell word, unsigned argc, v4_cell a, v4_cell b, v4_cell c, v4_cell d, unsigned nres, int min_d, int min_r) { unsigned char img0[BUFLEN], img1[BUFLEN]; v4_cell args[4], want[4]; unsigned i; int dh, rh; args[0] = a; args[1] = b; args[2] = c; args[3] = d; memcpy(img0, img, BUFLEN); CHECK(call(word, argc, a, b, c, d), "%s runs", name); for (i = nres; i-- > 0; ) want[i] = res(); for (i = 0; i < BUFLEN; i++) img1[i] = node_byte(BUF + (v4_cell)i); for (dh = 0; dh < V4_DATA_DEPTH; dh++) if (!fits(word, argc, args, nres, want, img0, img1, (unsigned)dh + 1u, 0)) break; for (rh = 0; rh < V4_RET_DEPTH; rh++) if (!fits(word, argc, args, nres, want, img0, img1, 0, (unsigned)rh + 1u)) break; printf(" %s headroom: data %d below canary, return %d below its return address\n", name, dh, rh); CHECK(dh >= min_d && rh >= min_r, "%s leaves room", name); memcpy(img, img0, BUFLEN); } /* ---- the C reference ---------------------------------------------------- */ static uint32_t rng = 0x9E3779B9u; static uint32_t rnd(void) { rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; return rng; } static void random_img(unsigned alphabet) { unsigned i; for (i = 0; i < BUFLEN; i++) img[i] = (unsigned char)(alphabet ? 'a' + rnd() % alphabet : rnd() & 0xFFu); } static void text_at(unsigned off, const char *s) { memcpy(img + off, s, strlen(s)); } static int ref_compare(unsigned o1, long u1, unsigned o2, long u2) { long m, i; if (u1 < 0) u1 = 0; if (u2 < 0) u2 = 0; m = u1 < u2 ? u1 : u2; for (i = 0; i < m; i++) if (img[o1 + i] != img[o2 + i]) return img[o1 + i] < img[o2 + i] ? -1 : 1; return u1 < u2 ? -1 : (u1 > u2 ? 1 : 0); } /* Returns the offset of the match in string 1, or -1. */ static long ref_search(unsigned o1, long u1, unsigned o2, long u2) { long i; if (u1 < 0) u1 = 0; if (u2 < 0) u2 = 0; for (i = 0; i + u2 <= u1; i++) if (u2 == 0 || memcmp(img + o1 + i, img + o2, (size_t)u2) == 0) return i; return -1; } int main(void) { unsigned char before[BUFLEN]; unsigned i, t; printf("v4 string tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); v4_node_reset(&n); v4_asm_begin(&as, &n, 16); build(); CHECK(v4_asm_ok(&as), "string words assemble"); CHECK(v4_asm_label(&as) < BUFW, "code stays below the text"); printf(" code: %ld of %u words\n", (long)v4_asm_label(&as) - 16, (unsigned)V4_NODE_WORDS); /* ---- the v3 transcripts ---- */ /* S" abc" S" abd" COMPARE . abc abc abd abc ab abc abc ab -> -1 0 1 -1 1 */ memset(img, 0, BUFLEN); text_at(0, "abc"); text_at(8, "abd"); text_at(16, "abc"); text_at(24, "ab"); CHECK(call(w_compare, 4, BUF, 3, BUF + 8, 3) && res() == -1 && clean(), "v3: abc abd COMPARE"); CHECK(call(w_compare, 4, BUF, 3, BUF + 16, 3) && res() == 0 && clean(), "v3: abc abc COMPARE"); CHECK(call(w_compare, 4, BUF + 8, 3, BUF, 3) && res() == 1 && clean(), "v3: abd abc COMPARE"); CHECK(call(w_compare, 4, BUF + 24, 2, BUF, 3) && res() == -1 && clean(), "v3: ab abc COMPARE"); CHECK(call(w_compare, 4, BUF, 3, BUF + 24, 2) && res() == 1 && clean(), "v3: abc ab COMPARE"); /* S" hello world" S" o w" SEARCH . . DROP -> -1 7 * S" hello world" S" xyz" SEARCH . . DROP -> 0 11 * S" hello" S" " SEARCH . . DROP -> -1 5 */ memset(img, 0, BUFLEN); text_at(1, "hello world"); text_at(32, "o w"); text_at(40, "xyz"); CHECK(call(w_search, 4, BUF + 1, 11, BUF + 32, 3) && res() == -1 && res() == 7 && res() == BUF + 5 && clean(), "v3: hello world / o w SEARCH"); CHECK(call(w_search, 4, BUF + 1, 11, BUF + 40, 3) && res() == 0 && res() == 11 && res() == BUF + 1 && clean(), "v3: hello world / xyz SEARCH"); CHECK(call(w_search, 4, BUF + 1, 5, BUF + 40, 0) && res() == -1 && res() == 5 && res() == BUF + 1 && clean(), "v3: hello / empty SEARCH"); /* S" hi " -TRAILING . DROP S" aaab" 97 SKIP . DROP S" hello" 108 SCAN . DROP * S" hello" 122 SCAN . DROP S" " -TRAILING . DROP -> 2 1 3 0 0 */ memset(img, 0, BUFLEN); text_at(2, "hi "); text_at(16, "aaab"); text_at(33, "hello"); text_at(48, " "); CHECK(call(w_trailing, 2, BUF + 2, 5, 0, 0) && res() == 2 && res() == BUF + 2 && clean(), "v3: -TRAILING"); CHECK(call(w_skip, 3, BUF + 16, 4, 97, 0) && res() == 1 && res() == BUF + 19 && clean(), "v3: SKIP"); CHECK(call(w_scan, 3, BUF + 33, 5, 108, 0) && res() == 3 && res() == BUF + 35 && clean(), "v3: SCAN"); CHECK(call(w_scan, 3, BUF + 33, 5, 122, 0) && res() == 0 && res() == BUF + 38 && clean(), "v3: SCAN, absent"); CHECK(call(w_trailing, 2, BUF + 48, 4, 0, 0) && res() == 0 && res() == BUF + 48 && clean(), "v3: -TRAILING, all spaces"); /* CREATE B 16 ALLOT B 16 46 FILL B 4 BLANK B 8 + 3 ERASE B 16 DUMP * 20 20 20 20 2E 2E 2E 2E 00 00 00 2E 2E 2E 2E 2E * "ABCDEF" B 6 CMOVE B B 2 + 6 CMOVE> B B 1 + 6 CMOVE B 16 DUMP * 41 41 41 41 41 41 41 46 00 00 00 2E 2E 2E 2E 2E * (v3 went on to B 3 + B 5 MOVE, a byte move; MOVE here is FORTH-79's and moves cells.) */ { static const unsigned char d1[16] = { 0x20,0x20,0x20,0x20,0x2E,0x2E,0x2E,0x2E,0,0,0,0x2E,0x2E,0x2E,0x2E,0x2E }; static const unsigned char d2[16] = { 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x46,0,0,0,0x2E,0x2E,0x2E,0x2E,0x2E }; const v4_cell B = BUF + 21; #define STEP(word, argc, a, b, c) do { \ CHECK(call(word, argc, a, b, c, 0) && clean() && !err(), "v3 block-copy step returns"); \ for (i = 0; i < BUFLEN; i++) img[i] = node_byte(BUF + (v4_cell)i); } while (0) memset(img, 0x77, BUFLEN); text_at(100, "ABCDEF"); STEP(w_fill, 3, B, 16, 46); STEP(w_blank, 2, B, 4, 0); STEP(w_erase, 2, B + 8, 3, 0); CHECK(memcmp(img + 21, d1, 16) == 0, "v3: FILL, BLANK, ERASE"); STEP(w_cmove, 3, BUF + 100, B, 6); STEP(w_cmoveup, 3, B, B + 2, 6); STEP(w_cmove, 3, B, B + 1, 6); CHECK(memcmp(img + 21, d2, 16) == 0, "v3: CMOVE, CMOVE>, CMOVE"); for (i = 0; i < BUFLEN; i++) if ((i < 21 || i >= 37) && (i < 100 || i >= 106) && img[i] != 0x77) break; CHECK(i == BUFLEN, "nothing outside B was written"); } /* 3 B C! 65 66 67 ... B COUNT . B - . -> 3 1 */ memset(img, 0, BUFLEN); img[9] = 3; text_at(10, "ABC"); CHECK(call(w_count, 1, BUF + 9, 0, 0, 0) && res() == 3 && res() == BUF + 10 && clean(), "v3: COUNT"); /* ---- against C ---- */ /* COUNT: every byte value, every position in a cell */ random_img(0); for (i = 0; i < BUFLEN; i++) CHECK(call(w_count, 1, BUF + (v4_cell)i, 0, 0, 0) && res() == img[i] && res() == BUF + (v4_cell)i + 1 && clean() && img_matches(), "COUNT [%u]", i); /* FILL, BLANK, ERASE */ for (t = 0; t < 400; t++) { unsigned off = rnd() % 200, len = rnd() % 40; v4_cell ch = (v4_cell)(rnd() % 3 == 0 ? rnd() : rnd() & 0xFFu); unsigned which = t % 3; random_img(0); memcpy(before, img, BUFLEN); if (which == 0) { CHECK(call(w_fill, 3, BUF + (v4_cell)off, (v4_cell)len, ch, 0) && clean(), "FILL returns"); } else if (which == 1) { CHECK(call(w_blank, 2, BUF + (v4_cell)off, (v4_cell)len, 0, 0) && clean(), "BLANK returns"); ch = 32; } else { CHECK(call(w_erase, 2, BUF + (v4_cell)off, (v4_cell)len, 0, 0) && clean(), "ERASE returns"); ch = 0; } memset(img + off, (int)((v4_ucell)ch & 0xFFu), len); CHECK(img_matches() && !err(), "FILL/BLANK/ERASE [%u] off %u len %u", t, off, len); } /* CMOVE, CMOVE>, MOVE: apart and overlapping both ways */ for (t = 0; t < 900; t++) { unsigned len = rnd() % 48, src = rnd() % (BUFLEN - 48), dst; unsigned which = t % 3, k; if (t % 2) dst = rnd() % (BUFLEN - 48); else { int d = (int)(rnd() % 17) - 8; dst = (unsigned)((int)src + d < 0 ? 0 : (int)src + d); if (dst > BUFLEN - 48) dst = BUFLEN - 48; } random_img(0); if (which == 2) { /* MOVE: cells, not bytes */ len /= 4; src /= 4; dst /= 4; CHECK(call(w_move, 3, BUFW + (v4_cell)src, BUFW + (v4_cell)dst, (v4_cell)len, 0) && clean(), "MOVE returns [%u]", t); for (k = 0; k < 4 * len; k++) img[4 * dst + k] = img[4 * src + k]; } else { CHECK(call(which == 0 ? w_cmove : w_cmoveup, 3, BUF + (v4_cell)src, BUF + (v4_cell)dst, (v4_cell)len, 0) && clean(), "move returns [%u]", t); if (which == 0) for (k = 0; k < len; k++) img[dst + k] = img[src + k]; else for (k = len; k-- > 0; ) img[dst + k] = img[src + k]; } CHECK(img_matches() && !err(), "%s [%u] src %u dst %u len %u", which == 0 ? "CMOVE" : which == 1 ? "CMOVE>" : "MOVE", t, src, dst, len); } /* a negative count: nothing written, NODE-ERROR set */ { static const v4_cell neg[] = { -1, -7, (v4_cell)V4_MSB }; random_img(0); for (i = 0; i < 3; i++) { CHECK(call(w_cmove, 3, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "CMOVE negative count [%u]", i); CHECK(call(w_cmoveup, 3, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "CMOVE> negative count [%u]", i); CHECK(call(w_move, 3, BUFW, BUFW + 12, neg[i], 0) && clean() && img_matches() && !err(), "MOVE moves nothing for n < 0, up [%u]", i); CHECK(call(w_move, 3, BUFW + 12, BUFW, neg[i], 0) && clean() && img_matches() && !err(), "MOVE moves nothing for n < 0, down [%u]", i); CHECK(call(w_fill, 3, BUF, neg[i], 65, 0) && clean() && img_matches() && err(), "FILL negative count [%u]", i); CHECK(call(w_blank, 2, BUF, neg[i], 0, 0) && clean() && img_matches() && err(), "BLANK negative count [%u]", i); CHECK(call(w_erase, 2, BUF, neg[i], 0, 0) && clean() && img_matches() && err(), "ERASE negative count [%u]", i); } } /* -TRAILING */ for (t = 0; t < 600; t++) { unsigned off = rnd() % 200, len = rnd() % 40, sp = rnd() % 41, k, want; random_img(t % 4 == 0 ? 0 : 3); for (k = 0; k < sp && k < len; k++) img[off + len - 1 - k] = 32; if (t % 5 == 0 && len) img[off] = (unsigned char)len; /* v3 would have taken this for a counted string */ for (want = len; want > 0 && img[off + want - 1] == 32; want--) { } CHECK(call(w_trailing, 2, BUF + (v4_cell)off, (v4_cell)len, 0, 0) && res() == (v4_cell)want && res() == BUF + (v4_cell)off && clean() && img_matches() && !err(), "-TRAILING [%u] len %u", t, len); } CHECK(call(w_trailing, 2, BUF + 5, -3, 0, 0) && res() == 0 && res() == BUF + 5 && clean() && !err(), "-TRAILING of a negative count is 0"); /* SCAN and SKIP */ for (t = 0; t < 1200; t++) { unsigned off = rnd() % 200, len = rnd() % 40, k; v4_cell ch; random_img(t % 6 == 0 ? 0 : 2 + t % 3); ch = (v4_cell)(len && t % 3 ? img[off + rnd() % len] : (unsigned char)rnd()); if (t % 7 == 0) ch += 256 * (v4_cell)(1 + rnd() % 100); /* only the low byte counts */ if (t % 2 == 0) { for (k = 0; k < len && img[off + k] != ((v4_ucell)ch & 0xFFu); k++) { } CHECK(call(w_scan, 3, BUF + (v4_cell)off, (v4_cell)len, ch, 0) && res() == (v4_cell)(len - k) && res() == BUF + (v4_cell)(off + k) && clean() && img_matches() && !err(), "SCAN [%u] len %u", t, len); } else { for (k = 0; k < len && img[off + k] == ((v4_ucell)ch & 0xFFu); k++) { } CHECK(call(w_skip, 3, BUF + (v4_cell)off, (v4_cell)len, ch, 0) && res() == (v4_cell)(len - k) && res() == BUF + (v4_cell)(off + k) && clean() && img_matches() && !err(), "SKIP [%u] len %u", t, len); } } CHECK(call(w_scan, 3, BUF + 5, -3, 65, 0) && res() == 0 && res() == BUF + 5 && clean() && !err(), "SCAN of a negative count"); CHECK(call(w_skip, 3, BUF + 5, -3, 65, 0) && res() == 0 && res() == BUF + 5 && clean() && !err(), "SKIP of a negative count"); /* COMPARE */ for (t = 0; t < 3000; t++) { unsigned o1 = rnd() % 100, o2 = 128 + rnd() % 100; long u1 = (long)(rnd() % 24), u2 = (long)(rnd() % 24); int want; random_img(t % 10 == 0 ? 0 : 2); if (t % 3 == 0) { unsigned same = rnd() % 24; memcpy(img + o2, img + o1, same); } if (t % 11 == 0) img[o1 + rnd() % 4] = (unsigned char)(0x80u + rnd() % 128); /* bytes compare unsigned */ if (t % 13 == 0 && u1) img[o1] = (unsigned char)u1; /* not a counted string */ if (t % 50 == 0) u1 = -(long)(1 + rnd() % 5); if (t % 70 == 0) u2 = -(long)(1 + rnd() % 5); want = ref_compare(o1, u1, o2, u2); CHECK(call(w_compare, 4, BUF + (v4_cell)o1, (v4_cell)u1, BUF + (v4_cell)o2, (v4_cell)u2) && res() == (v4_cell)want && clean() && img_matches() && !err(), "COMPARE [%u] u1 %ld u2 %ld want %d", t, u1, u2, want); } CHECK(call(w_compare, 4, BUF + 9, 12, BUF + 9, 12) && res() == 0 && clean(), "COMPARE of a string with itself"); /* SEARCH */ for (t = 0; t < 3000; t++) { unsigned o1 = rnd() % 80, o2 = 160 + rnd() % 60; long u1 = (long)(rnd() % 40), u2 = (long)(rnd() % 6), at, c1, c2; v4_cell f, u3, a3; random_img(t % 10 == 0 ? 0 : 2); if (t % 3 == 0 && u1 >= u2) memcpy(img + o2, img + o1 + rnd() % (unsigned)(u1 - u2 + 1), (size_t)u2); if (t % 4 == 0) u2 = (long)(rnd() % 45); if (t % 50 == 0) u1 = -(long)(1 + rnd() % 5); if (t % 70 == 0) u2 = -(long)(1 + rnd() % 5); at = ref_search(o1, u1, o2, u2); c1 = u1 < 0 ? 0 : u1; c2 = u2; (void)c2; CHECK(call(w_search, 4, BUF + (v4_cell)o1, (v4_cell)u1, BUF + (v4_cell)o2, (v4_cell)u2), "SEARCH returns [%u]", t); f = res(); u3 = res(); a3 = res(); if (at >= 0) CHECK(f == -1 && u3 == (v4_cell)(c1 - at) && a3 == BUF + (v4_cell)o1 + (v4_cell)at && clean(), "SEARCH found [%u] u1 %ld u2 %ld at %ld", t, u1, u2, at); else CHECK(f == 0 && u3 == (v4_cell)c1 && a3 == BUF + (v4_cell)o1 && clean(), "SEARCH not found [%u] u1 %ld u2 %ld", t, u1, u2); CHECK(img_matches() && !err(), "SEARCH writes nothing [%u]", t); } /* What they leave their caller (D-2). */ random_img(2); text_at(40, "needle"); text_at(200, "needle"); text_at(64, "tail "); headroom("COUNT", w_count, 1, BUF + 3, 0, 0, 0, 2, 6, 6); headroom("CMOVE", w_cmove, 3, BUF + 3, BUF + 90, 9, 0, 0, 3, 5); headroom("CMOVE>", w_cmoveup, 3, BUF + 3, BUF + 90, 9, 0, 0, 3, 5); headroom("MOVE", w_move, 3, BUFW + 1, BUFW + 9, 5, 0, 0, 4, 6); headroom("FILL", w_fill, 3, BUF + 3, 9, 65, 0, 0, 3, 5); headroom("BLANK", w_blank, 2, BUF + 3, 9, 0, 0, 0, 3, 5); headroom("-TRAILING", w_trailing, 2, BUF + 64, 8, 0, 0, 2, 4, 6); headroom("COMPARE", w_compare, 4, BUF + 40, 6, BUF + 200, 6, 1, 3, 5); headroom("SEARCH", w_search, 4, BUF + 20, 40, BUF + 200, 6, 3, 2, 5); headroom("SCAN", w_scan, 3, BUF + 64, 8, 32, 0, 2, 4, 5); headroom("SKIP", w_skip, 3, BUF + 68, 4, 32, 0, 2, 4, 5); CHECK(v4_node_guards_intact(&n), "guards intact"); printf(" %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; }