6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Nov 21, 2024 1:59 pm

All times are UTC




Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 10, 11, 12, 13, 14, 15, 16 ... 24  Next
Author Message
PostPosted: Sun Jul 25, 2021 9:11 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I've briefly mentioned Fleet Forth's cold start and warm start routines in previous posts. I'll try to explain them in this post.
When loading and running Fleet Forth, the cold start routine starts with Fleet Forth's one line of BASIC:
Code:
10 SYS XXXXX

where XXXXX is the address of the cold start routine. There is a subroutine used by both the cold start and warm start routines. It is headerless and has no code field since it is a subroutine, but it has the label (WARM) in the Fleet Forth source.
Here is the source for (WARM) :
Code:
HEX
HSUBR (WARM)
   SEI  CLD
   2F # LDA  0 STA
   36 # LDA  1 STA
   USER.DATA    LDA  UP    STA
   USER.DATA 1+ LDA  UP 1+ STA
   6C # LDA  W 1- STA
   'THERE USER.DATA - 1- # LDY
   BEGIN
      USER.DATA ,Y LDA  UP )Y STA
      DEY
   0< UNTIL
   CLI
   >FORTH
   SP! AP! [COMPILE] [
   IORESET SINGLE DECIMAL
   PAGE BOOTCOLORS
   >ASSEM
   RTS
END-CODE

The subroutine (WARM) has a mix of low and high level code.
It first disables maskable interrupts and clears the decimal mode. The memory map for Fleet Forth is set (switching out the BASIC rom) and the opcode for an indirect jump is stored at address W-1.
The label USER.DATA points to an area just after the BASIC fuse but before the first Forth word. This area holds the bootup, or startup, values for the first nine user variables. The first value is the startup value of the first user variable and the value for the Forth virtual machine register UP , the user pointer. It is copied to UP then the startup value for the first eight user variables are copied to the user area.
The first three are unnamed. They are reserved for the multitasker, which is not a part of Fleet Forth's kernel.
The next five are:
RP0 The address of the base of the return stack.
SP0 The address of the base of the data stack.
SPLIM The limit or "full mark" for the data stack. Since the data stack is only checked for overflow (and underflow) by the word ?STACK , which is normally only used by INTERRPET , there are fourteen cells available past this point.
AP0 The address of the base of the auxiliary stack.
APLIM The limit of the auxiliary stack. Since the words to move items to and from the auxiliary stack perform bounds checking on the auxiliary stack, this limit is a "hard" limit.

This subroutine then transitions to high level Forth and executes the following words:
SP! Clears the data stack. This word is a primitive that uses the value from the user variable SP0 to initialize the data stack.
AP! Clears the auxiliary stack using the value in AP0 .
[ Sets the Forth state to interpreting.
IORESET Resets the deferred words used for I/O to their default values.
SINGLE Switches off multitasking by setting PAUSE to a no-op.
DECIMAL Sets the number base to decimal.
PAGE Clears the screen.
BOOTCOLORS Sets the screen colors to their bootup values.
And finally >ASSEM causes a transition back to low level for an RTS back to the caller.

Here is the source for Fleet Forth's word COLD:
Code:
HEX // USER COLD START
: COLD  ( -- )
   SAVE-BUFFERS
   ACLOSE     // CLOSE ALL FILES
   >ASSEM
   // POWERUP COLD START
   // PATCH THE BASIC FUSE
   DECIMAL HERE 0 <# #S #>  HEX
   FUSE  OVER - SWAP CMOVE>V
   SEI  CLD  FF # LDX  TXS
   FF87 JSR  // RAMTAS
   FF8A JSR  // RESTOR
   FFE7 JSR  // CLALL
   FF84 JSR  // IOINIT
   FF81 JSR  // CINT
   WARM  SPLIT SWAP
      # LDA  $300 STA
      # LDA  $301 STA
   (WARM) JSR
   >FORTH
   EMPTY 0 DRIVE CONFIGURE
   #12 SPACES
   [ HERE #18 + >A ]
   ." C64 FLEET FORTH  COPYRIGHT (C) 1995-2021 BY JAMES BOYD "
   [ HERE 1- >A ]
   INITIAL QUIT ; -2 ALLOT
   $D A> C!  $D0D A> !

COLD is used from within Fleet Forth to perform a cold start.
It saves the contents of the block buffers with SAVE-BUFFERS and closes all open files with ACLOSE .
COLD then transitions to a code word and flows into the cold start routine.
This section of the source:
Code:
   DECIMAL HERE 0 <# #S #>  HEX
   FUSE  OVER - SWAP CMOVE>V

patches the one line of BASIC so the SYS instruction points to the cold start routine.
In the cold start routine interrupts are disabled, the processor stack is initialized and some Commodore 64 Kernal routines are called. I performed an experiment by replacing those five JSR instructions with fifteen NOP instructions and saving the modified Forth system. The test system loaded and ran just fine. This is why those five C64 kernal calls are there. Some of the C64 Kernal routines are vectored through page 3 so that the user can replace these routines or add functionality. This added functionality can be in code that is stored in the C64 tape buffer or in a range of RAM from $C000 TO $CFFF (49152 to 53247). Fleet Forth uses both of these memory areas so the C64 Kernal routines are called to reset RAM and reset these vectors to their default values.

$FF87 RAMTAS Clears pages 0 2 and 3 to zeros. It also performs a nondestructive test of memory to set the Kernal bottom and top of memory pointers and sets screen memory to $400.
$FF8A RESTOR As its name implies, restores the default C64 Kernal vectors in page 3.
$FFE7 CLALL Although this routine's name implies that it closes all, it merely sets the count of open files to zero at address $98 and falls through to the C64 kernal routine CLRCHN, which clears the channels and sets the keyboard as the input device and the screen as the output device.
$FF84 IOINIT This routine initializes the Complex Interface Adapter chips, sets CIA #1 to generate an interrupt via timer A every sixtieth of a second, and turns off the volume of the SID chip.
$FF81 CINT Initializes the screen editor and the VIC chip.

The cold start routine then stores the address of the warm start routine at addresses $300-$301 (768-769). The warm start routine will execute when the STOP and RESTORE keys are pressed. The RESTORE key is hardwired to generate a non-maskable interrupt. This is a convenient way to get out of an infinite loop without the need to reset the machine. A BRK instruction executed will also cause a Fleet Forth warm start.

The cold start routine calls (WARM) then transitions to high level Forth and executes the following:
EMPTY Empties the dictionary to its latest empty point. It accomplishes this by fetching the startup value for the ninth user variable with a value in the boot area, DP , the dictionary pointer. EMPTY stores a copy of this value in the variable FENCE before branching into FORGET . FORGET does the "heavy lifting" of removing all vocabularies defined after the "forget point" and pruning the remaining vocabularies.
0 DRIVE Sets the default drive to device 8.
CONFIGURE Initializes the block buffer system with all buffers empty.
The cold start routine prints the startup message before executing the word INITIAL and then QUIT .
INITIAL is a deferred word which is normally set to the no-op NOOP .

Here is the source for Fleet Forth's warm start routine:
Code:
HSUBR WARM
   $FF # LDX  TXS
   (WARM) JSR
   >FORTH
   FORTH DEFINITIONS
   ." RWARM START."
   ABORT ; -2 ALLOT

The processor stack is initialized and the subroutine (WARM) is called. The warm start routine then transitions to high level. It sets the CONTEXT and CURRENT search orders to FORTH , displays the warm start message in reverse video and aborts.

The biggest difference between the cold start and warm start routines for a programmer is this: The warm start routine will not empty the dictionary, alter the default disk drive or empty the block buffers.


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 19, 2021 8:26 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I've cleaned up Fleet Forth's multitasker source. Fleet Forth has a round robin cooperative multitasker, although another type could be written. It only has support for the one main task with background tasks. Each task uses a portion of page one for its return stack and a portion of the area from address $02 to address $7E in page zero for its data stack. A task is a CREATE word, like a variable but with a larger parameter field. A task's user area is in its parameter field. A task's parameter field may also have room for a task to have its own PAD .
In this post, any mention of a task's return stack refers to the portion of the return stack used by the task. The same goes for mention of a task's data stack.
Here is the multitasking lexicon:
Code:
(PAUSE)  ( -- )
ENTRY  ( -- ADR )
READY  ( -- ADR )
TOS  ( -- ADR )
MULTI  ( -- )
SINGLE  ( -- )
WAKE  ( TADR -- )
SLEEP  ( TADR -- )
LOCAL  ( TADR ADR1 -- ADR2 )
       ( ADR1 TADR -- ADR2 )
STOP  ( -- )
LINK-TASK  ( TASK -- )
UNLINK-ALL  ( -- )
UNLINK-TASK  ( TASK -- )
TASK  ( U SP0 RP0 -- )
ACTIVATE  ( TASK -- )

(PAUSE) is the task switcher. Here is the source:
Code:
CODE (PAUSE)  ( -- )
   IP 1+ LDA  PHA       // IP ON RET
   IP LDA  PHA          //
   TXA  PHA             // SP ON RET
   TSX  TXA             // RP STORED
   4 # LDY  UP )Y STA   // IN TOS
   BEGIN
      0 # LDY
      UP )Y LDA  TAX  INY
      UP )Y LDA  UP 1+ STA  INY
      UP STX  UP )Y LDA
   0= NOT UNTIL
   4 # LDY  UP )Y LDA  TAX  TXS
   PLA  TAX
   ' EXIT @ JMP  END-CODE

