Jump to content
IGNORED

Adding READ RESTORE to Forth: Tutorial 1


TheBF

Recommended Posts

While looking at the following web site; http://briantw.com/ti-99/4a/LIST.TXT which I found in older posts here I found this little BASIC Program. I ran it to see what it did and it made a picture of Pamela Anderson I think. (don't get excited, it's just her face) :)

I wondered what it would take to translate it to Forth. The following is a little tutorial on one way to manage data in Forth and create something equivalent to DATA READ and RESTORE. My hope is that the BASIC programmer and those new to Assembler or Forth will see how simple data can be inside the machine.

Anyone who has tried TI Forth or TF or the new FB Forth will quickly realize that Forth requires you to know more about the internal arrangement of memory just as you do in Assembly Language. By comparison TI BASIC has numbers and strings and you never really need to know how the computer stores the information to use them.

In the example BASIC program there is a large list of DATA statements with strings to change characters.

Forth has no DATA, READ or RESTORE words so here is how we added them just as a demonstration.

The first program change is Forth requires that you define what a new word is before you use it so we need to define our DATA at the beginning of the program.

We create a name for our data with the words CREATE DATA. All this does is add the name DATA to Forth and if you run DATA you get the address of the next available memory location on the stack. Not very useful by itself.

BASIC uses TEXT strings in the DATA statements, that must be converted to integers before they can be loaded into VDP memory to shape a character. In Forth we will put integers into memory using the ‘comma’ which compiles an integer into the next Dictionary memory cell and advances the Dictionary pointer by 2. (The Dictionary is the memory where Forth keeps all of its words.)

Forth equivalent to the DATA statements

\ DATA simply returns the base address of all these numbers
HEX
CREATE DATA  ( -- addr) 
\ [ --------8 bytes--------] [---------8 bytes----------]
  FFFF , FFFF , FFFF , FFFF , FFFF , FFFF , FFFE , FFFF , 
  FAED , FEF7 , BEEA , F4B5 , AA02 , 80F8 , D68A , 2401 ,
  A9B6 , AAAD , 3695 , 564B , BFD5 , BBA9 , D1A2 , D069 , 
  FFB5 , FF6F , FD5F , BB5F , FFDB , 7EEB , BEEB , 5DF6 ,
  FFFF , FEFB , FDEB , FAD5 , FBEE , BBDD , 6AF6 , AD75 , 
  D5A2 , 54A2 , A9A4 , 1249 , B64A , AA12 , 4825 , 8822 ,
  A5AA , 5791 , 4A28 , 9425 , A0DA , 68A8 , CA9A , 6CB6 , 
  6D97 , 5E13 , 2A05 , 0A05 , DBED , B6FB , EDBD , F73E ,
  FAA9 , F5BB , EAB5 , D6F3 , ADF5 , 565A , D5B5 , D66A , 
  204A , 8054 , 5148 , A488 , 0094 , 2196 , 2902 , 9044 ,
  920A , A449 , 0229 , 0293 , DAAA , B76B , AD57 , FFBF , 
  000A , 2004 , 81C8 , C5A2 , D72F , B517 , 4513 , 4205 ,
  59EA , A9D1 , 7888 , 8880 , AD35 , 5A2B , 955A , 2DB6 , 
  3244 , 9045 , 75A8 , 9281 , 1082 , 100A , F45A , 4AB6 ,
  0AA5 , 15A7 , 2A8B , 9585 , FBF2 , FB1E , A955 , 02A9 , 
  AAAA , ADAB , 552D , AA2C , A289 , 4249 , 9241 , A8D2 ,
  8882 , 8A85 , 8642 , 4342 , 9AAA , AD1A , D7AC , 5A8A , 
  BECB , 7FAF , FFBD , DEB5 , FDFE , C4EE , B4D5 , 9049 ,
  65A2 , AAA9 , 542A , 4A2A , AAD5 , B4D2 , AAB9 , 5CAB , 
  562B , AB95 , 562B , AA57 , A550 , AAD1 , A8D4 , E258 ,
  4345 , 52A1 , A1A0 , A1B0 , 154D , 880D , 448E , 0282 , 
  5AB7 , 542A , 5154 , AAD5 , 0450 , 8940 , 2280 , 2501 ,
  2A55 , 2D55 , 2B8A , 5005 , AD0A , 2D55 , 5AA5 , 965B , 
  2AD5 , 5B6D , AAB5 , EA77 , EAE8 , 6AB4 , D59A , EA5A ,
  1141 , 7151 , ECEE , FBEF , 0201 , 02A1 , A0C0 , 70AA , 
  AAD5 , B65B , 160D , 0200 , 9421 , 3AC4 , 5A20 , 5A44 ,
  2A15 , A556 , 955D , AB15 , D554 , 6192 , 146A , 55AE , 
  AA7B , ADB5 , EBB7 , DAAB , ED75 , BEED , 7EFE , FFED ,
  B3FD , B7DD , F7AE , FBF8 , F0AC , FAF6 , FDB6 , DEAE , 
  8001 , A448 , A012 , 80A0 , DA6A , 6D2B , AD1F , 5F3A ,
  AAAB , 5555 , AD75 , DBFE , DA68 , 2150 , 4A51 , 56EB , 
  4FAB , 2F97 , 6E5F , FDBF , FFB6 , DBEF , B3DB , EDBB ,

