\ A test framework in standard Forth \ Joshua Grams March 2008 \ public domain \ Syntax: \ T{ test code => expected results }T \ T{ test code => expected results }( types )T \ T{ test code }( types )T \ That last syntax is for when you want to check some property rather \ than comparing against an expected value. \ Built-in types: \ X - unspecified cell \ R - real number (floating-point) \ The tolerance of floating point checks is controlled by the (positive) \ values in RELATIVE-TOLERANCE (1E-12) and ABSOLUTE-TOLERANCE (0E). \ To add a new type FOO, define: \ TEST-FOO ( -- flag ) using \ TESTED ( u -- false | u*x true ) \ COMPARED ( u -- false | u*x u*x true ) \ FTESTED ( u -- false | u*r true ) \ FCOMPARED ( u -- false | u*r u*r true ) \ && ( flag -- ) EXIT (returning flag) if flag is false. \ PRINT-FOO ( -- ) \ DISPLAYED ( u -- false | u*x true ) \ FDISPLAYED ( u -- false | u*r true ) \ See built-in types for a simple example. \ Forth200x structures : +FIELD ( n "name" -- ) CREATE OVER , + DOES> ( addr -- addr' ) @ + ; : FIELD: ( "name" -- ) ALIGNED 1 CELLS +FIELD ; \ Short-circuit logic : && ( flag -- ) POSTPONE DUP POSTPONE 0= POSTPONE IF POSTPONE EXIT POSTPONE THEN POSTPONE DROP ; IMMEDIATE \ Conditional compilation : SKIP-LINE-UNLESS 0= IF SOURCE >IN ! DROP THEN ; :NONAME S" FLOATING" ENVIRONMENT? DUP IF DROP THEN ; EXECUTE CONSTANT FLOATS? : \F FLOATS? SKIP-LINE-UNLESS ; IMMEDIATE FALSE \F DROP :NONAME DEPTH >R 0E DEPTH >R FDROP R> R> = ; EXECUTE CONSTANT FSTACK? : \FS FSTACK? SKIP-LINE-UNLESS ; IMMEDIATE : !FS FLOATS? FSTACK? 0= AND SKIP-LINE-UNLESS ; IMMEDIATE \ Configuration 32 CONSTANT MAX-DEPTH \F FVARIABLE ABSOLUTE-TOLERANCE 0E ABSOLUTE-TOLERANCE F! \F FVARIABLE RELATIVE-TOLERANCE 1E-12 RELATIVE-TOLERANCE F! \ Parsing VARIABLE BEFORE-WORD : GET-WORD ( -- c-addr u ) >IN @ BEFORE-WORD ! BL WORD COUNT ; : PUT-WORD-BACK BEFORE-WORD @ >IN ! ; \ String handling : C+! ( char c-addr -- ) DUP >R C@ + R> C! ; : PLACE ( c-addr u c-addr2 -- ) 2DUP C! CHAR+ SWAP CHARS MOVE ; : +PLACE ( c-addr u c-addr2 -- ) 2DUP 2>R COUNT CHARS + SWAP CHARS MOVE 2R> C+! ; CREATE TMP 32 CHARS ALLOT : PREFIX ( c-addr1 u1 c-addr2 u2 -- c-addr ) TMP PLACE TMP +PLACE TMP ; \ Ignore data which doesn't belong to a test VARIABLE BASE-DEPTH : OUR-DEPTH ( -- n ) DEPTH BASE-DEPTH @ - ; \FS VARIABLE BASE-FDEPTH \FS : OUR-FDEPTH ( -- n ) FDEPTH BASE-FDEPTH @ - ; : RESULTS-START-HERE \FS FDEPTH BASE-FDEPTH ! DEPTH BASE-DEPTH ! ; \ Error flag VARIABLE ERROR VARIABLE WANT-ERROR : ERROR? ( -- flag ) ERROR @ WANT-ERROR @ <> ; : ?ERROR ( flag -- ) 0= ERROR @ OR ERROR ! ; \ Simple stacks ( pointer, items; grows up; no error checking ) 0 FIELD: SP FIELD: HEAD FIELD: SAVED-SP 0 +FIELD BOTTOM CONSTANT /STACK : CLEAR ( stack -- ) DUP BOTTOM OVER SP ! DUP BOTTOM OVER SAVED-SP ! DUP BOTTOM SWAP HEAD ! ; : MARK ( stack -- ) DUP SP @ SWAP SAVED-SP ! ; : RESTORE ( stack -- ) DUP SAVED-SP @ OVER SP ! DUP BOTTOM SWAP HEAD ! ; : #ITEMS ( stack -- +n ) DUP SP @ SWAP HEAD @ - 1 CELLS / ; : PUSH ( x stack -- ) DUP >R SP @ ! 1 CELLS R> SP +! ; : POP ( stack -- x ) -1 CELLS OVER SP +! SP @ @ ; : GET ( stack -- x ) DUP >R HEAD @ @ 1 CELLS R> HEAD +! ; : ALLOT-STACK ( u -- addr ) HERE >R CELLS /STACK + ALLOT R> ; : STACK ( u "name" -- ) CREATE ALLOT-STACK CLEAR ; \ Type handling (this is up here so it can use non-multiplexed stacks) MAX-DEPTH STACK TESTERS MAX-DEPTH STACK PRINTERS : FIND-METHODS ( c-addr u -- test-xt print-xt flag ) 2DUP S" TEST-" PREFIX FIND >R -ROT S" PRINT-" PREFIX FIND R> AND ; : PARSE-TYPES ( "types" -- ) TESTERS CLEAR PRINTERS CLEAR BEGIN GET-WORD FIND-METHODS WHILE PRINTERS PUSH TESTERS PUSH REPEAT 2DROP PUT-WORD-BACK TESTERS MARK PRINTERS MARK ; : DEFAULT-TYPES ( [#floats] #cells -- ) TESTERS CLEAR PRINTERS CLEAR S" X" FIND-METHODS IF ROT BEGIN DUP WHILE 1 - >R 2DUP PRINTERS PUSH TESTERS PUSH R> REPEAT THEN DROP 2DROP \FS S" R" FIND-METHODS IF ROT BEGIN DUP WHILE 1 - >R \FS 2DUP PRINTERS PUSH TESTERS PUSH \FS R> REPEAT THEN DROP 2DROP TESTERS MARK PRINTERS MARK ; : CHECK-TYPES BEGIN TESTERS #ITEMS WHILE TESTERS POP EXECUTE ?ERROR REPEAT ; : .RESULTS BEGIN PRINTERS #ITEMS WHILE PRINTERS GET EXECUTE REPEAT PRINTERS RESTORE ; \ These don't depend on where floats are stored \F : #FLOATS ( stack -- +n ) DUP SP @ SWAP HEAD @ - 1 FLOATS / ; \F : FGET ( stack -- r ) DUP >R HEAD @ F@ 1 FLOATS R> HEAD +! ; \ Floats on data stack !FS :NONAME DEPTH >R 0E DEPTH >R FDROP R> R> - ; !FS EXECUTE CONSTANT CELLS/FLOAT !FS : FPUSH ( r stack -- ) CELLS/FLOAT 0 DO DUP >R PUSH R> LOOP DROP ; !FS : FPOP ( stack -- r ) CELLS/FLOAT 0 DO DUP >R POP R> LOOP DROP ; \ Separate float stack \FS : FPUSH ( r stack -- ) DUP >R SP @ F! 1 FLOATS R> +! ; \FS : FPOP ( stack -- r ) -1 FLOATS OVER SP +! SP @ F@ ; \FS : ALLOT-FSTACK ( u -- addr ) \FS /STACK ALLOT FALIGN HERE /STACK - >R FLOATS ALLOT R> ; \ Combine integer and float stacks into one structure. \FS : DSTACK ( addr -- stack ) @ ; \FS : FSTACK ( addr -- fstack ) CELL+ @ ; \FS : CLEAR ( stack -- ) DUP DSTACK CLEAR FSTACK CLEAR ; \FS : MARK ( stack -- ) DUP DSTACK MARK FSTACK MARK ; \FS : RESTORE ( stack -- ) DUP DSTACK RESTORE FSTACK RESTORE ; \FS : STACK ( u "name" -- ) \FS DUP ALLOT-STACK >R ALLOT-FSTACK >R \FS CREATE HERE R> R> , , CLEAR ; \FS : #ITEMS ( stack -- +n ) DSTACK #ITEMS ; \FS : PUSH ( x stack -- ) DSTACK PUSH ; \FS : POP ( stack -- x ) DSTACK POP ; \FS : GET ( stack -- x ) DSTACK GET ; \FS : #FLOATS ( stack -- +n ) FSTACK #FLOATS ; \FS : FPUSH ( r stack -- ) FSTACK FPUSH ; \FS : FPOP ( stack -- r ) FSTACK FPOP ; \FS : FGET ( stack -- r ) FSTACK FGET ; \ Saving results MAX-DEPTH STACK ACTUAL MAX-DEPTH STACK EXPECTED VARIABLE WHICH CREATE STACKS ACTUAL , EXPECTED , : CURRENT ( -- addr ) WHICH @ CELLS STACKS + @ ; : FIRST-RESULTS 0 WHICH ! ; : NEXT-RESULTS 1 WHICH +! ; : CLEAR-RESULTS ACTUAL CLEAR EXPECTED CLEAR FIRST-RESULTS FALSE ERROR ! ; : SAVE-RESULTS ( i*x -- ) \FS OUR-FDEPTH 0 ?DO CURRENT FPUSH LOOP OUR-DEPTH 0 ?DO CURRENT PUSH LOOP CURRENT MARK NEXT-RESULTS ; : RESTORE-RESULTS FIRST-RESULTS ACTUAL RESTORE EXPECTED RESTORE ; \ Fetching results : TESTED ( u -- false | u*x true ) DUP ACTUAL #ITEMS > IF DROP FALSE ELSE 0 DO ACTUAL POP LOOP TRUE THEN ; : COMPARED ( u -- false | u*x u*x true ) DUP ACTUAL #ITEMS > OVER EXPECTED #ITEMS > OR IF DROP FALSE ELSE DUP >R 0 DO ACTUAL POP LOOP R> 0 DO EXPECTED POP LOOP TRUE THEN ; : DISPLAYED ( u -- false | u*x true ) DUP CURRENT #ITEMS > IF DROP FALSE ELSE 0 DO CURRENT POP LOOP TRUE THEN ; \F : FTESTED ( u -- false | u*r true ) \F DUP ACTUAL #FLOATS > IF DROP FALSE \F ELSE 0 DO ACTUAL FPOP LOOP TRUE THEN ; \F : FCOMPARED ( u -- false | u*r u*r true ) \F DUP ACTUAL #FLOATS > OVER EXPECTED #FLOATS > OR IF \F DROP FALSE \F ELSE \F DUP >R 0 DO ACTUAL FPOP LOOP \F R> 0 DO EXPECTED FPOP LOOP TRUE \F THEN ; \F : FDISPLAYED ( u -- false | u*r true ) \F DUP CURRENT #FLOATS > IF DROP FALSE \F ELSE 0 DO CURRENT FPOP LOOP TRUE THEN ; : CHECK-RESULTS-CLEAR ( -- flag ) \FS ACTUAL #FLOATS 0= ?ERROR EXPECTED #FLOATS 0= ?ERROR ACTUAL #ITEMS 0= ?ERROR EXPECTED #ITEMS 0= ?ERROR ; \ Built-in types : TEST-X ( -- flag ) 1 COMPARED && = ; : PRINT-X 1 DISPLAYED IF . THEN ; \F : TEST-R ( -- flag ) \F 1 FCOMPARED && FOVER FOVER \F RELATIVE-TOLERANCE F@ FNEGATE F~ >R \F ABSOLUTE-TOLERANCE F@ F~ R> OR ; \F : PRINT-R 1 FDISPLAYED IF F. THEN ; \ Error display : .EXTRA \ display extra stack items S" X" FIND-METHODS IF CURRENT #ITEMS ?DUP IF 0 DO DUP EXECUTE LOOP THEN THEN 2DROP \FS S" R" FIND-METHODS IF \FS CURRENT #FLOATS ?DUP IF 0 DO DUP EXECUTE LOOP THEN \FS THEN 2DROP ; : .ERROR S" Test Failed: " TYPE SOURCE TYPE CR RESTORE-RESULTS S" Received: " TYPE .RESULTS .EXTRA CR WANT-ERROR @ 0= IF NEXT-RESULTS S" Expected: " TYPE .RESULTS .EXTRA CR THEN ; : FAIL: TRUE WANT-ERROR ! ; : T{ CLEAR-RESULTS RESULTS-START-HERE ; : => ( i*x -- ) SAVE-RESULTS ; : }( ( i*x -- ) SAVE-RESULTS PARSE-TYPES ; : )T CHECK-TYPES CHECK-RESULTS-CLEAR ERROR? IF .ERROR CR THEN FALSE WANT-ERROR ! ; : }T ( i*x -- ) SAVE-RESULTS \FS ACTUAL #FLOATS ACTUAL #ITEMS DEFAULT-TYPES )T ;