The next three words, ENTRY , READY , and TOS are user variables with offsets 0, 2, and 4 respectively. These offsets are reserved in the Fleet Forth kernel for multitasking.
ENTRY points to the beginning, or entry, of the user area for the next task. READY has the logic value true if the task is "awake" and ready to go or false if it is "asleep", although (PAUSE) only checks the low byte. TOS is used to store a task's return stack pointer.
(PAUSE) saves the value of IP to the task's return stack as well as the value of the current task's data stack pointer. The return stack pointer is saved in the user variable TOS . (PAUSE) then follows ENTRY to the next task, testing READY until it finds a task which is awake. Once a task which is awake is found, its return stack pointer is fetched from TOS and stored in the S register and the data stack pointer is restored from the task's return stack. A jump to EXIT fetches the saved value of IP from the task's return stack and returns to NEXT to resume execution of the Forth thread for this task.
Code:
0 USER ENTRY   2 USER READY
4 USER TOS
: MULTI  ( -- )
   READY ON
   ['] (PAUSE) IS PAUSE ;
: WAKE  ( TADR -- )  2+ ON ;
: SLEEP  ( TADR -- )  2+ OFF ;
: LOCAL  ( BASE ADR1 -- ADR2 )
   [ ASSEMBLER ] UP [ FORTH ]
   @ - + ;
: STOP  ( -- )
   BEGIN
      READY OFF  PAUSE
   AGAIN ; -2 ALLOT

MULTI assures the task executing it is "awake" (normally the main task) and sets the deferred word PAUSE to (PAUSE) . The counterpart of MULTI is SINGLE . It is the only word in the multitasker lexicon defined in Fleet Forth's kernel. SINGLE sets PAUSE to NOOP a no operation word. NOOP is a word without a parameter field. Its code field points to NEXT .
WAKE takes a task's address and wakes it up.
SLEEP takes a task's address and puts it to sleep.
LOCAL takes a task's address and the address returned by a user variable in the currently executing task (usually main) and returns the address of that user variable in the given task. For example, suppose there is a task named DUMMY1 . I can tell if DUMMY1 is awake or not with the phrase:
Code:
DUMMY1 READY LOCAL ?

where ? displays the contents of an address. LOCAL has two stack comments because it doesn't matter whether it is used with the user variable address on top or the task address, it returns the same result.
STOP is used for a one-shot task, one which does not have an infinite loop. STOP puts the task to sleep then pauses. STOP has an infinite loop in the event that a stopped task is woken up inadvertently. The task will put itself back to sleep and pause the next time it runs.

Linking and unlinking:
Code:
: LINK-TASK  ( TASK -- )
   DUP SLEEP
   ENTRY 2DUP @ SWAP! ! ;
: UNLINK-ALL  ( -- )
   [ ASSEMBLER UP @ ] LITERAL
   DUP UP !  ENTRY ! ;
: UNLINK-TASK  ( TASK -- )
   [ ASSEMBLER ] UP [ FORTH ]
   BEGIN
      @ DUP @
      [ ASSEMBLER ] UP [ FORTH ] @ =
      IF  2DROP EXIT  THEN
      DUP @ 2PICK =
   UNTIL
   SWAP @ SWAP! ;

LINK-TASK takes a task's address and puts it to sleep because a newly created task does not have anything to do and would "crash and burn" if awake. It then links the task into the round robin list of tasks. It is important that a task does not get linked into the round robin list more than once!
UNLINK-ALL , as its name implies, unlinks all the tasks by setting UP , Forth's user pointer, to the main task and setting ENTRY for the main task to point to itself. The ability to unlink all the tasks makes it safe to forget a task. I've read somewhere that once a task is created, it should not be forgotten. It seems to me that this would make prototyping harder with the need to leave the Forth system and reload it to remove background tasks. To provide a little more protection, FORGET switches off multitasking. If the need to forget a Forth word arises, one or more tasks may be forgotten in the process. It would be prudent to unlink all tasks before switching multitasking back on and link them back into the round robin list one at a time. A cold or warm start will unlink all tasks as a side effect of setting up the user area and user pointer. ABORT or any word which executes ABORT switches multitasking off.
UNLINK-TASK takes the address of a task and will unlink just that task from the round robin list of tasks. If the address on the stack is not the address of a task linked in the round robin list, UNLINK-TASK will, after traversing the list, exit without changing anything.

Task creation and activation:
Code:
: TASK  ( U SP0 RP0 -- )
   CREATE
      HERE RP0 LOCAL !
      HERE SP0 LOCAL !
      // OPTIONAL
      10 HERE BASE LOCAL !
      HERE #USER + HERE DP LOCAL !
      10 UMAX ALLOT ;
: ACTIVATE  ( TASK -- )
   DUP WAKE
   R> OVER RP0 LOCAL @ 1- DUP>R !
   DUP SP0 LOCAL @ R@ 1- C!
   TOS LOCAL R> 2- SWAP! ;

As I said, TASK is a CREATE word, it returns the address of its parameter field. The definition of TASK has been changed. The original definition took three parameters, the size of the user area for the task being created, the size of its portion of data stack, and the size of its portion of return stack. There were some constants and values used to keep track of how much of the stacks were set aside for each task. With the ability to forget tasks, this would necessitate resetting the values used to keep track of the portion of the stacks set aside for the background tasks.
The new version of TASK also takes three parameters. The size of the user area for the task, the address of the base of the data stack for the task being created, and the address of the base of its return stack.
A task's parameter field will have room for at least five user variables, possibly more. The five required user variables are:
ENTRY , READY , TOS , RP0 , and SP0 . RP0 holds the address of the base of the return stack and SP0 holds the address of the base of the data stack. When a stack is created, its value of BASE is set to ten and its DP , dictionary pointer, is set to the address after the latest user variable.
ACTIVATE takes the address of a task and is used in a word to give a task something to do. Here is an example with two tasks. They both flash the border, but at different intervals.
Code:
180 VALUE DELAY1 175 VALUE DELAY2
0 $1C $12F TASK DUMMY1
0 $38 $15F TASK DUMMY2
: FLASH1
   DUMMY1 ACTIVATE
   BEGIN
      $D020 C@ 1+ BORDER
      DELAY1 JIFFIES
   AGAIN ;
: FLASH2
   DUMMY2 ACTIVATE
   BEGIN
      $D020 C@ 1+ BORDER
      DELAY2 JIFFIES
   AGAIN ;

Code:
SEE FLASH1
FLASH1
 7DB4 7D84 DUMMY1
 7DB6 7CB2 ACTIVATE
 7DB8  A35 LIT D020
 7DBC 1298 C@
 7DBE 11BF 1+
 7DC0 455F BORDER
 7DC2 7D66 DELAY1
 7DC4 42A6 JIFFIES
 7DC6  BD0 BRANCH 7DB8
16
 OK

FLASH1 is executed from the main task. ACTIVATE wakes the task DUMMY1 . It then places the address $7DB8 on the task's return stack. The data stack pointer for DUMMY1 is then placed on its return stack. The adjusted return pointer for DUMMY1 is stored in its version of TOS . DUMMY1 is now ready for the Forth thread at address $7DB8 to run.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 03, 2021 7:32 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I posted about Fleet Forth's cold and warm start routines here.
There was an error in the cold start routine in the section of C64 Kernal calls which has since been corrected.
Code:
   FF87 JSR  // RAMTAS
   FF8A JSR  // RESTOR
   FFE7 JSR  // CLALL
   FF84 JSR  // IOINIT
   FF81 JSR  // CINT

The Commodore 64 Kernal routine CLALL clears the table of open files, but it also falls through to the CLRCHN routine. This routine restores the default input device (the keyboard) and the default output device (the screen). It will also send an UNTALK or UNLISTEN command if necessary. The problem is that the C64 routine RAMTAS clears the memory locations holding the necessary information.
Here is the corrected list of C64 Kernal calls.
Code:
   FFE7 JSR  // CLALL
   FF87 JSR  // RAMTAS
   FF8A JSR  // RESTOR
   FF84 JSR  // IOINIT
   FF81 JSR  // CINT

I've never had a problem with this because I load and run Fleet Forth immediately after powerup, or in recent years immediately after starting the VICE C64 simulator.


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 03, 2021 8:47 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

Fleet Forth has a word, DIGIT , which is not in the Forth-83 standard, but it is in Fig Forth, F83 and F-PC. DIGIT is a primitive which attempts to convert a character into a valid digit in the specified number base and returns the result and a flag.
In Fig Forth, DIGIT has the following stack effect.
Code:
DIGIT  ( CHAR BASE -- N TRUE )
       ( CHAR BASE -- FALSE )

However, in F83 and F-PC it has this stack effect.
Code:
DIGIT  ( CHAR BASE -- N TRUE )
       ( CHAR BASE -- CHAR FALSE )

I've also seen this stack effect where accessing the value of BASE is built in.
Code:
DIGIT  ( CHAR -- N TRUE )
       ( CHAR -- CHAR FALSE )

Which version would be best for a Forth-83 Standard system such as Fleet Forth?
Of the first two versions, the Fig Forth version has the advantage that CONVERT does not need to discard the unneeded character when a nonconvertible character is encountered, thus saving one cell. On the other hand, the F83 and F-PC version could be more generally useful since it always returns two stack items. Here is an example from Inside F83 by C. H. Ting where the result of the conversion is not needed.
Code:
: DIGIT? ( char -- f )
   BASE @ DIGIT
   NIP ;

The third version is the one I'm leaning toward. The built in access of BASE saves two cells in each word which uses DIGIT , making that word slightly smaller and faster. This change only makes DIGIT two bytes longer comparing the value in the accumulator with the value of BASE
Code:
   ' BASE >BODY C@ # LDY
   UP )Y CMP

