6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Nov 21, 2024 8:31 am

All times are UTC




Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 20, 21, 22, 23, 24
Author Message
PostPosted: Tue Jul 30, 2024 9:27 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
Fleet Forth had a word named TEXT= . This was a nonstandard primitive which took an address of a string, a count, and an address of a second string. It returned TRUE if the two strings were equal, otherwise it returned FALSE.
Code:
CODE TEXT=  ( ADR1 +N ADR2 -- F )
   HERE 1+
   $BAD JSR
   0= NOT IF
      ' FALSE @ JMP
   THEN
   ' TRUE @ JMP
   HERE SWAP!
   3 # LDA  SETUP JSR
   BEGIN
      BEGIN
         N 2+ LDA  N 3 + ORA
      0= NOT WHILE
         N 4 + )Y LDA  N )Y CMP
         0= NOT IF
            RTS
         THEN
         N 2+ LDA
         0= IF  N 3 + DEC  THEN
         N 2+ DEC
         INY
      0= UNTIL  CS-SWAP
      N 5 + INC  N 1+ INC
   REPEAT
   RTS  END-CODE

The definition looks a little odd because I wrote it so I could reuse the subroutine portion of TEXT= to define -TEXT , a word from the Uncontrolled Reference Words in the Forth-83 Standard.
I decided to replace TEXT= with -TEXT .
Code:
CODE -TEXT  ( ADR1 +N ADR2 -- F )
   3 # LDA  SETUP JSR
   BEGIN
      BEGIN
         N 2+ LDA  N 3 + ORA
      0= NOT WHILE
         N 4 + )Y LDA  N )Y CMP
         0= NOT IF
            CS IF
               ' 1 @ JMP
            THEN
            ' TRUE @ JMP
         THEN
         N 2+ LDA
         0= IF  N 3 + DEC  THEN
         N 2+ DEC
         INY
      0= UNTIL  CS-SWAP
      N 5 + INC  N 1+ INC
   REPEAT
   ' FALSE @ JMP
   END-CODE

This version of -TEXT takes an address of a string, a length, and an address of another string. It returns FALSE if the strings are equal. If the strings are not equal it returns TRUE if the first non matching character in the first string has a lower PETSCII (Commodore's version of ASCII) value than the first non matching character in the second string, otherwise it returns ONE . This is a little different from the Standard.
Code:
 -TEXT        addr1 +n1 addr2 -- n2                   "dash-text"
      Compare two strings over the length +n1 beginning at addr1
      and addr2.  Return zero if the strings are equal.  If
      unequal, return n2, the difference between the last
      characters compared:  addr1(i) - addr2(i).

The use of -TEXT will require changes to the source in five places in the system loader.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 30, 2024 11:07 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
JimBoyd wrote:
Fleet Forth had a word named TEXT= . This was a nonstandard primitive which took an address of a string, a count, and an address of a second string. It returned TRUE if the two strings were equal, otherwise it returned FALSE.

On the HP-71 where I started in Forth, it's called S= .

_________________
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: Sun Aug 04, 2024 8:27 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
Replacing TEXT= with -TEXT only affected three words in Fleet Forth's system loader: MATCH , the nameless word used by COMMENT: , and the nameless word used by [ELSE] .

MATCH is Fleet Forth's word to find a search string within a larger string. I will refer to this larger string as a buffer. The parameters for MATCH are: The address and length of the buffer and the address and length of the search string. If the search string is not found, MATCH returns the length of the buffer and FALSE . If the search string is found, MATCH returns the offset into the buffer one byte past where the last matching character is found and TRUE .

Replacing TEXT= with -TEXT 0= made MATCH take about ten percent longer. This version of MATCH also has two problems. It uses a ?DO LOOP written to loop through every address in the buffer. The first problem with this approach is when a possible match is found. The offset needs tested against the length of the buffer to insure a match is not found which extends past the buffer. Although this is not a serious problem, more of a nuisance, it seems inelegant.
The other problem is more serious. Because the loop works through all the addresses in the buffer, if a match is not found as the loop index nears the end of the buffer, -TEXT ( and originally TEXT= ) will read past the buffer. If the buffer in question is adjacent to a section of I/O, the devices mapped to that address will be inadvertently read. On the Commodore 64 this does not seem to be a big problem as the first addresses in the I/O section return the coordinates of video sprites; nonetheless, I/O should not be read unintentionally. The source below is for an improved MATCH which does not read past the search buffer. It does require the buffer length to be a signed positive number. The size of this version, not counting the code field, is fifty eight (58) bytes.
Code:
: MATCH  ( ADR1 LEN1 ADR2 LEN2 -- OFFSET FLAG )
   2OVER  2PICK - 1+ 0 MAX BOUNDS  \ Limit the search range to the buffer length
   ?DO                             \ plus one minus the search string length
      2DUP I -TEXT 0=              \ test if address I holds search string
      IF                           \ if so
         I 2NIP + SWAP - TRUE      \ add address I to search string length and
                                   \ subtract buffer address to obtain offset
                                   \ discarding other parameters. Place TRUE on
                                   \ data stack.
         UNLOOP EXIT               \ discard loop parameters and exit.
      THEN
   LOOP
   2DROP NIP FALSE ;               \ Drop all parameters save the buffer length
                                   \ and place FALSE on data stack.

The following version works with unsigned lengths. The size of this version is sixty six (66) bytes.
Code:
: MATCH  ( ADR1 LEN1 ADR2 LEN2 -- OFFSET FLAG )
   2PICK OVER U< 0=                \ If buffer is big enough to contain     
   IF                              \ the search string
      2OVER  2PICK - 1+ BOUNDS     \ Limit the search range to the buffer length
      ?DO                          \ plus one minus the search string length
         2DUP I -TEXT 0=           \ test if address I holds search string
         IF                        \ if so
            I 2NIP + SWAP - TRUE   \ add address I to search string length and
                                   \ subtract buffer address to obtain offset
                                   \ discarding other parameters. Place TRUE on
                                   \ data stack.
            UNLOOP EXIT            \ discard loop parameters and exit.
         THEN
      LOOP
   THEN
   2DROP NIP FALSE ;               \ Drop all parameters save the buffer length
                                   \ and place FALSE on data stack.

With a buffer size of one kilobyte, both of these versions take about the same amount of time as the original using -TEXT . All take ten percent longer than the original using TEXT= because of the extra primitive ( 0= ).
As long as -TEXT does not find a match, the following primitives are executed in the loop.
Code:
2DUP I -TEXT 0= ?BRANCH LOOP

Since both of these versions eliminate reading past the end of the buffer, they both eliminate the need to test a match to eliminate false positives.
And yet there is a better version.
By not using a ?DO LOOP, the following version of MATCH is about an order of magnitude faster. Its size is sixty four (64) bytes.
Code:
: MATCH  ( ADR1 LEN1 ADR2 LEN2 -- OFFSET FLAG )
   2OVER                      \ Preserve copy of original buffer length
   BEGIN
      2PICK OVER SWAP U<      \ Is remaining buffer too small to hold string?
      IF                      \ If so
         D= ROT DROP  EXIT    \ convert top four numbers to FALSE, discard
      THEN                    \ buffer address and exit.
      2OVER 2OVER DROP -TEXT  \ Test for a match. FALSE = match.
   WHILE                      \ While no match
      1 /STRING               \ reduce buffer size by one
      3 PICK C@ SCAN          \ Scan for first character of search string
   REPEAT
   ROT 2NIP - - NIP TRUE ;    \ subtract search string length from remaining
                              \ buffer size then subtract that from original
                              \ buffer size to obtain offset. Discard the
                              \ three addresses.

A copy of the original buffer address and length is made with 2OVER . As long as the search string is not found, this copy is reduced by incrementing its address and decrementing its length.

At the start of the BEGIN loop, the size of the buffer is compared to the size of the search string. If the buffer is too small, the search failed. The buffer address and length will not be equal to the search string address and length (the buffer is smaller than the search string) so D= converts these four numbers to FALSE. The original address of the buffer is rotated to the top of the stack and dropped. MATCH exits with the original buffer size and FALSE on the stack.

If the buffer is not too small, the search string address and length as well as the buffer address are copied to the top of the data stack. -TEXT consumes all three of its parameters and returns a FALSE flag if there is a match.
If the search string is not found, the buffer size is reduced by one byte with /STRING . This is an Ans Forth word I've found useful.

SCAN is a Fleet Forth primitive which is used by WORD and 'TEXT . It scans for a character within a buffer (or string) SCAN takes the address of a buffer, its length and a character. If the character is not found in the buffer, SCAN returns the address just past the buffer and a length of zero. If the character is found, SCAN returns the address in the buffer where the character is found and the remaining length of the buffer.

MATCH copies the address of the search string to the top of the stack and fetches the first character of the search string for use by SCAN. SCAN might not change the buffer size which is why /STRING is used to reduce its size after -TEXT and before SCAN .

If the search string was found by -TEXT , the loop exits. The search string length is subtracted from the remaining buffer size, this is subtracted from the original buffer size. The three addresses are discarded. TRUE is placed on the stack and MATCH exits with the offset to just past the search string and TRUE on the data stack.
At first glance, it looks like this version would be slow; however, the use of SCAN to scan ahead reduces the number of high level loop iterations, resulting in MATCH taking about one tenth the time. I've seen it take closer to one thirteenth the time.
There is one case where this version is much slower, but that is a case not likely to be seen in practice.
For example:
Code:
100 BLOCK B/BUF ASCII L FILL
100 BLOCK " LOOP" COUNT MATCH

With a one kilobyte buffer filled with the character 'L' and a search for any word which begins with 'L', this version of MATCH takes about 2.4 times as long as the other versions.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 05, 2024 10:09 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
To put things in perspective, Fleet Forth's new MATCH was used to find DECIMAL in the following screen from Blazin' Forth's system loader.
Code:
( I/O EXTENSIONS PADDLE PADDLE BUTTON                SDBJUN85 )
HEX
CODE PADDLE   SEI, DC02 LDA, PHA, 0C0 # LDA, DC02 STA,
     BOT LDA, .A LSR, 80 # LDA, TAY, CS IF, .A LSR, THEN,
     DC00 STA, BEGIN, NOP, DEY, 0< UNTIL,
     BOT LDA, 1 # EOR, TAY, D419 ,Y LDA,
     BOT STA, PLA, DC02 STA, CLI, NEXT JMP, END-CODE
CODE PADDLEBUTTON  BOT LDA, 2 # CMP, CS
     IF, DC01 LDA, ELSE, DC00 LDA, THEN, PHA,
     BOT LDA, .A LSR, PLA, CS IF, 8 # AND, ELSE, 4 # AND, THEN,
     0= IF, DEY, THEN, TYA, PHA, PUT JMP, END-CODE
DECIMAL

To obtain more acurate timing data, MATCH was run in a test word to get it to run the same match one thousand times. The times were on a Commodore 64 simulation running at 1 MHz.
Code:
: TIMEIT
   ' 0 0 JIFFY!  EXECUTE  JIFFY@ CR D. ;
DEFER TESTWORD
: TESTIT
   0
   DO
      TESTWORD
   LOOP ;

On the Commodore 64 sixty (60) jiffies are in one second.
Running the following testword took 36 jiffies.
Code:
: TEST
   2OVER 2OVER
   2DROP
   2DROP ;
' TEST IS TESTWORD
17 BLOCK B/BUF " DECIMAL" COUNT
1000 TIMEIT TESTIT
2DROP 2DROP

Running MATCHTEST took 5867 jiffies.
Code:
: MATCHTEST
   2OVER 2OVER MATCH 2DROP ;
' MATCHTEST IS TESTWORD
17 BLOCK B/BUF " DECIMAL" COUNT
1000 TIMEIT TESTIT
2DROP 2DROP

Subtracting the overhead of duplicating parameters and dropping results, running MATCH 1000 times took around 5831 jiffies or about 0.0972 seconds to run once to find DECIMAL in this dense screen of Blazin' Forth system loader source.
MATCH took closer to 0.04185 seconds to find MAL in that same screen.


Top
 Profile  
Reply with quote  
PostPosted: Sun Aug 18, 2024 8:55 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
The Commodore 64 kernal routine CHRIN is called in a loop to return one character at a time. This routine is the Commodore 64's screen editor so it does not return any characters until after it receives a carriage return. So that the Commodore's screen editor could be used by Fleet Forth while still allowing multitasking and avoid "stuffing" a carriage return into the keyboard buffer (which introduces problems of its own), Fleet Forth's (KEY) , the vector for the deferred word KEY , was split into two parts.
Code:
NH CODE WKEY  ( -- )
   BLINK.ENABLE STY
   >FORTH
      BEGIN  PAUSE #KBD C@  UNTIL
   >ASSEM   // LEAVES Y-REG=0
   INY  BLINK.TIMER STY
   BEGIN
      BLINK.ON? LDA
   0= UNTIL
   BLINK.ENABLE STY
   NEXT JMP  END-CODE

CODE ?KEY  ( -- C )
   XSAVE STX
   $FFE4 JSR  // GETIN
   XSAVE LDX
   APUSH JMP  END-CODE

: (KEY)  ( -- C )
   WKEY  ?KEY ;

' (KEY) IS KEY

NH is a metacompiler word used to switch off header creation.
(EXPECT) , the vector for the deferred word EXPECT , uses both halves of (KEY) , WKEY and ?KEY . WKEY switches on cursor blinking and waits in a loop for a key press, but does not read and remove a character from the keyboard buffer. It also executes the task switcher, PAUSE , while in the loop.
After there is a character available in the keyboard buffer, (EXPECT) checks if the character is a carriage return. If it is not, (EXPECT) uses ?KEY to get the character from the buffer. (EXPECT) echoes the character to the screen. If the character is a carriage return, (EXPECT) shifts to low level and uses the Commodore 64 kernal routine CHRIN to read the characters from the line on the screen and return them one character at a time with each call to CHRIN .
When I wanted to add a the ability to intercept function keys, to support an experimental editor, that capability was added directly to (EXPECT) because it used the components of (KEY) but did not use KEY .
KEY was not used to avoid additional delay in reading keys from the keyboard buffer.
A slight modification to the headerless word WKEY overcomes this problem.
Code:
NH CODE WKEY  ( -- )
   #KBD LDA
   0= IF
      BLINK.ENABLE STY
      >FORTH
         BEGIN  PAUSE #KBD C@  UNTIL
      >ASSEM   // LEAVES Y-REG=0
      INY  BLINK.TIMER STY
      BEGIN
         BLINK.ON? LDA
      0= UNTIL
      BLINK.ENABLE STY
   THEN
   NEXT JMP  END-CODE

Now if a character is in the keyboard buffer, WKEY branches directly to the jump to NEXT . Enabling cursor blinking or pausing for another task is only done if the keyboard buffer is empty. With this improvement there is no real penalty when following WKEY with KEY rather than ?KEY ; therefore, ?KEY was replaced with KEY in (EXPECT) .
Since KEY is a deferred word, it can be set to a version of (KEY) capable of intercepting function keys. Since (EXPECT) now uses KEY , Function key interception can take place in (EXPECT) through KEY ; therefore, that functionality was removed from Fleet Forth's (EXPECT) .

Here is one possible way of implementing that functionality.
Code:
: FKEY  ( -- CHAR )
   BEGIN
      (KEY)
      7 OVER #133 - U< ?EXIT
      #63 +  4 /MOD SWAP 2* +
      " F0KEY" TUCK 2+ C!
      FIND
      IF  DUP EXECUTE  THEN
      DROP
   AGAIN ;

' FKEY IS KEY

Since KEY is supposed to return a key press even if it is intercepting function keys, FKEY is in a loop until a non-function key is received.
Here is a trivial test case.
Code:
CODE BORDER?  ( -- CHAR )
   $D020 LDA  $F # AND
   AYPUSH JMP
   END-CODE

: F1KEY  ( -- )  BORDER? 1+ BORDER ;
: F2KEY  ( -- )  BORDER? 1- BORDER ;

BORDER is a Fleet Forth word which takes a number and sets the border color on the Commodore 64.

Keep in mind that removing this functionality from (EXPECT) means that it doesn't have to exist at all! It can be added later, through a new vector for the deferred word KEY *IF* and only if the functionality is actually desired.


Top
 Profile  
Reply with quote  
PostPosted: Tue Sep 10, 2024 9:20 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
Fleet Forth's WHERE reports where an error occurred and it has been enhanced to make it more useful when using ordinary text files.
Fleet Forth has a variable, LINES , which holds the count of lines ( carriage returns) since the last time LINES was reset by typing PAGE or emitting the clear screen character or even switching LINES off.
When loading from a block WHERE shows the block number and line number where the error occurred, as shown in this session log excerpt.
Code:
1 RAM LOAD
SCR# 16385 LINE# 5
   BINARY  1100 AND  DECIMAL
   ^^^^^^
WHAT?

There was an error because BINARY is not in the dictionary. These days I wouldn't even try changing the number base.
Code:
   %1100 AND

When not loading a block WHERE now shows the value in LINES , the number of lines typed at the console.
Code:
   OK
   OK
   OK
   OK
   OK
OCTAL
LINE# 7
OCTAL
^^^^^
WHAT?

As with BINARY , OCTAL is not in the dictionary.
Fleet Forth's INCLUDED , an optional extra, takes advantage of this fact. INCLUDED clears LINES and increments it for each line loaded.
Code:
: ?FNAME  ( CNT -- )
   0= ABORT" FILE NAME REQUIRED" ;

: FOPEN  ( ADR CNT -- CMD# )
   DR# CLOSE  DCLOSE  CMD -ROT
   DR# DUP DUP OPEN IOERR  ?DISK ;

: FEXPECT  ( ADR CNT -- )
   DR# CHKIN IOERR (EXPECT) CLRCHN ;

: INCLUDED ( ADR CNT -- )
   DUP ?FNAME FOPEN         ( CMD#)
   >R  LINES OFF
   BEGIN
      -2 BUFFER DUP B/BUF   ( ADR ADR $400)
      FEXPECT  STATUS       ( ADR STATUS)
      LINES @  2>R  SPAN @  ( ADR SPAN)
      EVALUATE
      2R>  1+ DUP BORDER    ( STATUS LINES)
      LINES !               ( STATUS)
      DONE? OR              ( FLAG)
   UNTIL
   DR# CLOSE R> CLOSE
   BOOTCOLORS ;

The lines from an INCLUDED file can and probably will change what is on the data stack; therefore, INCLUDED removes what it places on the data stack, other than the address and count of the line to be evaluated, just before evaluating a line.
To clarify the operation of INCLUDED , the potential changes to the data stack due to interpretation and compilation from the source are not shown.
the only parameters on the data stack when EVALUATE executes, other than what was already on the stack before the filename and length were placed there for INCLUDED and parameters added or removed by the loading of the source, are the address of the buffer and the length of the line read from disk.
EVALUATE consumes the address and length leaving nothing in the way, as can be seen from this session log excerpt.
Code:
.S EMPTY  OK
" .S" COUNT EVALUATE EMPTY  OK
.S EMPTY  OK
1 2 4  3 5 2>A  OK
.S     1     2     4  OK
.AS     3     5  OK
" CR .S CR .AS" COUNT EVALUATE
    1     2     4
    3     5  OK
.S     1     2     4  OK
.AS     3     5  OK
SP! AP!  OK

SP! does not take a parameter. It uses the value of the user variable SP0 to clear the data stack. Likewise, AP! does not take a parameter. It uses the value of the user variable AP0 to clear the auxiliary stack.
WHERE has one other change. It temporarily changes BASE to decimal.

As a bonus, this new version of WHERE also makes it easier to find where an error occurred when using Fleet Forth with the Commodore 64 simulator and pasting text into the simulation. Set LINES to TRUE (-1) before pasting in text (-1 to compensate for the carriage return).


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 15, 2024 11:00 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
The Commodore 64 returns the character value $A0, $80 ORed with SPACE, when the space key is pressed while the shift key pressed. I can not see any value in returning a 'shifted' SPACE character; although, it has bitten me a few times. If I try to save the Forth system with the following name "FORTH & TOOLS" but I don't release the shift key quickly enough, there will be a shifted SPACE in the filename. This shifted SPACE looks exactly like a SPACE character while typing, so I can not see the typo. When the new Forth system is saved to disk it will show up in the directory as "FORTH &"TOOLS .
It can be loaded with LOAD"FORTH &",8,1 ;however, this is not the intended filename and I have become tired of slowly typing things to avoid the shifted SPACE when I need to type a shifted character.
For this reason I rewrote Fleet Forth's ?KEY , which is used by (KEY) . The original version:
Code:
HEX
CODE ?KEY  ( -- C )
   XSAVE STX
   FFE4 JSR  // GETIN
   XSAVE LDX
   APUSH JMP  END-CODE

And the new version:
Code:
HEX
CODE ?KEY  ( -- C )
   XSAVE STX
   $FFE4 JSR  // GETIN
   XSAVE LDX
   $A0 # CMP
   0= IF  BL # LDA  THEN
   APUSH JMP  END-CODE

If a shifted SPACE is typed, the new version of ?KEY will replace it with a normal SPACE.
The way Fleet Forth's (EXPECT) is written, it is also affected by this change.
Can anyone think of a reason why Fleet Forth should not replace a shifted SPACE with a normal SPACE?


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 15, 2024 11:25 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8543
Location: Southern California
I haven't used a C64, but in other contexts, having another character that looks like a space is nice for example when you want to prevent automatic line-wrap from separating two words.  It also sounds handy for having multi-word file names, with what looks like spaces in them.  (I think it's done differently though in modern systems that allow super long file names.)  On my website and in forum posts and comments under videos and articles, I also use the non-breaking space character (&nbsp; in html, otherwise <Ctrl><Shift>U 00a0 <Ctrl><Shift>U <BS>, which I've gotten super fast at doing on my computer, or <Alt>160 in others) to keep modern software from discarding the second space between sentences, as such software was written by people too young to have taken the standard two years of high-school typing (back when it was all typewriters) where we learned standard block format and we'd be marked down if we only put one space between sentences (or they just forgot).  Even after all these years that it's been done with only one space between sentences, I still have to back up sometimes and re-read something because it didn't make sense the first time because I didn't catch that a new sentence was being started, especially if the next sentence starts with a number, or a lower-case letter like "eBay," or something that always starts with a capital, like someone's name.

_________________
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: Sun Sep 15, 2024 11:47 pm 
Offline

Joined: Fri May 05, 2017 9:27 pm
Posts: 895
My Fleet Forth for the Commodore 64 does not have automatic line wrap nor does it automatically eliminate "excess" spaces.
Since KEY is a deferred word, if there is a need for a shifted SPACE character, a version of (KEY) , the vector for KEY , could be written which converts the value returned from one of the Commodore 64 function keys to a shifted SPACE. This would at least be an intentional shifted SPACE, not an accidental one.
The Commodore 64 already supports filenames with spaces in them. If a shifted SPACE is accidentally typed, it messes up the filename.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 354 posts ]  Go to page Previous  1 ... 20, 21, 22, 23, 24

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


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: