Jump to content
IGNORED

TF, camel, FB Forth fun


GDMike

Recommended Posts

54 minutes ago, FarmerPotato said:

Oops.  I understood VFILL to mean VDP fill and that it went horizontal. Like I'd think of 0 768 32 VFILL to clear the screen.

 

I also found it annoying that vaddr is below len and char.  If I'm iterating on vaddr, it requires a nasty 2DUP I -ROT to prepare the stack for FILL/VFILL.

I imagine a ! order: 

: VFILL! ( char len vaddr -- )

  SETVWA

  0 DO DUP VDPWD ! LOOP ;
 

You are correct. I was trying to say that where HCHAR uses x,y   VFILL uses a VDP address as a starting point. 

 

Typically VFILL like FILL is a code word so we can access args randomly. 

 

But if I had to do VFILL in Forth I might do this:

: VFILL ( Vaddr len char -- )
        -ROT   ( char vaddr len)
        OVER + SWAP  ( char Vend Vstart)
        DO 
           DUP I C!
        LOOP 
        DROP ; 

 

Your version would work faster with SETWA defined and using the -ROT (reverse rotate) operator.

I actually did it this way in my "RECOMPILER" as an experiment. :)  Check out this driver in Forth.

(FOR NEXT is a simple down counter. Way less code than DO/LOOP.

https://github.com/bfox9900/RECOMPILER/blob/main/src/LIB/VDPDRVR-FORNEXT.FTH

 

 

See here for my ALC version use in Camell99 Forth 

https://github.com/bfox9900/CAMEL99-ITC/blob/master/Compiler/cc9900/SRC.ITC/TI99IOX.HSF

 

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Still working on cleaning up my repository and I came across the old sevens problem. 

 

I always wanted a version that was a literal translation of the BASIC so I did one. 

It's fun to look at.  I did not use any tricks unless you call one DUP a trick. :) 

 

Here is the TI BASIC version that runs in 27 minutes.  I am afraid I have lost the accreditation for it so speak up if it's yours. (Lucien?)

Edit: Original program was written by @unhuman.

I modified lines 340 to 360 to speed up printing. The original used HCHAR. 

 

Spoiler
1 rem for comparison to Forth
2 rem TI BASIC version runs in 27mins and 20 seconds 
10 OPEN #1:"CLOCK"
20 INPUT #1:A$,DATE$,START$
100 DIM A(256)
110 PRINT "7's Problem"
120 A(1)=7
130 WIN=0
140 POWER=1
150 NUMLEN=1
160 POWER=POWER+1
170 PRINT "7 ^";POWER;"IS:"
180 CARRY=0
190 INAROW=0
200 FOR I=1 TO NUMLEN
210    A(I)=A(I)*7+CARRY
220    CARRY=INT(A(I)/10)
230    A(I)=A(I)-CARRY*10
240    IF A(I)<>7 THEN 290
250    INAROW=INAROW+1
260    IF INAROW<>6 THEN 300
270    WIN=1
280    GOTO 300
290    INAROW=0
300 NEXT I
310 A(I)=CARRY
320 IF CARRY=0 THEN 340
330 NUMLEN=NUMLEN+1
340 FOR I=NUMLEN TO 1 STEP -1
350     PRINT CHR$(A(I)+48);
360 NEXT I
363 REM we're done get the time
365 INPUT #1:A$,DATE$,END$
370 PRINT ::
380 IF WIN<>1 THEN 160
390 PRINT "WINNER IS 7 ^";POWER
394 Print "Sevens started:";START$
400 PRINT "Ended at ";END$
410 CLOSE #1
420 END

 

 

And here is a Forth literal translation that runs in edit:  1 minute 10 seconds on my indirect threaded system. 

( Re-timed using a stop-watch because the there was too much VDP I/O playing with the ISR timer)

I have included the BASIC code as comments. 

It would be faster if I replace the VARIABLEs with VALUEs  but this is vanilla Forth. 

Most systems have character array creator CARRAY or it can be made easily enough. 

 

Spoiler
\ literal translation of BASIC program to Forth 

INCLUDE DSK1.TOOLS 
INCLUDE DSK1.ELAPSE 
INCLUDE DSK1.ARRAYS 

DECIMAL 
: ?BREAK   ?TERMINAL ABORT" *BREAK*" ; 

\ must define all data before use
VARIABLE WIN 
VARIABLE POWER 
VARIABLE NUMLEN 
VARIABLE CARRY 
VARIABLE INAROW 
VARIABLE NDX   ( transfers loop index out of DO LOOP )

 256 CARRAY ]A                  \  100 DIM A(256)
: RUN 
  CR ." 7's Problem "           \ 110 PRINT "7's Problem"
  0 ]A 256 0 FILL               \ init ]A to zero
  7 0 ]A C!                     \ 120 A(1)=7
   WIN OFF                      \ 130 WIN=0
  1 POWER !                     \ 140 POWER=1
  0 NUMLEN !                    \ 150 NUMLEN=1
  BEGIN POWER 1+!               \ 160 POWER=POWER+1
    ." 7 ^" POWER @ . ." IS:"   \ 170 PRINT "7 ^";POWER;"IS:"
    ?BREAK 
    CARRY OFF                   \ 180 CARRY=0
    INAROW OFF                  \ 190 INAROW=0
    NUMLEN @ 1+ 0               \ 200 FOR I=1 TO NUMLEN
    DO    
        I NDX !                 \ copy I for later 
        I ]A C@ 7 *  CARRY @ +  \ 210 A(I)=A(I)*7+CARRY
\ We avoid some math with divide & mod function 
        0 10 UM/MOD  CARRY !    \ 220 CARRY=INT(A(I)/10)
        I ]A C!                 \ 230 A(I)=A(I)-CARRY*10
        I ]A C@ 7 =             \ 240 IF A(I)<>7 THEN 290
        IF     
            INAROW 1+!          \ 250 INAROW=INAROW+1
            INAROW @ 6 =        \ 260 IF INAROW<>6 THEN 300
            IF                
              WIN ON            \ 270 WIN=1
              LEAVE 
            THEN                
        ELSE                    \ 280 GOTO 300 
            INAROW OFF          \ 290 INAROW=0
        THEN 
        ?BREAK 
    LOOP                        \ 300 NEXT I

    CARRY @ 
    DUP NDX @ 1+ ]A C!          \ 310 A(I)=CARRY
    IF                          \ 320 IF CARRY=0 THEN 340
        NUMLEN 1+!              \ 330 NUMLEN=NUMLEN+1
    THEN 
    CR                          \ replaces PRINT 
    0 NUMLEN @                  \ 340 FOR I=NUMLEN TO 1 
    DO   
      I ]A C@ >DIGIT (EMIT)     \ 350 PRINT CHR$(A(I)+48);
    -1 +LOOP                    \ 360 NEXT I ( STEP -1)
    CR CR                       \ 370 PRINT ::
    WIN @                       \ 380 IF WIN<>1
  UNTIL                         \     THEN 160
  ." Winner is 7 ^" POWER @ .   \ 390 PRINT "WINNER IS 7 ^";POWER
;                               \ 420 END

 

 

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

10 hours ago, TheBF said:

Here is the TI BASIC version that runs in 27 minutes.  I am afraid I have lost the accreditation for it so speak up if it's yours. (Lucien?)

 

