Jump to content
IGNORED

fbForth—TI Forth with File-based Block I/O [Post #1 UPDATED: 06/09/2023]


Lee Stewart

Recommended Posts

You want to add SGN at the end to get the sign of the value, ala TI BASIC. Not sure if fbForth has that as a built-in word. TF doesn't.

 

I define it as follows:

: SGN ( n -- -1|0|+1) dup 0< SWAP 0> - ;

 

Nice! I would have to write it

: SGN DUP 0< SWAP 0 > - ;

I came up with

: SGN DUP IF 1 SWAP +- THEN ;

and

: SGN DUP ABS / ;

The former is 22 bytes, the same size as yours; but, the latter is only 16 bytes, albeit depending on fbForth's "divide by 0" behavior of always leaving 0.

 

BTW, did you know that TurboForth does not leave 0, but -1, if the dividend is positive? Is that normal Forth-83 behavior?

 

...lee

  • Like 1
Link to comment
Share on other sites

: SGN DUP ABS / ;

...lee

 

Ooh! Swoon! That's genius! I tried it on TF (V1.2) and it works (used 9, -9 and 0 as test values).

 

 

 

BTW, did you know that TurboForth does not leave 0, but -1, if the dividend is positive? Is that normal Forth-83 behavior?

 

...lee

 

No idea. With resepct to which version of SGN?

Link to comment
Share on other sites

Good thing I made SGN part of the kernel. Now I can start reducing the size of the graphics primitives! A word called SGN is defined there that increases a negative or positive difference by 1. I think I'll rename it SNW for "wide SGN". Here is how TI defined it (with my new name):

*

: SNW DUP IF DUP 0< IF -1 ELSE 1 ENDIF ELSE 0 ENDIF + ;

*

I will define it thus:

*

: SNW DUP SGN + ;

*

...and now, back to the manual! :P

 

...lee

Link to comment
Share on other sites

Hrmph... Be advised that DUP ABS / fails with the maximum negative value (-32768).

 

:-(

 

I suspected that might be the case with TurboForth. In fbForth and TI Forth, however, the results of the ALC DIV instruction are not manipulated, but merely passed on to the Forth routine. ALC division by zero sets the overflow bit and leaves the destination registers alone, which means that whatever was in the left dividend register is still there as the quotient. Because the count byte of a string in fbF and TIF (even in TF as a count word of 2 bytes) won't spill into that left register, it starts and ends with 0. TF, I would guess, manipulates the overflow into reporting a quotient with all bits set to indicate the overflow.

 

...lee

Link to comment
Share on other sites

OK...Here's the current, overlap-safe version of CPYBLK

 

 

 

( >>>>> CPYBLK and support words definitions... <<<<< )

DECIMAL 
0 VARIABLE SFL 0 VARIABLE DFL   ( pointers to source and destination filename strings)           
: SCMP   ( str1 str2 --- -1|0|+1 )  
    OVER C@ OVER C@ OVER OVER - SGN >R   ( dup addresses, take diff; get sign & push to return stack)
    MIN 1+ 0 SWAP 1 DO  ( get min strlen; increment for limit; 0 flag start; swap with limit; start at 1)
        DROP            ( drop 0 flag)
        OVER I + C@     ( get next char of str1)
        OVER I + C@ - SGN  ( get next char of str2, take diff and get sign)
        DUP IF LEAVE THEN  ( dup it and leave loop if not equal)
    LOOP
    R> OVER 0=      ( pop cnt diff from return stack; copy loop result; see if 0)
    IF              ( it's 0, so OR with cnt diff for final answer)
        OR
    ELSE            ( it's not 0, so drop cnt diff )
        DROP
    THEN
    SWAP DROP SWAP DROP  ( get rid of leftover str1 and str2 pointers)
;
: GNUM BL WORD HERE NUMBER DROP ; ( get number from terminal)
( GBFL gets HERE to stack; stores filename at HERE; establishes new HERE;
  stores string address in variable passed on stack)
: GBFL ( addrvar -- )  HERE 0 BFLNAM SWAP !  ;  
0 CONSTANT XD  ( +1|-1 added to get next screen)
: CPYBLK    ( should we FLUSH before we start?)
    1 ' XD !    ( assume we're copying from low- to high-numbered blocks)
    HERE    ( save address where we'll copy current blocks filename; maybe use R)
    BPB BPOFF @ + 9 +     ( get address of blocks filename's char-count byte)
    DUP VSBR 1+         ( get count byte and increment it for copy count)
    HERE SWAP DUP =CELLS ALLOT VMBR  ( get current blocks name to HERE and move HERE past it)
    GNUM GNUM OVER - 1+ >R ( get start & end block#s; figure block count; count to return stack)        
    SFL GBFL            ( get source filename to HERE and store address, moving HERE)        
    GNUM                ( get destination start block#)        
    DFL GBFL            ( get destination filename to HERE and store address, moving HERE)                                
    SFL @ DFL @ SCMP 0= ( src, dst same file?)
    IF      ( yes...see if we need to change copy direction)
        OVER OVER - DUP     ( dup src & dst; take difference; dup it)
        ( need to copy in reverse direction only if overlap is low to high with cnt > overlap)
        0< SWAP R MINUS > + 2 =  
        IF      ( we're going from the top down)      
            SWAP R + 1- SWAP R + 1-  ( get highest src & dst screens)
            -1 ' XD !   ( we're decrementing the next screens)
        THEN
    THEN
    R> 0 DO         ( cnt from return stack so we're doing " cnt 0 DO ") 
        OVER OVER       ( dup src & dst)
        SFL @ (UB)      ( open source blocks file)
        SWAP BLOCK      ( load next src block)
        2- !            ( decrement block address and store destination block #)
        DFL @ (UB)      ( open destination blocks file)
        UPDATE FLUSH    ( write block to destination blocks file)
        XD + SWAP XD + SWAP     ( get next src & dst screens)
    LOOP 
    DROP DROP   ( drop the leftover src & dst)
    DUP         ( DUP old HERE)
    (UB)        ( restore original blocks file to 'current' status)
    DP !        ( restore dictionary pointer)
;   

 

 

 

I managed to cram it all into one messy-looking block of code—phew! :-o Now, I really am getting back to the manual—though, the graphics primitives are calling me to clean them up a bit. :D

 

@Willsy, you're welcome to any part of this code. You will need to write a SGN word that has the stack signature, "( n --- -1|0|+1 )" because SCMP relies on it.

 

...lee

Link to comment
Share on other sites

Oh, crap! :twisted: —One more change to CPYBLK

 

It now checks to see whether the start block # is higher than the end block #. If it is, it swaps them before continiuing. The user is on her/his own after that! Here's the code:

 

 

 

DECIMAL 
0 VARIABLE SFL 0 VARIABLE DFL   ( pointers to source and destination filename strings)           
: SCMP   ( str1 str2 --- -1|0|+1 )  
    OVER C@ OVER C@ OVER OVER - SGN >R   ( dup addresses, take diff; get sign & push to return stack)
    MIN 1+ 0 SWAP 1 DO  ( get min strlen; increment for limit; 0 flag start; swap with limit; start at 1)
        DROP            ( drop 0 flag)
        OVER I + C@     ( get next char of str1)
        OVER I + C@ - SGN  ( get next char of str2, take diff and get sign)
        DUP IF LEAVE THEN  ( dup it and leave loop if not equal)
    LOOP
    R> OVER 0=      ( pop cnt diff from return stack; copy loop result; see if 0)
    IF              ( it's 0, so OR with cnt diff for final answer)
        OR
    ELSE            ( it's not 0, so drop cnt diff )
        DROP
    THEN
    SWAP DROP SWAP DROP  ( get rid of leftover str1 and str2 pointers)
;
: GNUM BL WORD HERE NUMBER DROP ; ( get number from terminal)
( GBFL gets HERE to stack; stores filename at HERE; establishes new HERE;
  stores string address in variable passed on stack)
: GBFL ( addrvar -- )  HERE 0 BFLNAM SWAP !  ;  
0 CONSTANT XD  ( +1|-1 added to get next screen)
: CPYBLK    ( should we FLUSH before we start?)
    1 ' XD !    ( assume we're copying from low- to high-numbered blocks)
    HERE    ( save address where we'll copy current blocks filename; maybe use R)
    BPB BPOFF @ + 9 +     ( get address of blocks filename's char-count byte)
    DUP VSBR 1+         ( get count byte and increment it for copy count)
    HERE SWAP DUP =CELLS ALLOT VMBR  ( get current blocks name to HERE and move HERE past it)
    GNUM GNUM OVER OVER ( get start & end block#s and dup them) 
    > IF SWAP THEN      ( if start# > end#, swap them)
    OVER - 1+ >R        ( figure block count; count to return stack)        
    SFL GBFL            ( get source filename to HERE and store address, moving HERE)        
    GNUM                ( get destination start block#)        
    DFL GBFL            ( get destination filename to HERE and store address, moving HERE)                                
    SFL @ DFL @ SCMP 0= ( src, dst same file?)
    IF      ( yes...see if we need to change copy direction)
        OVER OVER - DUP     ( dup src & dst; take difference; dup it)
        ( need to copy in reverse direction only if overlap is low to high with cnt > overlap)
        0< SWAP R MINUS > + 2 =  
        IF      ( we're going from the top down)      
            SWAP R + 1- SWAP R + 1-  ( get highest src & dst screens)
            -1 ' XD !   ( we're decrementing the next screens)
        THEN
    THEN
    R> 0 DO         ( cnt from return stack so we're doing " cnt 0 DO ") 
        OVER OVER       ( dup src & dst)
        SFL @ (UB)      ( open source blocks file)
        SWAP BLOCK      ( load next src block)
        2- !            ( decrement block address and store destination block #)
        DFL @ (UB)      ( open destination blocks file)
        UPDATE FLUSH    ( write block to destination blocks file)
        XD + SWAP XD + SWAP     ( get next src & dst screens)
    LOOP 
    DROP DROP   ( drop the leftover src & dst)
    DUP         ( DUP old HERE)
    (UB)        ( restore original blocks file to 'current' status)
    DP !        ( restore dictionary pointer)
; 

 

 

 

The relevant change is in lines 30–31.

 

...lee

 

Link to comment
Share on other sites

This is looking better and better :) Great work indeed! As you add more features, I think a cart version will become a must in order to maximize user space. Just saying ;)

 

Thanks. I'm sure you're right about the increasing need to get all this into a cartridge. In TI Forth (and fbForth by extension) one rarely keeps one of the editors in memory after editing Forth blocks because they take up too much space, especially the 64-column editor! Of course, the editors can be kept in memory in a cartridge without needing to load them into RAM. I hope I can combine the 40-column and 64-column (and, possibly, 80-column) editors in less space than the sum of the two separately. The cartridge, I fear, is going to tax my time and abilities. We'll see.

 

...lee

Link to comment
Share on other sites

Regarding an 80-column editor with the F18A—I'm sure I will need to rearrange VRAM to accommodate the doubling in size of the text mode screen image table. @matthew180? @Willsy? That will take me a little more time, but should be doable. I'm not sure I can have all this ready by Faire time, but I'll try.

 

...lee

  • Like 1
Link to comment
Share on other sites

I don't think so, since things like sprites are disabled in 80 column mode (including the F18A, unless you explicitly turn them on IIRC). Also, the colour sets that are used in 32 column mode are ignored, so you can just blast right over them.

 

Check out GMODE in TF to see how 80 column mode is established in TF.

Link to comment
Share on other sites

I don't think so, since things like sprites are disabled in 80 column mode (including the F18A, unless you explicitly turn them on IIRC). Also, the colour sets that are used in 32 column mode are ignored, so you can just blast right over them.

 

Check out GMODE in TF to see how 80 column mode is established in TF.

 

That's true as far as what you've stated; but, there are a couple of blocks in that area that I cannot ignore in fbForth. The 40-column screen image table is 3C0h bytes in size and it must necessarily be double that, viz., 780h bytes, for 80-column mode. There are two blocks of memory in the area between 3C0h and 77Fh that do get used in all modes except bitmap mode and they are the value stack and the user PABs area. I will just need to change the pointer, VSPTR (836Eh) and PAB (user variable) to point somewhere else. There is a problem with PAB if the user decides to change to 80-column text mode while they have files open (not including the current blocks file). If PAB is automatically changed to point to another block of memory, the files' PAB pointers will be invalidated. Two solutions come to mind: (1) Warn the user that they must not change modes with open files (this is the unavoidable case with bitmap mode) or (2) move the PAB area for all modes except bitmap mode to the area immediately above the fbForth system area that contains the Forth system messages, true lowercase characters and blocks file information, which currently is 135Eh. With 3 open files (including 1 blocks file) this area is 247Ah (9338d) bytes, which would be reduced by 206h (518d) bytes for each additional open file. I guess the easier solution is (2) and there's probably plenty of room for PABs and record buffers. I can just tell the user in the manual that the 320h (800d) bytes at 460h are available if s/he needs them in all modes except bitmap and 80-column text modes.

 

The only other problem (I have no control over this one) is the VDP rollout area (32 bytes) at 3C0h used by the transcendental functions. I will have to warn the user, who wants to use the transcendental functions, that s/he must save and restore that area or the screen will have trash there in 80-column mode. Users of TI Forth are already aware of this for bitmap mode, so this is not a stretch.

 

All of this extra work makes me wonder whether I should go down the 80-column road at this time. I'm inclined to do it, but... :?

 

...lee

Link to comment
Share on other sites

SAMS support isn't too hard, and can actually fit in with Forth quite well. The SAMS switches on 4K blocks, so, if one ensures that code doesn't spill out of the end of one 4K block and into the beginning of the next, it's possible to switch SAMS banks, which has the effect of switching in/out parts of the dictionary.

 

TF has >MAP ("to mapper") which allows one to map any SAMS memory bank into any 4K boundary. You're free to use >MAP, Lee, if it's something you would like to include. The source for >MAP is here.

Edited by Willsy
Link to comment
Share on other sites

Hey Lee, any consideration for SAMS support by any chance? This could obviate the need for a cart version.

 

I have not considered it. I'm not sure how SAMS would obviate the need for a cartridge version, considering how few of us actually own SAMS cards (myself included). I realize that this is not a problem for an emulator or simulator and that SAMS is easy when using one; but, the real iron is my focus for this project. The other problem with SAMS is that I would need to devise a scheme for its use that would accommodate systems with/without SAMS or write two different versions, one that requires SAMS and one that doesn't. The situation is complicated by the limited availability of convenient memory locations on 4KB boundaries.

 

The obvious place to map SAMS is the lower 8K RAM; but, I cannot use any of that because that is where the Forth block buffers, low-level system support and return stack are located. Even with a cartridge, the only area that will get freed up is where the low-level system support currently resides (currently, 2394 bytes). To free up a 4KB block would require dropping the number of Forth block buffers from five to three.

 

Regarding using the upper 24KB RAM, the Forth kernel starts at A000h and the parameter stack starts at FFA0h, with the stack and the dictionary growing toward each other. The most practical way to handle SAMS would be to move the TIB (terminal input buffer) and the base of the stack down in 4KB decrements to accommodate the desired number of SAMS banks. I have no idea how many SAMS banks might be desirable or practical.

 

I also don't know, yet, how to thread the dictionary with bank switching. It's something both @Willsy and I have contemplated for use in a cartridge larger than 16KB; but, managing a multibank dictionary presents some difficult challenges to me. The dictionary is primarily a unidirectional linked list of words with lists of execution addresses and I am having difficulty devising an addressing scheme that would work in that context. It would seem to me that the inner interpreter would need to know what to do based solely on the 16-bit address and the outer interpreter would need to know how to code those addresses.

 

...lee

Link to comment
Share on other sites

SAMS support isn't too hard, and can actually fit in with Forth quite well. The SAMS switches on 4K blocks, so, if one ensures that code doesn't spill out of the end of one 4K block and into the beginning of the next, it's possible to switch SAMS banks, which has the effect of switching in/out parts of the dictionary.

 

TF has >MAP ("to mapper") which allows one to map any SAMS memory bank into any 4K boundary. You're free to use >MAP, Lee, if it's something you would like to include. The source for >MAP is here.

 

Thanks, Mark... But, see my last post.

 

What happens to the main dictionary when a 4K SAMS block is switched in? Does it replace part of the main dictionary or does it add to it?

 

Walid... It depends on where it gets put. Linking is the main problem (see my last post).

 

...lee

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