Now we have all the data in block of memory, but we need to get at it 8 bytes at a time.

First we will make word that takes a number off the stack and adds it to the address of DATA. This is actually all it takes to make a byte array.

I use my own notation here which is whenever I make a word that works like an array I give it a ‘]’ as the first character. This reminds me that it needs an index value and that it is an array.

So our new word that makes a simple byte array out of the word DATA is just:

 
: ]DATA  ( n -- addr)      \ read DATA like an array of 1 byte elements
           DATA +  ;       \ calculate address N+DATA, return result to stack
 

Next we need a variable to keep track of which data record to read. That’s easy.

  VARIABLE P               \ holds the index of the next record to read

With these two simple words we can create a READ word. In BASIC READ has to know how to READ floating point numbers, HEX number strings, and text strings. All of this adds overhead because the interpreter has to figure out what it’s reading each time. Forth thinking says make each word do one thing. If you need to do another thing, make another word. So our READ only reads our DATA array and only reads 8 byte records. We could do any other thing we wanted, but for this program this is all we need so this is all we make.

: READ ( -- addr ) \ returns the address of next 8 bytes of data

P @ ]DATA \ fetch value of P, use as index into the data array

8 P +! ; \ advance 'p' by 8, ie to the next record #

In BASIC using READ involves copying bytes from DATA to a variable. This takes lots of time.

READ in this implementation will just return the address of the data we want to use to the top of the stack. Some languages call this a 'pointer'. Forth keeps it simple and like Assembly Language calls it just what it really is, a memory address.

You should note READ has no protection. You could read past the end of the data. If you want protection in Forth you have to make it yourself.

And to be complete we need the word RESTORE. This is the easiest of all. We just reset the data pointer variable to zero.

: RESTORE ( -- ) 0 P ! ;

In Chapter 2 we will use these words to translate the example program to Forth.

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

In Chapter 1 of this tutorial we created "dedicated to one DATA set" version of READ, DATA and RESTORE.

 

Now using those words we translate the BASIC program LIST.TXT to CAMEL99 Forth.

The general concepts are applicable to FB Forth or TF, but you will have to refer to the documentation for some specific equivalent words.

 

We still need a couple of additions to make this READ statement work. We decided to make a very fast CALL CHAR. This is done by

Copying bytes directly from CPU RAM to VDP RAM using an assembly language routine. READ will give us the CPU RAM address

but how do we find the correct address in VDP RAM.

 

In CAMEL99 Forth Graphics Mode, the Pattern Description table is located at address HEX 800 in VDP RAM.

Let's name that address 'PDT' as a constant.

 0800 CONSTANT PDT         \ VDP address of "PATTERN DESCRIPTOR TABLE"

Now we can create an array that gives the address of the pattern descriptor for any character. The math is simple:

 

pattern_address = ascii# * 8 + PDT

 

​So in Forth we can write:

