Jump to content
TheBF

Camel99 Forth Information goes here

Recommended Posts

The question becomes how long will it take to find text using SEARCH.

I wrote a test suite (by re-purposing the MORE utility, that loads a text file into an 8K buffer as contiguous text

Then using the elapsed timer I sent SEARCH looking for one of the last words in the buffer. (at byte 5669)

The screen shot shows the timing. Spoiler has the test code.

Not really speedy but not bad for the old TI-99 using a brute force algorithm.

 

 
\ TEST SEARCH IN A LARGE BUFFER

INCLUDE DSK1.TOOLS
INCLUDE DSK1.ELAPSE 

NEEDS SEARCH     FROM DSK1.SEARCH
NEEDS OPEN-FILE  FROM DSK1.ANSFILES
NEEDS VALUES     FROM DSK1.VALUES

0 VALUE LINEBUFF

CREATE FILEBUFF 2000 ALLOT

\ buffer management
VARIABLE BP
: FALLOT     BP +! ;
: FHERE      FILEBUFF BP @ + ;
: FILEBUFF$!  ( addr n -- ) TUCK FHERE SWAP CMOVE  FALLOT ;
: BUFFC,     ( c -- ) FHERE C! 1 FALLOT ;
: FBSIZE     ( -- n ) FHERE FILEBUFF - ;

1A CONSTANT ^Z

DECIMAL
: LOADFILE ( <filename>)
    80 MALLOC TO LINEBUFF
    BL PARSE-WORD DUP ?FILE
    DV80 R/O OPEN-FILE ?FILERR >R
    BEGIN
      LINEBUFF DUP 50 R@ READ-LINE ?FILERR ( -- addr n ?)
    WHILE
    \ 2DUP CR TYPE
      FILEBUFF$!  ." ."
      ?TERMINAL
      IF
         R> CLOSE-FILE  2DROP CR ." HALTED" ABORT
      THEN
    REPEAT
    R> CLOSE-FILE
    ^Z BUFFC,        \ ^Z at the end of the text
    2DROP DROP
    80 MFREE
    CR FBSIZE .  ." bytes in buffer" ;

DECIMAL

 

 

FORTHSEARCHTEST.png

Edited by TheBF
Fixed spoiler
  • Like 2

Share this post


Link to post
Share on other sites
5 hours ago, TheBF said:

I have library file for COMPARE IN Forth, that is code published by a late pioneer of Forth named Neil Baud

(also know by the pseudonym Wil Baden)

 

That is a very clever use of COUNT:o I do not believe I ever would have thought of using it that way.

 

I also do not usually think of using EXIT in the middle of a definition in that manner, but I will be keeping it in mind in the future. Of course, if I want to use it in the middle of a DO loop in fbForth, I will need to define UNLOOP , which I presume is

: UNLOOP   R> R> DROP DROP  ;

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
9 minutes ago, Lee Stewart said:

 

That is a very clever use of COUNT:o I do not believe I ever would have thought of using it that way.

 

I also do not usually think of using EXIT in the middle of a definition in that manner, but I will be keeping it in mind in the future. Of course, if I want to use it in the middle of a DO loop in fbForth, I will need to define UNLOOP , which I presume is

: UNLOOP   R> R> DROP DROP  ;

 

...lee

Ya Neil was some smart cookie.  I noticed that too. I would probably never had used COUNT but would have made a new word that did the same thing. :) 

I beleive he was involved in some kind of text analysis of books and wrote his own tools.

You can see his style here:  http://www.wilbaden.com/neil_bawd/

 

I have only recently started to realize the EXIT and factoring can allow a modest form of "un-structuring" in Forth programs (as I did with (SRCH) allowing us to branch out of a loop and land inside another word. Some might call it cheating. 

 

 

 

  • Like 1

Share this post


Link to post
Share on other sites
13 minutes ago, TheBF said:

I have only recently started to realize the EXIT and factoring can allow a modest form of "un-structuring" in Forth programs (as I did with (SRCH) allowing us to branch out of a loop and land inside another word. Some might call it cheating. 

 

I do not see why. You are not actually branching into the middle of a word, you are terminating the current word early and landing where you would have with a normal completion, anyway. Maybe it is not intuitively obvious to the casual observer, but I certainly would not even think of calling it cheating. :)

 

...lee

  • Haha 1

Share this post


Link to post
Share on other sites

Not that you really need it but I made unloop in CODE and it is part of my DO LOOP system, but can be called separately as well.

I waffled on the big version or the smaller 1 instruction version.

CODE: UNLOOP
              RP  4 ADDI, ( *RP+ *RP+ CMP,) 
              NEXT,
              END-CODE

 

Edited by TheBF
Added the word You.
  • Like 1

Share this post


Link to post
Share on other sites
16 minutes ago, Lee Stewart said:

 

I do not see why. You are not actually branching into the middle of a word, you are terminating the current word early and landing where you would have with a normal completion, anyway. Maybe it is not intuitively obvious to the casual observer, but I certainly would not even think of calling it cheating. :)

 

...lee

By using a separate word and EXITing it is like I jumped over 1 /STRING and the REPEAT word which puts me back inside SEARCH. 

 

I have not built these "quotations" that I read about in comp.lang.forth but I believe they would allow me to do what I did without putting the word (SRCH) in the dictionary. Something like this:

HEX
: SEARCH ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 flag )
          100 DUP >R MALLOC TO SBUFF
       [: SBUFF PLACE    ( QUOTATION starts here)
          BEGIN
             DUP
          WHILE
             SBUFF COUNT
             2OVER SAMELEN COMPARE
             0= IF  EXIT THEN      \ NOW EXIT jumps to ;]
             1 /STRING
          REPEAT ;]
         DUP 0>
          R> MFREE
          0 TO SBUFF ;

Here it is clearer to see that I am doing a GOTO :) 

 

I may have to try and create these quotation things...

 

  • Like 1

Share this post


Link to post
Share on other sites
11 minutes ago, TheBF said:

Not that really need it but I made unloop in CODE and it is part of my DO LOOP system, but can be called separately as well.

I waffled on the big version or the smaller 1 instruction version.

 

The ADDI, is definitely faster than CMP, by 16 clock cycles and 3 memory accesses!  What was your more-than-1-instruction version?

 

...lee

Share this post


Link to post
Share on other sites
Just now, Lee Stewart said:

 

The ADDI, is definitely faster than CMP, by 16 clock cycles and 3 memory accesses!  What was your more-than-1-instruction version?

 

...lee

I mis-spoke, I should have said 2 CELLs vs 1 CELL version.

  • Like 1

Share this post


Link to post
Share on other sites

Just to round off the discussion of quotations, I found this document and I think the code is very possible for Camel Forth.

http://www.forth200x.org/quotations.txt

 

Implementation
--------------
It is not possible to define quotations in ISO Forth. The following
is an outline definition, where SAVE-DEFINITION-STATE and RESTORE-
DEFINITION-STATE require carnal knowledge of the system and are left
to the implementor.

: [: ( c: -- quotation-sys colon-sys )
  postpone ahead save-definition-state :noname ; immediate
  
: ;] ( c: quotation-sys colon-sys -- ) ( s: -- xt )
  postpone ; >r restore-definition-state
  postpone then r> postpone literal ; immediate

 

Share this post


Link to post
Share on other sites

OK, that makes my head hurt a little. I guess that construct somehow compiles what is within [: ... ;] as a word with no header such that the effect is the same as before, but saving the 10 bytes for a header in the process.

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
2 minutes ago, Lee Stewart said:

OK, that makes my head hurt a little. I guess that construct somehow compiles what is within [: ... ;] as a word with no header such that the effect is the same as before, but saving the 10 bytes for a header in the process.

 

...lee

Exactly.  And if you read the paper they talk about dealing RECURSE and DOES> and that makes my head hurt.  I would just make those two illegal along with ;CODE

:) 

 

Topic Shift

 

I was just looking at Neil's page after a long time away and found this:

\  The traditional definition for  (.)  is:

\ : (.)                            ( n -- str len )
\     dup ABS 0 <#  #S  ROT SIGN  #> ;

\  But we like to see TRUE and SIGN-BIT in hex as FFFFFFFF and
\  80000000, and also see bit masks as unsigned.  It would be nice to 
\  let the program do it.

\ : (.)                            ( n -- str len )
\     BASE @ 10 = IF  dup ABS  ELSE  0 SWAP  THEN
\     0 <#  #S  ROT SIGN  #> ;

\  Even nicer would be the following.  This helps in distinguishing
\  similar binary numbers.  Like 0FFFFFFF from FFFFFFFF and 08000000
\  from 80000000.

: (.)                             ( n -- str len )
    CASE BASE @
    10 OF  dup ABS 0 <# #S ROT SIGN #>                       ENDOF
    16 OF          0 <#  BEGIN  # #  2dup OR 0= UNTIL #>     ENDOF
     2 OF          0 <#  BEGIN  # # # #  2dup OR 0= UNTIL #> ENDOF
                   0 <#  #S #>
     0 ENDCASE ;

\  Of course we want with whichever one...

: .  ( n -- )  (.) TYPE SPACE ;
 

I think I am going to add the big version to my programmers tools file with .S and DUMP and the like.

I hate seeing signed HEX numbers and having to use U. all the time to print them when debugging stuff.

He also uses this to define .S 

 

 

  • Like 2

Share this post


Link to post
Share on other sites

After seeing the VDP I/O performance of FbForth in the sevens problem topic I decided to dig in on what I could do to improve text writing speeds while at the same time keeping the hi-level code in Forth for instructional purposes. I had a faster scroll in place but that was only part of the issue.  The Forth word TYPE was written in Forth and although  the word that wrote to the VDP memory (VC!) was Assembler,  Forth was used to update the column variable, detect the end of screen and then to call SCROLL.

I wanted to see how much things could move faster if I improved TYPE but I didn't want to re-write everything in Assembler because it probably would take more space and my 8K limit was looming.  How about removing setting up the address for each character and leverage the VDP hardware's auto-increment capability?

I gave Forth direct access to the set the VDPWA which disables interrupts and is a callable routine in the kernel.

Then the mate to it is VEMIT  which writes a character, increments the VCOL variable and returns the new VCOL. 

 

New Assembler routines: 

Note on STWP below. System (USER) variables are located below the workspace so that each task can have its own copy.

No. $34 is VCOL (video column) variable

\ *NEW*  For faster TYPE routine in Forth
CODE: VDPWA! ( Vaddr -- )
             TOS R0 MOV,
             WMODE @@ BL,
             TOS POP,
             NEXT,
             END-CODE

\ VEMIT writes to VDP address set by VDPWA! and updates VCOL user variable
\ VDP auto increments the address for faster character output
CODE: VEMIT ( char -- column')
             TOS SWPB,
             TOS VDPWD @@ MOVB,  \ write char to vdp data port
             R1     STWP,
             34 (R1) INC,        \ Bump  VCOL user variable
             34 (R1) TOS MOV,    \ fetch VCOL to TOS
             NEXT,
             END-CODE

By returning the VCOL value it can be passed to the conditional newline routine called ?CR. 

If VCOL >= chars-per-line ,  ?CR calls the newline routine CR. 

CR resets VCOL and increments VROW.

If VROW >= lines-per-screen it calls SCROLL.

With all this in place the new faster TYPE routine reduces to:

: CR   ( -- )
          PAUSE
          VCOL OFF  VROW 1+!
          VROW @ L/SCR 1- >  IF  SCROLL  THEN  ;

: ?CR    ( column -- ) C/[email protected] 1- > IF  CR  THEN ;

: TYPE   ( adr cnt -- ) VPOS VDPWA!  
         BOUNDS ?DO  I [email protected] VEMIT ?CR   LOOP ;

Using Lee's version of the Seven's Problem the slowest combination of SCROLL and TYPE ran in 1:25 on Camel99 Forth.

(FbForth time is:  0:53 seconds using the ELAPSE timer)

 

Using the fast scroll and and fast type the program ran in 1:06.  That's a 29% improvement. :)

I still have 50 bytes left in the kernel for an emergency change so V2.54 is going to be the current system.

 

I can't beat an I/O driver written with integrated Assembler scroll and newline but I got much closer.

 

Edit: I also had to add one line to SCROLL to set the VDPWA to the bottom line to make this all work.

 

 

 

Edited by TheBF
  • Like 1

Share this post


Link to post
Share on other sites

It was all fun and games trying to make a faster VDP I/O system for the sevens problem, but I forgot one little detail...

 

CAMEL99 Forth is supposed to Support multi-tasking. :)

 

So if you set the VDP write address, write a character and then change tasks and that new task does some screen I/O guess what happens?

Now I could control for that but forcing screen typing to hog the system until it completes everything including newlines and screen scrolls but that makes other tasks "lumpy" in the way they run so I have reverted. For a cooperative Forth tasker it's best that the fundamental I/O operation does a task switch so in this case I have a low level (EMIT) word that does the job.  It also shaved 42 bytes out the system by not having the faster TYPE routine.  I can live with it.

 

I did find one thing that makes a speedup and that is a word I call '[email protected]'   It increments a variable and fetches the new value in one word. This makes screen variable management for row and column quite a bite faster as the fastest way to do it in Forth is:

 

\ Standard Forth options
 1 VCOL +!  VCOL @ 
   VCOL 1 OVER +! @ 

\ Versus
 VCOL [email protected]  

Time to watch the Grey Cup, Canadian football final game. 

 

  • Like 1

Share this post


Link to post
Share on other sites

CAMEL99 Forth V2.5  Release

 

I am a poor administrator but I have finally committed to one version of the kernel program CAMEL99. It seems pretty stable now but I am the only one who has used it.

Attached is a ZIPped up version of DSK1. to make it simple to try it out. The biggest difference between CAMEL99 and the other TI-99 Forths out there is the that CAMEL99 attempts to be ANS/ISO compliant. Not a big deal for hobby coding but there it is.

 

If you want to try it mount the DSK1 in the ZIP file to Classic99, use Editor/Assembler cartridge and select E/A 5.  Program name is DSK1.CAMEL99.

"COLD" <enter> restarts the system.

 

Try INCLUDE DSK1.COOLSPRITE to compile a demo program with libraries.

(It takes 36 seconds to compile the approximately 480 lines of code at normal speed)

Type RUN to make it go. BREAK will stop it.

Type BYE to exit.

 

A slightly updated version of the instruction manual (mostly typo corrections) will be up there some time later today.

I realize Forth doesn't win popularity contests :) but if anybody wants to try it I am happy to answer any and all questions.

 

### Nov 28, 2019  V2.5
Indirect Threaded Version
- Settled on one build of CAMEL99 Forth. All variations are removed.
- 25% speed up of CREATE DOES> structures by using BRANCH & LINK instruction
- Fixed DSK1.ANSFILES file handle bug. Errors did not release current file handle.
- Improved VDP screen driver using [email protected] code word
- Improved DSK1.VALUES. Faster TO and +TO
- Cleaned up LIB.ITC. TI99 versions are in DSK1.
- Added DSK1.TRAILING. (-TRAILING -LEADING TRIM)
- Added DSK1.HEXNUMBER. H# is a prefix word to interpret numbers as radix 16.
- DSK1.TOOLS now includes VDUMP for VDP ram and SDUMP code for SAMS card.
  (HEX and BINARY numbers alway print unsigned after tools are loaded.)
- DSK1.CODEMACROS provides native 9900 indexed addressing arrays.
- DSK1.VTYPE improved VTYPE updates VCOL. AT" ( x,y) placing text.
- DSK1.AUTOMOTION provides Automatic sprite motion like Extended BASIC


### Known BUG
- When INCLUDE is used for a file on a disk other than DSK1, the library files will try to load from that same disk.  Investigating our FILESYSX for the problem.
- Temporary fix is to keep programs on disk one or load libraries manually before loading a program from DSK2 or DSK3.

 

DSK1.zip

Edited by TheBF
No bug when loading from DSK2. Not tested on hardware
  • Like 1

Share this post


Link to post
Share on other sites

I am reviewing, running and editing, when necessary, all the DEMO programs on GITHUB.

https://github.com/bfox9900/CAMEL99-V2/tree/master/DEMO

 

It looks like V2.5 is a better multi-tasking kernel.

I was able to run 43 total tasks. (42+console)  This consumes almost the entire lower 8k for the task blocks.

Version 2.x Would only do 30 tasks and then it would blow up.

 

Each task is just running this:

HEX 10 CONSTANT STKSIZE

VARIABLE X   \ used to test if tasks are running

: DROPS   ( n --)  0 DO DROP PAUSE LOOP ; \ drop items from the stack

: STKTHING   \ fill and clear data stack so it can be seen in debugger
          BEGIN
            STKSIZE 0 DO PAUSE DEAD  LOOP
            STKSIZE DROPS

            STKSIZE 0 DO PAUSE BEEF  LOOP
            STKSIZE DROPS
            1 X +!
          AGAIN ;

 

Share this post


Link to post
Share on other sites

On the outside chance that anyone cares... :)  there is an updated version of the Manual for CAMEL99 Forth.

https://github.com/bfox9900/CAMEL99-V2/blob/master/DOCS/Camel99 for TI-BASIC Programmers Rev1.9.pdf

 

Or if you would rather something smaller, there is a list of the library files with one liner of what they do. (Extracted from the Manual)

https://github.com/bfox9900/CAMEL99-V2/blob/master/DOCS/Camel99 Forth Library files.pdf

 

The most complete list of lib files in TI-99 format will always be here:

https://github.com/bfox9900/CAMEL99-V2/tree/master/DSK1.ITC

 

Some font files are here:

https://github.com/bfox9900/CAMEL99-V2/tree/master/DSK3

 

 

  • Like 1

Share this post


Link to post
Share on other sites

So here is something that I don't understand. (A common motif) :)

 

I went looking for a bug that I found just recently where I try to load a file on DSK3.

 

The file on DSK3. begins to load, but the file has "nested" statements that say INCLUDE DSK1.FILE#1,  INCLUDE DSK1.FILE#2 etc

Even though the statements specify DSK1 for the extra includes, the system goes looking for FILE#1 and FILE#2 on DSK3.  Hmm...

 

However when I put the identical file on DSK2. it begins to load and loads the nested includes from DSK1. as the statements in the file request.

 

The screen shot shows what I am trying to describe.  You can see the failure including DSK3.SAMSDEMO at the top.

Next I load DSK2.SAMSDEMO and it moves through the 5 lib files on DSK1 first before completing the load on DSK2.

 

Is there something different about DSK3 that makes it unique compared to DSK1 and DSK2 ?

 

 

dsk3nestedfilebug.png

Share this post


Link to post
Share on other sites

What da' ya' mean you want another stack?

 

Forth already has two stacks.  Why would you ever need another one?  Well a stack is a handy data structure. I am working on a demonstration of the shunting yard algorithm to convert infix math to rpn. For that I need to keep a stack for the brackets and operators.  I looked at some code from my very old HsForth system and modified it for Camel99 Forth.  I have not tried it on other TI-99 Forth systems but it should not be too hard to adapt.  You might need to add these definitions:

: [email protected] ( adr -- n1 n2 )   DUP 2+ @ SWAP @ ;
: CELL+  ( n -- n') 2+ ;

 

The word to create the data structure here is called LIFO:   ( last in first out) which is a fancy name for a stack. You might prefer the word stack.

LIFO:  creates 3 integer locations to manage the stack.  The PUSH and POP words have built in error detection which can save your bacon in a real program.

 

Spoiler removed until bugs are killed...

 

 

 

 

LIFODEMO.png

Edited by TheBF
  • Like 1

Share this post


Link to post
Share on other sites
2 hours ago, TheBF said:

The word to create the data structure here is called LIFO:   ( last in first out) which is a fancy name for a stack. You might prefer the word stack.

LIFO:  creates 3 integer locations to manage the stack.  The PUSH and POP words have built in error detection which can save your bacon in a real program.

 

2-! looks like a victim of cut-n-paste. It should read

: 2-!  ( addr -- ) DUP @ 2- SWAP ! ;

 

A couple of other items:

  • I think the comments for the first two items are confusing. The stored numbers are the number of bytes rather than the number of cells.
  • Does the stack pointer slot have a future purpose not obvious from the current code?

I definitely like the idea of a protected stack. Very nice!

 

...lee

Share this post


Link to post
Share on other sites

There are some big bugs here I made it work for for my other project and was happy to have a user stack. :)

 

Sincere apologies.  I will work it over.

 

 

Share this post


Link to post
Share on other sites
2 hours ago, TheBF said:

There are some big bugs here I made it work for for my other project and was happy to have a user stack. :)

 

Sincere apologies.  I will work it over.

 

Don’t mind me! Certainly no apologies are necessary. I was, mayhap, a bit too quick on the uptake. Part of this is just my wish to insure that we avoid confusing the host of passersby! |:)

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
23 hours ago, Lee Stewart said:

 

2-! looks like a victim of cut-n-paste. It should read

: 2-!  ( addr -- ) DUP @ 2- SWAP ! ;

 

A couple of other items:

  • I think the comments for the first two items are confusing. The stored numbers are the number of bytes rather than the number of cells.
  • Does the stack pointer slot have a future purpose not obvious from the current code?

I definitely like the idea of a protected stack. Very nice!

 

...lee

Fixed the bug with 2-!.  It was the real culprit. Thanks.

I fixed the comment changing cells to bytes.

 

I went back into the archives of old HsForth files, circa 1990ish, and discovered that the comment I used for the stack pointer was only in my re-write and that in fact it looks like Jim added an extra cell with a zero in it for unknown reasons. Perhaps it is a safety zone? 

 

 

  • Like 1

Share this post


Link to post
Share on other sites

Searching Faster

 

I revisited my search code and I had needlessly complicate my conversion of the S= ("string equals") code routine in Camel Forth to the ANS standard word COMPARE.

I then improved the definition of 2OVER by using  "4TH" a little routine borrowed from Neil Bauds toolbox that picks the 4th stack item and pushes onto the top of the stack.

Due to the 9900 instruction set this Forth command is the same speed as OVER. So  "4TH 4TH" makes a very fast 2OVER word.

 

For maximum speed I rolled these up as text macros. Arguably they should be state smart but since they will be embedded in an editor I am going to leave them as is.

 

Bottom Line:   This version does the same search of DSK1.ASM9900 in 4.48 seconds vs 7 seconds.

 
 \ search.fth   Forth 2012 word
\  INCLUDE DSK1.TOOLS
 NEEDS VALUE FROM DSK1.VALUES
 NEEDS 3RD   FROM DSK1.3RD4TH

\ S= is CAMEL Forth primitive
: COMPARE  ( a1 n1 a2 n2 -- -1|0|1 ) S" ROT MIN S=" EVALUATE ; IMMEDIATE
: 2OVER   ( d d2 -- d d2 d) S" 4TH 4TH" EVALUATE ; IMMEDIATE

0 VALUE SBUFF     \ temp buffer for search string
: (SRCH)  ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3)
          SBUFF PLACE
          BEGIN
             DUP
          WHILE
             SBUFF COUNT
             2OVER COMPARE
             0= IF EXIT THEN    ( jump to ';')
             1 /STRING
          REPEAT
;

HEX
: SEARCH ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 flag )
          100 DUP >R MALLOC TO SBUFF
         (SRCH) DUP 0>
          R> MFREE
          0 TO SBUFF ;

 

 

searchfaster.png

Edited by TheBF
  • Like 1

Share this post


Link to post
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.

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...