The short answer is, it can’t be done. The long answer is that it is possible, but not the way you’re doing it, and it’s likely to be very difficult. Most old-school BASICs, including the Commodore 64, don’t have an eval function, which is basically what you’re talking about. (According to David Lien, BBC BASIC had an EVAL command, and one of the Apple BASICs had an EXEC command that could read text from a file as if it had been typed from a keyboard, which would allow a slower emulation of an EVAL command.)
What the Commodore 64 and most old-school BASICs do have are calls to existing machine language routines. The BASIC commands are in memory somewhere, and you can transfer control to those commands if you know the memory address where the routine resides. In Microsoft variants, this is often the EXEC command. On the Commodore 64, it’s the SYS command.
The syntax is SYS <ADDRESS>
, where ADDRESS is the memory location you want to transfer control to. As long as that address contains a routine that has a return code, it will do its job and then transfer control back to your BASIC program.
Often, you’ll combine the SYS call with some POKEs (to provide data to the machine language subroutine) and/or some PEEKs (to look at what the routine has done).
Here’s an example inspired by the C64 Wiki:
9 rem clear screen
10 print chr$(147)
19 rem random cursor column and line
20 co = int(rnd(1)*40)
30 ln = int(rnd(1)*25)
39 rem position cursor
40 poke 211,co
50 poke 214,ln
60 sys 58640
70 print "x";
79 rem wait for keypress and quit
80 get i$
90 if i$="" then 80
91 if i$="r" then 20
99 end
This program creates a random number from 0 to 39 for CO, and then from 0 to 24 for LN. It pokes the value of C into memory location 211, the value of LN into memory location 214, and then calls the machine language routine at memory location 58640.
That routine interprets memory location 211 as the column, and location 214 as the line, to place the cursor at. So what this program does is randomly print an “x” somewhere on the screen; if you press “r”, it does it again, until you press some other key.
The program is in lower case because I used the VICE emulator for testing, and VICE (at least on macOS) automatically converts lowercase to uppercase, and uppercase to graphics characters, when pasting.
In your example, it’s much more difficult. While the entry point for the LIST command’s routine is easily discovered (42652, or hex $A69C), how you provide the line number or range to that routine is less easily discovered. Judging from this Commodore 64 disassembly, it may need to be provided as text. (Follow the LIST routine to the LINGET routine in the disassembly.)
And then you’d need to do this for every command you wanted to emulate.
It might also be possible to run your string through the BASIC evaluator routine at $AD9E for a true eval, but that would likely be an even more involved task.
If I were forced to do something like this, I would look into these options:
- Use PEEK to locate a dummy line in a subroutine, and then POKE to rewrite that dummy line as the line to evaluate; then, GOSUB to that subroutine.
- Use the LOAD command to load the line to be evaluated after first writing it as a BASIC file.
- Because BASIC on the C64 is interactive, there should be a routine to call that evaluates individual lines. Find that, and determine how to provide that call the text you want evaluated.
- Scour the magazines and bulletin boards to see if anyone wrote an EVAL command for C64 BASIC back in the day.
Here, for example, is a very rough example of option 1:
10 rem example of how to rewrite a line of code
20 ev$ = "list"
30 gosub 100
99 end
100 rem subroutine to create eval code
109 rem locate dummy line 1010;ls=line start;nl=next line
110 ls = 2049:rem start of basic in ram
120 nl = peek(ls)+peek(ls+1)*256
130 if peek(ls+2)+peek(ls+3)*256 <> 1010 then ls=nl:goto 120
139 rem found location, start writing
140 ls=ls+5
150 for i=1 to len(ev$)
160 poke ls+i,asc(mid$(ev$,i,1))
170 next i
180 poke ls+i,0
199 return
1000 rem subroutine to place eval code
1010 rem dummy line with lots of text to make it possible to put code here
1020 return
If you run this, you will see that line 1010 changes from a long remark to a remark that contains what is in EV$. There would still be a lot of work to do get this to be a real eval, however:
- It does not verify that EV$ is short enough to fit on line 1010; if EV$ is long, it will continue past the end of line 1010 and overwrite 1020 as well.
- It does not replace the text of EV$ with the tokenization necessary for the code to actually run. You would need a means of converting the word “LIST” to the tokenization of LIST (most likely either an array or a SYS call) for this to actually run the LIST (or whatever else is in EV$).
- It doesn’t update the length of line 1010, although this may not be necessary. Updating the length of line 1010 would also mean having to move line 1020 in memory, whereas leaving the length of line 1010 alone means not having to move line 1020.