versus comparing it with the value on the top of the data stack.
Code:
   0 ,X CMP

DIGIT? would then be
Code:
DIGIT?  ( char -- f )
   DIGIT NIP ;


[Edit: Fixed some typos]


Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 03, 2021 11:15 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
I thought I'd look up what Starting Forth has to say about DIGIT since it often has foot notes telling the differences in figForth, Forth-79, Forth-83, etc.; but I was shocked to see it wasn't even there! I have DIGIT> which has the stack effect you show first.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 12, 2021 1:04 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I've decided to change Fleet Forth's word DIGIT to have the third stack effect mentioned in my previous post.
Code:
DIGIT  ( CHAR -- N TRUE )
       ( CHAR -- CHAR FALSE )

Code:
CODE DIGIT  ( CHAR -- N TRUE )
            ( CHAR -- CHAR FALSE )
   SEC  0 ,X LDA  $30 # SBC
   ' FALSE @ CS NOT BRAN
   $A # CMP
   CS IF
      7 # SBC  $A # CMP
      ' FALSE @ CS NOT BRAN
   THEN
   ' BASE >BODY C@ # LDY
   UP )Y CMP
   ' FALSE @ CS BRAN
   0 ,X STA
   ' TRUE @ JMP END-CODE

DIGIT is a primitive and including the access of the value in BASE only makes it two bytes bigger than before. This change saves two cells, or four bytes, in CONVERT , the high level word which uses DIGIT .
Returning two stack items even when conversion is unsuccessful adds a cell to CONVERT for a DROP to discard the unconverted character when it branches out of its loop.
Code:
: CONVERT  ( D1 ADR1 -- D2 ADR2 )
   1+
   BEGIN
      COUNT DIGIT         \ count base @ digit
   WHILE
      2SWAP BASE @ *
      SWAP BASE @ UM* D+
      ROT
      DPL @ 0< 1+ DPL +!
   REPEAT
   DROP 1- ;              \ 1- ;

The comments show the original lines.
As I mentioned in my previous post, having DIGIT always return two stack items makes it more generally useful. Although this change will make CONVERT two bytes bigger, if I only intended DIGIT to be used by CONVERT , I would have made it headerless.


Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 12, 2021 2:46 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I mention Fleet Forth's word NUMBER? here. Back then it was named -NUMBER . I've managed to reduce the size of its body to 132 bytes.
I've tried two other versions. One is 122 bytes but requires the first character (after any leading conversion base specifier or minus sign) to be a valid digit. The following would all be valid numbers with this version:
Code:
$0.1234
%234.5.8.
#-42.3
123.

And these would not be valid:
Code:
$.1234
.1234
0.1..2
123...45

The other version also requires the first digit after any conversion specifier or minus sign to be a valid digit. It is only 116 bytes because it allows more than one valid non-digit punctuation character in a row. Here are examples of valid numbers with this version:
Code:
$0.123.....56.
123...45.2..
#-137....2



Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 20, 2021 11:28 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I have previously mentioned the word VALID? . This word is used to test if a non-digit character is valid punctuation for a number. It is a deferred word and by default is set to (VALID?) , which only recognizes the period ( . ) as valid punctuation in a double number. Since VALID? is a deferred word, what is accepted as valid punctuation for a number can be changed. Starting Forth mentions these as valid punctuation for a double number:
Code:
, . / - :

Some of these, as well as other punctuation, could be added by using a new version of (VALID?) . Taking this into consideration, I have decided to use the version of NUMBER? which requires valid non-digit punctuation in a number be separated by at least one digit. I believe a typo would be less likely slip by as a valid number with the tighter restriction and that it is well worth the extra six bytes.
My fellow Forthwrites, am I being a bit too cautious? Is it worth the extra six bytes?


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 20, 2021 11:47 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
I say it's worth it, but you should just follow your gut ... any decision you make can be easily revised later.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 21, 2021 12:40 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
barrym95838 wrote:
I say it's worth it, but you should just follow your gut


Thanks.
This was one of those decisions where I could have gone either way, but leaned toward the more restrictive, and slightly larger, version of NUMBER? .


Top
 Profile  
Reply with quote  
PostPosted: Thu Oct 21, 2021 1:58 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I have previously made brief mention of Fleet Forth's word WHERE . It is used to display the location of an error when (ABORT") aborts with an error message. WHERE can be useful apart from (ABORT") to display an error message which is not known beforehand.
-SET is one example:
Code:
: -SET  ( -- )
   WHERE
   R@ 2- @ >NAME CR RON ID. ROFF
   ."  NOT SET" ABORT ; -2 ALLOT

-SET is the vector for a new deferred word until a new vector is set with IS :
Code:
' NOOP IS PAUSE
' (PAUSE) IS PAUSE
' (EMIT) IS EMIT

If the new deferred word, prior to being set to a new vector, is executed from the command line, -SET displays the odd message that EXECUTE is not set. When executed from within a high level Forth word, the message shows the name of the deferred word which is not set.
Another example is ?D , the word which checks the disk drive for an error condition after block read/write.
Code:
: ?D  ( -- )
   $0F >SLF# (?D) 0EXIT
   WHERE
   CR ." DISK ERROR:" CR
   DEB S? ABORT ;  -2 ALLOT

WHERE displays where the error occurred. The message 'DISK ERROR:' is displayed, followed my the message in the Disk Error Buffer or DEB , which comes from the disk drive as a text string.
To get a better feel of what Fleet Forth error messages look like, here are some examples.
This one is an example of using PAD as a buffer for sector 10 of track 37 on disk 0 of the current drive to read (r/w flag value is 1) 256 bytes with the sector read/write word. Device 8 is a 1541 disk drive and does not have 37 tracks. I've added comments after the fact.
Code:
8 DRIVE  OK              \ select device 8 as the current drive
DOPEN  OK                \ open current drive for block access
PAD 10 37 0 1 256 SR/W   \ try to read sector 10 of track 37
PAD 10 37 0 1 256 SR/W   \ this line shown by Fleet Forth in reverse video
                  ^^^^   
DISK ERROR:
66,ILLEGAL TRACK OR SECTOR,37,10

