NOW LET US – AI RAG SaaS Studio TP.HCM
NOW LET US
Digital Product Studio
Back to news
DEV-TOOLS...5 min read

Commodore 64 Basic for PostgreSQL

Share
NOW LET US Article – Commodore 64 Basic for PostgreSQL

PL/CBMBASIC is a unique procedural language extension for PostgreSQL that executes functions using the legendary Commodore 64 BASIC V2 interpreter from 1982. By statically recompiling the 6502 ROM into C, it runs 1,000 times faster than the original hardware, bringing a nostalgic programming experience to modern databases.

LOAD "PL/CBMBASIC",8,1: Commodore 64 BASIC for PostgreSQL

If you are of a certain age, the words 38911 BASIC BYTES FREE

will do something to you that no amount of therapy can undo. You remember the blue screen. You remember typing in three pages of a listing from a magazine, getting ?SYNTAX ERROR IN 2340

, and not knowing which of the three pages contained the typo. You remember that the disk drive was device 8, and that you had time to go make a cup of tea before it would finish loading.

All of that now runs inside PostgreSQL.

PL/CBMBASIC is a procedural language extension that executes function bodies on Commodore 64 BASIC V2. The actual Microsoft/Commodore interpreter from 1982, by way of Michael Steil's cbmbasic project, which statically recompiled the 6502 ROM into C. That C is compiled into the extension's shared library, so the interpreter lives inside your backend process. Every function call is an in-memory power cycle: zero the 64KB RAM array, reset the CPU registers, and re-enter the ROM at $E394. The whole thing costs about 15 to 20 microseconds, which is roughly a thousand times faster than the original C64 ever managed, and quick enough to call per row over a large table without too much waiting.

CREATE EXTENSION plcbmbasic;
CREATE FUNCTION hello(who text) RETURNS text AS $$
10 PRINT "HELLO, ";WHO$;"!"
$$ LANGUAGE plcbmbasic;
SELECT hello('WORLD'); -- HELLO, WORLD!

Yes, those are line numbers, and they are mandatory. User code starts at line 10, because lines 0 to 9 are reserved: the extension injects your function arguments there as ordinary BASIC assignments before your code runs. A text

parameter named who

ends up as WHO$

, a smallint

named lives

becomes a LIVES%

, and everything numeric otherwise lands in a 40-bit CBM float.

The validator has opinions, because BASIC V2 had opinions

Anyone who programmed a C64 discovered that you could not have a variable called TOTAL

. The tokeniser truncated keywords, including inside identifiers, so TOTAL

contained TO

. SCORE

contained OR

. BUDGET

contained GET

. Only the first two characters of a name were significant, so USERNAME

and USERID

... actually those were fine, US$

and US

are different variables, but ALPHA

and ALPS

silently became the same string. And TI

and ST

were taken by the system.

The extension ships a validator, so PostgreSQL now delivers these opinions at CREATE FUNCTION

time instead of leaving you to rediscover them at runtime:

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 parameters, by walking the variable table

When a BASIC program ends, its variables are still sitting in the emulated 64KB of RAM. So for OUT and INOUT parameters, the handler uses BASIC's own simple-variable table, the 7-byte entries between VARTAB at $2D/$2E and ARYTAB at $2F/$30, decodes the type-encoded name bytes, and converts the 5-byte floats, 16-bit integers, and string descriptors back into SQL values.

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

PEEKing another process's memory for its results is not a pattern I expect to see in the PostgreSQL documentation any time soon.

The database is device 8

On a Commodore 64, your data lived on the disk drive, device 8, and you spoke to it with OPEN

, INPUT#

, GET#

, PRINT#

, CLOSE

, and the ST

status variable. So in PL/CBM-BASIC, device 8 is the database. The "filename" you OPEN is an SQL statement, executed through SPI inside your transaction:

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;

Column values stream back one CR-terminated record at a time, and ST

picks up the EOF bit (64) on the final byte (like the 1541 did).

Secondary address 15 was the drive's command channel, the one you would PRINT#

DOS commands to and read 00, OK,00,00

