PostgreSQL 的 Commodore 64 BASIC

Hacker News Top 工具

摘要

PL/CBMBASIC 是一个 PostgreSQL 过程语言扩展,集成了原始的 Commodore 64 BASIC V2 解释器,允许用户编写带有行号的 BASIC 数据库函数。它用 C 语言模拟 6502 解释器,提供对 BASIC 命名约束的验证,并通过读取模拟变量表来支持 OUT 参数。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/03 11:13

# LOAD "PL/CBMBASIC",8,1:用于 PostgreSQL 的 Commodore 64 BASIC 来源:https://thombrown.blogspot.com/2026/07/load-plcbmbasic81-commodore-64-basic.html 如果你到了某个年纪,`38911 BASIC BYTES FREE` 这几个字会对你造成任何心理治疗都无法抹去的印记。你会记得那片蓝屏。你会记得从杂志上抄了三页代码列表,然后遇到 `?SYNTAX ERROR IN 2340`,却不知道三页里哪一页有拼写错误。你会记得磁盘驱动器是设备 8,而且它比大陆漂移还慢。我有一些消息要告诉你:现在这一切都能在 PostgreSQL 内部运行了。 PL/CBMBASIC 是一种过程语言扩展,它在 Commodore 64 BASIC V2 上执行函数体。不是仿制品,不是致敬表演——而是真正的 Microsoft/Commodore 1982 年解释器,通过 Michael Steil 的 `cbmbasic`(https://github.com/mist64/cbmbasic)项目实现,该项目将 6502 ROM 静态重编译为 C 语言。这个 C 代码被直接编译进扩展的共享库中,因此解释器就活在你的后端进程里。每次函数调用都是一次内存中的电源循环:清零 64KB RAM 数组,重置 CPU 寄存器,然后在 $E394 处重新进入 ROM。整个过程大约花费 15 到 20 微秒,这比硬件当年快大约一千倍,而且快到可以在大表上逐行调用而不必感到愧疚。 ```sql CREATE EXTENSION plcbmbasic; CREATE FUNCTION hello(who text) RETURNS text AS $$ 10 PRINT "HELLO, ";WHO$;"!" $$ LANGUAGE plcbmbasic; SELECT hello('WORLD'); -- HELLO, WORLD! ``` 是的,那些是行号。是的,它们是强制的。用户代码从第 10 行开始,就像大自然规定的那样,因为 0 到 9 行被保留:扩展会在你的代码运行前,将函数参数以普通 BASIC 赋值语句的形式注入进去。一个名为 `who` 的 `text` 参数会变成 `WHO$`,一个名为 `lives` 的 `smallint` 会变成真正的 16 位 `LIVES%`,而其他所有数值类型都会落入一个 40 位的 CBM 浮点数中,包括它那九个完整的有意义数字。 ## 验证器有自己的意见,因为 BASIC V2 就有意见 任何一个在 C64 上编程超过一小时的人都会发现,你不能使用名为 `TOTAL` 的变量。词法分析器会在任何位置压缩关键字,包括在标识符内部,所以 `TOTAL` 包含了 `TO` 而变成了垃圾。`SCORE` 包含了 `OR`。`BUDGET` 包含了 `GET`。名字的前两个字符才有效,所以 `USERNAME` 和 `USERID`……实际上它们没问题,`US$` 和 `US` 是不同的变量,但 `ALPHA` 和 `ALPS` 会静默地变成同一个字符串。而且 `TI` 和 `ST` 被系统占用了。 该扩展附带了一个验证器,因此 PostgreSQL 现在会在 `CREATE FUNCTION` 时给出这些意见,而不是让你在运行时重新发现它们: ``` ERROR: parameter name "total" contains the BASIC keyword TO HINT: This is why nobody could ever have a variable called TOTAL on the Commodore 64. ``` 有些创伤值得比我们第一次遇到时更好的错误信息。 ## OUT 参数,通过遍历变量表 当 BASIC 程序结束时,它的变量仍然停留在模拟的 64KB RAM 中。因此对于 OUT 和 INOUT 参数,处理程序做了唯一合理的事情:遍历 BASIC 自己的简单变量表——位于 $2D/$2E 的 VARTAB 和 $2F/$30 的 ARYTAB 之间的 7 字节条目,解码类型编码的名称字节,然后将 5 字节浮点数、16 位整数和字符串描述符转换回 SQL 值。 ```sql CREATE FUNCTION divmod(num int, den int, OUT quot int, OUT rmd int) AS $$ 10 QUOT=INT(NUM/DEN) 20 RMD=NUM-QUOT*DEN $$ LANGUAGE plcbmbasic; SELECT * FROM divmod(47, 5); -- quot | rmd -- ------+----- -- 9 | 2 ``` PEEK 另一个进程的内存来获取结果并不是我预期很快能在 PostgreSQL 文档中看到的模式,但在这里感觉非常合适。 ## 数据库就是设备 8 这是我最满意的部分。在 Commodore 64 上,你的数据存放在磁盘驱动器(设备 8)上,你用 `OPEN`、`INPUT#`、`GET#`、`PRINT#`、`CLOSE` 和 `ST` 状态变量与它通信。所以,在 PL/CBM-BASIC 中,设备 8 就是数据库。你 OPEN 的“文件名”是一个 SQL 语句,通过你的事务内部的 SPI 执行: ```sql CREATE FUNCTION top_scores() RETURNS text AS $$ 10 OPEN 1,8,0,"SELECT NAME, SCORE FROM HISCORES ORDER BY SCORE DESC" 20 INPUT#1,N$,S 30 IF ST<>0 AND N$="" THEN 60 40 PRINT N$;" ";S 50 IF ST=0 THEN 20 60 CLOSE 1 $$ LANGUAGE plcbmbasic; ``` 列值以每行 CR 结尾的形式流回,而 `ST` 会在最后一个字节时捕获 EOF 位(64),就像它从 1541 驱动器读取时一样。你在 1985 年编写的读-直到-完成的循环可以直接运行,包括需要小心空文件的那一部分——这就是第 30 行在做的事。 更棒的是,辅助地址 15 是驱动器的命令通道——你之前会用 `PRINT#` 向它发送 DOS 命令,并从它那里读回 `00, OK,00,00`。现在这个也管用: ```basic 10 OPEN 15,8,15 20 PRINT#15,"DELETE FROM HISCORES WHERE SCORE < 1000" 30 INPUT#15,EN,EM$,RC,ES ``` 状态记录是 `0,OK,,0`,以向原始版本致敬。每个 `PRINT#` 会追加内容,而终止的 CR 会执行,因此你可以分段发送超过 BASIC 255 字符字符串限制的语句。INSERT、UPDATE、DDL,随你便。这是一个仅限超级用户使用的不安全语言,所以你本来就能做可怕的事情;现在你可以用 `FOR` 循环和没有小写字母的方式来实现它们。 运行时错误会通过解释器自己的错误向量($0300)捕获——与 Simons' BASIC 使用的相同插件机制——因此错误会以正确的 PostgreSQL 错误形式呈现,并携带真正的 ROM 错误代码: ``` ERROR: BASIC error: ?DIVISION BY ZERO ERROR IN 20 ``` 而且因为 KERNAL 的 STOP 例程(RUN/STOP 键扫描,在每条语句前轮询)被修补为调用 `CHECK_FOR_INTERRUPTS()`,所以那个不朽的 `10 GOTO 10` 会在 `statement_timeout` 时干净地终止。RUN/STOP 键终于可靠地工作了。只花了 44 年。 ## 它快吗?定义“快” 我针对相同的函数对 PL/Python 进行了基准测试。PL/Python 胜出,正如你对一个保持温暖解释器而不是每次函数调用都重启 Commodore 64 的语言所期望的那样:在简单调用上快大约 14 到 19 倍,对于从函数内部查询 100 行的负载,差距缩小到大约 6 倍,因为双方都需要支付相同的 SPI 成本。但是 C64 的底线是那 15 微秒的电源循环,这与非内联 SQL 函数的成本相当,而解释器每秒处理约一百万条 BASIC 语句。这个 ROM 最初所在的机器每秒只能处理大约一千条。一个 1982 年的 8 位解释器,在每次调用都执行完全上电复位的情况下,能接近 CPython 的 1.5 个数量级之内,这已经比任何人有权期望的要好得多了。 ## 细节说明 里面是 1982 年的世界,扩展也没有假装不是。所有内容都是大写的,包括你的字符串字面量,因为 C64 的默认字符集几乎不包含小写字母。字符串最多 255 个字符。浮点数有九位有效数字。没有 NULL,因为 NULL 那时还没被发明出来;它表现为空字符串,如果你在意的话可以在查询中使用 COALESCE。`INPUT` 会引发错误,因为你的数据库没有连接键盘。`POKE` 和 `SYS` 可以针对模拟的 64KB 生效(如果你喜欢冒险的话),而野生 POKE 能做的最坏情况就是让机器混乱直到下一次调用的电源循环。 代码位于 `github.com/darkixion/pl-cbmbasic`(https://github.com/darkixion/pl-cbmbasic),附带回归测试套件和包含其余细节说明的 README。需要超级用户权限,生产部署……会是个选择,怀旧是必须的。 ``` READY. █ ```

相似文章

Boriel BASIC

Hacker News Top

Boriel BASIC 是一款现代开源的 BASIC 编译器 SDK,主要为 ZX Spectrum 设计,提供增强功能、整数类型以及内联汇编支持,适用于复古游戏开发。

Postgres by Example

Hacker News Top

一份使用带注释的SQL示例的PostgreSQL实践入门,涵盖从基础到高级主题。

PostgreSQL 适用于一切场景

Hacker News Top

本文认为,PostgreSQL 是一个功能全面的数据库解决方案,足以替代搜索引擎、消息队列和缓存等多种专用技术,从而简化 IT 架构。

PGSimCity - PostgreSQL 工作原理

Hacker News Top

PGSimCity 是一个 3D 交互模型,可直观展示 PostgreSQL 数据库引擎的内部运作,为理解其架构提供教育工具。