Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

Posted (edited)

OK so this is preliminary but from what I can see this new version which uses both of @Tursi 's  recommendations is really fast.

 

1. Keep the virtual-to-real address convertor out of the Assembler code loops.

2. Use a 16bit fill operation 

 

But the implementation is a bit different. 

  • The system has FILL built in so we use that for potential odd number writes at beginning and end
  • We use Forth to chunk through the addresses and serve them to the assembler routines.
  • The way this code works now it outputs the first chunk, ie: from the start address to the first page boundary.
  • Then if there is more than another >1000 bytes it enters the loop and uses FILLW for the entire loop which is 2x faster. 
  • And the last line takes whatever is left over and uses FILL again. 
HEX 
1000   CONSTANT 4K

\ compute start of next SAMS page boundary ( 1000,2000,3000 ETC)
: BOUNDARY ( addr -- addr') F000 AND   4K + ;

: CHUNK  ( addr len -- addr len addr1 n1) 
        OVER  DUP BOUNDARY  OVER -  1FFF AND ;

: NEXTCHUNK   ( addr len -- addr len addr len')  
        CHUNK  DUP>R               \ compute chunk and save length 
        2SWAP  R> /STRING  2SWAP   \ reduce size of data by length 
; 

\ fast 16bit character fill 
CODE FILLW ( adr len char --)
    TOS R1 MOV,    \ dup char in R1 
    R1 SWPB,       \ shift to other byte  
    R1 TOS ADD,    \ make char in both bytes of TOS 
    
   *SP+ R0 MOV,
   *SP+ W  MOV,
    BEGIN,
      TOS *W+ MOV,  \ 2 chars are in TOS register
      R0 DECT,      \ decr. count by two
    NC UNTIL,       \ loop until carry flag reset
    TOS POP,
    NEXT,
 ENDCODE
 
HEX
: SAMS.FILL ( addr len char --)
        >R  
        NEXTCHUNK 
        SWAP >REAL SWAP  R@ FILL  \ handle 1st chunk 
        BEGIN
            DUP FFF U> 
        WHILE     
            NEXTCHUNK       \ LENGTH always = >1000 
            SWAP >REAL SWAP R@ FILLW  
        REPEAT 
        ABS SWAP >REAL SWAP R> FILL 
;

 

 

Here is the timing result for 64K bytes filled in SAMS memory. :) 

 

 

 

FAST SAMS 64K fill 2024-08-04 3_40_36 PM.png

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

Posted (edited)

Green software...

 

While working with this thing a bit more and trying to extend what I learned to MOVE, I discovered that I needed to detect the condition when a fill does not cross a page boundary. 

So another definition call CROSSING?  and some re-naming to save bytes and the final SAMS.FILL looks like this: 

 

I am discovering that testing the possible variations for ( addr addr len ) MOVE  is going to be a pain unless I get a magic insight. 

 

 

HEX 
1000   CONSTANT 4K

\ compute start of next SAMS page boundary ( 1000,2000,3000 ETC)
: BOUNDARY ( addr -- addr addr') F000 AND   4K + ;

\ test (address,len) crosses a page boundary 
: CROSSING? ( addr len -- addr len ?) OVER BOUNDARY -ROT +  U< ;

: CHUNK  ( addr len -- addr len addr1 n1) 
        OVER  DUP BOUNDARY  OVER -  1FFF AND ;

: NEXTCHUNK   ( addr len -- addr len addr len')  
        CHUNK  DUP>R               \ compute chunk and save length 
        2SWAP  R> /STRING  2SWAP   \ reduce size of data by length 
; 

\ fast 16bit character fill 
CODE FILLW ( adr len char --)
    TOS R1 MOV,    \ dup char in R1 
    R1 SWPB,       \ shift to other byte  
    R1 TOS ADD,    \ make char in both bytes of TOS 

   *SP+ R0 MOV,
   *SP+ W  MOV,
    BEGIN,
      TOS *W+ MOV,  \ 2 chars are in TOS register
      R0 DECT,      \ decr. count by two
    NC UNTIL,       \ loop until carry flag reset
    TOS POP,
    NEXT,
 ENDCODE
 
: SAMS.MEM  ( addr len -- sams len) SWAP >REAL SWAP ; 

HEX
: SAMS.FILL ( Virtual len char --)
        >R  
\ No page crossing fill handler         
        2DUP CROSSING? 0= IF  SAMS.MEM R> FILL  EXIT THEN 

\ Page crossing handler      
        NEXTCHUNK 
        SAMS.MEM R@ FILL  \ handle 1st chunk 
        BEGIN
            DUP FFF U> 
        WHILE     
            NEXTCHUNK       \ LENGTH always = >1000 
            SAMS.MEM R@ FILLW  
        REPEAT 
        ABS SAMS.MEM R> FILL 
;

 

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

Conclusion after this effort is that SAMS is still pain to use. :)

 

After getting stomped by all the corner cases I eventually ended up with this final code, which works but seems way to complicated for a FILL routine. 

 

It was a good learning process but when I looked at  doing the same to CMOVE it got pretty crazy. 

It might be easier using variables or values but It would still involve a lot of incremental moves since the source and destination could be anything. 


"Doctor it hurts when I do this"   "Well then don't do that!"

 

I am going to regroup.  I have a clean interface with cell and byte access for fetch and store and a clean way to convert a virtual address to a real address. 

I think I need to use SAMS in chunks of 1K (like BLOCK) or  power of 2 byte sized chunks. Doing that makes it simple to avoid page crossing moves and fills. 

 

And I think it makes more sense to do the virtual address to real address conversions where you need them using >REAL, rather than tons of stack gyrations to make is look prettier.

More to come... ;)

 

 

Spoiler
\ SAMS.FILL experiment
HEX 
1000   CONSTANT 4K
: SAMS.MEM  ( addr len -- sams len) SWAP >REAL SWAP ; 
: BOUNDARY  ( addr -- addr addr') F000 AND   4K + ;
: CROSSING? ( addr len -- addr len ?) OVER BOUNDARY -ROT +  U< ;
: CHUNK1    ( a u -- a u  a' u')  OVER DUP BOUNDARY  OVER -  ; 
: /CHUNK    ( a u a1 u1 -- a' u' a1 u1) DUP>R 2SWAP  R> /STRING 2SWAP ;  
: 1STCHUNK  ( a1 len -- addr len addr1 n1) CHUNK1 /CHUNK ;  

\ used ONLY on even page boundaries 
: NEXTCHUNK ( addr len -- addr' len' addr len') 
    2DUP  BOUNDARY 1000 UMIN  /CHUNK ; 

\ debug tool 
\ : .OUTPUT  ." //" SWAP . . CR ;
\ : SNIFF    2DUP .OUTPUT ;

\ fast 16bit fill 
HEX
CODE FILLW ( adr len char --)
    TOS R1 MOV,    \ dup char in R1 
    R1 SWPB,       \ shift to other byte  
    R1 TOS ADD,    \ char in both bytes of TOS 

   *SP+ R0 MOV,
   *SP+ W  MOV,
    BEGIN,
        R0 DECT,      \ decr. count by two
    OC WHILE,
        TOS *W+ MOV,  \ 2 chars are in TOS register
    REPEAT,           \ loop until carry flag reset
    TOS POP,
    NEXT,
 ENDCODE

\ a little protection 
: ?64K ( addr len -- ?)  0 SWAP 0 D+ NIP ABORT" SAMS: >64K" ;

HEX
: SAMS.FILL ( Virtual len char --)
    >R  
    2DUP ?64K
    OVER EFFF U> >R           \ last page? 
    2DUP CROSSING? 0=         \ NOT crossing pages  
    R> OR                     \ either condition true   
    IF  
       SAMS.MEM R> FILL EXIT  \ simple fill and get out 
    THEN 
\ multi-page fill 
    1STCHUNK SAMS.MEM R@ FILL 
    BEGIN DUP FFF U> 
    WHILE NEXTCHUNK SAMS.MEM R@ FILLW 
    REPEAT
    SAMS.MEM R> FILL  \ leftover bytes 
;

: SAMS.ERASE   0 SAMS.FILL ;

 

 

  • Like 1
Link to comment
Share on other sites

OK.  I have the beginning of something useful for holding compact string data in SAMS pages and loading  them from a file. 

 

I played with a number of options and the best I could come up with was:

  1. Always use "virtual addresses" ( >0000..>FFFF) and only convert to "real" addresses (ie: map in a page) when you want to read or write something into SAMS memory
  2. Place counted strings in SAMS starting at virtual address 0 and load strings until there is not room in the 4K page, for a full text record of 80 bytes + the count 
  3. When the SAMS page is "full" per #2, advance to the next page boundary.  This is quite efficient using AND and +

 

It means that as you are loading strings you must test for the boundary condition, but it seems pretty quick.

 

To read the string from SAMS we do the same as with normal RAM and use the count-byte of the string to compute the jump to the next string.

The only different is now when we advance, we check for a boundary and if the page is full per the previous method, we jump to the next SAMS page. 

 

All of this is in the pursuit of making it possible to edit larger files in VI99 or other editors without wasting tons of memory by using 80 byte records in SAMS.

The hard part will be doing insertion and deletions into this mess but it should be possible. 

 

The video has a 284 line file being loaded and partially printed. 

 

Spoiler
\ HEAPSTR-SAMS.FTH loads dv80 into SAMS segment Aug 13 2024 Brian Fox
\ Very compact string storage
\ Loads file data as counted strings in a SAMS segment

NEEDS DUMP      FROM DSK1.TOOLS 
NEEDS READ-LINE FROM DSK1.ANSFILES
NEEDS >REAL     FROM DSK1.SAMSPROG 

\ HERE

\ ===[ virtual SAMS.HEAHEREnagement ]====
HEX
1000 CONSTANT 4K 
8000 CONSTANT SAMS.SIZE 


DECIMAL
\ select the segment and return the start of virtual memory 
: TXTBUFFER ( -- ) 1 SEGMENT ;  

: ?TXTMEM  ( n --) SAMS.SIZE 0 WITHIN ABORT" SAMS.ALLOT err" ;

\ sams dictionary pointer holds a "virtual address" in a SAMS segment 
VARIABLE SDP   

: SAMS.HERE   SDP @ ;
: SAMS.ALLOT ( n --) SDP 2DUP @ + ?TXTMEM  +! ;
: SAMS,      ( n --) SAMS.HERE  !L   2 SAMS.ALLOT ;
: SAMSC,     ( c --) SAMS.HERE C!L   1 SAMS.ALLOT ;
: SAMS.ALIGN ( -- )  SAMS.HERE ALIGNED SDP ! ;

\  fill 4k pages at a time. *NO PROTECTION* 
: SAMS.FILL  ( addr len char --)
    >R 
    BOUNDS 
    BEGIN 
      2DUP <>
    WHILE    
      DUP >REAL 4K R@ FILL 
      4K +
    REPEAT
    R> DROP 
    2DROP 
; 

\ purge erases all pages, resets line counter
HEX 
: PURGE  ( -- ) 
    SDP OFF 
    0 SAMS.SIZE  0 SAMS.FILL 
    LINES OFF 
    0 SAMSC, ;  \ 0th string is null 

HEX 
\ compute the address of the next page boundary after address
: BOUNDARY  ( addr -- addr') F000 AND  4K + ;

\ test if page can hold one more 80 char line + count byte (aligned)
: PAGEFULL? ( Virtaddr -- ?)  0FFF AND  [ 4K 52 - ] LITERAL > ; 

\ forces SAMS memory pointer to even 4K boundary if full
: ?BOUNDARY  SAMS.HERE PAGEFULL? IF  SAMS.HERE BOUNDARY SDP !  THEN ;

: SAMS.PLACE  ( src n sams -- ) >REAL PLACE ;

\ compile RAM string as a counted string into SAMS segment 
: SAMS.S,    ( c-addr u -- )  
    1 MAX ?BOUNDARY 
    SAMS.HERE OVER 1+ SAMS.ALLOT SAMS.PLACE ;


DECIMAL
\ copy VDP string into SAMS segment as a counted string 
: SAMS.VPLACE ( VDP n sams -- ) >REAL 2DUP C! 1+ SWAP VREAD ;


\ compile VDP string as counted string into SAMS segment 
: SAMS.VS,  ( Vaddr u -- ) 
      1 MAX ?BOUNDARY 
      SAMS.HERE OVER 1+ SAMS.ALLOT SAMS.VPLACE ;


\ return the VDP address and length of the record in the PAB 
: FDATA  ( -- Vaddr len) [PAB FBUFF] V@  [PAB CHARS] VC@ ;

\ ----------------------------
\ user API ...
\ usage:  S" DSK1.MYFILE" READ-FILE
: READ-FILE ( addr len -- )
        DV80 R/O OPEN-FILE ?FILERR >R  \ file handle to rstack 

        PURGE  LINES OFF
        BEGIN
          2 FILEOP 0= \ faster than ANS read-line
        WHILE
          FDATA SAMS.VS,
          LINES 1+!
        REPEAT
        R> CLOSE-FILE ?FILERR 
;

\ advance virtual address by the length of the string +1 (for count byte) 
: >NEXT  ( Virtaddr -- Virtaddr') DUP C@L + 1+ ;
: SAMS.NEXT$ ( Virtaddr -- Virtaddr') >NEXT DUP PAGEFULL? IF BOUNDARY THEN ;

DECIMAL 
\ seek Nth string in SAMS buffer using string lengths to jump 
: NTH ( virtaddr n -- virtaddr')  0 ?DO  SAMS.NEXT$  LOOP ; 

\ ------------
\ debugging 
: .SAMS$  ( VIRT -- Virt ) DUP >REAL COUNT TYPE ; 
: LISTALL ( -- )
    0    ( start of buffer )
    BEGIN 
       CR .SAMS$
       SAMS.NEXT$ 
       ?TERMINAL ABORT" halt" 
       DUP C@L WHILE 
    REPEAT 
;    

\ usage:  S" DSK1.MYFILE" WRITE-FILE
: WRITE-FILE ( addr len -- )
        DV80 R/W OPEN-FILE ?FILERR >R
        1   \ text starts at byte 1
        BEGIN
          DUP C@L 
        WHILE
          DUP >REAL COUNT \ 2DUP CR TYPE 
          R@ WRITE-LINE ?FILERR
          SAMS.NEXT$
        REPEAT
        DROP
        R> CLOSE-FILE DROP ;

 

 

 

 

  • Like 3
Link to comment
Share on other sites

Posted (edited)

Newest Kernel and SAMSPROG files.

 

In case anybody actually plays with this system besides me, :)  here is what I am running these days in both the high-ram version and the super-cart version. 

Put the TI-99 files on your CAMEL99 system disk and mount in DSK1. 

  • CAMEL99 is the high RAM kernel 
  • CAML99SC  is the super cart kernel that gives you and extra EIGHT THOUSAND BYTES of extra dictionary space 
  • SAMSINI is common code to all my SAMS experiments now. SAMSPROG needs it 
  • SAMPROG is a SAMS virtual memory system the uses a separate workspace. Registers are used instead of variables
  • START is loaded and compiled when the kernel COLD boots. (or you type CODE)  It loads a font file. If you don't want comment out those lines. 
  • SYSTEM adds some Forth words to make the KERNEL have the CORE standard Forth dictionary (Don't edit that without making a copy)  :)

 

SAMSPROG  lets you do the nifty conversion from a virtual address in a 64K "segment" of SAMS memory, to a "real" address in low RAM with the word >REAL.

It also lets you access memory in the active SAMS segment with "long" versions of  fetch and store called   @L  !L  C@L  C!L  

These words are all sub-programs called by BLWP.

 

ALL of low RAM is used for two 4K "windows" and the nifty code will always put the SAMS page into the opposite window of the one you last used.

This way you can copy and move (with caveats) from one place in SAMS to another place in SAMS without copying into another buffer. 

The current SAMSPROG loads the ASM9900 assembler, TOOLS,  ELAPSE, the stuff I used for development. 

I need to make a machine code version but I want to make SAMPROG do something useful first to find bugs.

 

Edit removed kernel programs with non-compliant 2>R 2R> 

SAMSPROG SAMSINI START SYSTEM

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

Posted (edited)

Mea culpa.

 

I am trying to make something work with linked lists. (more to come) 

I found a little package at forthstandard.org that looked interesting.

 

It uses 2>R  and then picks off an item with R>.   

 

My 2>R put things on the return stack upside down. :(  Oopsie. 

 

To that one very special person who downloaded the new kernels, please replace your kernels with these two versions.

 

CAMEL99 CAML99SC

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

Comparing 9900 code to Intel. 

 

In the course of fixing 2>R and 2R>  I took a look at how GForth did it. Oh my goodness.

I didn't even try to understand it but look at how many instructions were used.

SEE 2>R
Code 2>r
( $403188 )  mov     dword ptr 41F2D8 , ebp 
( $40318E )  mov     edx , dword ptr 41F2E8
( $403194 )  mov     esi , dword ptr 4 [edi] 
( $403197 )  mov     dword ptr AC [esp] , esi
( $40319E )  mov     ecx , dword ptr [edi]
( $4031A0 )  lea     eax , dword ptr F8 [edx]  
( $4031A3 )  mov     41F2E8 ,  eax
( $4031A8 )  mov     dword ptr A8 [esp] , ecx  
( $4031AF )  mov     dword ptr FC [edx] , esi  
( $4031B2 )  add     edi , # 8 
( $4031B5 )  mov     eax , 41F2E8
( $4031BA )  mov     dword ptr [eax] , ecx  
( $4031BC )  mov     eax , dword ptr 0 [ebp] 
( $4031BF )  add     ebp , # 4 
( $4031C2 )  mov     esi , eax  \ $89 $C6
( $4031C4 )  mov     ebx , dword ptr [eax]
( $4031C6 )  jmp     40152A
end-code
SEE 2R>
Code 2r>
( $4031CB )  mov     dword ptr 41F2D8 , ebp
( $4031D1 )  mov     eax , 41F2E8  
( $4031D6 )  sub     edi , # 8 
( $4031D9 )  mov     edx , dword ptr 4 [eax]
( $4031DC )  mov     dword ptr A4 [esp] , edx
( $4031E3 )  mov     edx , dword ptr [eax] 
( $4031E5 )  mov     ecx , dword ptr A4 [esp]
( $4031EC )  add     eax , # 8 
( $4031EF )  mov     dword ptr A0 [esp] , edx
( $4031F6 )  mov     41F2E8 ,  eax 
( $4031FB )  mov     dword ptr 4 [edi] , ecx
( $4031FE )  mov     dword ptr [edi] , edx 
( $403200 )  mov     eax , dword ptr 0 [ebp]
( $403203 )  add     ebp , # 4  
( $403206 )  mov     esi , eax 
( $403208 )  mov     ebx , dword ptr [eax] 
( $40320A )  jmp     40152A 
end-code

 

Here are my versions (correct now) in lovely 9900 Forth Assembler 

CODE 2>R    ( d -- ) ( r-- n n)
      RP -4 ADDI, 
      TOS    *RP MOV, 
      *SP+ 2 (RP) MOV,
      TOS POP, 
      NEXT,
ENDCODE

CODE 2R>     ( -- d )
      TOS PUSH,
      SP DECT, 
      TOS RPOP,
      *SP  RPOP, 
      NEXT, 
ENDCODE

 

  • Like 3
Link to comment
Share on other sites

So I have gone down the Rabbit Hole on learning about the old VI editor. 

I read that it uses a swapfile so that you can edit a file larger than memory. 

I tried making an editor like that using a block file. It worked but was not practical when you tried to delete and insert lines. 

And even the way I made VI99 will bog down if have 7K+ of text. 

 

So... I am experimenting using a linked list that holds a record number in a 64K block file.

The goal is to make something that works even on a Floppy drive based TI99. 

 

I found my old files that make a LISP style pool of link cells and I had to relearn how they worked.

This is from a "Mini-LISP" written by Martin Tracy in 1985, but I need an entire LISP so I had to "Forthify" it. 

Using a few years more experience I made it work a bit better.

 

The concept for the editor is the file records will live on disk as counted strings with 4 - 1K RAM buffers.

32K of file is for editing and 32K starting from the other end is HEAP of records for insertions.

 

The link pool will support 800 links.  The records are connected via the linked list.

When you insert a line you get a line from the disk file, get it a link and then insert that link into the list. 

Deletions will mark the link free and set the record count on disk to zero. 

 

 

The problem I have is I don't have a method yet for reclaiming links from the pool that I don't need.

I have never created a garbage collector before... so that could take awhile. :)  

 

The LISP approach may be to heavy and I could make simpler linked list system but the LISP system kind of amazing. 

 

Here is the working lisp code.  It takes some grokking to figure this thing out. 

\ static lists in Forth                           Updated: Mar 10 2022
\ based on code by Martin J Tracy  Nov 17 1985

NEEDS DUMP      FROM DSK1.TOOLS
NEEDS MARKER    FROM DSK1.MARKER
NEEDS MALLOC    FROM DSK1.MALLOC

MARKER /LISTS

\ utility words
: ERASE  ( addr len -- ) 0 FILL ;
: CELLS+ ( n -- n')  CELLS + ;
: NOT    ( n -- ? ) 0= ;
: .$    ( addr -- ) COUNT TYPE ;

\ lists are two cell structure with a pointer to the next item
\ and a pointer  to the data
\
\  [link],[^data]
\ For historical reasons the link is called the CDR
\ The data pointer is called the CAR


\ There we can create an "NIL" list in Forth like this
\            cdr   car
\           ----- -----
CREATE NIL   NIL , NIL ,

\ extracts the value pointer of the first link of a list
: CAR  ( list -- ^value) CELL+ @  ;

\ extracts the next link of a list (follows the list)
: CDR  ( list -- list')  @ ;

: CADR  ( list -- next-item)  CDR CAR ;


\ returns true if list is NIL (empty)
: NULL  ( list -- f) NIL = ;

\  Allocate a Pool of links
DECIMAL
            1000 CONSTANT #LINKS    \ 1000 links for now.
         2 CELLS CONSTANT CONSIZE   \ size of 1 CONS structure
#LINKS CONSIZE * CONSTANT POOLSIZE

\ CONS cells are allocated in the HEAP as "pool"
 POOLSIZE MALLOC CONSTANT POOL

VARIABLE FREE-CONS         \ points to an available link

\ Create pool of CONS cells as one list of "FREE-CONSs"
: NEW-LINKS  ( --)
     POOL  POOLSIZE ERASE
     POOL
     #LINKS 1- 0           \ all links but last
     DO
        DUP CONSIZE + OVER ! \ make cons cell
        CONSIZE +          \ bump address to next cell
     LOOP
     NIL SWAP !            \ last link is NIL
     POOL FREE-CONS !  ;  \ set FREE-CONS to start of CONS memory

  NEW-LINKS          \ init the POOL array now

: ?MEMFULL  ( addr --) NIL = ABORT" () No free links" ;

: NEW-CELL  ( -- cell) \ allocate a free cell and return its address
     FREE-CONS @ DUP ?MEMFULL
     DUP CDR FREE-CONS ! ;

\ CONS is the fundamental LIST contructor
\ add a link whose CAR (1st list item)  points to the value
: CONS  ( ^value list -- list') NEW-CELL DUP>R  2!  R>  ;

\ =================================================================
\ list validation. (too complicated?)
DECIMAL
: UWITHIN  ( address low-bound high-bound -- ?)
   >R OVER  R> U< -ROT  U< NOT AND  ;

: INHEAP? ( addr -- ?) POOL [ POOL POOLSIZE + ] LITERAL UWITHIN ;

: ALIST?  ( addr -- ?)
\ true if the address argument is a list or NIL
  DUP INHEAP? IF  CDR 0 <>  ELSE  NULL  THEN  ;

: ?LIST ( list -- list) DUP ALIST? NOT ABORT" Not a list" ;
: ?NIL  ( list -- list)  ?LIST DUP NIL = IF ." (NIL)" CR ABORT THEN ;

\ =======================[ list operations ] =======================

: NTH  ( list n -- list) 0 ?DO  CDR  LOOP ;

: LENGTH  ( list -- n)  \ finds the number of items in a list.
   0 ( accumulator)
   BEGIN 
      OVER NIL <>
   WHILE
     1+
     SWAP CDR SWAP
   REPEAT
   NIP ;

\ perform XT on every item in the list 
: MAP  ( list2 xt) 
   >R 
   ?LIST 
   BEGIN
      DUP NIL <>
   WHILE
      DUP CAR R@ EXECUTE 
      CDR
   REPEAT
   R> 2DROP 
;

: '('      ." ( " ;
: ')'      ."  )"; 

: .LIST    ( list --)  '('   ['] .   MAP  ')' ;
: U.LIST   ( list -- ) '('   ['] U.  MAP  ')' ;

: REVERSE  ( list -- new-list)
   ?NIL
   NIL  SWAP
   BEGIN
     DUP NIL <>
   WHILE
     DUP  CAR  ROT CONS   ( create a new list with CONS)
     SWAP CDR
   REPEAT
   DROP
;

: LAST  ( list -- list') \ returns last link of given list as a list.
   ?NIL 
   BEGIN 
      DUP CDR NIL <>
   WHILE
      CDR
   REPEAT
;

: APPEND  ( list1 list2 -- list1+list2)
\ appends two lists.  The originals are "surgically" joined
   OVER ?LIST  OVER ?LIST
   DUP NIL <>
   IF OVER NIL = 
      IF  NIP  
      ELSE  OVER LAST ! NIP NIP 
      THEN 
   ELSE    
      DROP    
   THEN 

;

: LERASE ( list -- ) \ erases, but does not return links to pool... YET
   BEGIN
      DUP CDR NIL <>
   WHILE
      DUP CAR OFF
      CDR
   REPEAT
   CAR OFF ;

\ LIST-VARS are Forth variables that hold a list
\ Access them with @ and !
: LIST-VAR     CREATE   NIL , ;

\ add an item to the start of a list 
: !!  ( item listvar -- ) DUP>R  @ CONS  R> ! ;

: #ITEMS ( - n)  CSP @ SP@ - 2/ 1- ; 

: {  ( -- ) !CSP ;
: }  ( -- nil n) #ITEMS NIL SWAP 0 ?DO CONS LOOP ;

 

And here is how it can be used

\  Manually Construct a list with CONS and a list "variable"
LIST-VAR LIST1 
 1 2 3  NIL CONS CONS CONS  LIST1 !

\ make a list using curly brackets 
LIST-VAR LIST2 
{ 4 5 6 7 8 9 } LIST2 ! 

0 LIST1 !! 

LIST1 @ .LIST 

LIST1 @  LAST .LIST

LIST2 @ .LIST 

LIST2 @ REVERSE .LIST 

LIST1 @ LIST2 @ APPEND .LIST 

 

Link to comment
Share on other sites

Something I always wanted for myself was a command to go back to the E/A menu rather than to the title screen.

Since I have yet to make an text-file editor that I am totally happy with, this makes it much easier to use the E/A editor or Fred's improved editor as you choose. 

 

So here are couple of "scripts" than make a more complete version of Forth; more of a development version we could say. 

We throw in the Assembler + labels, the TOOLS and add DIR CAT and MORE (to see text file content).

 

And we change the BYE command so it returns to the E/A menu. 

 

For that one lonely person out there who tries this system, maybe this will make life a little less painful.

 

Here are two scripts.  One that builds on the regular kernel and the 2nd builds on the Supercart Kernel. 

I save the binary files off to DSK7 on my Classic99 setup but you can change that to where you want it to go. 

 

I have also attached the working binary files so you can use them right away. 

Spoiler
\ DEVSYS.fth   Builds a big system on Camel99  Aug 2024 Brian Fox

CR .( compiling development tools )
\ Load what you need here
INCLUDE DSK1.MARKER 
INCLUDE DSK1.WORDLISTS
INCLUDE DSK1.ANSFILES

HEX
 : GRAPHICS  ( -- )
  1 VMODE !    
  0 3C0  0 VFILL 
  00  2 VWTR  
  E0 DUP 83D4 C!  1 VWTR 
  0E 3 VWTR   01 4 VWTR   06 5 VWTR 
  01 6 VWTR   0 380 10 10 VFILL 
  8 7 VWTR    20 C/L!   
  PAGE
;

\             83E0 LWPI,     0070 @@ B, 
CODE MENU   02E0 , 83E0 ,   0460 , 006A , ENDCODE

\ BYE will now return to E/A menu, TI-99 GPL System
: BYE   GRAPHICS MENU ;

DECIMAL
VOCABULARY TOOLS
VOCABULARY ASSEMBLER

ONLY FORTH ALSO TOOLS DEFINITIONS
INCLUDE DSK1.TOOLS
INCLUDE DSK1.ELAPSE
\ INCLUDE DSK1.CATALOG
INCLUDE DSK1.DIR
INCLUDE DSK1.MORE

ONLY FORTH ALSO TOOLS ALSO ASSEMBLER DEFINITIONS
INCLUDE DSK1.ASM9900
INCLUDE DSK1.ASMLABELS


ONLY FORTH ALSO TOOLS DEFINITIONS ALSO FORTH DEFINITIONS
: NEWBOOT
  WARM  INIT-WORDLISTS
  ONLY FORTH ALSO TOOLS ALSO ASSEMBLER ALSO FORTH DEFINITIONS
  CR CR ." Camel99 Development System." CR  .FREE  ABORT ;

LOCK
INCLUDE DSK1.SAVESYS

' NEWBOOT SAVESYS DSK7.CAMEL99DEV

 

 

Supercart version 

Spoiler
\ DEVSYS.fth  build a big system on SUPERCART Forth  Nov 2022 Brian Fox

INCLUDE DSK1.MARKER

MARKER KERNEL   
HEX
: ?SUPERCART  ['] ;  A000 U> ABORT" Must use Camel99 SuperCART Forth" ;
  ?SUPERCART ( Test we are running in SuperCart memory now )

KERNEL  \ removes ?SUPERCART test code  

\ Load what you need here
INCLUDE DSK1.WORDLISTS
INCLUDE DSK1.ANSFILES

HEX
 : GRAPHICS  ( -- )
  1 VMODE !    0 3C0  0 VFILL 
  00  2 VWTR  
  E0 DUP 83D4 C!  1 VWTR 
  0E 3 VWTR   01 4 VWTR   06 5 VWTR 
  01 6 VWTR   0 380 10 10 VFILL 
  8 7 VWTR    20 C/L!   
  PAGE
;

\             83E0 LWPI,     0070 @@ B, 
CODE MENU   02E0 , 83E0 ,   0460 , 006A , ENDCODE

\ BYE will now return to E/A menu, TI-99 GPL System
: BYE   GRAPHICS  MENU ;

VOCABULARY TOOLS
VOCABULARY ASSEMBLER

ONLY FORTH ALSO TOOLS DEFINITIONS
INCLUDE DSK1.TOOLS
INCLUDE DSK1.ELAPSE
INCLUDE DSK1.CATALOG
INCLUDE DSK1.DIR
INCLUDE DSK1.MORE

ONLY FORTH  ALSO ASSEMBLER DEFINITIONS 
INCLUDE DSK1.ASM9900
INCLUDE DSK1.ASMLABELS  \ for label based jumping 

ONLY FORTH ALSO TOOLS DEFINITIONS ALSO FORTH DEFINITIONS
: NEWBOOT
  WARM  INIT-WORDLISTS
  ONLY FORTH ALSO TOOLS ALSO ASSEMBLER ALSO FORTH DEFINITIONS
  CR CR ." Camel99 SuperCart Development System." CR  .FREE  ABORT ;

LOCK
INCLUDE DSK1.SUPERSAVE

' NEWBOOT SUPERSAVE DSK7.SUPERFORTH

 

 

A quick look at how it works:

 

 

 

SUPERFORTH SUPERFORTI CAMEL99DEV CAMEL99DEW

  • Like 3
Link to comment
Share on other sites

4 hours ago, TheBF said:

Something I always wanted for myself was a command to go back to the E/A menu rather than to the title screen.

Since I have yet to make an text-file editor that I am totally happy with, this makes it much easier to use the E/A editor or Fred's improved editor as you choose. 

 

So here are couple of "scripts" than make a more complete version of Forth; more of a development version we could say. 

We throw in the Assembler + labels, the TOOLS and add DIR CAT and MORE (to see text file content).

 

And we change the BYE command so it returns to the E/A menu. 

 

For that one lonely person out there who tries this system, maybe this will make life a little less painful.

 

Here are two scripts.  One that builds on the regular kernel and the 2nd builds on the Supercart Kernel. 

I save the binary files off to DSK7 on my Classic99 setup but you can change that to where you want it to go. 

 

I have also attached the working binary files so you can use them right away. 

  Reveal hidden contents
\ DEVSYS.fth   Builds a big system on Camel99  Aug 2024 Brian Fox

CR .( compiling development tools )
\ Load what you need here
INCLUDE DSK1.MARKER 
INCLUDE DSK1.WORDLISTS
INCLUDE DSK1.ANSFILES

HEX
 : GRAPHICS  ( -- )
  1 VMODE !    
  0 3C0  0 VFILL 
  00  2 VWTR  
  E0 DUP 83D4 C!  1 VWTR 
  0E 3 VWTR   01 4 VWTR   06 5 VWTR 
  01 6 VWTR   0 380 10 10 VFILL 
  8 7 VWTR    20 C/L!   
  PAGE
;

\             83E0 LWPI,     0070 @@ B, 
CODE MENU   02E0 , 83E0 ,   0460 , 006A , ENDCODE

\ BYE will now return to E/A menu, TI-99 GPL System
: BYE   GRAPHICS MENU ;

DECIMAL
VOCABULARY TOOLS
VOCABULARY ASSEMBLER

ONLY FORTH ALSO TOOLS DEFINITIONS
INCLUDE DSK1.TOOLS
INCLUDE DSK1.ELAPSE
\ INCLUDE DSK1.CATALOG
INCLUDE DSK1.DIR
INCLUDE DSK1.MORE

ONLY FORTH ALSO TOOLS ALSO ASSEMBLER DEFINITIONS
INCLUDE DSK1.ASM9900
INCLUDE DSK1.ASMLABELS


ONLY FORTH ALSO TOOLS DEFINITIONS ALSO FORTH DEFINITIONS
: NEWBOOT
  WARM  INIT-WORDLISTS
  ONLY FORTH ALSO TOOLS ALSO ASSEMBLER ALSO FORTH DEFINITIONS
  CR CR ." Camel99 Development System." CR  .FREE  ABORT ;

LOCK
INCLUDE DSK1.SAVESYS

' NEWBOOT SAVESYS DSK7.CAMEL99DEV

 

 

Supercart version 

  Reveal hidden contents
\ DEVSYS.fth  build a big system on SUPERCART Forth  Nov 2022 Brian Fox

INCLUDE DSK1.MARKER

MARKER KERNEL   
HEX
: ?SUPERCART  ['] ;  A000 U> ABORT" Must use Camel99 SuperCART Forth" ;
  ?SUPERCART ( Test we are running in SuperCart memory now )

KERNEL  \ removes ?SUPERCART test code  

\ Load what you need here
INCLUDE DSK1.WORDLISTS
INCLUDE DSK1.ANSFILES

HEX
 : GRAPHICS  ( -- )
  1 VMODE !    0 3C0  0 VFILL 
  00  2 VWTR  
  E0 DUP 83D4 C!  1 VWTR 
  0E 3 VWTR   01 4 VWTR   06 5 VWTR 
  01 6 VWTR   0 380 10 10 VFILL 
  8 7 VWTR    20 C/L!   
  PAGE
;

\             83E0 LWPI,     0070 @@ B, 
CODE MENU   02E0 , 83E0 ,   0460 , 006A , ENDCODE

\ BYE will now return to E/A menu, TI-99 GPL System
: BYE   GRAPHICS  MENU ;

VOCABULARY TOOLS
VOCABULARY ASSEMBLER

ONLY FORTH ALSO TOOLS DEFINITIONS
INCLUDE DSK1.TOOLS
INCLUDE DSK1.ELAPSE
INCLUDE DSK1.CATALOG
INCLUDE DSK1.DIR
INCLUDE DSK1.MORE

ONLY FORTH  ALSO ASSEMBLER DEFINITIONS 
INCLUDE DSK1.ASM9900
INCLUDE DSK1.ASMLABELS  \ for label based jumping 

ONLY FORTH ALSO TOOLS DEFINITIONS ALSO FORTH DEFINITIONS
: NEWBOOT
  WARM  INIT-WORDLISTS
  ONLY FORTH ALSO TOOLS ALSO ASSEMBLER ALSO FORTH DEFINITIONS
  CR CR ." Camel99 SuperCart Development System." CR  .FREE  ABORT ;

LOCK
INCLUDE DSK1.SUPERSAVE

' NEWBOOT SUPERSAVE DSK7.SUPERFORTH

 

 

A quick look at how it works:

 

 

 

SUPERFORTH 8.13 kB · 1 download SUPERFORTI 7.88 kB · 0 downloads CAMEL99DEV 8.13 kB · 1 download CAMEL99DEW 7.13 kB · 0 downloads

I still got four super carts for sale

Link to comment
Share on other sites

It always take me longer than I expected but I re-wrote Eliza using this idea of making lists of executable code rather than data. 

 

In most languages you have to make Eliza with strings. And on some of the strings you need to put a magic character at the end that means Eliza will echo back your statement as a question in something called Rogerian analysis. (I think)

 

In the original LISP version this was not done with data but by executing LISP code.

So this Forth version also uses CODE not data to create the replies.

There is a word called <ROGERIAN>  and you just put that in the code definition that does the reply to the users input.

 

The code for <ROGERIAN> is

: <TOPIC>   INPUT$ COUNT /TAIL /CONJUGATE ;

 

This makes the reply code look like this: 

(EDIT: KEY words have been added to the responses. Much clearer to read) 

 

 KEY" CAN YOU"    
    { :: CR ." Don't you believe that I can " <TOPIC?> ;
      :: CR ." Perhaps you would like to be able to " <TOPIC?> ;
      :: CR ." You want me to be able to " <TOPIC?> ;
    } REPLY: CANYOU

    KEY" CAN I"      
    { :: CR ." Perhaps you don't want to " <TOPIC> '.' ;
      :: CR ." Do you want to be able to " <TOPIC?> ;
    } REPLY: CANI

    KEY" YOU ARE"    
    { :: CR ." What makes you think I am " <TOPIC?> ;
      :: CR ." Does it please you to believe that I am " <TOPIC?> ;
      :: CR ." Perhaps you would like to be " <TOPIC> '.' ;
      :: CR ." Do you sometimes wish you were " <TOPIC?> ;
    } REPLY: YOUARE

 

 

The final program loop looks like this :) 

: ELIZA    ( -- )
      DECIMAL
      .OPENING 
      GREETING
      BEGIN
        LISTEN FIND-KEYWORD REPLY
      AGAIN ;

 

 

I need to fix some bugs in the replies but it's a least a working program that I can fight with. 

 

Another thing different about this is that I did not use a "string package" with a string stack and all. 

Rather I used Forth "stack strings" along with SCAN and -TRAILING.

Stack strings are processed on the data stack as an address and length pair. 

This lets the normal data stack fulfil the role of the string stack which saves a lot space. 

 

The current  final state of the program is attached. 

I made use of my newish program saver that can save LOW RAM as a separate file for the E/A loader.

I put the reply code down there. 

 

Program source code

CAMEL99-ITC/DEMO/ELIZA/ELIZA3.2.FTH at master · bfox9900/CAMEL99-ITC · GitHub

 

Responses code in new format

CAMEL99-ITC/DEMO/ELIZA/ELIZADATA4.FTH at master · bfox9900/CAMEL99-ITC · GitHub

 

Edit: Removed very preliminary program files.  These are current 

 

ELIZA ELIZB ELIZC

  • Like 3
Link to comment
Share on other sites

I have ignored dealing with cartridges etc. for ten years so I thought it was time to start.

 

We always see this done in Assember, but "We don't need no stinking Assembler senor!"

Here is some Forth code to put a cartridge header in super cart RAM that points to a program that starts at >A000

\ GROM header in Forth   2024

\ This is just a bit trickier in Forth because we don't have
\ forward referencing. The solution is to use the data stack 
\ to record the address that needs to be filled later.

  HERE      \ remember current dictionary pointer 

  HEX  
  6000 DP ! \ set dictionary pointer to cartridge space 

  \ begin compiling the header into Super-Cart space
  AA C,     \ indicates a standard header
  01 C,     \ version number
  00 C,     \ number of programs (optional)
  00 C,     \ not used

  0000 ,    \ pointer to power-up list (can't use in cartridge rom)

\ Start of program chain
\ HERE records the  "pointer to program list" address on DATA stack. 
\ We will patch it later.
   HERE     \ remember this address  ( -- addr1 )
   0000 ,   \ ... and compile an empty pointer for now 

   0000 ,   \ pointer to dsr list (unused)

\ Start of subprogram list
   HERE     \ get the  "program chain record" address 
   0000 ,   \ ... and compile zero in it as well
   SWAP     \ reverse addresses for the store operator 
  ( -- addr2 addr1 ) !  \ Patch address2 into address1 
  
  A000 ,                 \ Entry point for program
  S" CAMEL99 FORTH" S,   \ Compile program name as a counted string 
  ALIGN                  \ align to even address

  DP !       \ restore original dictionary pointer    

 

The video shows something you have never seen before on the main menu. :) 

 

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

2 hours ago, TheBF said:

I have ignored dealing with cartridges etc. for ten years so I thought it was time to start.

 

We always see this done in Assember, but "We don't need no stinking Assembler senor!"

Here is some Forth code to put a cartridge header in super cart RAM that points to a program that starts at >A000

\ GROM header in Forth   2024

\ This is just a bit trickier in Forth because we don't have
\ forward referencing. The solution is to use the data stack 
\ to record the address that needs to be filled later.

  HERE      \ remember current dictionary pointer 

  HEX  
  6000 DP ! \ set dictionary pointer to cartridge space 

  \ begin compiling the header into Super-Cart space
  AA C,     \ indicates a standard header
  01 C,     \ version number
  00 C,     \ number of programs (optional)
  00 C,     \ not used

  0000 ,    \ pointer to power-up list (can't use in cartridge rom)

\ Start of program chain
\ HERE records the  "pointer to program list" address on DATA stack. 
\ We will patch it later.
   HERE     \ remember this address  ( -- addr1 )
   0000 ,   \ ... and compile an empty pointer for now 

   0000 ,   \ pointer to dsr list (unused)

\ Start of subprogram list
   HERE     \ get the  "program chain record" address 
   0000 ,   \ ... and compile zero in it as well
   SWAP     \ reverse addresses for the store operator 
  ( -- addr2 addr1 ) !  \ Patch address2 into address1 
  
  A000 ,                 \ Entry point for program
  S" CAMEL99 FORTH" S,   \ Compile program name as a counted string 
  ALIGN                  \ align to even address

  DP !       \ restore original dictionary pointer    

 

The video shows something you have never seen before on the main menu. :) 

 

 

And I assume that  ram disc OS could possibly be written with forth as well. Not that we need one but maybe something easier than what we do have, something a little more colorful or even 80 column for the people that have F18A

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

39 minutes ago, GDMike said:

And I assume that  ram disc OS could possibly be written with forth as well. Not that we need one but maybe something easier than what we do have, something a little more colorful or even 80 column for the people that have F18A

For sure.  If ram disc can be accessed as blocks, then you can build a DOS style file system pretty easily and the Forth interpreter is the shell ... for free. 

 

Give me a spec sheet :) 

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