Jump to content
IGNORED

TF, camel, FB Forth fun


GDMike

Recommended Posts

While studying on how I could "improve" Mark's SAMS code compiler, (still wondering if it's possible :))

I realized that using the TF BRANCH word was the key to making tail-call optimization in Indirect threaded Forth.

 

This version should be translatable to the other systems if one wanted to do it.

I am using the manual version name that Chuck Moore used in his Machine Forth.

 

The next trick would be to make it smart enough to know if the last word in a definition can be jumped to directly.

I did some preliminary testing for COLON and CODE words but it failed on DEFER.  (sounds familiar) 

 

Edit: GOTO changed. We do not need to auto-increment the *IP register because we are jumping somewhere else. This made it a bit faster

 

\ tail call optimizizing semicolon from machine forth    nov 27 2022
\ For ITC Forth
HEX
CODE GOTO   C259 ,  ( *IP IP MOV,)  NEXT, ENDCODE

: CELL-   2- ;
: PREVXT ( -- XT)  HERE CELL- @ ; \ fetch the XT of previous compiled word

: -;  ( -- ) \ programmer controlled
       PREVXT >BODY          \ get previous XT, compute data field
       -2 ALLOT              \ erase the previous XT
       POSTPONE GOTO  ,      \ compile the address for GOTO
       POSTPONE [            \ turn off compiler
       REVEAL
       ?CSP
; IMMEDIATE
\ -; does not end with EXIT because it is branching directly to another
\ list of tokens. That other list will end in EXIT or NEXT.

 

 

I tried it out on a 1,000,000 nest/unnest benchmark that I found here:  The ultimate Forth Benchmark (theultimatebenchmark.org)

(Section 13.7) 

 

Normal time is                : 2:30.7 

With tail-call optimization: 1:55.5 1:54.06

 

In another test I did 1 call per definition on 20 definitions.

Normal semi-colon was 1533 uS.

Optimized is 873  852uS.

 

The TAIL* definitions compile to continuous calls to NEXT and the return stack doesn't move! :)

 

 

Spoiler
\ TAIL CALL OPTIMIZATION TEST

NEEDS -;     FROM DSK1.TAILCALL

: ACTION   ;

: NEST1   ACTION ;
: NEST2   NEST1 ;
: NEST3   NEST2 ;
: NEST4   NEST3 ;
: NEST5   NEST4 ;
: NEST6   NEST5 ;
: NEST7   NEST6 ;
: NEST8   NEST7 ;
: NEST9   NEST8 ;
: NEST10  NEST9 ;
: NEST11  NEST10 ;
: NEST12  NEST11 ;
: NEST13  NEST12 ;
: NEST14  NEST13 ;
: NEST15  NEST14 ;
: NEST16  NEST15 ;
: NEST17  NEST16 ;
: NEST18  NEST17 ;
: NEST19  NEST18 ;


: TAIL1   ACTION -;
: TAIL2   TAIL1 -;
: TAIL3   TAIL2 -;
: TAIL4   TAIL3 -;
: TAIL5   TAIL4 -;
: TAIL6   TAIL5 -;
: TAIL7   TAIL6 -;
: TAIL8   TAIL7 -;
: TAIL9   TAIL8 -;
: TAIL10  TAIL9 -;
: TAIL11  TAIL10 -;
: TAIL12  TAIL11 -;
: TAIL13  TAIL12 -;
: TAIL14  TAIL13 -;
: TAIL15  TAIL14 -;
: TAIL16  TAIL15 -;
: TAIL17  TAIL16 -;
: TAIL18  TAIL17 -;
: TAIL19  TAIL18 -;

DECIMAL
: .uS     213 10 */ . ." uS" ;

: TEST1   TMR@  NEST19 TMR@ -  .uS ; \ 1533uS
: TEST2   TMR@  TAIL19 TMR@ -  .uS ; \ 852uS

 

 

 

  • Like 3
Link to comment
Share on other sites

This code is a more complete version that you can turn the optimizing on or off with a variable set to true/false, or just use the manual version as you see fit.

The smart semi-colon will only optimize CODE words or COLON definitions if they are the last word before the semi-colon.

 

I realize this is an academic thing. Not really needed. 

This was mostly my need to understand how you would do this in an ITC Forth system. :) 

 