When WHERE shows the line in a screen or the text input buffer or an evaluated string, it shows that line in reverse video so it stands out. The carets ( ^ ) identify the text which caused the error.
Here are some errors due to (intentional) typos.
From the console:
Code:
DECIMAL  OK
CR 1 2 + . 123OOPS 4 5 * .
3
CR 1 2 + . 123OOPS 4 5 * .
           ^^^^^^^
WHAT?

and from a screen:
Code:
1 RAM LOAD 7
SCR# 32769 LINE# 7
2 5 + . 321OOPS  7 9 * .
        ^^^^^^^
WHAT?

Missing delimiter for a string:
Code:
: TEST2
CR ." START TEST2 TEST1
CR ." START TEST2 TEST1
   ^^
" MISSING

and using a deferred word which has not been set:
Code:
DEFER PWM  OK
: TEST  50 PWM ;  OK
TEST
TEST
^^^^
PWM NOT SET

Here is a screen shot of trying to forget a word protected by FORGET's internal 'fence'.
Attachment:
forget test fleet forth.png
forget test fleet forth.png [ 18.96 KiB | Viewed 11124 times ]



Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 31, 2021 8:41 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
JimBoyd wrote:

I've decided to change Fleet Forth's word DIGIT to have the third stack effect mentioned in my previous post.
Code:
DIGIT  ( CHAR -- N TRUE )
       ( CHAR -- CHAR FALSE )

Code:
CODE DIGIT  ( CHAR -- N TRUE )
            ( CHAR -- CHAR FALSE )
   SEC  0 ,X LDA  $30 # SBC
   ' FALSE @ CS NOT BRAN
   $A # CMP
   CS IF
      7 # SBC  $A # CMP
      ' FALSE @ CS NOT BRAN
   THEN
   ' BASE >BODY C@ # LDY
   UP )Y CMP
   ' FALSE @ CS BRAN
   0 ,X STA
   ' TRUE @ JMP END-CODE



I should have seen this before. I changed the jump at the end of DIGIT to a branch.
Code:
CODE DIGIT  ( CHAR -- N TRUE )
            ( CHAR -- CHAR FALSE )
   SEC  0 ,X LDA  $30 # SBC
   PUSH.FALSE CS NOT BRAN
   $A # CMP
   CS IF
      7 # SBC  $A # CMP
      PUSH.FALSE CS NOT BRAN
   THEN
   ' BASE >BODY C@ # LDY
   UP )Y CMP
   PUSH.FALSE CS BRAN
   0 ,X STA
   PUSH.TRUE CS NOT BRAN
END-CODE



Top
 Profile  
Reply with quote  
PostPosted: Sun Oct 31, 2021 8:58 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
JimBoyd wrote:

My fellow Forthwrites, am I being a bit too cautious? Is it worth the extra six bytes?


It's actually eight bytes. Here is the source for both versions of NUMBER? . Just a reminder that DIGIT takes a character, it accesses the value of BASE internally, and returns either:
1) The numerical value of the digit and a true flag.
2) The character if it is not a valid digit and a false flag.
NUMBER? takes the address of a counted string which is followed by a trailing blank and returns a double number and a flag. The flag is true for a successful conversion. NUMBER? also sets the user variable DPL to the count of how many digit characters occur after the last valid punctuation character, or TRUE (-1) if there is no punctuation in the numeric string.
The larger stricter version:
Code:
: NUMBER?  ( ADR -- D FLAG )
   RB
   DUP 1+ C@ ASCII # - DUP 3 U<
   IF
      BASE.TABLE + C@ BASE !
      1+ DUP
   THEN
   DROP
   DPL ON  0 0 ROT
   1+
   COUNT ASCII - <> DUP>R +
   COUNT DIGIT NIP >R
   2-
   BEGIN
      CONVERT DUP C@ VALID?
   WHILE
      DPL OFF
      DUP 1+ C@ VALID?
   UNTIL
   THEN
   C@ BL =  R> AND
   R>  ?EXIT
   >R DNEGATE R> ;

The smaller and less strict version:
Code:
: NUMBER?  ( ADR -- D FLAG )
   RB
   DUP 1+ C@ ASCII # - DUP 3 U<
   IF
      BASE.TABLE + C@ BASE !
      1+ DUP
   THEN
   DROP
   DPL ON  0 0 ROT
   1+
   COUNT ASCII - <> DUP>R +
   COUNT DIGIT NIP >R
   2-
   BEGIN
      CONVERT DUP C@ VALID?
   WHILE
      DPL OFF
   REPEAT
   C@ BL =  R> AND
   R>  ?EXIT
   >R DNEGATE R> ;



Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 15, 2021 12:42 am 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895

I previously mentioned fixing Fleet Forth's multitasking support in the kernel. This allows PAUSE to execute while the user is typing at Forth's command line, or even just thinking about what to type. It's nice when your background tasks don't freeze while you're sitting at the keyboard thinking.
If round robin multitasking will not be used, to avoid splitting the stacks into smaller areas for each task, PAUSE can be vectored to a 'run to completion' word. There are two caveats. Any word PAUSE is vectored to must leave the stacks as they were when it began. It, and all the words it executes, must not execute PAUSE to avoid runaway recursion.
Background tasks normally execute PAUSE to switch tasks. In that case PAUSE is vectored to an actual task switcher.
The following Fleet Forth words execute PAUSE directly or indirectly through another word.
Code:
TYPE
QTYPE
KEY
(EXPECT)
INTERPRET
QUIT
DJIFFIES
JIFFIES