: ]PDT   ( char# -- 'pdt[n] ) 
           8* PDT + ;      \ ]PDT is now a byte array in VDP ram

Now to make a fast CALL CHAR, we use a routine that TI calls VMBW.

In CAMEL Forth I named it VWRITE. This routine needs a CPU Address, a VDP address and the number of bytes to write.

So with READ and ]PDT we have all we need to write:

\ CALLCHAR is like CALL CHAR, but it takes a data address and ascii value
\ and writes 8 bytes directly to VDP RAM (very fast)
: CALLCHAR  ( data_addr char# -- ) ]PDT 8 VWRITE ; 

The rest of the program is academic now. We broke out each part into separate routines (WORDs) which good style in Forth and allows separate testing at the console of each little piece.

 

Here are the final parts with the BASIC lines as comments.

: READ-DATA
     CLEAR                 \ 100 CALL CLEAR
     104 40 DO             \ 110 FOR A=40 TO 103
        READ               \ 120 READ A$
       ( addr) I CALLCHAR  \ 130 CALL CHAR(A,A$)  ( Forth loop index is 'I')
     LOOP  ;               \ 140 NEXT A

: SHOW-FACE
     8 0 DO                \ 150 FOR R=0 TO 7
       8 0 DO              \ 160 FOR C=0 TO 7
            I 12 +   J 8 +   J 8* 40 + I +  1 HCHAR  \ 170 CALL HCHAR(R+8,C+12,40+R*8+C)
       LOOP                \ 180 NEXT C
     LOOP ;                \ 190 NEXT R

: COLOR-SCR
      17 3 DO              \ 200 FOR A=3 TO 16
          I SCREEN         \ 210 CALL SCREEN(A)
          2000 MS          \ 220 CALL SOUND(2000,44733,0)
      LOOP  ;              \ 230 NEXT A

\ *CAMEL99 Forth has 'MS' which delays in milliseconds, so we don't need to use SOUND

: RUN
     GRAPHICS              \ change to 32 col graphics mode
     RESTORE               \ we need RESTORE if we run more than once
     READ-DATA
     SHOW-FACE
     COLOR-SCR ;

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Here is the complete listing of LIST.TXT translated to CAMEL99 Forth.

\ Exploring Forth programming from a BASIC perspective

\ a ladies face. data source: http://briantw.com/ti-99/4a/LIST.TXT

\ BASIC stores this data as text bytes which must be converted
\ at run time. (slow)
\ Here they will be store the data as integers in memory cells
\ In Forth we must declare a name before we can use it so DATA is first

\ DATA simply returns the base address of all these numbers
HEX
CREATE DATA  ( -- addr) 
\ [ --------8 bytes--------] [---------8 bytes----------]
  FFFF , FFFF , FFFF , FFFF , FFFF , FFFF , FFFE , FFFF , 
  FAED , FEF7 , BEEA , F4B5 , AA02 , 80F8 , D68A , 2401 ,
  A9B6 , AAAD , 3695 , 564B , BFD5 , BBA9 , D1A2 , D069 , 
  FFB5 , FF6F , FD5F , BB5F , FFDB , 7EEB , BEEB , 5DF6 ,
  FFFF , FEFB , FDEB , FAD5 , FBEE , BBDD , 6AF6 , AD75 , 
  D5A2 , 54A2 , A9A4 , 1249 , B64A , AA12 , 4825 , 8822 ,
  A5AA , 5791 , 4A28 , 9425 , A0DA , 68A8 , CA9A , 6CB6 , 
  6D97 , 5E13 , 2A05 , 0A05 , DBED , B6FB , EDBD , F73E ,
  FAA9 , F5BB , EAB5 , D6F3 , ADF5 , 565A , D5B5 , D66A , 
  204A , 8054 , 5148 , A488 , 0094 , 2196 , 2902 , 9044 ,
  920A , A449 , 0229 , 0293 , DAAA , B76B , AD57 , FFBF , 
  000A , 2004 , 81C8 , C5A2 , D72F , B517 , 4513 , 4205 ,
  59EA , A9D1 , 7888 , 8880 , AD35 , 5A2B , 955A , 2DB6 , 
  3244 , 9045 , 75A8 , 9281 , 1082 , 100A , F45A , 4AB6 ,
  0AA5 , 15A7 , 2A8B , 9585 , FBF2 , FB1E , A955 , 02A9 , 
  AAAA , ADAB , 552D , AA2C , A289 , 4249 , 9241 , A8D2 ,
  8882 , 8A85 , 8642 , 4342 , 9AAA , AD1A , D7AC , 5A8A , 
  BECB , 7FAF , FFBD , DEB5 , FDFE , C4EE , B4D5 , 9049 ,
  65A2 , AAA9 , 542A , 4A2A , AAD5 , B4D2 , AAB9 , 5CAB , 
  562B , AB95 , 562B , AA57 , A550 , AAD1 , A8D4 , E258 ,
  4345 , 52A1 , A1A0 , A1B0 , 154D , 880D , 448E , 0282 , 
  5AB7 , 542A , 5154 , AAD5 , 0450 , 8940 , 2280 , 2501 ,
  2A55 , 2D55 , 2B8A , 5005 , AD0A , 2D55 , 5AA5 , 965B , 
  2AD5 , 5B6D , AAB5 , EA77 , EAE8 , 6AB4 , D59A , EA5A ,
  1141 , 7151 , ECEE , FBEF , 0201 , 02A1 , A0C0 , 70AA , 
  AAD5 , B65B , 160D , 0200 , 9421 , 3AC4 , 5A20 , 5A44 ,
  2A15 , A556 , 955D , AB15 , D554 , 6192 , 146A , 55AE , 
  AA7B , ADB5 , EBB7 , DAAB , ED75 , BEED , 7EFE , FFED ,
  B3FD , B7DD , F7AE , FBF8 , F0AC , FAF6 , FDB6 , DEAE , 
  8001 , A448 , A012 , 80A0 , DA6A , 6D2B , AD1F , 5F3A ,
  AAAB , 5555 , AD75 , DBFE , DA68 , 2150 , 4A51 , 56EB , 
  4FAB , 2F97 , 6E5F , FDBF , FFB6 , DBEF , B3DB , EDBB ,

\ ==============================================================
\ Forth does not have READ DATA RESTORE
\ But we can create words to work similar to BASIC
\ The math is easy.  DATA+0 will give us the address of the 1st record
\                    DATA+8 gives us the address of the 2nd record etc...

DECIMAL


: ]DATA  ( n -- addr)      \ read DATA like an array of 1 byte elements
           DATA +  ;       \ calculate address N+DATA, return result to stack

  VARIABLE P               \ holds the index of the next record to read