Could be @lucien2, but check the first few posts of 

 

 

@Willsy opened with saying he had coded a TI Basic version, but I don’t know whether he ever posted it.

 

...lee

  • Thanks 1
Link to comment
Share on other sites

When I reviewed the thread on this program I found the results of this program compiled with the BASIC compiler. 

  On 3/24/2011 at 8:59 PM, unhuman said:

Compiled it kicks Forth's butt!

       "Maybe versus Weiand's Forth:

        Compiled-Basic: 1m40s

        Weiand-Forth: 3m"

 

But the Weiand Forth version was written by Lucien who admitted that he was new to Forth and the algorithm is completely different. 

 

Now, with a line by line translation, we can see that indirect-threaded Forth and compiled BASIC are running about the same speed.

If we compiled my BASIC version with simpler printing it would probably take the extra 20 seconds off or maybe more.

 

@Lee Stewart wrote a version using Forth idiomatically at it runs on my system in 1:02 with the default scroll code.

If increase the scroll buffer to the entire screen it runs in 55 seconds, but that's wasteful.

 

LOL. I am moving over to the old Seven's problem post. I wonder if my JIT can swallow the new version... ? ;) 

 

 

 

 

 

 

Link to comment
Share on other sites

I'm pretty sure the original source for this is a magazine article. I'm 90% sure it would have been a TI*MES article since I'm a Brit and us Brit 99ers were fed on a diet of TI*MES back in the day. Stephen Shaw will probably remember. I can't remember his handle on this forum.

 

Yeah, I was pretty impressed with the Forth cut, until @sometimes99er did a version in assembly which blew everything away! Doh!

  • Like 3
Link to comment
Share on other sites

2 hours ago, Willsy said:

I'm pretty sure the original source for this is a magazine article. I'm 90% sure it would have been a TI*MES article since I'm a Brit and us Brit 99ers were fed on a diet of TI*MES back in the day. Stephen Shaw will probably remember. I can't remember his handle on this forum.

 

Yeah, I was pretty impressed with the Forth cut, until @sometimes99er did a version in assembly which blew everything away! Doh!

 

Stephen Shaw is @blackbox here.

 

...lee

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

  • 2 months later...

Cleaning up I found and fixed this experiment.

I have not used it in a project but if you ever needed a hardware timer ...

I think it would port to the other Forth systems without much trouble.

 

(

 SUB, would need to change to S, 

 Change the NEEDS line to conditionally load the assembler in your system of choice

 If you don't have ?DO  change it to 1 MAX DO for some protection. 

)

 

It consumes 86 bytes of your dictionary but gives you access to the 9901 internal timer. 

Convert it to machine code to remove the need for the assembler 

 

\ hardtime.fth  very un-cooperative but accurate

NEEDS MOV,  FROM DSK1.ASM9900

HERE
DECIMAL
CREATE _TMR ( -- )  \ read the TMS9901 timer
    0 LIMI,
    R12 2 LI,       \ cru address for timer 
    -1 SBO,         \ SET bit 0 TO 1, Enter timer mode
    R0 14 STCR,     \ read timer (14 bits)
    -1 SBZ,         \ reset bit 1, exit timer mode
    RT,

CODE 1MS ( -- )   
    _TMR @@ BL,     \ read time to R0
    R0 R1 MOV,      \ dup time into R1 
    BEGIN,
        _TMR @@ BL, \ read time again 
        R1 R0 SUB,  \ subtract from dupped read
        R0 ABS,     
        R0 46 CI,   \ compare to 46. ~= 1000uS
    GTE UNTIL,
    2 LIMI,
    NEXT,
ENDCODE

: HARDMS ( n -- ) 0 ?DO 1MS LOOP ; 

HERE SWAP -  . .( bytes)

 

  • Like 1
Link to comment
Share on other sites

Posted (edited)

The timer has a maximum value of >3FFF (16383) x 21.3uS = 348957.9 uS =  349 mILLIseconds :) 

Not very much.

 

So I put the timer reading in a loop to make 1mS which is a reasonable time for TI-99. 

 

Then by putting 1MS in a hi-level loop you can do a delay as long as your loop can spin. 

 

Forgot to answer: This version has a resolution of 1mS. 

If you wanted shorter time change the value 46 to 23 to get .5 mS for example.

It can get wonky if you go too low because the 9900 is not that fast at looping compared to the time you want. 

 

 

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

@Vorticon

I may have lead you astray in that I leave the 9901 timer running continuously and I didn't show you that code. 

 

Here is how I set the timer to specific number. ( TOS is R4 aliased)  After this function runs the timer is down-counting forever. 

Then you can read it anytime you need it. 

 

I found this timer was a bit fast for polling in Forth so that would be true in PASCAL as well but you could make something that loads the timer with 46 or so and wait for it to expire.  That would be 1 milli-second as well. 

(Never tried it this way.)

 

CODE TMR!   ( n -- )         \ load TMS9901 timer from stack
             0 LIMI,
             R12 CLR,        \ CRU addr of TMS9901 = 0
             0   SBO,        \ SET bit 0 to 1, Enter timer mode
             R12 INCT,       \ CRU Address of bit 1 = 2 , I'm not kidding
             TOS 14 LDCR,    \ Load 14 BITs from TOS into timer
            -1  SBZ,         \ reset bit 0, Exits clock mode, starts decrementer
             2 LIMI,
             TOS POP,
             NEXT,
             ENDCODE

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

  • 4 weeks later...

 Just a little lesson for the Forth student on making Forth run faster. 

The Assembler is Camel Forth specific but the concepts are applicable to any Forth system. 

 

While studying TI-Forth for some insights into Graphics2 mode I found this code that initializes the image table in VDP RAM.

It needs to fill  HEX 300 bytes of VDP memory with sequential numbers bytes, so 0 .. FF  over and over 3 times.

 

: TI-INIT
  -1 1B00 1800 DO   1+ DUP 0FF AND I VSBW  LOOP  DROP  ;

Timings are for running these routines 256 times. Equivalent to 70,200 byte writes to VDP RAM. 

 

The TI Forth version ran in 82 seconds. 

 

It seemed a little verbose because VSBW writes a byte and so it doesn't need  0FF AND  in the loop to mask any number that's too big. 

Instead of using a start and end address I use the start address and >300  to feed BOUNDS which converts (address,size) to (end-address,start-address) for me. 

That's a tiny bit slower but harder to make a mistake with in my opinion. 

I start with 0 on the data stack, write it to VDP RAM and then increment it with 1+ which seem less confusing than starting on -1. 

 

I re-wrote it like this and it ran in 64.2 seconds, 28% faster, using a stopwatch since an interrupt driven timer might be wrong with all this VDP action. 

Anytime you can remove stuff from an inner loop in Forth speed improves nicely. 

1B00 CONSTANT IMG
: INIT2 ( -- ) 0 IMG 300 BOUNDS DO   DUP I VC! 1+  LOOP  DROP ;


Reminder VC! is just VSBW under a different name and it has to turn off interrupts and set the VDP address before every byte that it writes.

Not ideal since the VDP chip increments that address by itself after every write. So...

 

How about we fix that? 

You have to turn interrupts off when writing to VDP so a little CODE word was made to do that. 

I tried writing to the VDP port using a Forth word and there was a 10% improvement at 57 seconds.

 

But it's only 3 instructions to do a VDP byte write in Forth Assembler and that dropped the time to 33 seconds, 48% faster than the original code.  

If we use VC! on the first byte, the VDP address is set and ready to go for any future writes. :)

 

 

\ Turn interrupts off in Forth
CODE 0LIMI    0 LIMI,  NEXT, ENDCODE

\ : VC!++   8C00  C! ;

CODE VC!++
     TOS SWPB,
     TOS 8C00 @@ MOV,
     TOS POP,
     NEXT,
ENDCODE

: INIT3
   0 IMG VC!               \ write first byte, which sets VDP write address
   0LIMI                   \ turn off interrupts while we loop. 
\ now we just write the loop index into VDP memory with VC!++
   300 1 DO  I VC!++ LOOP ; 

\ 57 secs Forth VC!++,  33 secs. with ALC 

 

  • Like 2
Link to comment
Share on other sites

Looking through my "demo" code "work in progress" I found a copy of mergesort  that I had copied from Rossetta code. 

DECIMAL 

\ camel99 forth translation harness 
: RDROP  POSTPONE R> POSTPONE DROP ; IMMEDIATE 
: <=     POSTPONE 1+  POSTPONE < ; IMMEDIATE 

1 CELLS CONSTANT CELL 

: MERGE-STEP ( right mid left -- right mid+ left+ )
  OVER @ OVER @ < IF
    OVER @ >R
    2DUP - OVER DUP CELL+ ROT MOVE
    R> OVER !
    >R CELL+ 2DUP = IF RDROP DUP ELSE R> THEN
  THEN CELL+ ;

: MERGE ( right mid left -- right left )
  DUP >R BEGIN 2DUP > WHILE MERGE-STEP REPEAT 2DROP R> ;

: MID ( l r -- mid ) OVER - 2/ CELL NEGATE AND + ;

: MERGESORT ( right left -- right left )
  2DUP CELL+ <= IF EXIT THEN
  SWAP 2DUP MID RECURSE ROT RECURSE MERGE ;
  
: MSORT ( addr len -- )  CELLS BOUNDS MERGESORT 2DROP ;

I wondered how it would compete with quicksort.  The spoiler is:  Not as well as I thought it would. (this is an "in place" mergesort so probably it would benefit from a secondary buffer.

On a 1,000 item array of integers, reversed, this mergesort took 41 seconds. Quick sort was 4.4!!

 

I could not remember how my "teacher's pet" combsort did so I pulled it up and of course realized that it was not as general purpose as these other sorts. 

The correct way to do these things is to pass the array address and a size on the data stack. I had made a test version that had the array name hard coded in the sort. 

So I fixed combsort to behave correctly and I changed the  "exchange" code to work on addresses. 

 

Same test, combsort came it at 18.4 seconds. Not too shabby for a glorified bubble-sort! :) 

 

Here is the updated combsort with timings for all the different tests.  Combsort doesn't vary much with different data. 

(Quicksort's worst case was all values the same. It took 8.18 seconds to complete that one so almost 2X)

 

It should not be too hard to make a bit of "harness" to use this in FbForth or TurboForth. 

If you need help in that area, just holler. 

 

 

Spoiler
\ combsort for the non-forth programmer

INCLUDE DSK1.TOOLS
INCLUDE DSK1.RANDOM
INCLUDE DSK1.MALLOC
INCLUDE DSK1.ELAPSE

NEEDS VALUE FROM DSK1.VALUES

HEX
\ gratuitous variables for clarity
0 VALUE  GAP
VARIABLE SORTED

\ divide by 1.35 using Forth's scaling operator
\ found this ratio to be the fastest
: 1.35/  ( n -- n' ) 100 135 */ ; 

: XCHG  ( addr1 addr2 -- ) 2DUP @ SWAP @  ROT ! SWAP ! ; 

: COMBSORT ( addr n -- )
    DUP>R  TO GAP                    \ save n on Rstack and in GAP 
    BEGIN
      GAP 1.35/  TO GAP              \ re-compute the gap
      SORTED ON
      R@ ( -- n) GAP -  0           \ n-gap is loop limit
      DO ( -- addr )
         DUP I CELLS +  DUP GAP CELLS +  ( -- addr1 addr2 )
         OVER @ OVER @ >             \ compare the contents of cells 
         IF
            XCHG                     \ Exchange the data in the cells
            SORTED OFF               \ flag we are not sorted
         ELSE 
           2DROP 
         THEN
      LOOP
      SORTED @  GAP 0=  AND          \ test for complete
   UNTIL
   R> 2DROP
;

\ ============ TESTING COMMANDS ======================
\ load the array with different kinds of mixed up data
HEX
2000 H !   \ reset the heap 

DECIMAL
1000 CONSTANT SIZE

SIZE CELLS  MALLOC CONSTANT Q[]

\ macro is a bit faster
: ]Q    S" CELLS Q[] + "  EVALUATE ;  IMMEDIATE

: ERASE   0 FILL ;

: CLEARIT  ( -- ) 0 ]Q SIZE CELLS ERASE ;     \ all the same data 17.7
: REVERSED ( -- ) SIZE  0 DO  SIZE I -  I ]Q !      LOOP ; \ 18.0 seconds 
: ASCENDING ( -- ) SIZE  0 DO     I  I ]Q !      LOOP ; \ 17.7
: RANDIT   ( -- ) SIZE  0 DO  256 RND I ]Q !   LOOP ;  \ 19.6
: TWOIT    ( -- ) CLEARIT   99 45 ]Q !  777 SIZE 2/ ]Q ! ; \ 2 different records 17.7
: TURTLES  ( -- ) \ 18.0
           SIZE 0
            DO
               I I CHAR+ ]Q !   \ each 2 elements are reversed
               I CHAR+ I ]Q !
            2 +LOOP ;


\ use this to print the array (use FNCT 4 to break out (ALT 4 on PC)
: .Q   ( -- ) CR  SIZE  0 DO  I ]Q @ U. ?BREAK  LOOP ;

: GO   Q[] SIZE COMBSORT ;  

 

 

  • Like 3
Link to comment
Share on other sites

Constant Folding in Forth ("Armstrong" Method. ie: do it yourself) 

 

While pondering how to add a compiler feature called "constant folding"  I realized that Forth allows the programmer to do this under programmer control.

It was not obvious to me when I started using Forth that this was available or even desirable so here is some info for those new to Forth. 

 

"Constant folding" in compilers lets the compiler search for expressions with numbers or constants that have some kind of math operation applied to them.

For example:

: EXAMPLE    X 7 + ; 

 

If X is a constant, adding 7 to it never changes.  We could perform the math when the program compiles and put the final answer into the program as a number instead of computing it every time.

Easy to understand why that is beneficial. The trick is to make the compiler understand when to do this and when NOT to do this. 

 

Forth "compilers" are typically very simple beasts. Chuck believed he knew better than a computer what should be in the program. 

So *traditional Forth has no automatic constant folding but there tools that let us do it ourselves. 

 

(* Mecrisp Forth and VFX Forth have automatic constant folding but they are, you know,  from the 21st century) :) 

 

The word LITERAL lets us compile a literal number into a program.  Normally we let the Forth compiler do this for us automatically when we write a word like this:

: GREATCPU   9900 ; 

 

If we decompile the word GREATCPU  we see something like this:

SEE GREATCPU

: GREATCPU  
     LIT 9900 ;

 

What is LIT?  LIT is a little Forth word that does this when it runs:

  1. Get the contents of the next memory cell
  2. Push the content it got (a number) onto the data stack

How did LIT get there?  The Forth compiler saw a "literal" number in our program and it knows that it needs to compile the LIT word first to handle the number properly. 

 

The next tool we need is the Forth word  [  

What?  Not obvious but that little "word" turns off the compiler, meaning the interpreter is activated. 

 

And then we will need this word  ]    which turns the compiler on. 

 

So here is how we could improve our EXAMPLE program:

: EXAMPLE2    [  X  7 + ] LITERAL  ;  

 

Step by step analysis of EXAMPLE2: 

  1. colon compiles a new name in the dictionary like normal
  2.   [  turns off the compiler so the interpreter is running now
  3.  X is a constant so when interpreted, it puts its value on the data stack
  4.  7 is a number so it goes onto the data stack too
  5.  +  adds the two numbers on the data stack and leaves the answer on the data stack
  6.  ] turns the compiler back on 
  7. LITERAL  is an IMMEDIATE word that compiles LIT into a definition and compiles a number from the data stack into memory (right after LIT) 
  8. ;  compiles EXIT to end the definition and then it runs [  to turn off the compiler

 

Read that again if it was not clear, because this is some Forth weirdness here. 

 

So you can see that we could put any amount of computation between [    ]  in our definition and melt it all down to one number for LITERAL to handle. 

 

I thought of a way to make it look a bit more obvious by adding this word:

 

For ANS Forth  (Camel99, GForth)

: ]FOLD    ]   POSTPONE LITERAL  ;  IMMEDIATE 

 

For FigForth/Forth83  (FbForth, TurboForth) 

: ]FOLD    ]   [COMPILE] LITERAL ;  IMMEDIATE 

 

And then it looks like this:

: BIGEXAMPLE   [  X Y + 8 /  12 * ]FOLD  ;  

 

And that entire expression is dissolved into one number.  

*MAKE SURE X AND Y ARE NOT VARIABLES*  :) 

 

 

 

 

Edited by TheBF
added item 8.
  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

Getting cool fonts into Forth from source code. 

 

So the BASIC and Assembler programmers have this amazing resource in the form of the Magellan.

It gives you ready to go source code that you can drop into your program. What's a Forth programmer supposed to do?

 

Here is method that works by using search and replace on Assembler program data and taking advantage of the Forth memory management words. 

You can import a font from the GIF files in the attached ZIP file. 

 

With your chosen font in Magellan, select Export Assembly DATA to get the dialog box below.

You want to select only the "Include Character Data" option. We don't need anything else.

 

 

image.png.77081b8fe55d48065c6b43791eceee6c.png

 

Press Export and enter the file name for this font. It will be a .A99 file because it is for Assembly language.

The file will look like this:

 

Spoiler
PAT0   DATA >0000,>0000,>0000,>0000
PAT1   DATA >0000,>0000,>0000,>0000     
PAT2   DATA >0000,>0000,>0000,>0000     
PAT3   DATA >0000,>0000,>0000,>0000     
PAT4   DATA >0000,>0000,>0000,>0000     
PAT5   DATA >0000,>0000,>0000,>0000     
PAT6   DATA >0000,>0000,>0000,>0000     
PAT7   DATA >0000,>0000,>0000,>0000     
PAT8   DATA >0000,>0000,>0000,>0000     
PAT9   DATA >0000,>0000,>0000,>0000     
PAT10  DATA >0000,>0000,>0000,>0000     
PAT11  DATA >0000,>0000,>0000,>0000     
PAT12  DATA >0000,>0000,>0000,>0000     
PAT13  DATA >0000,>0000,>0000,>0000     
PAT14  DATA >0000,>0000,>0000,>0000     
PAT15  DATA >0000,>0000,>0000,>0000     
PAT16  DATA >0000,>0000,>0000,>0000     
PAT17  DATA >0000,>0000,>0000,>0000     
PAT18  DATA >0000,>0000,>0000,>0000     
PAT19  DATA >0000,>0000,>0000,>0000     
PAT20  DATA >0000,>0000,>0000,>0000     
PAT21  DATA >0000,>0000,>0000,>0000     
PAT22  DATA >0000,>0000,>0000,>0000     
PAT23  DATA >0000,>0000,>0000,>0000     
PAT24  DATA >0000,>0000,>0000,>0000     
PAT25  DATA >0000,>0000,>0000,>0000     
PAT26  DATA >0000,>0000,>0000,>0000     
PAT27  DATA >0000,>0000,>0000,>0000     
PAT28  DATA >0000,>0000,>0000,>0000
PAT29  DATA >0000,>0000,>0000,>0000     
PAT30  DATA >0000,>0000,>0000,>0000     
PAT31  DATA >0000,>0000,>0000,>0000     
PAT32  DATA >0000,>0000,>0000,>0000     
PAT33  DATA >1010,>1010,>0000,>1000     
PAT34  DATA >2828,>2800,>0000,>0000     
PAT35  DATA >2828,>7C28,>7C28,>2800     
PAT36  DATA >103C,>5038,>1478,>1000     
PAT37  DATA >6464,>0810,>204C,>4C00     
PAT38  DATA >2050,>5020,>5448,>3400     
PAT39  DATA >3010,>2000,>0000,>0000     
PAT40  DATA >0810,>2020,>2010,>0800     
PAT41  DATA >2010,>0808,>0810,>2000     
PAT42  DATA >0054,>387C,>3854,>0000     
PAT43  DATA >0010,>107C,>1010,>0000     
PAT44  DATA >0000,>0030,>3010,>2000     
PAT45  DATA >0000,>007C,>0000,>0000     
PAT46  DATA >0000,>0000,>3030,>0000     
PAT47  DATA >0404,>0810,>2040,>4000     
PAT48  DATA >1028,>4444,>4428,>1000     
PAT49  DATA >1030,>1010,>1010,>3800     
PAT50  DATA >3844,>0408,>1020,>7C00     
PAT51  DATA >7C08,>1008,>0444,>3800     
PAT52  DATA >0818,>2848,>487C,>0800     
PAT53  DATA >7C40,>7804,>0444,>3800     
PAT54  DATA >3840,>4078,>4444,>3800     
PAT55  DATA >7C04,>0408,>1020,>2000     
PAT56  DATA >3844,>4438,>4444,>3800     
PAT57  DATA >3844,>443C,>0404,>3800     
PAT58  DATA >0000,>3030,>0030,>3000     
PAT59  DATA >0030,>3000,>3030,>1020     
PAT60  DATA >0810,>2040,>2010,>0800     
PAT61  DATA >0000,>7C00,>7C00,>0000     
PAT62  DATA >2010,>0804,>0810,>2000     
PAT63  DATA >3844,>0408,>1000,>1000     
PAT64  DATA >3844,>5C54,>5C40,>3C00     
PAT65  DATA >1028,>4444,>7C44,>4400     
PAT66  DATA >7844,>4478,>4444,>7800     
PAT67  DATA >3844,>4040,>4044,>3800     
PAT68  DATA >7844,>4444,>4444,>7800     
PAT69  DATA >7C40,>4078,>4040,>7C00     
PAT70  DATA >7C40,>4078,>4040,>4000     
PAT71  DATA >3844,>405C,>4444,>3800     
PAT72  DATA >4444,>447C,>4444,>4400     
PAT73  DATA >3810,>1010,>1010,>3800     
PAT74  DATA >3C08,>0808,>4848,>3000     
PAT75  DATA >4448,>5060,>5048,>4400     
PAT76  DATA >4040,>4040,>4040,>7C00     
PAT77  DATA >446C,>5454,>4444,>4400     
PAT78  DATA >4444,>6454,>4C44,>4400     
PAT79  DATA >3844,>4444,>4444,>3800     
PAT80  DATA >7844,>4478,>4040,>4000     
PAT81  DATA >3844,>4444,>5448,>3400     
PAT82  DATA >7844,>4478,>5048,>4400     
PAT83  DATA >3844,>4038,>0444,>3800     
PAT84  DATA >7C10,>1010,>1010,>1000     
PAT85  DATA >4444,>4444,>4444,>3800     
PAT86  DATA >4444,>4444,>2828,>1000     
PAT87  DATA >4444,>4454,>5454,>2800     
PAT88  DATA >4444,>2810,>2844,>4400     
PAT89  DATA >4444,>2810,>1010,>1000     
PAT90  DATA >7C04,>0810,>2040,>7C00     
PAT91  DATA >3820,>2020,>2020,>3800     
PAT92  DATA >4040,>2010,>0804,>0400     
PAT93  DATA >3808,>0808,>0808,>3800     
PAT94  DATA >1028,>4400,>0000,>0000     
PAT95  DATA >0000,>0000,>0000,>7C00     
PAT96  DATA >1810,>0800,>0000,>0000     
PAT97  DATA >0000,>3008,>3848,>3C00     
PAT98  DATA >4040,>7048,>4848,>7000     
PAT99  DATA >0000,>3840,>4040,>3800     
PAT100 DATA >0808,>3848,>4848,>3C00     
PAT101 DATA >0000,>3844,>7C40,>3C00     
PAT102 DATA >1820,>2070,>2020,>2000     
PAT103 DATA >0000,>3C44,>3C04,>4438     
PAT104 DATA >4040,>7048,>4848,>4800     
PAT105 DATA >1000,>3010,>1010,>3800     
PAT106 DATA >1000,>3010,>1010,>1060     
PAT107 DATA >4040,>4850,>6050,>4800     
PAT108 DATA >3010,>1010,>1010,>3800     
PAT109 DATA >0000,>2854,>5454,>5400     
PAT110 DATA >0000,>7048,>4848,>4800     
PAT111 DATA >0000,>3844,>4444,>3800     
PAT112 DATA >0000,>7048,>4870,>4040     
PAT113 DATA >0000,>1C24,>241C,>0404     
PAT114 DATA >0000,>5860,>4040,>4000     
PAT115 DATA >0000,>3840,>3804,>7800     
PAT116 DATA >2020,>7820,>2020,>1800     
PAT117 DATA >0000,>4848,>4848,>3400     
PAT118 DATA >0000,>4444,>2810,>1000     
PAT119 DATA >0000,>4444,>5454,>2800     
PAT120 DATA >0000,>4428,>1028,>4400     
PAT121 DATA >0000,>2424,>241C,>0438     
PAT122 DATA >0000,>7C08,>1020,>7C00     
PAT123 DATA >0C10,>1020,>1010,>0C00     
PAT124 DATA >1010,>1010,>1010,>1010     
PAT125 DATA >6010,>1008,>1010,>6000     
PAT126 DATA >2454,>4800,>0000,>0000     
PAT127 DATA >0000,>0000,>0000,>0000     

 

 

Now we use search and replace on our favourite text editor to do the following:

 

  1. Search for  'PAT'     replace with  '( '       * needs a trailing space
  2. Search for 'DATA'    replace with  ')'   
  3. Search for  '>'        replace with empty string 
  4. Search for ','          replace with ' , ' 
  5. at the end of every line type in a space and a comma  ' ,'  

 

Save the edited version with a .fth extension. 

And add the extra lines that are in this spoiler. 

Spoiler
\ assembler data converted to Forth format
\ loads into VDP RAM at compile time

HERE  \ remember end of dictionary

HEX
( 0   )  0000 , 0000 , 0000 , 0000 ,
( 1   )  0000 , 0000 , 0000 , 0000 ,
( 2   )  0000 , 0000 , 0000 , 0000 ,
( 3   )  0000 , 0000 , 0000 , 0000 ,
( 4   )  0000 , 0000 , 0000 , 0000 ,
( 5   )  0000 , 0000 , 0000 , 0000 ,
( 6   )  0000 , 0000 , 0000 , 0000 ,
( 7   )  0000 , 0000 , 0000 , 0000 ,
( 8   )  0000 , 0000 , 0000 , 0000 ,
( 9   )  0000 , 0000 , 0000 , 0000 ,
( 10  )  0000 , 0000 , 0000 , 0000 ,
( 11  )  0000 , 0000 , 0000 , 0000 ,
( 12  )  0000 , 0000 , 0000 , 0000 ,
( 13  )  0000 , 0000 , 0000 , 0000 ,
( 14  )  0000 , 0000 , 0000 , 0000 ,
( 15  )  0000 , 0000 , 0000 , 0000 ,
( 16  )  0000 , 0000 , 0000 , 0000 ,
( 17  )  0000 , 0000 , 0000 , 0000 ,
( 18  )  0000 , 0000 , 0000 , 0000 ,
( 19  )  0000 , 0000 , 0000 , 0000 ,
( 20  )  0000 , 0000 , 0000 , 0000 ,
( 21  )  0000 , 0000 , 0000 , 0000 ,
( 22  )  0000 , 0000 , 0000 , 0000 ,
( 23  )  0000 , 0000 , 0000 , 0000 ,
( 24  )  0000 , 0000 , 0000 , 0000 ,
( 25  )  0000 , 0000 , 0000 , 0000 ,
( 26  )  0000 , 0000 , 0000 , 0000 ,
( 27  )  0000 , 0000 , 0000 , 0000 ,
( 28  )  0000 , 0000 , 0000 , 0000 ,
( 29  )  0000 , 0000 , 0000 , 0000 ,
( 30  )  0000 , 0000 , 0000 , 0000 ,
( 31  )  0000 , 0000 , 0000 , 0000 ,
( 32  )  0000 , 0000 , 0000 , 0000 ,
( 33  )  1010 , 1010 , 0000 , 1000 ,
( 34  )  2828 , 2800 , 0000 , 0000 ,
( 35  )  2828 , 7C28 , 7C28 , 2800 ,
( 36  )  103C , 5038 , 1478 , 1000 ,
( 37  )  6464 , 0810 , 204C , 4C00 ,
( 38  )  2050 , 5020 , 5448 , 3400 ,
( 39  )  3010 , 2000 , 0000 , 0000 ,
( 40  )  0810 , 2020 , 2010 , 0800 ,
( 41  )  2010 , 0808 , 0810 , 2000 ,
( 42  )  0054 , 387C , 3854 , 0000 ,
( 43  )  0010 , 107C , 1010 , 0000 ,
( 44  )  0000 , 0030 , 3010 , 2000 ,
( 45  )  0000 , 007C , 0000 , 0000 ,
( 46  )  0000 , 0000 , 3030 , 0000 ,
( 47  )  0404 , 0810 , 2040 , 4000 ,
( 48  )  1028 , 4444 , 4428 , 1000 ,
( 49  )  1030 , 1010 , 1010 , 3800 ,
( 50  )  3844 , 0408 , 1020 , 7C00 ,
( 51  )  7C08 , 1008 , 0444 , 3800 ,
( 52  )  0818 , 2848 , 487C , 0800 ,
( 53  )  7C40 , 7804 , 0444 , 3800 ,
( 54  )  3840 , 4078 , 4444 , 3800 ,
( 55  )  7C04 , 0408 , 1020 , 2000 ,
( 56  )  3844 , 4438 , 4444 , 3800 ,
( 57  )  3844 , 443C , 0404 , 3800 ,
( 58  )  0000 , 3030 , 0030 , 3000 ,
( 59  )  0030 , 3000 , 3030 , 1020 ,
( 60  )  0810 , 2040 , 2010 , 0800 ,
( 61  )  0000 , 7C00 , 7C00 , 0000 ,
( 62  )  2010 , 0804 , 0810 , 2000 ,
( 63  )  3844 , 0408 , 1000 , 1000 ,
( 64  )  3844 , 5C54 , 5C40 , 3C00 ,
( 65  )  1028 , 4444 , 7C44 , 4400 ,
( 66  )  7844 , 4478 , 4444 , 7800 ,
( 67  )  3844 , 4040 , 4044 , 3800 ,
( 68  )  7844 , 4444 , 4444 , 7800 ,
( 69  )  7C40 , 4078 , 4040 , 7C00 ,
( 70  )  7C40 , 4078 , 4040 , 4000 ,
( 71  )  3844 , 405C , 4444 , 3800 ,
( 72  )  4444 , 447C , 4444 , 4400 ,
( 73  )  3810 , 1010 , 1010 , 3800 ,
( 74  )  3C08 , 0808 , 4848 , 3000 ,
( 75  )  4448 , 5060 , 5048 , 4400 ,
( 76  )  4040 , 4040 , 4040 , 7C00 ,
( 77  )  446C , 5454 , 4444 , 4400 ,
( 78  )  4444 , 6454 , 4C44 , 4400 ,
( 79  )  3844 , 4444 , 4444 , 3800 ,
( 80  )  7844 , 4478 , 4040 , 4000 ,
( 81  )  3844 , 4444 , 5448 , 3400 ,
( 82  )  7844 , 4478 , 5048 , 4400 ,
( 83  )  3844 , 4038 , 0444 , 3800 ,
( 84  )  7C10 , 1010 , 1010 , 1000 ,
( 85  )  4444 , 4444 , 4444 , 3800 ,
( 86  )  4444 , 4444 , 2828 , 1000 ,
( 87  )  4444 , 4454 , 5454 , 2800 ,
( 88  )  4444 , 2810 , 2844 , 4400 ,
( 89  )  4444 , 2810 , 1010 , 1000 ,
( 90  )  7C04 , 0810 , 2040 , 7C00 ,
( 91  )  3820 , 2020 , 2020 , 3800 ,
( 92  )  4040 , 2010 , 0804 , 0400 ,
( 93  )  3808 , 0808 , 0808 , 3800 ,
( 94  )  1028 , 4400 , 0000 , 0000 ,
( 95  )  0000 , 0000 , 0000 , 7C00 ,
( 96  )  1810 , 0800 , 0000 , 0000 ,
( 97  )  0000 , 3008 , 3848 , 3C00 ,
( 98  )  4040 , 7048 , 4848 , 7000 ,
( 99  )  0000 , 3840 , 4040 , 3800 ,
( 100 )  0808 , 3848 , 4848 , 3C00 ,
( 101 )  0000 , 3844 , 7C40 , 3C00 ,
( 102 )  1820 , 2070 , 2020 , 2000 ,
( 103 )  0000 , 3C44 , 3C04 , 4438 ,
( 104 )  4040 , 7048 , 4848 , 4800 ,
( 105 )  1000 , 3010 , 1010 , 3800 ,
( 106 )  1000 , 3010 , 1010 , 1060 ,
( 107 )  4040 , 4850 , 6050 , 4800 ,
( 108 )  3010 , 1010 , 1010 , 3800 ,
( 109 )  0000 , 2854 , 5454 , 5400 ,
( 110 )  0000 , 7048 , 4848 , 4800 ,
( 111 )  0000 , 3844 , 4444 , 3800 ,
( 112 )  0000 , 7048 , 4870 , 4040 ,
( 113 )  0000 , 1C24 , 241C , 0404 ,
( 114 )  0000 , 5860 , 4040 , 4000 ,
( 115 )  0000 , 3840 , 3804 , 7800 ,
( 116 )  2020 , 7820 , 2020 , 1800 ,
( 117 )  0000 , 4848 , 4848 , 3400 ,
( 118 )  0000 , 4444 , 2810 , 1000 ,
( 119 )  0000 , 4444 , 5454 , 2800 ,
( 120 )  0000 , 4428 , 1028 , 4400 ,
( 121 )  0000 , 2424 , 241C , 0438 ,
( 122 )  0000 , 7C08 , 1020 , 7C00 ,
( 123 )  0C10 , 1020 , 1010 , 0C00 ,
( 124 )  1010 , 1010 , 1010 , 1010 ,
( 125 )  6010 , 1008 , 1010 , 6000 ,
( 126 )  2454 , 4800 , 0000 , 0000 ,
( 127 )  0000 , 0000 , 0000 , 0000 ,
 DUP         \ keep a copy of start for later

 HERE OVER - ( addr size) \ now we have  address and size

 HEX 800     \ this where our pattern table starts in VDP Ram

 SWAP  ( -- addr Vaddr size )  \ arguments ready to use
 VMBW     \ FbForth word to write bytes to VDP RAM
\ VWRITE  \ use this word for Camel99 Forth

 DP !        \ restore dictionary back to "HERE" at the top of the file


 

 

You can paste this file into FbForth or Camel99 Forth and the font will be changed. 

In FbForth you can paste these lines into a block file and LOAD the blocks.

I would add the -->  word at the end of each block so that they all load with one command. 

 

In Camel99 you can paste these into the E/A editor and save as memorable file name like FONT005.

Then the command  INCLUDE DSK1.FONT005  will bring the font into service. 

 

 

fonts.zip

Edited by TheBF
Forgot the zip file
  • Like 1
Link to comment
Share on other sites

How Do You Change Fonts Without Writing a Program?

 

One of the very useful things about Forth is the interpreter.

The interpreter gives us access to hundreds of little "programs" but we just don't always think of them that way. 

 

Here are the "programs" we used and what they do:

 

DP         DP is the dictionary pointer. It is a Forth variable so it returns the place in memory where its data is stored

           

HERE     return the contents of DP, which is the address at the end of the dictionary (ie: start of un-used memory) 

 

HEX       switch the interpreter radix to use use base 16 numbers (hexadecimal) 

 

,           "comma" is not a delimiter in Forth

             Comma is a program that takes a number from the data stack and "compiles" it into the next available memory cell in the dictionary.

             Then comma advances the dictionary pointer by 1 cell. (2 bytes on TI99)

 

DUP      push the top item on the data stack to the 2nd item location

OVER     copies the 2nd item on the data stack onto the top of the data stack. 

SWAP    reverse the position of the top item and 2nd item of the data stack  

 

-           "minus"   subtract the 2nd item on the data stack from the top item on the data stack 

!           "store"    write the data in the 2nd item on the stack into the address on the top of the data stack 

 

VMBW   "VDP MULTIPLE BYTE WRITE"  (called VWRITE in Camel99 Forth) 

             given 3 parameters on the data stack ( RAMaddress  VDPaddress  bytes)

             write the specified number of bytes from CPU to VDP RAM

 

 

Method:  Use the dictionary as temporary storage for the data, write the data to VDP, reclaim the memory. 

 

Steps:

Use HERE to remember where the data starts

Use comma to copy all the patterns into dictionary memory.

Use DUP to get a copy of the start address.

Use HERE to get the new end of dictionary after all the data was "comma-ed" in

Subtract those two addresses to get the bytes used. 

use the start address, the VDP address of the pattern table and the size to write the font data into VDP RAM

 

Remember that copy of the start address?

If we store that start address into the DP variable we effectively "deallocate" all that dictionary space we just used.

It is now re-useable for making Forth words. To Forth, it's like it never happened. :) 

 

 

 

  • Like 2
Link to comment
Share on other sites

  • 2 months later...

Not many folks are going to care about this but I am kind of jazzed about it so here it is. :)

 

While learning more about implementing lists in Forth kind of like LISP I realized that I was always thinking of "lists" as data.

One of the cool things about LISP is that DATA can be interpreted like CODE.

 

Forth allows that kind of thing as well by passing strings to the word EVALUATE.

S" 34 9 + ."  EVALUATE

However if we were to just make lists of Forth words and interpret them it would be 10X slower than compiled Forth.

 

It occurred to me that the real answer is to make lists of "compiled" code and that way you could do anything that you could compile.

But how? 

 

ANS Forth added the new word :NONAME into the language.  :NONAME works like ":"  but instead of associating the "execution token" (an address) 

of a defined word with a text name, it just gives you that magic address and you can do what you want with it. 

 

Example 

: HI   CR ." This is a normal Forth definition" ;  

:NONAME  CR ." This definition has "no name" so we will record the execution token"  ;  CONSTANT HI-TOKEN  

 

To run the word HI we just use the name and it runs itself. 

To run the token held in HI-TOKEN  we need to pass it to the word EXECUTE like this:

HI-TOKEN EXECUTE 

 

The benefit of using the tokens rather that strings is that you can still have lists of data with these token lists. 

How?  Because every Forth word begins with a pointer to a piece of code that tells the data how to behave. I call these routines "executors"

 

So a CONSTANT has a few instructions that say: Get the number you are holding and put it on the data stack. 

A VARIABLE's executor has code for:  Take the address where you are keeping your contents and put it on the data stack 

Similarly as string literal started with S"   has an executor that says put your address and string length on the data stack. 

 

In other words if we "execute" the code for these data types they can't help but do the right thing. 

 

So I wrote up a little experiment to play with the idea.

This code gives us {   } which counts how many things are on the data stack between the braces and returns that number 

 

TOKENS:  gives us a way to take all those tokens on the data stack, compile them into memory, like an array of tokens, and give them name

 

When you run that word with an index number it reaches into that array of tokens and PERFORMs the one at the index.

(PERFORM is just like EXECUTE but it needs a memory address, it fetches the token and does EXECUTE all in one ALC word)

 

(I took the liberty of renaming :NONAME to :: to make the code look cleaner) 

 

VARIABLE STK  \ can't use CSP because it is used by the colon compiler 

: {  ( -- )   SP@ STK ! ;
: }  ( -- n)  STK @ SP@ - 2/ 1- ;

: TOKENS:  ( xt1 ... xtn  n -- )
   CREATE    
        DUP ,            \ compile the number of items into this word 
        0 ?DO  ,  LOOP   \ compile all the XTs on the data stack 

   DOES> ( ndx -- [output depends on the code] )  
        DUP CELL+ >R    \ save copy of xt base address
        @               \ get the # of strings 
        OVER U< ABORT" TOKEN: index out of range" 
        CELLS R> +      \ index into the array of XTs
        PERFORM         \ fetch the XT and EXECUTE it 
;          

: :: :NONAME ( -- XT ) ; \ renamed for brevity

 

Now for example we could make lookup array for ELIZA like this:

\ Since these are colon definitions, they will return the address
\ of the string when TOKEN does the PERFORM function 
   {  :: S" I AM" ;
      :: S" I HAVE" ;
      :: S" I'VE" ;   
      :: S" I'M" ;    
      :: S" I WILL" ; 
      :: S" I'D" ;    
      :: S" I'LL" ;   
      :: S" MINE" ;   
      :: S" ARE" ;    
      :: S" WERE" ;   
      :: S" YOU" ;    
      :: S" ME" ;     
      :: S" YOUR" ;   
      :: S" IS" ;     
      :: S" MY" ;     
      :: S" I" ;     
   }  TOKENS: ]PHRASES  

 

8 ]PHRASE TYPE   will show us MINE 

 

 

But that's not the magic part. 

If we wanted strings that typed themselves out when invoked we just use  ."   the Forth equivalent of  PRINT  rather than S"  

And... we can use any code at all in the array.

{ ::  CR ." This 1st string will print itself" ;
  ::  CR ." This 2nd string will also print itself" ; 
  ::  10 0 DO  CR ." And this string has a built in loop !!"  LOOP  ;

} TOKENS: ]MAGICSTRINGS 

 

 

We just made a string array with embedded code.

 

I can die happy. :)

 

 

 

 

  • Like 2
  • Haha 1
Link to comment
Share on other sites

21 hours ago, TheBF said:

Not many folks are going to care about this but I am kind of jazzed about it so here it is. :)

 

While learning more about implementing lists in Forth kind of like LISP I realized that I was always thinking of "lists" as data.

One of the cool things about LISP is that DATA can be interpreted like CODE.

 

Forth allows that kind of thing as well by passing strings to the word EVALUATE.

S" 34 9 + ."  EVALUATE

However if we were to just make lists of Forth words and interpret them it would be 10X slower than compiled Forth.

 

It occurred to me that the real answer is to make lists of "compiled" code and that way you could do anything that you could compile.

But how? 

 

ANS Forth added the new word :NONAME into the language.  :NONAME works like ":"  but instead of associating the "execution token" (an address) 

of a defined word with a text name, it just gives you that magic address and you can do what you want with it. 

 

Example 

: HI   CR ." This is a normal Forth definition" ;  

:NONAME  CR ." This definition has "no name" so we will record the execution token"  ;  CONSTANT HI-TOKEN  

 

To run the word HI we just use the name and it runs itself. 

To run the token held in HI-TOKEN  we need to pass it to the word EXECUTE like this:

HI-TOKEN EXECUTE 

 

The benefit of using the tokens rather that strings is that you can still have lists of data with these token lists. 

How?  Because every Forth word begins with a pointer to a piece of code that tells the data how to behave. I call these routines "executors"

 

