DEV Community

Cover image for Reviving a 20-year-old BASIC — and getting it to run in the browser
michel vilain
michel vilain

Posted on

Reviving a 20-year-old BASIC — and getting it to run in the browser

First, go press a button

Before the story, the thing itself. Open this in a browser tab:

👉 https://uglymike17.github.io/basic256/

That's the full BASIC256 editor and interpreter — running entirely in your browser, no install, no plugin, no account. Type PRINT "hello", hit run. Or load a bundled example straight from the URL: ?run=mandelbrot.

If that feels unremarkable, hang on — a year ago this program didn't build on a modern machine at all.

What BASIC256 is

BASIC256 (originally KidBASIC) is an easy, graphical dialect of BASIC built to teach absolute beginners how to program. Three panes: a code editor, a text output console, and a graphics canvas. You write a couple of lines and immediately see a circle, hear a beep, watch a sprite move. That instant feedback loop is the whole point — it's the on-ramp a lot of us had in the 80s and 90s, rebuilt for kids (and hobbyists) today.

It had a good run on SourceForge, reached version 2.0.0.11… and then stopped. Development stalled, apparently after a failed attempt to port it to Qt6. A few people tried to pick it up. The code drifted further from what modern toolchains expect every year.

I decided to have a go at bringing it back. I'll be honest about my qualifications up front, because it matters to the rest of this post: I'm a hobbyist, not a C++ developer. I'm comfortable with project direction, build configuration, and reading code, but I do not write production C++ from scratch. I've leaned heavily on AI tooling to get through the parts that are over my head. If that disqualifies me in your eyes, fair enough — but the thing runs, and I had a lot of fun.

The revival, in layers

The one lesson I'd pass on to anyone resurrecting an old codebase: do it in layers, and never try to modernize everything at once. A giant "fix it all" branch becomes a multi-month swamp. Small, verifiable steps kept me sane.

Roughly the order it happened in:

1. Get it onto modern infrastructure. Migrated the original SVN history to GitHub, then set up continuous integration so every commit at least tries to build. You can't modernize what you can't build.

2. qmake → CMake. The old .pro files were replaced with CMake. I kept qmake working in parallel for a while so I always had a fallback, then removed it once CI was green.

3. MinGW → MSVC on Windows. The classic "missing libstdc++-6.dll" deployment pain went away once I moved to MSVC with windeployqt. Fewer DLL surprises, better tooling.

4. One pipeline, four platforms. A single GitHub Actions workflow now builds Windows, Linux x86-64, Raspberry Pi (ARM64) and macOS, each gated behind the TestSuite so a broken build can't ship. The Raspberry Pi build was a highlight: GitHub now offers native ARM64 runners, so I could throw out the old QEMU-emulation approach entirely. Linux ships as an AppImage (built with linuxdeploy), Windows as an NSIS installer.

5. Qt5 → Qt6. The big one. This is the migration that had killed the project before. QRegExp became QRegularExpression, the multimedia layer was rewritten, and I got bitten by silent traps like a QAudioOutput that simply produced no sound because it was never explicitly attached to anything. But once it landed, Qt6 unlocked the payoff below.

6. WebAssembly. Because Qt6 supports WebAssembly, the same Qt application can be compiled to run in a browser. That's how you get the demo you (hopefully) already clicked.

The WebAssembly part, honestly

This is the bit people ask about, so here's what it actually took — including the ugly parts.

Keep Qt, keep QString. I didn't rewrite the app for the web. It's Qt-for-WebAssembly: the real interpreter, the real IDE, compiled with Emscripten. No parallel "web version" to maintain.

The interpreter blocks a thread — on purpose. BASIC256's interpreter runs on its own QThread and blocks (a running program should be able to sit in a loop). The browser hates blocking the main thread. The multithreaded Qt WASM build, which needs SharedArrayBuffer, is what makes this model survive the move.

SharedArrayBuffer means COOP/COEP headers — which GitHub Pages won't send. To use those threads the page has to be "cross-origin isolated," which requires specific HTTP headers. GitHub Pages doesn't let you set them. The workaround is a service-worker shim (coi-serviceworker) that injects the headers client-side. Slightly cursed, completely functional.

Some things just can't exist in a browser. Six subsystems have no meaning in a sandbox — launching external processes, serial ports, an embedded SQL database, printing, running a TCP server, and OS text-to-speech. Rather than crash, each is stubbed to raise a clean runtime error in the web build, so a program that hits one gets a message instead of a white screen.

Audio fought back. Getting sound working in the WASM build meant a genuinely annoying makeDynCall problem, which I ended up solving with direct KEEPALIVE exports so the audio sink's callbacks survive Emscripten's optimizer. I will not pretend I found that elegant. It works.

Run programs from a link. Because "no install" is the superpower, I made programs shareable by URL:

parameter what it runs
?run= a bundled Example (e.g. ?run=mandelbrot)
?url= a .kbs file hosted on the same site
?src= the source itself, base64-encoded into the link

Add &mode= to pick the window layout (these mirror the desktop command-line switches). ?url= is deliberately restricted to the page's own site, so a link can't point the app at somebody else's server.

The reason I actually did this

Underneath the build-system archaeology, I have a soft spot for creative coding — fractals and generative art. BASIC256's immediate canvas is a lovely little playground for it, and being able to hand someone a link that runs a Julia set in their browser, no setup, is exactly the kind of low-friction magic that got me into programming in the first place.

So a chunk of the examples lean that way — Julia sets, Strange attractors, Slime-mold simulations, Quiverbloom. Try one, then open the source and change a number. That "change a number, watch the whole picture change" loop is BASIC256 doing what it was always for.

Documentation, rebuilt

The original help lived on a DokuWiki I don't control. So I rebuilt it as a Docusaurus site: https://uglymike17.github.io/Basic256-Docs. It's now the in-app online help, and pressing F1 on any keyword jumps straight to its page. Migrating hundreds of wiki pages into MDX was its own small adventure of encoding bugs and namespace collisions, but it's a real, searchable docs site now.

What's rough (it's a Beta, and I mean it)

  • The browser build can't do the OS-level things listed above; they'll report an error rather than work.
  • macOS builds currently target Apple Silicon (arm64) only.
  • It's a Beta. Some behaviour will change before 2.1 final, and there are surely bugs I haven't hit.

That's the deal, stated plainly, so nobody feels misled.

A note on building this with AI

Since I'm not a C++ developer, this revival simply wouldn't exist without AI assistance — for the Qt6 API changes, the CMake and CI plumbing, and especially the WASM edge cases. I don't think that's something to hide. It's a real example of a non-specialist shipping something non-trivial by pairing domain interest with these tools, and then verifying the results by actually building and running them across five targets. I learned an enormous amount in the process, which was half the point.

Try it, break it, tell me

If you teach beginners, tinker with retro BASIC, or just want to make a fractal appear in a browser tab in four lines of code, I'd love for you to kick the tyres — and I especially want to hear what breaks.

Top comments (0)