It works for most code, but there are things that it breaks. 

That is why it needs TAILCALL ON / TAILCALL OFF, which is not very practical except for a new project. 

 

 

\ tail call optimizizing semicolon for Camel99 Forth  Nov 27 2022 Brian Fox

HEX
CODE GOTO   C259 ,  ( *IP IP MOV,)  NEXT, ENDCODE

: CELL-   2- ;
: PREVXT ( -- XT)  HERE CELL- @ ; \ fetch the XT of previous compiled word

: -;  ( -- ) \ programmer controlled
       PREVXT >BODY          \ get previous XT, compute data field
       -2 ALLOT              \ erase the previous XT
       POSTPONE GOTO  ,      \ compile the address for GOTO
       POSTPONE [            \ turn off compiler
       REVEAL
       ?CSP
; IMMEDIATE

: CODE?   ( xt -- ?) DUP @ CELL- = ;
: COLON?  ( xt -- ?) @ ['] DOCOL @ = ;

VARIABLE TAILCALL  \ control tail call optimizizing with this variable
                   \ TAILCALL ON  turns optimizer on

: TAILCALL? ( xt --?) DUP CODE? SWAP COLON? OR  TAILCALL @ AND ;

: ;   ( -- )
      PREVXT TAILCALL?
      IF   POSTPONE -;
      ELSE POSTPONE ;
      THEN ; IMMEDIATE

\ -; does not end with EXIT because it is branching directly to another
\ list of tokens. That other list will end in EXIT or NEXT.

 

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

For your reading pleasure. Found on comp.lang.forth 

 

"Forth's most important feature has little to do with the fact that it is a stack language,

it has instead to do with the way it interacts as a whole with the user. Forths extensibility,

structure, modularity and very simple syntax are key attributes that give the programmer

freedom to structure solutions for problems in ways that programmers of other languages

cannot understand or attempt."

 

An Interview with Tom Zimmer (wordpress.com)

  • Like 3
Link to comment
Share on other sites

If you have ever wondered why GPL is slower than Forth on TI-99, this short lecture by Dr. Brad Rodriguez might explain why.

 

Brad is the author of Camel Forth. In the lecture he explains his work on making a version that uses byte-codes ("token-threading) on the MSP430.

A byte-code Forth operates in a similar way to GPL in that Forth "instructions" are abstracted to single bytes. 

 

Spoiler alert: The conventional "address threading" method (like Forth in this neighbourhood) is about 2x faster than byte-codes on MSP430.

I would expect a similar result for TMS9900. 

 

  • Like 3
Link to comment
Share on other sites

  • 1 month later...

I have continued trying to debug my file to compile Forth to SAMS using more CODE words with limited success.

It's very appealing because I got the call overhead down to under 0.3%, the and dictionary headers are only 4 cells, with the rest of the definition going into SAMS.  

That is 5X faster than the Forth versions which have about 1.6% call overhead dictionary entries that are 8 CELLS total size 

 

At one point I went back to Mark's orginal code and implemented my ideas in Forth.

 

In the file below I have back-migrated what I did so that it runs on Turbo Forth.

I can't remember if I posted something like this before, but I think this version is cleaner and uses 672 bytes of dictionary. 

If you need a lot SAMS pages for Forth code this version saves valuable dictionary space because it doesn't need the array of dictionary pointers.

The dictionary pointers are maintained in each SAMS page in the last memory location.

 

The changes are:

  1. Replace the array of dictionary pointers with pointer at the end of the each SAMS page
  2. Write a magic number in each SAMS page to indicate page is ready to use (not used but it's there) 
  3. BANKS now performs the initialization of each SAMS memory page
Spoiler
\ COMPILE TO SAMS MEMORY for TurboForth:  
\ http://turboforth.net/resources/sams.html

\ BFox changes:
\ - Replace array _HERES with pointer at the end of the each SAMS page
\ - Write a magic number in each SAMS page to indicate page is ready to use 
\ - BANKS now performs the initialization of each page

create _bnkstk 20 cells allot  \ bank stack

$F9F9 _bnkstk ! \ force first entry on bank stack to $F9

\ SAMS CARD management
HEX        3000 CONSTANT CSEG    \ MASTER REFERENCE CODE SEG WINDOW 

 \ SAMS memory addresses for management variables 
    CSEG 0FFE + CONSTANT SAMSDP   \ variable at end of SAMS page
    SAMSDP 2-   CONSTANT SAMSID   \ ID field that this is a valid page
          ABBA  CONSTANT MAGIC# 


_bnkstk value _bsp             \ pointer into bank stack
-1 value _bank                 \ current bank
0 value _nhere                 \ "normal" here
0 value _maxBank

: CMAP  ( n -- ) CSEG >MAP ; \ "code map"

: >bank ( n --)  2 +to _bsp   dup _bsp ! CMAP ;
: bank> ( -- n) -2 +to _bsp   _bsp @     CMAP ;

 \ reserve space for here pointers for n banks
: BANKS ( n -- )  
  DUP TO _MAXBANK
  DUP 1+ 0 
  DO   
    I CMAP 
    CSEG SAMSDP ! 
    MAGIC# SAMSID ! 
  LOOP 
  CR 4 * U. ." K SAMS code" CR ;

: b: ( n -- )
  \ begin compiling a banked definition in bank n
  _bank -1 <> if
    :
  \ runtime stuff   
    compile lit _bank ,     
    compile >bank
    compile branch  SAMSDP @ dup ,
\ compile time stuff     
    here to _nhere          \ save "normal here"
    h !                     \ set h to _bank's "here"
    _bank CMAP              \ map in the bank
  else : then ;


: _bfree ( -- ) \ print free memory in the bank...
  $4000  SAMSDP @ -  .   ." bytes free." cr ;
  
  
: ;b ( -- ) \ end banked compilation 
  compile branch  _nhere ,
  here  SAMSDP ! \ update here for bank
  _bfree
  _nhere h !                    \ restore h to "normal" memory
  compile bank>   [compile] ; ;
   
: :  ( -- )  _bank -1 = if : else b: then ;

-1 TO _BANK 
: ;  ( -- )  _bank -1 = if [compile] ; else ;b then ; immediate 

: setBank ( bank -- )
  to _bank
  _BANK -1 _maxBank within 0= abort" Illegal bank number"
  _BANK -1 = if cr ." Compiling to 32K memory." cr EXIT then 
  cr ." Bank " _bank . ." active. "  _bfree ;


HERE SWAP -   DECIMAL .  .( bytes)

 

 

Test code 

Spoiler
DECIMAL 
32 BANKS

HERE 
16 SETBANK
: TEST   CR ." This has got to work!" ;

17 SETBANK
: HELLO   CR ." Hello from SAMS"  ;

18 SETBANK
: NESTED1  ." nesting 1" ;

19 SETBANK
: NESTED2  NESTED1  ."  2 "  ;

20 SETBANK
: NESTED3  CR NESTED2  ."  3 "  CR TEST   ;

16 SETBANK ( Use 240 again for a circular nest)
: GO   NESTED3 HELLO  ;

HERE SWAP - . .( bytes)

GO 

 

 

  • Like 4
Link to comment
Share on other sites

  • 2 weeks later...

@oddemann demonstrated a BASIC program he had generated by ChatGPT.

 

This post is for people who might want to try Forth but aren't sure what it's like.

 

I am always curious how Forth compares to BASIC for various programming tasks.

The Forth version of this program takes advantage of the word #INPUT which in a library for Camel99 Forth.

#INPUT gives Forth an easy to use INPUT statement for numbers, that behaves like TI BASIC INPUT but for numbers only. 

(It would not be hard to make a version of #INPUT for any Forth system. I would do it for anyone who wanted it)

 

TI BASIC Version

1 rem written by ChatGPT
2 rem minor changes for TI-BASIC
10 RANDOMIZE
20 N=INT(RND*10)+1
30 PRINT "I'm thinking of a number between 1 and 10."
40 INPUT "What is your guess? ": GUESS
50 IF GUESS=N THEN 90
60 PRINT "Sorry, that's not the number I was thinking of."
70 PRINT "Try again!"
80 GOTO 40
90 PRINT "Congratulations! You guessed the number!"
100 END


Things to Notice about Forth vs BASIC

Forth systems use files of source code (text) to add functionality to the language. See the INCLUDE statements.

Different Forth systems will stuff more or less into the base system depending on the implementer's taste.

Camel99 is a minimal system so typically needs more "INCLUDE" statements. 

 

Forth has no GOTO statement. It uses structured looping (BEGIN    WHILE   REPEAT and others)

That can be adjustment when you are used to BASIC.

 

Forth only understands what you have already told it so variables must be declared before you can use them.

Newlines only happen if you want them. CR means "carriage return" from the days of the teletype. 

I have heard that ChatGPT writes crappy Forth code. I guess I should ask it myself.

 

Here is my Forth equivalent program.

INCLUDE DSK1.RANDOM
INCLUDE DSK1.INPUT 

VARIABLE N
VARIABLE GUESS

: RUN
   RANDOMIZE
   10 RND 1+ N !
   CR ." I'm thinking of a number between"
   CR ." 1 and 10."
   BEGIN
     CR ." What is your guess? " GUESS #INPUT
     GUESS @ N @ <>
   WHILE
     CR ." Sorry, that's not the number I was thinking of."
     CR ." Try again!"
   REPEAT
   CR ." Congratulations! You guessed the number!"
   CR ." ** DONE ** "
;

image.png.8f9351094cca898d0d41d42054786dde.png

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

  • 3 weeks later...

I thought I would summarize the work on the Byte Magazine Sieve Benchmark with my attempts to make it faster in Forth.

I put the information here since it applies to any of the TI-99 Forth systems.

It's can be a surprise to see what really makes programs go faster.

 

Something I find interesting is that putting all the data into VDP RAM only slowed this program down by 12%.

The sieve  is something of a worst case because we are hitting the VDP RAM one byte at a time, except for the initial fill with '1'.

If you use VDP RAM for bigger data structures that deal with chunks of memory, it would be even less of a performance hit.

Forth strings like  ." and S" could arguably be kept in VDP RAM with not too much performance reduction. 

 

We can also see that a lot is gained by making words to index and read/write the array faster. 

 

 

 

 

bytesieve-optimization-summary.png

  • Thanks 1
Link to comment
Share on other sites

  • 1 month later...

From some discussion on comp.lang.forth I got this link:

 rabbithat/Takeuchi_function: Used in benchmarking uLisp. Here translated to mecrisp-stellaris Forth for a comparison benchmark (github.com)

 

This gives me for the first time a comparison of uLISP which a very neat system for writing apps on limited hardware and Forth doing the same thing on the same CPU. 

To be fair I am sure there are optimizations that could be done with uLISP like making macros that could speed it up so we should just compare the vanilla versions. You can read it for yourself but here is the topline.

 

I pretty was surprised at the difference between uLISP and Forth. 

Mecrisp Forth is a pretty fast implementation in native code.

 

uLISP     8,102   milliseconds
Forth      134   milliseconds
C           30.5 milliseconds

 

As for comparing Ardunio to our beloved TI-99

Camel99 Forth  ... 99.36 seconds.  :) 

 

Spoiler
\ Takeuchi function benchmark
\ ----------------------------------------------------------------------------------------
\
\ Lisp definition of Takeuchi function (Copied from: http://www.ulisp.com/show?1EO1):
\ (defun tak (x y z)
\   (if (not (< y x))
\      z
\     (tak
\      (tak (1- x) y z)
\      (tak (1- y) z x)
\      (tak (1- z) x y))))
	 
\ ----------------------------------------------------------------------------------------
\ Equivalent Forth definition of Takeuchi function:

\ Edited for Camel99 Forth 
NEEDS ELAPSE FROM DSK1.ELAPSE  \ timer 

\ Stuff not in Camel99 kernel 
: ROLL \ nn..n0 n -- nn-1..n0 nn ; 6.2.2150
  DUP>R PICK
  SP@ DUP CELL+ R> 1+ CELLS MOVE DROP ;

: >=   S" 1- >" EVALUATE ; IMMEDIATE

\ ------------------------------------------------
\ ( x y z -- x y z boolean)
: NOT_Y<X?  1 PICK 3 PICK >= ;

\ ( x y z -- z )
: ONLY_Z SWAP DROP SWAP DROP ;

\ ( x y z -- x y z x-1 y z)	 
: TAKX 2 PICK 1- 2 PICK 2 PICK ;

\ ( x y z result1 -- x y z result1 y-1 z x)
: TAKY 2 PICK 1- 2 PICK 5 PICK ;

\ ( x y z result1 result2 -- result1 result2 (z-1) x y)
: TAKZ 2 ROLL 1- 4 ROLL 4 ROLL ; 

\ ( x y z -- result )
: TAK NOT_Y<X? 
   IF ONLY_Z 
   ELSE TAKX RECURSE 
        TAKY RECURSE 
        TAKZ RECURSE RECURSE 
   THEN ;

\ 18 12 6 TAK   are the benchmark arguments 

 

 

However the Forth code has really not translated the algorithm very well.

We can speed this up with proper word substitutions:

1 PICK  should be  OVER 

SWAP DROP  should be  NIP

 

And Camel99 has the words 3RD and 4TH to replace 2 PICK and 3 PICK  

Then the time is: 85.53 seconds. 

Spoiler
\ Edited for Camel99 Forth 
NEEDS ELAPSE FROM DSK1.ELAPSE  \ timer 

\ Stuff not in Camel99 kernel 
: ROLL \ nn..n0 n -- nn-1..n0 nn ; 6.2.2150
  DUP>R PICK
  SP@ DUP CELL+ R> 1+ CELLS MOVE DROP ;

: >=   S" 1- >" EVALUATE ; IMMEDIATE

\ ------------------------------------------------
INCLUDE DSK1.3RD4TH 

\ ( x y z -- x y z boolean)
: NOT_Y<X?   OVER 4TH >= ;

\ ( x y z -- z )
: ONLY_Z     NIP NIP ;

\ ( x y z -- x y z x-1 y z)	 
: TAKX   3RD 1-  3RD 3RD ;

\ ( x y z result1 -- x y z result1 y-1 z x)
: TAKY  3RD 1-  3RD 5 PICK ;

\ ( x y z result1 result2 -- result1 result2 (z-1) x y)
: TAKZ   2 ROLL 1-  4 ROLL 4 ROLL ; 

\ ( x y z -- result )
: TAK NOT_Y<X? 
   IF ONLY_Z 
   ELSE TAKX RECURSE 
        TAKY RECURSE 
        TAKZ RECURSE RECURSE 
   THEN ;

 

 

 

 

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

And some proof that ELSE is overhead. 

Changing the final definition to this lets the code jump out early if the first test is true.

Time is 83.98 seconds 

: TAK 
   NOT_Y<X? IF ONLY_Z  EXIT THEN 
   TAKX RECURSE 
   TAKY RECURSE 
   TAKZ RECURSE RECURSE 
;

 

  • Like 4
Link to comment
Share on other sites

I think the rabbithat programmer that translated the LISP TAKEUCHI code was new to Forth.

 

There are two traditional commandments in Forth from the "oral tradition".  :)

  1. "Thou shalt not PICK"  
  2. "Thou shalt never ROLL" 

 

So by just removing the sacrilegious ROLL word used in TAKZ the time drops to  62.31 seconds. 38% faster

\ ( x y z result1 result2 -- result1 result2 (z-1) x y)
\ : TAKZ 2 ROLL 1- 4 ROLL 4 ROLL ; 
: TAKZ     ROT 1- >R  2SWAP  R> -ROT ;

 

Then if we only use only standard Forth words and replace the faulty use of 2 PICK with OVER and "OVER SWAP" with NIP, the time is 54.25,  45% faster. 

 

And finally if we use the special 3RD and 4TH words available to Camel99 the time drops to 48.48 seconds, 51% or over 2X faster. 

 

If we extrapolate these results to the Mecrisp Forth numbers it would be  134/2= 67 milliseconds or 120 times faster than list and about 1/2 speed to C. 

 

So that's why it's good to keep the commandments. :) 

 

 

  • Like 2
Link to comment
Share on other sites

Down the rabbit hole I go. 

I found an big error in the Ultimate Forth Benchmark website.

The TAK version shown there runs in no time and with 18 12 6 TAK it returns 6. The correct answer is 7.

 

So this final version, removes all the superfluous subroutine calls and uses EXIT to leave as fast a possible. 

The runtime now is 35.5 seconds. This is almost 3X faster than the original code on my ITC Forth system.

(The DTC Forth result for the same code is 29.7 seconds)

 

\ Equivalent Forth definition of Takeuchi function:

\ Edited for Camel99 Forth 
NEEDS ELAPSE FROM DSK1.ELAPSE  \ timer 

: >=   S" 1- >" EVALUATE ; IMMEDIATE

\ ------------------------------------------------
INCLUDE DSK1.3RD4TH 

: TAK  ( x y z -- result ) 
    OVER 4TH >=  IF NIP NIP  EXIT THEN 
    3RD 1-  3RD 3RD    RECURSE 
    3RD 1-  3RD 5 PICK RECURSE 
    ROT 1- >R  2SWAP  R> -ROT RECURSE 
    RECURSE 
;

: TAKTEST   18 12 6 TAK . ;

image.png.efd4dbae4e8ed331b981d39dff2e5bae.png

 

And the things you can find on the internet!

AcornUser052-Nov86 : Free Download, Borrow, and Streaming : Internet Archive

 

Here is a page of results on BBC Acorn computers with Z80 and 6502 CPUs.

Unfortunately the article does not show the Forth source that they used so we can't do a direct comparison. 

They mention that the benchmark code was "optimized for speed" 

The old 99 is in the ballpark. 

image.thumb.png.3a5bb6a80d67485f4c7328f0cf089c4a.png

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

  • 3 months later...

I accidently stumbled across this Forth 2020 site and found this 1968 listing of Forth by Chuck Moore. 

Wonderful to see it there.

 

Forth2020 - Forth 1968Listing

 

There is also a bunch of other neat things on the site for the Forth aficionado, including this interview with Chuck that really gives some insight into how he thinks.

 

Forth2020 - About Forth

  • Like 4
Link to comment
Share on other sites

Since this topic is about Forth "fun" I thought I would share some stuff I am having "fun" with. :)

I wanted to add the "next word/previous word" function to VI99. 

 

Years ago I was quite confused/indignant about the new Forth way of handling strings as an (address, length) pair on the data stack.

Seemed too complicated managing two items for every string. 

I was pretty sure that my byte counted strings were good enough for any purpose under the sun. :mad:

 

Now I am getting why some senior Forth people changed things. 

 

We all have -TRAILING to remove trailing spaces from a string and return the stack string pair that defines the new string. 

For the editor I took a page from this idea and made:

: LASTCHAR  ( addr len -- char) 2DUP + 1- C@ ;

: -ASCII ( addr len -- addr' len') 
    BEGIN  
      DUP 
    WHILE   
      LASTCHAR BL <> 
    WHILE  
       1-  0 MAX 
    REPEAT 
    THEN ;

 

With these tools the PREVWORD definition is simple. 

 

: PREVWORD   -TRAILING  -ASCII ;

Where -TRAILING removes any "trailing blanks" from the string and then -ASCII scans backwards past anything that is NOT a blank character. 

 

In the case of PREVWORD the resulting string length is the same as the screen column so we just need this code to set the editor column. 

( addr len) NIP  COL ! 

 

 

Although not standard words most modern Forth systems have SKIP and SCAN which search a string from start to end.

I think these first showed up in  FPC for DOS by Tom Zimmer and used the intel x86 string scanning instructions. They stuck with the community. 

Like -TRAILING, SKIP and SCAN return a new address, length pair string. This means they can eat their own output:)

 

To get a string that starts with the next word we just need this:

: NEXTWORD      BL SCAN  BL SKIP ; 

Where BL SCAN searches forward for the next occurrence of a "blank" (old term for space character) 

Then BL SKIP  skips past "blank" characters until it finds a non-blank character ie: a word.

 

Getting the editor column value is a bit more complicated because NEXTWORD it is cutting off the front of the string word by word. 

So we need to subtract the new length from the full length of original line in the editor to compute the COL value.

 

There is a bit more to the final code because at the end of a line we need to go up or go down in the file depending on the direction.

And VI editors can be given a numeric argument and so they can go ahead or back any number of words.

 

I will be putting up the code on Github this week so you will just have to wait until then for the full story but here is a peek. :)  

 