back from. That works too:

10 OPEN 15,8,15
20 PRINT#15,"DELETE FROM HISCORES WHERE SCORE < 1000"
30 INPUT#15,EN,EM$,RC,ES

The status record is 0,OK,<rows>,0

, same as the original. Each PRINT#

appends and the terminating CR executes, so you can build statements longer than BASIC's 255-character string limit by sending them in pieces. INSERT, UPDATE, DDL, whatever you like. It is an untrusted language for superusers only.

Runtime errors are trapped through the interpreter's own ERROR vector at $0300, the same plugin mechanism Simons' BASIC used, so a mistake returns a proper PostgreSQL error carrying the ROM error code:

ERROR: BASIC error: ?DIVISION BY ZERO ERROR IN 20

And because the KERNAL's STOP routine (the RUN/STOP key scan, polled before every statement) is patched to CHECK_FOR_INTERRUPTS()

, the immortal 10 GOTO 10

dies cleanly to statement_timeout.

Is it fast? Define fast

I benchmarked identical functions against PL/Python. PL/Python wins, as you would expect from a language that keeps a warm interpreter around instead of rebooting a Commodore 64 for every function call: roughly 14 to 19 times quicker on trivial calls, narrowing to about 6 times on a workload that queries 100 rows from inside the function, where both sides have the same SPI overhead. But the C64's floor is that 15 microsecond power cycle, which puts it in the same cost bracket as a non-inlined SQL function, and the interpreter gets through about a million BASIC statements per second. The machine this ROM shipped on managed about a thousand.

The fine print

It is ancient C64 BASIC in there, so everything is uppercased, including string literals, because the C64's default character set had no lower case as such. Strings top out at 255 characters. Floats have nine significant digits. There is no NULL, it arrives as an empty string, and you can COALESCE in the query if you care. INPUT

raises an error, because there is no keyboard attached to your database. POKE

and SYS

work against the emulated 64KB if you enjoy danger, and POKEs only have an effect until the next call's power cycle.

The code is at github.com/darkixion/pl-cbmbasic.

READY.
█

Comments

© 2026 Now Let Us. All rights reserved.

Source: Hacker News

Advertisement
Ad slot ready: 5887729102

More in this category

NOW LET US Related – Wordgard: The new in-browser rich-text editor from the creator of ProseMirror

dev-tools

Wordgard: The new in-browser rich-text editor from the creator of ProseMirror

The creator of ProseMirror has introduced Wordgard, a new toolset for building highly customizable in-browser rich-text editors with a focus on structured content control.

NOW LET US Related – Half-Baked Product

dev-tools

Half-Baked Product

A satirical yet realistic story of a hardware startup's journey. From flawless Excel spreadsheets and multi-million dollar VC pitches to the harsh reality of engineering compromises and enterprise demands.

NOW LET US Related – The Safari MCP server for web developers

dev-tools

The Safari MCP server for web developers

Apple has introduced the Safari MCP server in Safari Technology Preview 247, allowing AI agents to connect directly to the browser for automated debugging. This tool helps developers optimize performance, check compatibility, and test accessibility right from their terminal.

NOW LET US Related – CarPlay Is Additive

dev-tools

CarPlay Is Additive

Rivian's refusal to support Apple CarPlay is driving away potential customers. CarPlay is an optional, additive feature that enhances the driving experience without replacing the car's native system.

NOW LET US Related – An American Privacy Emergency

dev-tools

An American Privacy Emergency

A controversial directive from the U.S. Department of Commerce bans modern privacy-preserving techniques like differential privacy, threatening both individual privacy and the utility of national statistical data.

NOW LET US Related – crustc: entirety of `rustc`, translated to C

dev-tools

crustc: entirety of `rustc`, translated to C

crustc is a fully functional Rust compiler translated entirely into C, allowing it to be built using GCC and make. Powered by the 'cilly' toolchain, this project aims to bring Rust support to legacy and obscure platforms that lack LLVM backend support.

EXPLORE TOPICS

Discover All Categories

Deep dive into the specific technology sectors that matter most to you.