As well as any word which displays text, with the exception of EMIT since it displays a single character.
The error handling words ( ABORT ABORT" WHERE ) also execute PAUSE by way of TYPE but, they should not be in a background task. ABORT and ABORT" switch off any kind of multitasking by executing SINGLE , setting PAUSE to the no-op NOOP .

The following will cycle the border through all sixteen colors.
Code:
: BORDER?  ( -- B )
   $D020 C@ ;
: CYCLE  ( -- )
   BORDER? 1+ BORDER ;
' CYCLE IS PAUSE

Something like the cyclic executive Garth mentions can be set up easily:
Code:
: BACKGROUND
   TASK1
   TASK2
   TASK3
   TASK4 ;
' BACKGROUND IS PAUSE

What is missing is a way for a 'task' to run to a certain stopping point and resume where it left off. Leo Brodie's DOER/MAKE could be used. I propose a little structure of my own, inspired by DOER/MAKE, halting colon definitions. One of these runs to the halting point, where it exits. It resumes where it left off.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ >A ]
   DOES>  ( -- )
      @ >R ;
: (HALT)  ( -- )
          ( R: ADR -- )
   R@ 2+  R> @ >BODY ! ;
: HALT
   LATEST NAME> @ [ A> ] LITERAL <>
   ABORT" NON HALTING WORD"
   COMPILE (HALT)  [COMPILE] RECURSE
   ; IMMEDIATE

Or if an auxiliary stack is not available, a pair of free zero page locations can be used.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ 2 ! ]
   DOES>  ( -- )
      @ >R ;
: (HALT)  ( -- )
          ( R: ADR -- )
   R@ 2+  R> @ >BODY ! ;
: HALT
   LATEST NAME> @ [ 2 @ ] LITERAL <>
   ABORT" NON HALTING WORD"
   COMPILE (HALT)  [COMPILE] RECURSE
   ; IMMEDIATE

Here is a test halting word.
Code:
H: HTEST  ( -- )
   BEGIN
      CR ." STARTING HTEST" HALT
      CR ." MIDDLE OF HTEST" HALT
      CR ." END OF HTEST" HALT
   AGAIN ;

Each time HTEST executes, it displays one of the messages and exits.
Code:
HTEST
STARTING HTEST OK
HTEST
MIDDLE OF HTEST OK
HTEST
END OF HTEST OK
HTEST
STARTING HTEST OK

Here is an example without a BEGIN AGAIN loop.
Code:
H: TEST2  ( -- )
   CR ." BEFORE HALTING." HALT
   CR ." AFTER HALTING." ;

The very first time it is executed, it will display the message "BEFORE HALTING" and every time after that it will, when executed, display "AFTER HALTING" .
Here is what gets compiled for TEST2 .
Code:
' TEST2 >BODY :DIS
 22937 22939
 22939  7024 CR
 22941  6724 (.") BEFORE HALTING.
 22959 22754 (HALT)
 22961 22935 TEST2
 22963  7024 CR
 22965  6724 (.") AFTER HALTING.
 22982  2930 EXIT
47
 OK

Halting colon definitions even support recursion.
Code:
H: TEST  ( N -- N )
   BEGIN
      CR DUP SPACES ." BEGIN"
      1+ DUP 4 <
      IF  RECURSE  THEN
      HALT
      CR DUP SPACES ." MIDDLE"
      1+ DUP 6 <
      IF  RECURSE  THEN
      HALT
      CR DUP SPACES ." END"
      1- DUP 0< 0=
      IF  RECURSE  THEN
      HALT
   AGAIN ;

Code:
0 TEST
BEGIN
 BEGIN
  BEGIN
   BEGIN OK
TEST
    MIDDLE
     MIDDLE OK
TEST
      END
     END
    END
   END
  END
 END
END OK
. 0  OK

If PAUSE is set to a halting colon definition, or a regular colon definition which executes more than one halting colon definition, the halting colon definition(s) will perform an action each time PAUSE is executed. This may be too frequent. Garth also mentioned alarms. An interval timer would be nice. How about the down-counter by Tim Hendtlass?
Here is DOWN-COUNTER for Fleet Forth.
Code:
: DOWN-COUNTER
   2VARIABLE  ( -- )
   DOES>  ( -- ADR )
      JIFFY@ DROP
      OVER 2+ @
      OVER -  0 MIN
      2PICK +!
      OVER 2+ ! ;

That '0 MIN' is to handle the case when the jiffy timer resets to zero when it reaches twenty four hours.
A down-counter, a child word of DOWN-COUNTER , has two cells of storage. The first cell holds a value. The second cell holds the low cell of the Commodore 64 jiffy clock from the last time the down-counter was executed. Each time a down-counter is executed the amount of time, in jiffies, which passed from the last time is subtracted from the value in the first cell.
Code:
DOWN-COUNTER DELAY1 OK
300 DELAY1 ! OK
DELAY1 ? 209  OK
DELAY1 ? 118  OK
DELAY1 ? 5  OK
DELAY1 ? -445  OK

Of course, the values returned each time I type "DELAY1 ?" depends on how long I wait.
Here is sample code to cycle the Commodore 64 border and cursor colors through predetermined values without an actual multitasker other than PAUSE built into certain kernel words. It cycles the colors while I'm still able to use Fleet Forth for other things.
Code:
DOWN-COUNTER DELAY1
H: CYCLE-INK  ( -- )
   BEGIN
      DELAY1 @ 0< 0EXIT
      GREEN INK
      60 DELAY1 !  HALT
      DELAY1 @ 0< 0EXIT
      BROWN INK
      120 DELAY1 !  HALT
      DELAY1 @ 0< 0EXIT
      BLACK INK
      180 DELAY1 !  HALT
   AGAIN ;

DOWN-COUNTER DELAY2
H: CYCLE-BORDER  ( -- )
   BEGIN
      DELAY2 @ 0< 0EXIT
      GREEN BORDER
      30 DELAY2 !  HALT
      DELAY2 @ 0< 0EXIT
      CYAN BORDER
      90 DELAY2 !  HALT
      DELAY2 @ 0< 0EXIT
      YELLOW BORDER
      60 DELAY2 !  HALT
   AGAIN ;

: CYCLE  ( -- )
   CYCLE-INK  CYCLE-BORDER ;

' CYCLE IS PAUSE

Maybe halting colon definitions, or halting words, will be useful outside the realm of simulated multitasking, I don't know. Which will be more useful, halting words or DOER/MAKE? I don't know that either.

Note: My first attempt to port halting words to Blazin' Forth did not work. Blazin' Forth's word ] doesn't just change to the compiling state, it is the colon compiler. As a result, when Blazin' Forth's ] executes, parsing of the text stream and compiling begin immediately.
The only workaround I could come up with was to incorporate the functionality of : into H: without compiling : directly.
Code:
: H:                       \ nothing placed on stack?
   CREATE                  \ create a new header
      SMUDGE               \ hide name
      CURRENT @ CONTEXT !  \ set CONTEXT equal to CURRENT
      !CSP                 \ Blazin' Forth checks for a change in stack depth
      HERE 2+ ,            \ allocate and initialize one cell of storage
      ]                    \ include this AFTER everything up to DOES>
   DOES>
      @ >R ;

