构建mrbuffer:适用于31年历史的计算机的文本编辑器
Apple IIgs于1986年9月15日问世。它具有2.8kw的WDC 65816 CPU(为SNES和那个时代的其他类似计算机供电的同一个CPU,具有24位寻址的16位CPU),256k或1MB RAM(可升级到8 MB)和一个Ensoniq 8位立体声合成器(这是Apple II系列扬声器的受欢迎升级)。 作为参考,原始的Apple II系列围绕6502 CPU(8位,16位寻址)构建,并且在IIe和II +中最多具有1 MB RAM。 但是,直到1988年,Apple才为新计算机发布了能够有效利用更新硬件的操作系统。 GS / OS用本地16位代码编写,更重要的是,旨在通过其新的闪亮GUI使用。
本文是关于从头到尾如何为IIg构建一个小型“文本编辑器”的信息。
我们需要处理什么?
在该项目开始时,我提出了一些要求。 我的文字编辑器应:
- 可从GS / OS启动,无论它是否在操作系统中实际运行在窗口中。
- 不会占用超过256k的RAM,因此它可以在任何 IIg上运行。
- 以本机16位模式运行。
如果您不熟悉此处理器,那么最后一个要点很令人困惑。 在IIg推出时,已经有大量与原始Apple II(使用6502)兼容的软件。 65816具有可切换的“仿真模式”,通过有选择地将其累加器和索引寄存器的宽度“减半”,可以有效地将'816变为6502(其他内容,我们将在后面介绍)。
GS / OS是一个16位的“一次单进程”操作系统。 对我来说,这意味着我的文本编辑器将在完成加载后成为系统上唯一正在运行的程序。 操作系统将保留在RAM中,但为了避免破坏OS代码,我将不得不放弃写入某些内存位置。 GS / OS将引导我的程序,然后将对计算机的完全控制权传递给它。 要退出该应用程序,我必须跳转(本机模式下的jsl
)到操作系统的入口点,然后它将接管该操作系统。
就编程环境而言,我们的选择看起来有些惨淡:
- ORCA / C ,ANSI C编译器,但必须在裸机或仿真器上运行。
- cc65 ,ANSI C编译器,但仅适用于6502。
- Merlin16 ,一个65816汇编程序,但必须在裸机或仿真器上运行。
- Merlin32 ,Merlin16宏兼容的汇编程序,可以在任何可以从源代码进行构建的现代计算机上运行。
我拥有物理IIg,但是希望能够通过现代计算机轻松进行开发。 此外, 在模拟器内部进行构建也很笨拙,并且无法通过构建脚本轻松地进行自动化(即,不是很好地利用时间来入侵模拟器,从而无法编写UI操作脚本),因此,唯一可行的选择是使用Merlin32。 另外,我将内容真正部署到物理计算机上的唯一方法是将CF卡与我的桌面保持气隙,这是唯一一台使用多卡读卡器的计算机。 在决定使用Merlin32之后,首要任务是弄清楚如何用我的程序写入磁盘映像,以便仿真器可以加载它。 我必须为BrutalDeluxe软盘映像工具编写POSIX文件API绑定 ,因为它仅在Windows上运行(这是一种基本的罪过),并且它是唯一可以轻松编写脚本的工具。 #pragma once
明智地使用#pragma once
和一些stat()
使我在几个小时内制作了Apple II磁盘映像。
COUT的旅程
不让您对困难的项目失去理智的唯一方法是尝试以尽可能多的面向目标的方式编写软件。 任何编辑器都必须能够实现的最低限度的功能是在屏幕上显示字符串。 在大多数编程语言中,您可以调用print()
某些变体来完成此任务。 在Apple II世界中,我们没有这个能力,尽管这有点是谎言,因为Apple II ROM包含一系列实现COUT
, RDKEY
和许多其他实用程序的工具箱功能。 工具箱实用程序的工作流程很简单:为寄存器准备一些数据(对于COUT
,将一个字符加载到A
寄存器,称为累加器),然后从程序中将jsr
加载到例程,它执行一些任意任务,然后调用rts
将控制权返回给您的代码。 这似乎正是我们想要的,所以为什么我们不能使用它呢?
现在该学习一些有关Apple IIgs内存架构的知识了。
GS / OS可执行文件以OMF(一种可重定位的可执行文件格式)存储。 如果您已经为Linux和/或Windows编写了软件,则这类似于您的ELF
或PE32
可执行文件格式,但适用于GS操作系统。 观察到每个内存块都流$FFFF->$0000
,但更明确地说,该内存被分为64k块。 为什么这值得注意? 请记住,65816可以完全模拟6502,它只能寻址多达64k(2´1)的RAM。 Apple IIe和IIc使用了6502,但由于硬件实现了存储区切换(因此在任何给定时间都可以看到总数中的64k“窗口”),因此支持超过1M的内存配置。 由于65816 CPU支持24位地址,因此只能使用一个额外的字节来表示存储体编号,如下图所示。
Bank $00
是特殊的( 也用于其他硬件特性,请在此处进行最佳解释 ),因为其中包含ROM和用于I / O的地址范围。
鉴于我们的GS / OS代码是可重定位的, 因此 无法保证程序会始终在同一位置加载(甚至在最终用户可能具有多种辅助内存配置的情况下也是如此)。 换句话说,操作系统可以在免费的内置或辅助RAM中的任何位置任意加载代码。 回想一下,我们之前曾讲过使用jsr
(带有例程位置的16位立即地址参数)在加载字符后调用工具箱的COUT
。 在幕后, jsr
指令在跳转到您指定为立即参数的地址之前,将下一行指令(即PC+1
)的内存位置放到堆栈上。 rts
指令(您用来退出例程的内容)通过拉入堆栈中的jsr
值来知道要返回的位置。 该值是一个16位地址,这意味着除非您的程序位于bank 0中,否则整个练习将无法进行。
我们刚刚讨论了65816如何支持24位寻址,因此指定存储区应该不是问题,对吗? 这是事实,但是还有另一个问题。 为了保持与现有Apple II软件的兼容性,IIg必须附带原始的Apple II ROM,以便为Apple II编写的软件仍然可以运行。 也就是说,如果原始Apple II程序使用COUT
例程,则无论6502仿真模式如何,它们都将需要在IIg上使用相同的例程。 因此,与本机65816组件相反,这些ROM功能以6502组件编写,这带来了两个问题。 假设我们调用一个jsl $00FDED
,它具有COUT
例程的24位“长”地址。 计算机将获得正确的指令,并赋予指令兼容性并与65816重叠,它将运行它们。 但是,在纯模式下,我们的寄存器大小不同(并且寻址模式可能不同!),因此代码将以意外的方式执行,并导致GS崩溃以进行监视(ROM包含一个小的汇编器/监视程序,在此过程中会被调用)。崩溃)。 然后,即使计算机成功执行了这些指令(并显示了字符),用于退出例程的jsr
也只会从堆栈中拉出两个字节(即16位而不是24位地址),因此如果银行界线被越过,您将永远不会回到您的代码。 我们将需要一个本机的IIgs ROM COUT
(我相信40字符的视频页面(?)中不存在该IOUT)。
显示字符
因此,我们仍然需要显示角色。 看来我们将不得不弄脏双手,自己编写例程。 上面的bank 0映射的地址$CFFF->$C000
标记为I / O。 在IIg上,这特别意味着您可以使用以下位置:
- 读键盘
- 将软开关设置为以下模式之一:文本,高分辨率或SHR“超高分辨率”视频页面
- 调用扬声器上的
lda
引起滴答声 - 阅读游戏控制器
前两点对我们很有价值,因为我们需要阅读用户输入并确保我们处于正确的视频模式。 我们首先要弄清楚如何切换软开关,因为我们可以在执行获取操作之前通过将字符代码加载到寄存器中(例如,作为lda
指令的立即值参数)来测试视频是否正常工作要显示的字符。 我们需要做什么? 让我们离开I / O部分,找出视频页面的位置。
上表中列出的16位地址仅在bank 0上可用 。如果我们在文本页中写入值(在这种情况下为char码),它们将出现在显示屏上。 65816的24位寻址允许我们写入任何24位地址,这意味着我们只需在上面的地址前面加上存储号即可获得$000400
并执行写入操作。 默认情况下(或在我的测试过程中看起来如此),GS / OS在80字符视频模式下加载程序,因此我们需要在打印之前切换视频软开关。 我们将在后面的部分中讨论。 现在,假设我们已经切换了适当的开关。 然后,显示字符所需的代码将如下所示:
但是,这样做有一个陷阱。 我们使用A
寄存器存储字符,然后使用stal
命令(具有24位长地址)存储40个字符的文本页面的第一位置。 纯模式下的65816的A
寄存器为16位或两个字节大。 一个字符只有一个字节,因此在这种情况下,我们的写操作会将两个字符写到页面上(取决于执行写操作时A
寄存器另一半剩余的内容)。 可以使用ldal #”AB”
同时操作两个字符,但是一次不得不写两个字符同时读取一个键将是一项令人烦恼的练习。 回想一下,我们讨论了6502仿真模式,其中涉及告诉65816使用较小的寄存器。 也可以在不退出纯模式的情况下更改寄存器的大小。 这意味着我们可以将A
的大小更改为8位,以便stal
执行8位写操作(或1个字符)。 这个难题是完整的,但是为了弄清楚如何使此代码执行我们想要的操作,我们需要了解处理器状态寄存器。
处理器状态寄存器和软开关
对于汇编程序员来说,这是一个很棒的地方。 查看仅用8位即可代表的所有信息! 诸如bcc
(“如果进位清零则分支”)之类的指令利用此寄存器进行分支逻辑。 其他指令(例如cmp
这些位置1,以为您提供有关您执行的操作的一些信息。 在cmp
的情况下,如果A
大于另一个操作数,则将进位位置A
。 E
位是仿真位,但是在本教程中我们不会碰它,因为该编辑器将保持16位本机状态。
在我们的情况下,我们需要将“存储器/累加器选择”设置为1
,以使其为8位宽。 为此,我们可以通过以下方式写入该寄存器。 如果不明显,则以下指令中使用的十六进制数字为8位大,代表寄存器的整个宽度。 另外,65816是低位优先的,因此在将数字与寄存器进行比较时请记住这一点!
要返回全宽:
回想一下,我们之前提到过I / O块中的软开关用于更改视频模式。 我们仍然需要将GS设置为40个字符的视频模式,因此我们开始这样做。 可以通过对交换机的地址执行lda
或sta
来切换软开关。 有关I / O块中的所有开关,请查阅Apple IIgs硬件参考。 在我们的案例中,您可以想象某种视频控制器侦听这些地址以寻找信号以执行某项操作。 我们只是通过对它执行一个内存访问操作来从字面上“切换”开关。
您必须处于8位累加器模式才能正常工作。 例如, $C00C
禁用80个字符的硬件,而$C00D
启用它。 如果将16位值写入$C00C
它还将覆盖$C00D
,因此将其关闭然后立即再次打开。 问我我怎么知道!
让我们结合到目前为止的所有内容,并添加软开关触发代码,以创建一个简单而命令式的hello world程序。 现在,我们已经组装了开始构建文本编辑器所需的几乎所有部分。
读键盘
我们剩下的挑战是弄清楚如何捕获用户输入。 在查阅IIgs硬件参考之后 ,我们获得了一些有用的I / O位置:
-
$C000
包含按下的键的字符代码和频闪位 -
$C010
包含任意键按下标志和频闪复位软开关
回想一下我们之前提到的字符代码可以容纳一个字节(或8位)。 想象一下,现在我刚刚按下F键并立即执行了ldal $00C000
。 A
寄存器如下所示:
| Strobe (7) | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|------------|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 |
位0–6表示字符代码$C6
,位7为选通脉冲。 反正这是什么选通脉冲,为什么会出现呢? 键盘控制器设置选通脉冲,以使程序员可以了解按键事件何时发生。 有人可能会说:“我不需要选通位来做到这一点,难道我不能只是检查一下值是否改变了?” 如果用户有意输入两次相同的字符怎么办? 此外,如果您希望使用此方法,则必须浪费一个额外的字节来存储先前输入的值。 选通脉冲位为解决该问题提供了更为优雅的解决方案,此外,我们还通过清除它来控制整个反馈回路,然后告诉键盘控制器可以轮询另一个按键事件并修改可用数据再次是$C000
。 当选通脉冲位置1时,键盘控制器将不会覆盖该位置!
逻辑最好写在纸上,所以让我们提出一个小事件循环:
- Read character data from $C000
- Determine if the strobe bit is set
- If yes, branch and handle input
- If not, proceed
- Read $C010, thereby toggling a softswitch causing the strobe bit to clear
现在,将此逻辑转换为65816:
该bit
指令将处理器状态寄存器的n
位设置为累加器中数据的高位。 对我们来说方便,这意味着我们可以在紧随其后使用bmi
指令(“如果减则分支”),仅在n
位为高(即设置为1
)时才分支。
开始时的汇编编程可能非常艰巨:过去的助记符指令没有任何作用。 当然,您的汇编器可能支持宏(可以代替汇编代码块的标签),但是否则,组织代码实际上没有任何便利。 在C语言中,我们可以将逻辑分组为函数。 在汇编中,最接近的类似便利是我们前面讨论的jsr
rts
和jsl
rtl
子例程指令。 同样,没有循环结构。 如上文所示,通过某种比较来防止跳转到其他地址,从而形成循环。 下面的C代码以与上面的汇编相同的精神实现:
上面的if
子句看似有点棘手,但是我们正在做的是在gs_char
的解除引用的值和数字1
向左移7次后执行按位AND
运算AND
从而二进制1
变为10000000
。
成功! 我们已经积累了为Apple IIgs打造非常基本的文本编辑器所需的全部知识。
文本编辑器中有什么?
从本质上讲,我只希望文本编辑器中不包含以下内容:
- 仅支持40个字符的文本页面
- 无需滚动(即没有可同步到文本页面的带有偏移的文本缓冲区,该文本页面是我们的存储空间)
- 箭头键可用于擦洗缓冲区并更改当前字符的位置
- 显示屏的右下角将显示格式为(XX,YY)的列和行输出
- “击中”第0列或第39列(最大值)将对扬声器执行ping操作
为了说明第二点,您使用的现代文本编辑器能够使文件加载了更多文本,这些文本可以适合您的显示。 这样,即使仅查看文件的特定部分,当前加载文件的所有文本也必须位于某处。 在IIg上,这意味着为所有文本保留一些单独的空间,但这需要做很多工作。 对于本教程而言,所有这一切意味着,我们将允许用户存储在内存中的最大文本量将是40个字符页面可以在屏幕上显示的最大文本量。
让我们用一个完整的功能集和一些变量来装饰我们较早的笔和纸事件循环:
寄存器中的变量
-
X
列 -
Y
行 -
A
,从当前键开始的字符
那些熟悉65816的人会发现我们不在“通用”登记册之列。 剩下两个寄存器:直接页寄存器和堆栈指针寄存器。 前者允许您将地址较小的指令用作立即数,因此,如果您不使用该模式,则可以从技术上将寄存器用于其他目的。 我们不能更改堆栈指针寄存器,因为我们要使用堆栈,而且堆栈是保存和恢复寄存器值的惯用工具,因此您可以释放它们以进行破坏性操作。 诸如pha
指令将A
推入堆栈,而pla
则将A
从堆栈中拉出。 当我们构建行和字符计数机制时,您将看到一个示例。
核心事件循环
- Read character data from $C000
- Determine if the strobe bit is set
- If yes, branch and handle input (via a subroutine)
- Is the key up, down, left, right, return, or backspace?
- If yes, handle those cases
- Otherwise, for any arbitrary key
- Write the character to the text page
- Increment column and row appropriately
- rts
- If not, proceed
- Invoke subroutine to display current character
- Read $C010, thereby toggling a softswitch causing the strobe bit to clear
- Jump to character reading address to repeat
开始实施
首先要做的是告诉我们的汇编程序Merlin32,我们要制作一个GS / OS可以识别的可重定位的OMF可执行文件。 rel
和typ
不是65816指令,而是只有Merlin32可以解析的助记符。 我们使用它们来设置文件名并在此处键入(对于OMF16
为$B3
)。
最后两个指令负责将数据库寄存器设置为与程序银行寄存器相同的值(通过将前者推入堆栈并将该值拉入后者寄存器)。 还记得我们谈到OMF
可重定位吗? 假设我们将字符串存储在汇编源中的某个位置。 我们可以通过依次使用db [byte]
指令和代表组成字符串的字符的字节来做到这一点。 方便地,Merlin32允许我们添加标签,因此以后可以在程序中使用标签来引用此数组(例如,使用lda
读取字符串)。 如果我们不将数据库寄存器指向与程序所在的库相同的库,则必须使用更大的24位地址来引用我们的字符串,而不是16位地址。 8位数据库寄存器附加到您提供的16位地址的高端 ,作为立即值,即为24位地址。 如果您对该字符串进行了大量引用,则可以节省几个字节的文件大小!
回到它,让我们添加我们的开关切换代码和核心事件循环:
您可以通过使用brk
实现keydown
例程并注释掉尚未实现的jsr drawpos
来验证您是否正确读取了**。 我们希望将断点放置在例程中,因为我们仅在实际按下键的情况下才跳转到例程。 否则,您将没有可靠的测试方法(因为$ 00C000是多少才有意义,直到我们知道我们引起了输入)。 无论如何,计算机会将其作为断点处理,并显示您在下面看到的监视器。 查看A的最右边字节以查看您刚刚按下的字符代码。
从字面上看,这是mrbuffer
的核心事件循环。 您可能会注意到尚无drawchar
例程。 请记住,我们将其作为keydown例程的一部分进行处理(因为如果按下某些键,则无需重绘)。 我们最终会解决这个问题,但让我们先避免keydown
失败。
实施按键程序
让我们重申一下我们的任务目标:
- Is the key up, down, left, right, return, or backspace?
- If yes, handle those cases
- Otherwise, for any arbitrary key
- Write the character to the text page
- Increment column and row appropriately
当然,这是一个ASCII表,可以简化生活。 请注意,最高有效(“ MSD”)标头下只有3位。 回想一下,最高位是选通位(因此在此省略)。 我很尴尬地依靠上面的brk
方法来识别字符,因为我很懒:
看来我们将很容易摆脱这一困境。 我们可以将一些cmp
链接在一起,并在它们beq
加上beq
s,以及上,下,左,右,返回和退格的char码的立即值。
我认为这里没有其他需要更多的解释了,所以我们继续。
再次显示一个字符
从上面引用Table 2-8
,我们可以看到40列文本页面开始于$0400
,结束于$07FF
。 我们的命令式hello world程序较早时确实在屏幕上打印了“ OHAI”,方法是在每个连续的地址$0400
写入一个字符。 表面上看,假设您一直坚持到$07FF
,那么您将回绕剩余的行,直到显示满为止。 但是,这是不正确的。 让我们看一下40个字符的文本页面的地图:
观察行的值。 代表完整行的下一个连续地址位于$0428
,但该行不是第1行,而是第8行! 通过编写程序以从第一行开始向页面写入41个字符来进行尝试。 您会看到最后一个字符显示在显示屏的下方! 此外,第8行紧接在第16行之后。最接近第16行末尾的下一行是第1行,但第1行却不连续。 第16行以$478
结尾,但第1行以$480
开始,行之间留出两个字节的空间。 不幸的是,对这个设计决定感到恼火既不能解决我们必须实施有效解决方案的事实, 也不能解决采用这种方式实现事物的实际原因 。
我们可以选择使用以下两种解决方案之一,它们都涉及分别存储在X和Y寄存器中的列,行数据:
- 定义一个指向(随行更新的)基本存储地址的指针(以及更新它的例程)。
- 为每行制作一个大
cmp
表。
尽管前者可能更简洁,但边缘情况会很烦人(例如,两个字节的洞),并且无论如何都可能会产生大量的cmp
意大利面。 当文档没有OCR时,最好的选择是必须具有可靠的功能。
假设我们已经处理了行和列的递增逻辑,并且寄存器包含正确的值(不用担心,我们将在下一节中介绍它)。 我们的解决方案如下所示:
我承认,这不是最优雅的解决方案,但是它确实可以可靠地工作。 凉! 现在,我们可以任意写入缓冲区中的任何位置。
管理行和列标记
如果我们可以了解有效值的外观,则更容易理解手头的任务:
-
X
:0-39(40个字符的行) -
Y
:0-22(共23行)
不,这不是一个错误,页面中确实有24行可用,但是我想保留最后一行以显示较小的(col,row)
输出,以便用户可以知道他们写了多少个字符一行或另一行,因此如果需要,我可以在以后的位置使用空格将热键定义(如nano
)添加到底部。
上面的代码显示的执行此操作的例程为up
, down
, left
, colinc
, return
和backspace
。 我们可以通过将cpx
(与X进行比较)和cpy
(与Y进行比较)以及边界的立即值来实现所有这些目标,以防止设置无效值。 由于我们正在检查X
和Y
寄存器,因此这也是ping扬声器的理想场所。 在本教程中,我们将不探讨如何实现ping
子例程,因为它应该自己编写。 如果您遵循此步骤,则将其删除(如果删除,则可以将下面的逻辑简化很多!)
由于常规按键将始终使列递增,并且由于右箭头键将使列相同,因此使用同一分支以使我们的递增逻辑统一是有意义的。 如果列位于位置39
,则将列重置为0
并使用从down
到上的逻辑来推进行(如果可能)。 在right
分支毗邻位于colinc
故意的,这样我就可以jmp
在未来比较指令(因为一个刚刚执行)。 此外,这是为了使我不希望说话者发出chi叫声,除非向下键明确地通过右箭头键发生。 同样,backspace命令用空格字符替换A
的最后一个值,然后在检查是否可以减小并执行减小后(例如,因为您想在清除当前字符之前尽可能删除前一个字符),然后调用drawchar
例程本身。 。
您还将注意到,我们正在对finkey
标签进行一次jmp
; 这些分支是原始keydown
子例程的一部分,因此我们需要跳回到该例程的末尾,以便它可以清除选通位,然后调用其rts
并将控制权传递回核心事件循环。 我确信这里有更优雅的解决方案,我计划在以后的文章中进行探讨。
显示行和列标记
听起来,这似乎是一个相当无辜的问题。 让我们获得C中数字的字符值,以演示第一印象的简单性:
任务完成。 实际上,为什么在拥有printf()
并且只想显示的情况下进行练习?
不幸的是,我们这里没有printf()
,也没有表现力。 例如,我们可以使用txa
A
的X
值全部加载到A
,但这不是有效的字符代码! 它只是个数字! 此外,只有数字0-9
是字符代码(因为您可以编写字符以显示字符串中以10为底的数字),因此第一个示例可能看起来有点误导,因为您似乎可以添加任意偏移量(例如57
并得到一个两字节的字符串,您不能这样做, 必须将它们组成。
让我们假设X
的值15代表第15列。 我们需要一种将该数字映射为两个一个字节的字符代码的方法。 查阅上面的ASCII表可知,我们可以执行类似于第一个C示例的操作,在第一个C示例中,我们将想要的数字加到表示字符'0'的基地址中。 在这种情况下,该基地址为$B0
,因此$B0+0
为“ 0”, $B0+1
为“ 1”,依此类推。 由于我们的行号和列号从不超过99,因此我们只需要考虑以10为底的整数的“ 1”和“十”位。
简而言之,我们可以执行以下操作:
- 从
X
或Y
存储的数字开始循环计数,直到10 - 减去10,每次您增加一个计数器(“十位”)
- 上一个操作的其余部分是“一个地方”
- 将每个加到
$B0
并按从最低有效位到最高有效位的顺序显示
由于我们只有3个可用的寄存器,并且我们要使用它们中的全部3个( A
, X
和Y
)来存储有意义的数据,所以我们有一个小问题。 如果不能使用寄存器,我们将如何计数? 这就是我前面提到堆栈有用性的地方:如果我们需要修改寄存器,我们可以在修改它们之前通过将它们的内容压入栈(使用pha
phx
phy
指令)来“保存”它们的先前值。
为了实现上述目的,我们只需要两个寄存器。 该算法可能看起来有些奇怪,但是很有意义,因为我们可以将X
(列号)或Y
(行号)直接传递给A
,然后在对X
进行计数时从A
减去。 然后,我们可以将“ 0”字符的基本偏移量添加到A
,然后将A
的值写入文本页面。 然后,将X
转移到A
,再次添加基本偏移并显示。
为了使事情干一点,因为我们将在行和列值中都使用它,所以最好将该逻辑放在其自己的子例程中。
在使用此子例程之前,请确保您调用phx
pha
来保存当前列和keydown char的值,因为我们通过减小A
的值来修改A
并在开始时用0覆盖X
(如果我们有一个像2这样的数字也很好,因为它在十位代表0)。 为什么子例程不这样做? 让我们看一下绘制字符位置以了解的整个实现:
由于我们在从列到行的过程中都用新的数字覆盖了A
以便显示,因此仅将其替换以恢复它是浪费的。 同样,我们不需要将Y
实际放置到堆栈上,因为我们只需要X
即可计数。 完成工作后,我们可以在最后恢复状态。
stal
位置表示位于第24行末尾的最后7个字符(即屏幕的右下角)。 我们绘制括号(用逗号表示),计算10的数量,然后将字符随机排列到屏幕上的位置。 最后,我们将状态和rts
恢复到我们的核心事件循环,该循环继续运行程序。
恭喜你! 您现在知道如何通过结合所有这些基本元素来在Apple IIgs上实现非常基本的文本编辑器!
结束
这里还有很多工作要做。 例如,我还没有实现退出,所以希望从我们今天停下来的地方摘录更多的技术文章。 汇编编程和围绕旧计算机体系结构的怪癖进行的工作非常耐心测试,但它们可以带来有益的学习体验。 我从对GS或Apple II系列产品的了解开始为0,奇怪的是,我变得更加动力十足,这是我无法真正完成“ Google&StackOverflow”这一任务的过程,因为我比平时更努力地推动自己,所以感到耳目一新将。 我花所有的时间进行所有这些研究并实施该计划,总共花了一个月左右的晚上和周末。 我希望有人尝试完成类似的工作对这篇文章有用。
我还要感谢#A2Central IRC频道和“ Apple IIgs发烧友” Facebook小组愿意回答我的问题。 那里有一个真正喜欢这台计算机的相当大的社区:人们正在自制以太网适配器,制作TCP / IP堆栈,软盘仿真器以及各种新的自定义硬件和软件,以保持这些计算机的生命。 我对此感到非常惊讶,在完成任务后,我明白了为什么。 它不运行孤岛危机或地雷cryptos,但有一些这样的“老派酷”关于这些,你发现自己在无意中做与他们每一个东西 ,现在又一遍。 This has been one of the most rewarding experiences I've had with a computer in my entire life. 感谢您的阅读。
Originally published on my weblog .
From: https://hackernoon.com/building-mrbuffer-a-text-editor-for-a-31-year-old-computer-59d5ea450507