如何在 c64 的 basic 中以编程方式输入命令?
How to enter commands programmatically in c64's basic?
我想在commodore 64中写一个非常简单的基本程序,输入其他基本命令。
这是一个例子:
10 print"list"+chr$(13)
这会打印列表但不会按回车键。
我的期望是得到列表命令的结果。
我怎样才能做到这一点?
简而言之,这是做不到的。长话短说,这是可能的,但不是你这样做的方式,而且可能非常困难。大多数老式 BASIC,包括 Commodore 64,都没有 eval 函数,这基本上就是您所说的。 (According to David Lien,BBC BASIC 有一个 EVAL 命令,其中一个 Apple BASIC 有一个 EXEC 命令,可以从文件中读取文本,就好像它是从键盘输入的一样,这将允许更慢的 EVAL 仿真命令。)
Commodore 64 和大多数老式 BASIC 做的 是对现有机器语言例程的调用。 BASIC 命令在内存中的某个地方,如果您知道例程所在的内存地址,就可以将控制转移到这些命令。在 Microsoft 变体中,这通常是 EXEC 命令。在 Commodore 64 上,它是 SYS 命令。
语法是 SYS <ADDRESS>
,其中 ADDRESS 是您要将控制权转移到的内存位置。只要该地址包含一个具有 return 代码的例程,它就会完成它的工作,然后将控制权转移回您的 BASIC 程序。
通常,您会将 SYS 调用与一些 POKE(为机器语言子例程提供数据)and/or 一些 PEEK(查看例程完成了什么)结合使用。
这是一个受 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
此程序为 CO 创建一个从 0 到 39 的随机数,然后为 LN 创建一个从 0 到 24 的随机数。它将 C 的值放入内存位置 211,将 LN 的值放入内存位置 214,然后调用内存位置 58640 的机器语言例程。
该例程将内存位置 211 解释为列,将位置 214 解释为行,以放置光标。所以这个程序所做的是在屏幕的某处随机打印一个“x”;如果你按“r”,它会再次这样做,直到你按下其他键。
程序是小写的,因为我使用the VICE emulator进行测试,VICE(至少在macOS上)在粘贴时自动将小写转换为大写,将大写转换为图形字符。
在你的例子中,这要困难得多。虽然 LIST 命令例程的入口点是 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,但可能需要以文本形式提供。 (反汇编中按照LIST套路到LINGET套路。)
然后您需要为每个要模拟的命令执行此操作。
也可能 运行 通过 the BASIC evaluator routine at $AD9E 您的字符串进行真正的评估,但这可能是一项更复杂的任务。
如果我被迫做这样的事情,我会考虑以下选项:
- 使用PEEK定位子程序中的虚线,然后POKE重写该虚线作为要评估的线;然后,GOSUB 到该子例程。
- 使用 LOAD command 加载要评估的行,先将其写入 BASIC 文件。
- 因为 C64 上的 BASIC 是交互式的,所以应该有一个例程可以调用来评估各个行。找到它,并确定如何提供调用您想要评估的文本。
- 搜索杂志和公告板,看看是否有人在当天为 C64 BASIC 编写了 EVAL 命令。
例如,这里是选项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
如果您 运行 这样做,您将看到第 1010 行从长注释变为包含 EV$ 中内容的注释。要使它成为一个真正的评估,还有很多工作要做,但是:
- 它不验证 EV$ 是否足够短以适合第 1010 行;如果 EV$ 很长,它将继续超过 1010 行的末尾并覆盖 1020。
- 它不会用 tokenization necessary for the code to actually run 替换 EV$ 的文本。您需要一种方法将单词“LIST”转换为 LIST 的标记化(很可能是数组或 SYS 调用),以便实际上 运行 LIST(或 EV$ 中的任何其他内容)。
- 它不会更新第 1010 行的长度,尽管这可能不是必需的。更新第 1010 行的长度也意味着必须在内存中移动第 1020 行,而单独保留第 1010 行的长度意味着不必移动第 1020 行。
解决方案非常简单:
10 list
您基本上可以输入任何基本命令,它会正常工作:
10 print"Loading..."
20 load"*",8,1
实际上,这就是我修补一些小自动化所需要的全部内容。
执行从字符串构建的 BASIC 命令的一种方法是操纵键盘缓冲区。
考虑以下 BASIC 子例程,它执行您在 GOSUBing 之前放入 CM$ 的任何 BASIC 命令:
100 PRINT CHR$(147)CHR$(17)CHR$(17)CHR$(17);CM$;
110 POKE631,13:POKE632,67:POKE633,79:POKE634,78:POKE635,84:POKE636,13
120 POKE198,6
130 PRINTCHR$(19):END
140 RETURN
100 清除屏幕,将光标向下移动几次,然后将您的命令以 CM$ 格式打印到空白屏幕上。
110 戳 RETURN,接着 'C' 'O' 'N' 'T' 再戳 RETURN 到键盘缓冲区。
120 告诉系统缓冲区中有 6 个新击键。
130 将光标移动到屏幕顶部,结束程序
这就是奇迹发生的地方。 C64 将开始处理键盘缓冲区中的字符。
140 这是执行命令后 BASIC 程序 return 到的位置。由于这是 GOSUBing 的例程,我只是在这里放了一个 RETURN 命令。
要测试子例程,请添加以下行:
10 CM$="LIST":GOSUB100
20 PRINT"MY PROGRAM CONTINUED RUNNING!": END
这是一个关于它的有趣页面:
Commodore 64 keyboard buffer tricks: deleting and creating BASIC lines from BASIC
我想在commodore 64中写一个非常简单的基本程序,输入其他基本命令。
这是一个例子:
10 print"list"+chr$(13)
这会打印列表但不会按回车键。
我的期望是得到列表命令的结果。
我怎样才能做到这一点?
简而言之,这是做不到的。长话短说,这是可能的,但不是你这样做的方式,而且可能非常困难。大多数老式 BASIC,包括 Commodore 64,都没有 eval 函数,这基本上就是您所说的。 (According to David Lien,BBC BASIC 有一个 EVAL 命令,其中一个 Apple BASIC 有一个 EXEC 命令,可以从文件中读取文本,就好像它是从键盘输入的一样,这将允许更慢的 EVAL 仿真命令。)
Commodore 64 和大多数老式 BASIC 做的 是对现有机器语言例程的调用。 BASIC 命令在内存中的某个地方,如果您知道例程所在的内存地址,就可以将控制转移到这些命令。在 Microsoft 变体中,这通常是 EXEC 命令。在 Commodore 64 上,它是 SYS 命令。
语法是 SYS <ADDRESS>
,其中 ADDRESS 是您要将控制权转移到的内存位置。只要该地址包含一个具有 return 代码的例程,它就会完成它的工作,然后将控制权转移回您的 BASIC 程序。
通常,您会将 SYS 调用与一些 POKE(为机器语言子例程提供数据)and/or 一些 PEEK(查看例程完成了什么)结合使用。
这是一个受 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
此程序为 CO 创建一个从 0 到 39 的随机数,然后为 LN 创建一个从 0 到 24 的随机数。它将 C 的值放入内存位置 211,将 LN 的值放入内存位置 214,然后调用内存位置 58640 的机器语言例程。
该例程将内存位置 211 解释为列,将位置 214 解释为行,以放置光标。所以这个程序所做的是在屏幕的某处随机打印一个“x”;如果你按“r”,它会再次这样做,直到你按下其他键。
程序是小写的,因为我使用the VICE emulator进行测试,VICE(至少在macOS上)在粘贴时自动将小写转换为大写,将大写转换为图形字符。
在你的例子中,这要困难得多。虽然 LIST 命令例程的入口点是 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,但可能需要以文本形式提供。 (反汇编中按照LIST套路到LINGET套路。)
然后您需要为每个要模拟的命令执行此操作。
也可能 运行 通过 the BASIC evaluator routine at $AD9E 您的字符串进行真正的评估,但这可能是一项更复杂的任务。
如果我被迫做这样的事情,我会考虑以下选项:
- 使用PEEK定位子程序中的虚线,然后POKE重写该虚线作为要评估的线;然后,GOSUB 到该子例程。
- 使用 LOAD command 加载要评估的行,先将其写入 BASIC 文件。
- 因为 C64 上的 BASIC 是交互式的,所以应该有一个例程可以调用来评估各个行。找到它,并确定如何提供调用您想要评估的文本。
- 搜索杂志和公告板,看看是否有人在当天为 C64 BASIC 编写了 EVAL 命令。
例如,这里是选项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
如果您 运行 这样做,您将看到第 1010 行从长注释变为包含 EV$ 中内容的注释。要使它成为一个真正的评估,还有很多工作要做,但是:
- 它不验证 EV$ 是否足够短以适合第 1010 行;如果 EV$ 很长,它将继续超过 1010 行的末尾并覆盖 1020。
- 它不会用 tokenization necessary for the code to actually run 替换 EV$ 的文本。您需要一种方法将单词“LIST”转换为 LIST 的标记化(很可能是数组或 SYS 调用),以便实际上 运行 LIST(或 EV$ 中的任何其他内容)。
- 它不会更新第 1010 行的长度,尽管这可能不是必需的。更新第 1010 行的长度也意味着必须在内存中移动第 1020 行,而单独保留第 1010 行的长度意味着不必移动第 1020 行。
解决方案非常简单:
10 list
您基本上可以输入任何基本命令,它会正常工作:
10 print"Loading..."
20 load"*",8,1
实际上,这就是我修补一些小自动化所需要的全部内容。
执行从字符串构建的 BASIC 命令的一种方法是操纵键盘缓冲区。 考虑以下 BASIC 子例程,它执行您在 GOSUBing 之前放入 CM$ 的任何 BASIC 命令:
100 PRINT CHR$(147)CHR$(17)CHR$(17)CHR$(17);CM$;
110 POKE631,13:POKE632,67:POKE633,79:POKE634,78:POKE635,84:POKE636,13
120 POKE198,6
130 PRINTCHR$(19):END
140 RETURN
100 清除屏幕,将光标向下移动几次,然后将您的命令以 CM$ 格式打印到空白屏幕上。
110 戳 RETURN,接着 'C' 'O' 'N' 'T' 再戳 RETURN 到键盘缓冲区。
120 告诉系统缓冲区中有 6 个新击键。
130 将光标移动到屏幕顶部,结束程序
这就是奇迹发生的地方。 C64 将开始处理键盘缓冲区中的字符。
140 这是执行命令后 BASIC 程序 return 到的位置。由于这是 GOSUBing 的例程,我只是在这里放了一个 RETURN 命令。
要测试子例程,请添加以下行:
10 CM$="LIST":GOSUB100
20 PRINT"MY PROGRAM CONTINUED RUNNING!": END
这是一个关于它的有趣页面: Commodore 64 keyboard buffer tricks: deleting and creating BASIC lines from BASIC