Forth Blocks

Forth makes explicit to the programmer two address spaces. One address space is the dictionary, where Forth definitions are compiled and referenced at run-time. The second address space is usually persistent, and is referenced using blocks. A block is defined to be a 1024 byte chunk of memory which the Forth application can reference, and optionally modify and update. Typically, a 16-bit Forth implementation will have up to 65535 usable blocks, giving a total persistent storage space of 64MB. A 32-bit Forth implementation will usually have up to 4.2 billion blocks, giving it a persistent storage space of over 4 terabytes. A 64-bit Forth will have more storage space available to it than most 64-bit operating systems will allow.

The storage devices that individual blocks map to is implementation dependent. Some Forths provide an identity mapping with available physical RAM, and rely on the fact that nobody will ever need more than the amount of RAM installed in the computer (e.g., ColorForth). In these Forth implementations, persistence is maintained by using only a small subset of the computer's total storage facilities (e.g., if you boot from a 1.44MB floppy, then only the first 1440 blocks are persistent. Or, if booting from a harddrive larger than 32MB, and you only have 32MB of RAM, only the first 32768 blocks are persistent). Other Forth implementations will treat the entirety of the computer's storage facilities equally, and employ cache management policies to provide the appropriate demand loading and necessary write-backs.

Unlike virtual memory in other operating systems, blocks are managed by a program explicitly. A block is referenced using the BLOCK word. If any modifications are (anticipated to be) made, UPDATE is used to mark the most recently referenced block as being dirty (e.g., it schedules it for updating). FLUSH is used to forcefully cause a writeback (similar to Linux sync command), while EMPTY-BUFFERS, new as of ANSI Forth, causes any pending dirty blocks to be discarded. Application specific virtual memory can be implemented using these four words in conjunction with the normal fetch and store operators in Forth.

For example:

 : LIST-LINEDUP 64 TYPE CR 64 + ;
 : BLOCK-ENDDUP 1024 + ;
 : LIST ( n -- )BLOCK BLOCK-END BEGIN 2DUP < WHILE LIST-LINE REPEAT 2DROP ;

lists a source block to the screen. As you might be able to tell from the code above, source blocks are arranged as 16 lines, each with 64 columns. This provides a nice "index card"-like interface when developing Forth software. Quite typically, a major component in each software system will fit in two of these blocks; many in only one.

But the above example only examines memory. Here's a persistent GameOfLife (in ANSI Forth; should run unmodified on at least GForth, among others. Requires the BLOCK wordset):

 VARIABLE STATEBLK
 VARIABLE LIFEBLK
 VARIABLE STATEP

\ : -ROTROT ROT ;

: WRAPYDUP 0< IF DROP 15 THEN DUP 15 > IF DROP 0 THEN ; : WRAPXDUP 0< IF DROP 63 THEN DUP 63 > IF DROP 0 THEN ; : WRAPWRAPY SWAP WRAPX SWAP ; : DECEASED?WRAP 64 * + LIFEBLK @ BLOCK + C@ 32 = ; : LIVING?DECEASED? 0= ; : (-1,-1)2DUP 1- SWAP 1- SWAP LIVING? 1 AND ; : (0,-1)>R 2DUP 1- LIVING? 1 AND R> + ; : (1,-1)>R 2DUP 1- SWAP 1+ SWAP LIVING? 1 AND R> + ; : (-1,0)>R 2DUP SWAP 1- SWAP LIVING? 1 AND R> + ; : (1,0)>R 2DUP SWAP 1+ SWAP LIVING? 1 AND R> + ; : (-1,1)>R 2DUP 1+ SWAP 1- SWAP LIVING? 1 AND R> + ; : (0,1)>R 2DUP 1+ LIVING? 1 AND R> + ; : (1,1)>R 1+ SWAP 1+ SWAP LIVING? 1 AND R> + ; : NEIGHBORS(-1,-1) (0,-1) (1,-1) (-1,0) (1,0) (-1,1) (0,1) (1,1) ; : BORN?NEIGHBORS 3 = ; : SURVIVES?2DUP LIVING? -ROT NEIGHBORS 2 = AND ; : LIVES?2DUP BORN? -ROT SURVIVES? OR ; : NEWSTATESTATEBLK @ BLOCK UPDATE STATEP ! ; : STATE!STATEP @ C! 1 STATEP +! ; : ALIVE42 STATE! ; : DEAD32 STATE! ; : ITERATE-CELL2DUP SWAP LIVES? IF ALIVE ELSE DEAD THEN ; : ITERATE-ROW 0 BEGIN DUP 64 < WHILE ITERATE-CELL 1+ REPEAT DROP ; : ITERATE-BLOCK0 BEGIN DUP 16 < WHILE ITERATE-ROW 1+ REPEAT DROP ; : GENERATIONLIFEBLK @ STATEBLK @ LIFEBLK ! STATEBLK ! ; : ITERATENEWSTATE ITERATE-BLOCK GENERATION ; : DONE?KEY [CHAR] Q = ; : PROMPTCR ." PRESS Q TO EXIT; OTHER KEY TO CONTINUE" ; : VIEWPAGE LIFEBLK @ LIST PROMPT ; : LIFEBEGIN VIEW ITERATE DONE? UNTIL ;

It's worth noting that a block editor can be quite small. A fairly minimal one (such as the following example) can easily fit into <1k of source. A more complete editor with VI-style key bindings has been written in under 5k. Blocks aren't used as widely as they once were, but they are still a useful way to store source and data.

 | RetroForth Block Editor (http://www.retroforth.org)
 | * Released into the public domain *
 |
 | This is the block editor from RetroForth Release 9.2.1
 | It splits the normal 1k block into two smaller 512-byte blocks,
 | the one on the left for code, and the one on the right for
 | documentation/comments. Both are displayed side by side.
 |
 | It makes use of some features specific to RetroForth, so it
 | will not work on an ANS FORTH system without changes. 

tib 1024 + constant <buffer> 128 variable: <#blocks> <buffer> variable: b0 variable current-block

: there b0 @ ; : #-of-blocks <#blocks> @ ;

: new there #-of-blocks 512 * 32 fill 0 current-block ! ; new

: (block) @current-block : block 512 * there + ; : (line) 32 * (block) + ;

: p 2 current-block -! ; : n 2 current-block +! ; : d (line) 32 32 fill ; : x (block) 512 32 fill ; : eb (block) 512 eval ; : el (line) 32 eval ; : e 16 for 16 r - el next ; : s !current-block ; : i 0 swap : ia (line) + lnparse rot swap move ; : \ 1 s e ;

loc: : | '| emit ;

: row dup 32 type 32 + ; : left# -16 + negate dup @base <if space then . ; : right# negate 32 + . ; : code|shadow row | swap row swap space ; : rows 16 for r left# code|shadow r right# cr next ; : x--- 2 for ." +---:---+---:---" next ; : --- space space space x--- | x--- cr ; : blocks @current-block 1+ block @current-block block ; here ] --- blocks rows 2drop --- ; ;loc is v

: edit [[ clear v ]] { is ui } ;


CategoryForth


EditText of this page (last edited July 25, 2006) or FindPage with title or text search