: GOFORWARD 
    DUP 0=              \ 0 means we are at end of line 
    IF  2DROP 
        GODOWN COL OFF 
        ELINE# SEEKTO 
    END
    NEXTWORD 
;

: CMD-w  \ next word command
    ELINE# SEEKTO RIGHTSIDE
    ARGS ?DO  GOFORWARD  LOOP      
    NIP SEEK$ @ LEN SWAP - 0 WIDTH CLIP  COL ! 
;

: GOBACK ( addr len -- addr' len') 
    DUP 1 <                    \ test for beginning of line 
    IF    2DROP                \ don't need this string now
          GOUP                 \ go up one line 
          ELINE# SEEKTO        \ re-seek to the new line
          DUP COL ! 
    END
   PREVWORD
;

: CMD-b   \ previous word command 
  ELINE# SEEKTO LEFTSIDE 1-   \ start scan at the cursor-1 
  ARGS ?DO  GOBACK  LOOP      
  NIP COL ! ;               

 

 

 

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

I realized in the NEXTWORD definition above, I forgot to give everyone a version of SKIP and SCAN with the magic word /STRING. These ones work in TurboForth. 

Now that Lee is adding these strange multiple While statements to FbForth, he can use this code too along with his fancy new REPEAT word. 

 

-ROT is a code word but ROT ROT will do the same thing. 

 

: /STRING  ( a u n -- a+n u-n) \ trim string
    ROT OVER + -ROT - ;

: SCAN (  adr len char -- adr' len')
        >R     \ remember char
        BEGIN
          DUP
        WHILE ( len<>0)
          OVER C@ R@ <>    \ test 1st char
        WHILE ( R@<>char)
            1 /STRING      \ cut off 1st char
        REPEAT
        THEN
        R> DROP            \ Rdrop char
;

 

And of course SKIP is very similar

: SKIP (  adr len char -- adr' len')
        >R     \ remember char
        BEGIN
          DUP
        WHILE ( len<>0)
          OVER C@ R@ =    \ test 1st char
        WHILE ( R@=char)
            1 /STRING      \ cut off 1st char
        REPEAT
        THEN
        R> DROP            \ Rdrop char
;

 

 /STRING should also be a code word. It's waaaay more efficient because the 9900 can reach into the stack memory directly. 

Here is the my version but it just needs a few tweaks to work in the other systems.

 

CODE /STRING ( c-addr1 u1 n -- c-addr2 u2 ) 
    TOS   *SP  SUB,  
    TOS 2 (SP) ADD,     
    TOS POP,     
    NEXT,       
    ENDCODE
[THEN]

 

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

I got this website via Reddit /Forth that gives a very comprehensive history of Forth. 

I looks like the writer has recently discovered Forth (created Feb 2023) and wrote up what he has found over the course of the year.

It's a good reference site for history and other web links. 

 

Forth: The programming language that writes itself: The Web Page (ratfactor.com)

  • Like 4
Link to comment
Share on other sites

53 minutes ago, Reciprocating Bill said:

There's a quip in here somewhere about a forthskin. But you won't hear it from me.

There should be a prize for that that post. :) 

 

(I think I have heard that term on comp.lang.lisp)

  • Haha 1
Link to comment
Share on other sites

  • 2 weeks later...

I just saw something on comp.lang.forth that I would have never thought of but apparently others have used it. 

It is not guaranteed to work on all Forth systems.  You need the Forth83 style word CREATE so it doesn't work in FbForth without making that word.

 

Let's say you wanted to create a vector table of these actions that you have written up. 

 

: FIZZ  ;
: BUZZ ;
: BLEEP ; 
: HALT ; 

 

In many Forth systems you can do this to make the table of executable "tokens" (addresses) 

 

CREATE TABLE  ] FIZZ BUZZ BLEEP HALT [ 

For the Forth student: How does it work?

Because ] turns on the compiler so the token for each word is looked up in the dictionary and is compiled into memory as each word is parsed.

Then [  turns off the compiler ie: turns on the interpreter again

 

And then to run them you might do something like this.

: DOIT  ( n -- ) CELLS TABLE +  @ EXECUTE ; 

\ Usage: 0 DOIT 1 DOIT  etc. 

(no protection on this example so you can blow up the system if you type 4 DOIT.

( I will leave it to the reader to implement protection ) (always wanted to say that) :) 

 

More stuff you'll probably never need but now you know. 

 

  • Like 3
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...