So a CONSTANT has a few instructions that say: Get the number you are holding and put it on the data stack. 

A VARIABLE's executor has code for:  Take the address where you are keeping your contents and put it on the data stack 

Similarly as string literal started with S"   has an executor that says put your address and string length on the data stack. 

 

In other words if we "execute" the code for these data types they can't help but do the right thing. 

 

So I wrote up a little experiment to play with the idea.

This code gives us {   } which counts how many things are on the data stack between the braces and returns that number 

 

TOKENS:  gives us a way to take all those tokens on the data stack, compile them into memory, like an array of tokens, and give them name

 

When you run that word with an index number it reaches into that array of tokens and PERFORMs the one at the index.

(PERFORM is just like EXECUTE but it needs a memory address, it fetches the token and does EXECUTE all in one ALC word)

 

(I took the liberty of renaming :NONAME to :: to make the code look cleaner) 

 

VARIABLE STK  \ can't use CSP because it is used by the colon compiler 

: {  ( -- )   SP@ STK ! ;
: }  ( -- n)  STK @ SP@ - 2/ 1- ;

: TOKENS:  ( xt1 ... xtn  n -- )
   CREATE    
        DUP ,            \ compile the number of items into this word 
        0 ?DO  ,  LOOP   \ compile all the XTs on the data stack 

   DOES> ( ndx -- [output depends on the code] )  
        DUP CELL+ >R    \ save copy of xt base address
        @               \ get the # of strings 
        OVER U< ABORT" TOKEN: index out of range" 
        CELLS R> +      \ index into the array of XTs
        PERFORM         \ fetch the XT and EXECUTE it 
;          

: :: :NONAME ( -- XT ) ; \ renamed for brevity

 

Now for example we could make lookup array for ELIZA like this:

\ Since these are colon definitions, they will return the address
\ of the string when TOKEN does the PERFORM function 
   {  :: S" I AM" ;
      :: S" I HAVE" ;
      :: S" I'VE" ;   
      :: S" I'M" ;    
      :: S" I WILL" ; 
      :: S" I'D" ;    
      :: S" I'LL" ;   
      :: S" MINE" ;   
      :: S" ARE" ;    
      :: S" WERE" ;   
      :: S" YOU" ;    
      :: S" ME" ;     
      :: S" YOUR" ;   
      :: S" IS" ;     
      :: S" MY" ;     
      :: S" I" ;     
   }  TOKENS: ]PHRASES  

 

8 ]PHRASE TYPE   will show us MINE 

 

But that's not the magic part. 

If we wanted strings that typed themselves out when invoked we just use  ."   the Forth equivalent of  PRINT  rather than S"  

And... we can use any code at all in the array.

{ ::  CR ." This 1st string will print itself" ;
  ::  CR ." This 2nd string will also print itself" ; 
  ::  10 0 DO  CR ." And this string has a built in loop !!"  LOOP  ;

} TOKENS: ]MAGICSTRINGS 

 

We just made a string array with embedded code.

 

I can die happy. :)

 

I will definitely port this to fbForth, but I will, first, need to define :NONAME and a terminator different from ; because ; toggles the smudge bit in the name-length byte of the latest-created word header in fbForth, which would not be the current, headerless word!

 

...lee

  • Like 3
Link to comment
Share on other sites

If it helps here is how I defined :NONAME.

(DISCLAIMER:  I peeked inside GForth and found (:NONAME) as a common factor.

 

HIDE and REVEAL are the Camel Forth words to smudge and and un-smudge the word. 

COMPILE, is just an alias for comma in simple Forths. The standards people needed something for native code systems. 

 

This is of course in my Cross-compiler Forth

The X:   ;X   stuff is the DOS Forth's  colon and sem-colon renamed, but I renamed them so I wouldn't loose my mind. :) 

 

 : (:NONAME) ( -- )  ['] DOCOL @ COMPILE,  HIDE  ]  ;
TARGET-COMPILING
 X: :         !CSP  HEADER (:NONAME)  ;X

 X: :NONAME   HERE  !CSP   (:NONAME)  ;X

 X: ;        [  REVEAL COMPILE EXIT ?CSP ;X  IMMEDIATE

 

 

And now I see from your comment  that I must be REVEALing the last word defined but I have not encountered a bug.  ?

I think because words are not hidden under normal circumstances. 

 

Link to comment
Share on other sites

25 minutes ago, Lee Stewart said:

 

I will definitely port this to fbForth, but I will, first, need to define :NONAME and a terminator different from ; because ; toggles the smudge bit in the name-length byte of the latest-created word header in fbForth, which would not be the current, headerless word!

 

...lee

So it never occurred to me to deal with the semi-colon. 

It doesn't seem to make a problem since it is resetting the smudge bit to the default condition and the last word defined.

 

  • Like 1
Link to comment
Share on other sites

47 minutes ago, TheBF said:

So it never occurred to me to deal with the semi-colon. 

It doesn't seem to make a problem since it is resetting the smudge bit to the default condition and the last word defined.

 

In fbForth (inherited from figFORTH), SMUDGE toggles the smudge bit of the word whose nfa is in LATEST. After booting up fbForth 3.0, type SMUDGE and type MENU. -FIND won’t find it because you just hid it from -FIND, so using ; to terminate :NONAME will definitely screw up the last defined word with a header.

 

Of course, if you define an even number of words with :NONAME ; before another colon definition (which would change LATEST), there is indeed, no harm done—not so for an odd number of :NONAME ; definitions. The word ; should definitely be redefined for :NONAME for fbForth.

 

...lee

  • Like 1
Link to comment
Share on other sites

1 hour ago, Lee Stewart said:

 

In fbForth (inherited from figFORTH), SMUDGE toggles the smudge bit of the word whose nfa is in LATEST. After booting up fbForth 3.0, type SMUDGE and type MENU. -FIND won’t find it because you just hid it from -FIND, so using ; to terminate :NONAME will definitely screw up the last defined word with a header.

 

Of course, if you define an even number of words with :NONAME ; before another colon definition (which would change LATEST), there is indeed, no harm done—not so for an odd number of :NONAME ; definitions. The word ; should definitely be redefined for :NONAME for fbForth.

 

...lee

 

I just made 3 colon definitions.

Then I made a :noname definition and the colon definitions were all "findable". 

 

I take that to be because even though :noname smudged the last word defined, there is no harm because the semi-colon at the end fixes it.

 

Am I missing something? (that's happened before) (a lot) 

 

Classic99 QI399.046 2024-08-27 11_21_34 PM.png

Edited by TheBF
fixed some gibberish
Link to comment
Share on other sites

7 hours ago, TheBF said:

 

I just made 3 colon definitions.

Then I made a :noname definition and the colon definitions were all "findable". 

 

I take that to be because even though :noname smudged the last word defined, there is no harm because the semi-colon at the end fixes it.

 

Am I missing something? (that's happened before) (a lot) 

 

Classic99 QI399.046 2024-08-27 11_21_34 PM.png

 

That is why I qualified my comment to show only what would happen in fbForth. Your research proves that HIDE and REVEAL do not work the same as TOGGLE and SMUDGE. (TOGGLE is used by CREATE in fbForth to explicitly set both the smudge and beginning terminator bits rather than using LATEST in the process.)

 

...lee

  • Thanks 1
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...