\ Instead of reading all the bytes into a string,
\ we will just return the address of the data we want to use
( some complicated languages call this "a pointer' but it's just a memory address)

: READ ( -- addr )         \ returns the address of next 8 bytes of data
          P @ ]DATA        \ fetch value of P, use as index into the data array
          8 P +! ;         \ advance 'p' by 8, ie to the next record #

\ *****************************************************************
\ *NOTICE* READ has no protection. You could read past the end.
\          If you want protection in Forth you make it yourself.
\ *****************************************************************

: RESTORE ( -- ) P OFF ;   \ just reset the data pointer variable to zero

\ ==============================================================
HEX
 0800 CONSTANT PDT         \ VDP address of "PATTERN DESCRIPTOR TABLE"

: ]PDT   ( char# -- 'pdt[n] )  
           8* PDT + ;      \ ]PDT is a byte array in VDP ram

\ CALLCHAR is like CALL CHAR, but it takes a data address and ascii value
\ and writes 8 bytes directly to VDP RAM (very fast)

: CALLCHAR  ( data_addr char# -- ) ]PDT 8 VWRITE ;
DECIMAL
: READ-DATA
     CLEAR                 \ 100 CALL CLEAR
     104 40 DO             \ 110 FOR A=40 TO 103
        READ               \ 120 READ A$
       ( addr) I CALLCHAR  \ 130 CALL CHAR(A,A$)  ( Forth loop index is 'I')
     LOOP  ;               \ 140 NEXT A

: SHOW-FACE
     8 0 DO                \ 150 FOR R=0 TO 7
       8 0 DO              \ 160 FOR C=0 TO 7
            I 12 +   J 8 +   J 8* 40 + I +  1 HCHAR  \ 170 CALL HCHAR(R+8,C+12,40+R*8+C)
       LOOP                \ 180 NEXT C
     LOOP ;                \ 190 NEXT R

: COLOR-SCR
      17 3 DO              \ 200 FOR A=3 TO 16
          I SCREEN         \ 210 CALL SCREEN(A)
          2000 MS          \ 220 CALL SOUND(2000,44733,0)
      LOOP  ;              \ 230 NEXT A

\ *CAMEL99 Forth has 'MS' which delays, so we don't need SOUND

: RUN
     GRAPHICS              \ change to 32 col graphics mode
     RESTORE               \ we need RESTORE if we run more than once
     READ-DATA
     SHOW-FACE
     COLOR-SCR ;

GRAPHICS



Edited by TheBF
Link to comment
Share on other sites

There's some prior art here that you might want to have a peek at. IIRC fbForth is also compatible with this:

 

http://turboforth.net/lang_ref/view_word.asp?ID=4

 

Yes this is definitely a nice word Willsy. I heartily endorse it.

 

I took the approach above to try and show those using BASIC that making data structures in raw memory is not black magic and understandable by anyone.

 

I just took a look at your source code for 'DATA' and it's a little above introduction level.

However you have cause me to think about making one for myself. :-)

 

