From cb66db11075c75f7e0e0736c468ea419df1dd5c6 Mon Sep 17 00:00:00 2001 From: rajames Date: Sat, 3 Oct 2026 17:25:27 -0400 Subject: [PATCH] feat(v4.0.0): the byte-string words FILL ERASE MOVE, COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP, as loops over the call-free C@ and C!, executed on the golden model at both cell widths against C and five recorded transcripts of the v3 binary. A negative count reads as 0 where v3 read it so, and elsewhere writes nothing and sets NODE-ERROR. v3's counted-string auto-detection is not kept in any of them. Co-Authored-By: Claude Opus 5.5 --- docs/v4.0.0/DECOMPOSITION.md | 93 ++++- v4/tests/test_strings.c | 706 +++++++++++++++++++++++++++++++++++ 2 files changed, 783 insertions(+), 16 deletions(-) create mode 100644 v4/tests/test_strings.c diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 829cbf5b..a9063159 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -147,8 +147,8 @@ IF body1 ELSE body2 THEN → if L1 drop body1 jump L2 times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word. **Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this -document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`: -`Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE SEND RECV`. +document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`: +`COMPARE SEARCH Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE SEND RECV`. **Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN. @@ -333,9 +333,9 @@ Section numbers match the v3 primitive reference. | `2!` | CAP | `a! SWAP !+ !` | | `C@` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). Leaves its caller 8 data cells and 7 return entries. | | `C!` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). | -| `FILL` | CAP | See below. | -| `MOVE` | CAP | `push 2DUP U< IF pop CMOVE> ELSE pop CMOVE THEN` | -| `ERASE` | CAP | `0 FILL` | +| `FILL` | CAP | `( baddr u c -- )`: the low byte of `c`. See below. Nothing for `u = 0`; for `u < 0` nothing is written and `NODE-ERROR` is set (v3 read a negative count as a huge unsigned one). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. Clobbers `A` and, on error, `B`. | +| `MOVE` | CAP | `( src dst u -- )`, bytes: `push over over - -if UP drop pop jump CMOVE> UP: drop pop jump CMOVE` — `CMOVE>` when `src < dst`, else `CMOVE`, so overlapping bytes are read before they are overwritten, as v3's `memmove`. The test is the sign of `src - dst`, in line. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. | +| `ERASE` | CAP | `0 FILL`, as `0 jump FILL`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. | | `CELLS` | IN | Empty (word-addressed). Becomes `2* 2*` if D-1 chooses bytes. | ```forth @@ -366,7 +366,10 @@ Section numbers match the v3 primitive reference. K0: drop @ -256 and + ! ; \ byte 0 : FILL ( baddr u c -- ) - SWAP BEGIN dup WHILE 1- push 2DUP SWAP C! SWAP 1+ SWAP pop REPEAT 2DROP drop ; + -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 ; ``` ### 5.4 Arithmetic @@ -554,14 +557,15 @@ width and print a flood of spaces. | Word | Fate | Notes | | --- | --- | --- | -| `COUNT` | CAP | `dup 1+ SWAP C@` | -| `CMOVE` | CAP | See below. | -| `CMOVE>` | CAP | See below. | -| `BLANK` | CAP | `32 FILL` | -| `-TRAILING` | CAP | Loop from the end while the character is a space. | -| `COMPARE` | CAP | Byte loop returning `-1`, `0`, or `1`. v3's counted-string auto-detection is not kept. | -| `SEARCH` | CAP | Nested loop over `COMPARE`. Same note. | -| `SCAN` `SKIP` | CAP | Byte loops. | +| `COUNT` | CAP | `( baddr -- baddr+1 c )`: `dup C@ push 1 + pop`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. Clobbers `A`. | +| `CMOVE` | CAP | `( src dst u -- )`, low byte first. See below. Nothing for `u = 0`; for `u < 0` nothing is written and `NODE-ERROR` is set, where v3 raised its error flag. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. | +| `CMOVE>` | CAP | `( src dst u -- )`, high byte first. See below. Errors as `CMOVE`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. | +| `BLANK` | CAP | `32 FILL`, as `32 jump FILL`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. v3's counted-string auto-detection is not kept. | +| `-TRAILING` | CAP | `( baddr u -- baddr u' )`. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. | +| `COMPARE` | CAP | `( a1 u1 a2 u2 -- n )`: `-1`, `0` or `1` by the first differing byte, unsigned, else the shorter string is less. See below. Negative counts read as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. Clobbers `A` and `B`. | +| `SEARCH` | CAP | `( a1 u1 a2 u2 -- a3 u3 flag )`: the rest of string 1 from the first place string 2 occurs and `-1`, or string 1 and `0`; an empty string 2 is found at the start. See below; the inner comparison is in line, not a call to `COMPARE`. Same notes. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 3 data cells and 5 return entries. Clobbers `A` and `B`. | +| `SCAN` `SKIP` | CAP | `( baddr u c -- baddr' u' )`: `SCAN` stops at the first byte equal to the low byte of `c` (or at the end, with `u' = 0`); `SKIP` stops at the first byte not equal to it. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 5 data cells and 5 return entries. Clobber `A`. | +| `(S)` | CAP | Variable, 4 cells: `COMPARE`'s two lengths; `SEARCH`'s string 2 and the whole of string 1. | | `BL` | IN | `32` | | `EXPECT` `QUERY` | CC | Built on `KEY` (DEV). | | `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. | @@ -571,11 +575,68 @@ width and print a flood of spaces. | `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. | ```forth +\ SWAP and the subtractions ( - ) are in line. : CMOVE ( src dst u -- ) - BEGIN dup WHILE 1- push over C@ over C! 1+ SWAP 1+ SWAP pop REPEAT drop 2DROP ; + -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 ; : CMOVE> ( src dst u -- ) - BEGIN dup WHILE 1- push over R@ + C@ over R@ + C! pop REPEAT drop 2DROP ; + -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 ; + +: -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: ; + +: 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: ; + +: 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 ; + +: 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 ; + +: 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 ; ``` ### 5.10 Terminal I/O diff --git a/v4/tests/test_strings.c b/v4/tests/test_strings.c new file mode 100644 index 00000000..b0d4ef3b --- /dev/null +++ b/v4/tests/test_strings.c @@ -0,0 +1,706 @@ +/* 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 ( src dst u -- ) whichever of the two does not overwrite + * bytes it has yet to read + * 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 ( src dst u -- ) + * push over over - -if UP drop pop jump CMOVE> src < dst + * UP: drop pop jump CMOVE + * The sign of src - dst, the subtraction in line. */ + w_move = v4_asm_label(&as); + O(PUSH); O(OVER); O(OVER); SUB_INLINE(); a = FWD(MINUS_IF); + O(DROP); O(RPOP); JUMP(w_cmoveup); + HERE_(a); + O(DROP); O(RPOP); JUMP(w_cmove); + + /* : 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 + * B 3 + B 5 MOVE B 16 DUMP + * 41 41 41 41 46 41 41 46 00 00 00 2E 2E 2E 2E 2E */ + { + 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 }; + static const unsigned char d3[16] = { 0x41,0x41,0x41,0x41,0x46,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"); + STEP(w_move, 3, B + 3, B, 5); + CHECK(memcmp(img + 21, d3, 16) == 0, "v3: MOVE"); + 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); + CHECK(call(which == 0 ? w_cmove : which == 1 ? w_cmoveup : w_move, 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 if (which == 1) for (k = len; k-- > 0; ) img[dst + k] = img[src + k]; + else memmove(img + dst, img + src, len); + 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, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "MOVE negative count, up [%u]", i); + CHECK(call(w_move, 3, BUF + 50, BUF, neg[i], 0) && clean() && img_matches() && err(), "MOVE negative count, 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, BUF + 3, BUF + 6, 9, 0, 0, 3, 5); + 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; +}