The test to make sure HALT is not used in normal colon definitions was left out of the version ported to Blazin' Forth. Because of the way Blazin' Forth handles compilation, it is not easy to test if a new definition is a child of H: until after the first line of text is parsed from the keyboard or until the block it is defined in is finished.

CREATE is used in both Fleet Forth and Blazin' Forth by all the words which create a header. It is basically VARIABLE without storage allotted.
Code:
\ Fleet Forth's VARIABLE
: VARIABLE
   CREATE  0 , ;


In regards to round robin multitasking, the problem with dividing the stacks so each task gets a portion is that it limits the number of tasks, as the background task's portion of the return stack has to have room for the nesting of that task and also for interrupts. One possibility is to have round robin multitasking with fewer background tasks. One or more background tasks could be a cyclic executive. The cyclic executive would need to be in a loop and include PAUSE .
Something like the following should work, but I haven't tested this yet.
Code:
\ define some halting words.
H: BG1 ... ;
H: BG2 ... ;
H: BG3 ... ;
0 $20 $13F TASK BACKGROUND
: CYCLE
   BACKGROUND ACTIVATE
   BEGIN
      BG1  BG2  BG3  PAUSE
   AGAIN ;
BACKGROUND LINK-TASK
\ define and link other tasks
   ...
\ switch on multitasking
MULTI



Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 15, 2021 8:07 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 255
JimBoyd wrote:
What is missing is a way for a 'task' to run to a certain stopping point and resume where it left off. Leo Brodie's DOER/MAKE could be used. I propose a little structure of my own, inspired by DOER/MAKE, halting colon definitions. One of these runs to the halting point, where it exits. It resumes where it left off.
Code:
: H:
   :  ( -- ADR TRUE )
      HERE 2+ ,
      [ HERE 2+ >A ]
   DOES>  ( -- )
      @ >R ;

You are breaking my brain (in a good way, I think). I was not aware you could put : in a colon definition (I would normally use CREATE, but I can see how they are different). Now that I think about it, of course you can do that. Forth lets you run with scissors by holding on to the sharp parts if you want.

Once I made it past that part, I see your DOES> pulls the address you left off (stored right at the beginning of the word's definition) at and puts it on the return stack just before the ; That immediately made me cringe because I've spent too many hours trying to balance my stores and fetches on the return stack, but of course you want that here. That's where it will "return" to and ; will bring you there. It's just like the co-routine code you showed earlier. It reminds me that >R and ; are just tools that can be used in any order if the resulting behavior is your desired behavior.

The rest was pretty easy to understand, once you understood what this part does. I haven't noodled my way all the way through your example yet, but I find it very interesting. Also, bonus points for showing off you auxiliary stack. I wrote one, but haven't really used it too much.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 10, 11, 12, 13, 14, 15, 16 ... 24  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 18 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: