Wednesday 11 April 2018

GOSUB variable in C64 BASIC

While writing a program with a student, we wanted to make an efficient dynamic jump table under C64 BASIC 2, where could have an array of line numbers that represent event handlers for particular events.  The idea is that we would then GOSUB JT%(EVENT) to dispatch to the appropriate handler, and be able to freely update the jump table as we go.

However, C64 BASIC doesn't allow this.  I am not really sure why they made the GOTO command (which is the heart of GOSUB also) unable to resolve variables. It would have meant that the ON ... GOTO command could have been left out, saving ROM space, for example.

Anyway, a bit of poking around found that other people have tried to do this, but none of their solutions really seemed particularly nice.

So, I thought, is it possible to do this just using BASIC 2, and without any assembly routines?

The answer is YES! Using the rather under utilised technique of self modifying BASIC programs.  Is this a horror beyond comprehension?  Probably.  But it also works a treat :)

So, here is how it works:

1. Have a GOSUB command somewhere in your basic program that you can reliably find in memory. I made it GOSUB,21456 in my program.  The number doesn't matter, provided it is 5 digits long, so that we have space to overwrite with any valid line number we might encounter.  The comma is there because GOSUB+COMMA = SYNTAX ERROR, and thus should not exist in any sensible program*. 

2000 REM THIS IS THE ROUTINE THAT WILL GET MODIFIED DURING EXECUTION
2010 GOSUB,21456
2020 RETURN

2. Find out where in memory that line lives, but searching for the GOSUB token (141) and the comma:

1000 FOR JA = 2048 TO 40959: IF PEEK(JA-1)<>141 OR PEEK(JA)<>44 THEN NEXT: RETURN


(By doing the comparison in the negative sense, the loop continues until it finds the location, and then aborts as soon as it finds it).

3. To GOSUB to any line, have a routine like this:

1100 REM GOSUB TO LINE IN VARIABLE LN
1110 LN$=STR(LN): REM GET STRING VERSION OF LINE NUMBER
1120 REM REPLACE ,21456 WITH CORRECT LINE NUMBER
1130 FORI=0TO5:POKEJA+I,32: REM FIRST RUB OUT WITH SPACES IN CASE LINE NUMBER IS SHORT
1140 FORI=0TOLEN(LN$)-1:POKEJA+I,ASC(RIGHT$(LEFT$(LN$,I),1)):NEXT
1150 GOSUB 2000
1160 POKEJA,44: REM PUT THE COMMA BACK READY FOR NEXT TIME
1170 RETURN

(The astute reader will realise that they can merge steps 1 and 3 to give something like:

1100 REM GOSUB TO LINE IN VARIABLE LN
1110 LN$=STR(LN): REM GET STRING VERSION OF LINE NUMBER
1120 REM REPLACE ,21456 WITH CORRECT LINE NUMBER
1130 FORI=0TO5:POKEJA+I,32: REM FIRST RUB OUT WITH SPACES IN CASE LINE NUMBER IS SHORT
1140 FORI=0TOLEN(LN$)-1:POKEJA+I,ASC(RIGHT$(LEFT$(LN$,I),1)):NEXT
1150 GOSUB,00000
1160 POKEJA,44: REM PUT THE COMMA BACK IN CASE WE WANT TO RUN AGAIN
1170 RETURN

Now you can GOSUB to any line you like with a simple program like:

10 GOSUB 1000: REM ONE-TIME ONLY LOOKUP PATCH ADDRESS
20 LN=12345: GOSUB 1100: REM GOSUB TO LINE 12345
999 END

Here is a complete program, and an example of it running. It is short enough to fit on a single screen, even with some comments!




* I don't claim that this program is sensible. It is also possible for it to show up in a string that has shift-M followed by a comma, but I can avoid that easily enough.

12 comments:

  1. Here is a really odd thing for you to try:

    0 DEF FN A(X) = 10
    1 PRINT FN A(0)
    2 GO TO FN A(0)
    3 PRINT "END GRACEFULLY"
    4 END
    10 PRINT "STOP"
    11 STOP

    ReplyDelete
    Replies
    1. Okay, now that IS weird! So there is no syntax error, but it works as GOTO 0, from what I can see.

      Delete
  2. I'm at work so I don't have my C128 or C64Mini here to test, but what happens if you change the value in line 2 to:

    2 GO TO FN A(10)

    Is it possible that it's somehow ignoring the FN A() bit and only looking at the number within the brackets?

    ReplyDelete
  3. 2 GOTO PRINT
    works the same. evaluates to GOTO 0

    ReplyDelete
  4. Hacker from a C64 user group did a mod to BASIC to allow params to be sent with a gosub

    https://www.commodoreserver.com/BlogEntryView.asp?EID=EB7662805E4B4A7ABA2623257BCC642E

    ReplyDelete
    Replies
    1. Thanks for the link -- this is quite an interesting little extension.

      Delete
  5. I remember -- and occasionaly still use (!) -- the SuperBASIC from Compute! magazine (maybe from 1985). It re-jiggered the GOTO to work with a variable. So GOTO X would go to line X. Just one of many useful improvements it gave in the same 8k footprint. Although, I wish it had had the DOS Wedge in it as well.

    ReplyDelete
  6. Another option along the lines of what you done would be to make the last line of your program like so:

    63999 ON IX GOTO 99999,99999,99999,etc...however many items in jump table

    Then in your code instead of searching and peeking through all the values of the code you could just use fre(0) to calculate the end of the program which will be just past your last 9 by 2 bytes. Do a little calculation to the 99999's you want, use your routine to clear with space and then put in the dynamic line no you want. I'd just make sure you maintain 5 characters there with leading spaces. Then you can just set IX to 1,2,3,4,5.....and goto or gosubb 63999.

    ReplyDelete
  7. Gosh forgot about variables so scratch that *blush*

    ReplyDelete
  8. Okay instead of using fre(0) use the formula:
    ?peek(46)*256+peek(45) - 4
    this will be the location of the last 9. Boy it is tough when you can't edit and change your answers before review :).

    ReplyDelete
  9. i am beginning to think that BASIC is not actually an easy programming language to use or understand

    ReplyDelete
    Replies
    1. Used _sensibly_ it is. What I did here is _not_ sensible.

      Delete