Thanks for weighing in.

 

BF

Link to comment
Share on other sites

There's some prior art here that you might want to have a peek at. IIRC fbForth is also compatible with this:

 

http://turboforth.net/lang_ref/view_word.asp?ID=4

 

Actually, DATA is not defined in fbForth 2.0, but can easily be managed with the DATA[ ... ]DATA construct:

HEX
: DATA  ( --- addr )  
   DATA[
      FFFF FFFF FFFF FFFF FFFF FFFF FFFE FFFF 
      FAED FEF7 BEEA F4B5 AA02 80F8 D68A 2401
      A9B6 AAAD 3695 564B BFD5 BBA9 D1A2 D069 
      FFB5 FF6F FD5F BB5F FFDB 7EEB BEEB 5DF6
      FFFF FEFB FDEB FAD5 FBEE BBDD 6AF6 AD75 
      D5A2 54A2 A9A4 1249 B64A AA12 4825 8822
      A5AA 5791 4A28 9425 A0DA 68A8 CA9A 6CB6 
      6D97 5E13 2A05 0A05 DBED B6FB EDBD F73E
      FAA9 F5BB EAB5 D6F3 ADF5 565A D5B5 D66A 
      204A 8054 5148 A488 0094 2196 2902 9044
      920A A449 0229 0293 DAAA B76B AD57 FFBF 
      000A 2004 81C8 C5A2 D72F B517 4513 4205
      59EA A9D1 7888 8880 AD35 5A2B 955A 2DB6 
      3244 9045 75A8 9281 1082 100A F45A 4AB6
      0AA5 15A7 2A8B 9585 FBF2 FB1E A955 02A9 
      AAAA ADAB 552D AA2C A289 4249 9241 A8D2
      8882 8A85 8642 4342 9AAA AD1A D7AC 5A8A 
      BECB 7FAF FFBD DEB5 FDFE C4EE B4D5 9049
      65A2 AAA9 542A 4A2A AAD5 B4D2 AAB9 5CAB 
      562B AB95 562B AA57 A550 AAD1 A8D4 E258
      4345 52A1 A1A0 A1B0 154D 880D 448E 0282 
      5AB7 542A 5154 AAD5 0450 8940 2280 2501
      2A55 2D55 2B8A 5005 AD0A 2D55 5AA5 965B 
      2AD5 5B6D AAB5 EA77 EAE8 6AB4 D59A EA5A
      1141 7151 ECEE FBEF 0201 02A1 A0C0 70AA 
      AAD5 B65B 160D 0200 9421 3AC4 5A20 5A44
      2A15 A556 955D AB15 D554 6192 146A 55AE 
      AA7B ADB5 EBB7 DAAB ED75 BEED 7EFE FFED
      B3FD B7DD F7AE FBF8 F0AC FAF6 FDB6 DEAE 
      8001 A448 A012 80A0 DA6A 6D2B AD1F 5F3A
      AAAB 5555 AD75 DBFE DA68 2150 4A51 56EB 
      4FAB 2F97 6E5F FDBF FFB6 DBEF B3DB EDBB
   ]DATA
   DROP  ( drop cell count and just leave address)  ;
DECIMAL

The fact that ]DATA is already defined in fbForth 2.0 does not preclude redefining it, but a newbie might do better with a different name.

 

...lee

Link to comment
Share on other sites

 

Actually, DATA is not defined in fbForth 2.0, but can easily be managed with the DATA[ ... ]DATA construct:

HEX
: DATA  ( --- addr )  
   DATA[
      FFFF FFFF FFFF FFFF FFFF FFFF FFFE FFFF 
      FAED FEF7 BEEA F4B5 AA02 80F8 D68A 2401
      A9B6 AAAD 3695 564B BFD5 BBA9 D1A2 D069 
      FFB5 FF6F FD5F BB5F FFDB 7EEB BEEB 5DF6
      FFFF FEFB FDEB FAD5 FBEE BBDD 6AF6 AD75 
      D5A2 54A2 A9A4 1249 B64A AA12 4825 8822
      A5AA 5791 4A28 9425 A0DA 68A8 CA9A 6CB6 
      6D97 5E13 2A05 0A05 DBED B6FB EDBD F73E
      FAA9 F5BB EAB5 D6F3 ADF5 565A D5B5 D66A 
      204A 8054 5148 A488 0094 2196 2902 9044
      920A A449 0229 0293 DAAA B76B AD57 FFBF 
      000A 2004 81C8 C5A2 D72F B517 4513 4205
      59EA A9D1 7888 8880 AD35 5A2B 955A 2DB6 
      3244 9045 75A8 9281 1082 100A F45A 4AB6
      0AA5 15A7 2A8B 9585 FBF2 FB1E A955 02A9 
      AAAA ADAB 552D AA2C A289 4249 9241 A8D2
      8882 8A85 8642 4342 9AAA AD1A D7AC 5A8A 
      BECB 7FAF FFBD DEB5 FDFE C4EE B4D5 9049
      65A2 AAA9 542A 4A2A AAD5 B4D2 AAB9 5CAB 
      562B AB95 562B AA57 A550 AAD1 A8D4 E258
      4345 52A1 A1A0 A1B0 154D 880D 448E 0282 
      5AB7 542A 5154 AAD5 0450 8940 2280 2501
      2A55 2D55 2B8A 5005 AD0A 2D55 5AA5 965B 
      2AD5 5B6D AAB5 EA77 EAE8 6AB4 D59A EA5A
      1141 7151 ECEE FBEF 0201 02A1 A0C0 70AA 
      AAD5 B65B 160D 0200 9421 3AC4 5A20 5A44
      2A15 A556 955D AB15 D554 6192 146A 55AE 
      AA7B ADB5 EBB7 DAAB ED75 BEED 7EFE FFED
      B3FD B7DD F7AE FBF8 F0AC FAF6 FDB6 DEAE 
      8001 A448 A012 80A0 DA6A 6D2B AD1F 5F3A
      AAAB 5555 AD75 DBFE DA68 2150 4A51 56EB 
      4FAB 2F97 6E5F FDBF FFB6 DBEF B3DB EDBB
   ]DATA
   DROP  ( drop cell count and just leave address)  ;
DECIMAL

The fact that ]DATA is already defined in fbForth 2.0 does not preclude redefining it, but a newbie might do better with a different name.

 

...lee

 

Indeed.

 

For those trying this using FB Forth with the DATA[ ]DATA construct, some other candidates for the picture array could be:

 

DATA+

DATA()

DATA[]

]PICTURE

 

or any other set of ASCII text characters that makes sense to your way of thinking about it.

It's Forth.

 

"It's freedom baby! ya!"

 

post-50750-0-11444600-1501724827.jpg

Edited by TheBF
  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...