Replaces individual malloc/free string management with a contiguous pool
(default 32KB) and compacting GC, matching the original GW-BASIC's
GETSPA/GARBAG architecture. Allocation is a bump pointer; gw_str_free()
is a no-op (descriptors are nulled, data reclaimed by GC). Compaction
runs at statement boundaries when the pool drops below 4KB free, walking
the variable table and array storage to relocate live strings.
FRE() now returns actual free pool space. FRE("") triggers a GC pass
before reporting. CLEAR n sets the string space size.
GW-BASIC 2026
A portable C reimplementation of Microsoft GW-BASIC, using the original 8088 assembly source (released by Microsoft in 2020) as the authoritative reference.
This is not a transpilation — it reimplements the algorithms in clean C11 with modern data structures while targeting bug-compatible behavior.
Building
mkdir -p build && cd build
cmake .. && make
Requires a C11 compiler and CMake 3.10+. PulseAudio (libpulse-simple)
is optional — detected at build time for SOUND/BEEP/PLAY support.
Usage
Interactive mode launches the authentic GW-BASIC full-screen editor:
$ ./gwbasic
GW-BASIC 2026 0.14.0
(C) Eremey Valetov 2026. MIT License.
Based on Microsoft GW-BASIC assembly source.
Ok
PRINT 2+2
4
Ok
FOR I=1 TO 5:PRINT I;:NEXT
1 2 3 4 5
Ok
Run a program file (ASCII or binary tokenized):
./gwbasic tests/programs/prime_sieve.bas
Pipe input:
echo '10 FOR I=1 TO 10:PRINT I*I;:NEXT' | ./gwbasic
What Works
Data types: INTEGER (%), SINGLE (!), DOUBLE (#), STRING ($)
Operators: + - * / ^ \ MOD AND OR XOR EQV IMP NOT < = > <= >= <>
Numeric functions: SGN, INT, ABS, SQR, SIN, COS, TAN, ATN, LOG, EXP, RND, FIX, CINT, CSNG, CDBL
String functions: LEN, ASC, CHR$, VAL, STR$, LEFT$, RIGHT$, MID$, SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$
Statements:
| Category | Statements |
|---|---|
| Output | PRINT, LPRINT, LLIST, PRINT USING, WRITE, CLS |
| Variables | LET, DIM, ERASE, SWAP, DEFINT/SNG/DBL/STR |
| Control flow | GOTO, GOSUB/RETURN, FOR/NEXT, IF/THEN/ELSE, WHILE/WEND, ON...GOTO/GOSUB |
| Input | INPUT, LINE INPUT, DATA/READ/RESTORE, INKEY$ |
| Program | RUN, RUN "file", CONT, STOP, END, NEW, LIST, CLEAR, AUTO, RENUM, DELETE, EDIT |
| Sequential I/O | OPEN, CLOSE, PRINT#, WRITE#, INPUT#, LINE INPUT# |
| Random-access I/O | FIELD, LSET, RSET, PUT, GET, CVI/CVS/CVD, MKI$/MKS$/MKD$ |
| Program I/O | SAVE (binary/ASCII), LOAD (auto-detects format), MERGE, CHAIN, COMMON |
| Event trapping | ON TIMER(n) GOSUB, TIMER ON/OFF/STOP, ON KEY(n) GOSUB, KEY(n) ON/OFF/STOP |
| Error handling | ON ERROR GOTO, RESUME, ERROR, ERR, ERL |
| User functions | DEF FN, RANDOMIZE |
| File management | KILL, NAME, FILES, MKDIR, RMDIR, CHDIR, SHELL |
| Date/time | DATE$, TIME$, TIMER |
| Screen | LOCATE, COLOR, WIDTH, SCREEN, KEY ON/OFF/LIST |
| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT, GET/PUT (sprites), BSAVE, BLOAD, VIEW, WINDOW, PALETTE, PMAP |
| Sound | SOUND, BEEP, PLAY (MML) |
| Memory | DEF SEG, PEEK, POKE |
Binary and ASCII File Formats
SAVE writes tokenized binary by default (just like the original), or ASCII
with the ,A flag. LOAD auto-detects the format — so you can load programs
saved in either format without any extra flags.
SAVE "myprog.bas" ' tokenized binary (compact, fast)
SAVE "myprog.bas",A ' ASCII text (human-readable)
LOAD "myprog.bas" ' auto-detects format
Binary files use the standard GW-BASIC 0xFF-header format. Command-line
loading (./gwbasic file.bas) also auto-detects, so binary .BAS files
just work.
INKEY$ Extended Keys
INKEY$ returns the classic GW-BASIC two-byte encoding for extended keys:
CHR$(0) + CHR$(scan_code). Arrow keys, Home, End, PgUp/PgDn, Insert,
Delete, and F1-F10 all produce the correct IBM PC scan codes — so programs
that poll for arrow-key input work as expected.
Full-Screen Editor (TUI)
When running interactively, GW-BASIC 2026 presents the authentic full-screen editor that people remember from the 1980s:
- 25×80 screen buffer with free cursor movement (arrow keys)
- Enter on any screen line re-enters it as BASIC input
- Insert/Overwrite toggle (Insert key, cursor shape changes)
- Function key bar on line 25 (
KEY ON/KEY OFF/KEY LIST) - Default F1-F10 bindings (F1=LIST, F2=RUN, F3=LOAD", etc.)
- Ctrl+C interrupts running programs
- Piped input bypasses the TUI entirely — scripts and test harnesses work unchanged
--fullflag adapts to the full terminal size instead of the classic 25×80
Printer Output (LPRINT/LLIST)
LPRINT and LLIST send output to a printer device or file:
- Modern systems (default): output is appended to
LPT1.TXTin the current directory - Real hardware: use
--lpt /dev/lp0(Linux) or--lpt LPT1(FreeDOS) to send output to a physical parallel port printer
./gwbasic --lpt /dev/lp0 myprogram.bas # print to hardware
./gwbasic --lpt report.txt myprogram.bas # print to file
./gwbasic myprogram.bas # default: LPT1.TXT
Graphics
Graphics mode is activated with SCREEN 1 (320×200, 4 colors) or
SCREEN 2 (640×200, monochrome). Drawing commands render to a virtual
framebuffer and output via Sixel graphics,
which works in terminals like xterm, mlterm, foot, and WezTerm.
SCREEN 1
LINE (0,0)-(319,199), 1
CIRCLE (160,100), 80, 2
PAINT (160,100), 3, 2
GET and PUT capture and blit rectangular sprites using CGA-compatible
packed pixel format. PUT supports action modes: XOR (default), PSET,
PRESET, AND, OR.
DIM S%(50)
GET (0,0)-(15,15), S%
PUT (100,50), S%, XOR
DEF SEG / PEEK / POKE
A virtual 8086 address space emulates the memory layout that GW-BASIC programs expected on a real IBM PC:
| Segment | Description |
|---|---|
0040 |
BIOS data area — video mode, cursor position, timer ticks (18.2 Hz), keyboard flags |
B800 |
CGA text buffer (text mode) or CGA framebuffer (graphics mode) |
DEF SEG = &HB800
POKE 0, 65 ' write 'A' to top-left screen cell
PRINT PEEK(1) ' read the color attribute
DEF SEG ' reset to default segment
Architecture
The interpreter follows the original GW-BASIC's internal structure:
Source text → Tokenizer (CRUNCH) → Token stream
↓
Expression evaluator (FRMEVL)
↓
Statement dispatcher (NEWSTT)
↓
TUI screen buffer (interactive)
↓
HAL (platform I/O)
| Module | File | Original |
|---|---|---|
| Tokenizer | tokenizer.c | GWMAIN.ASM |
| Evaluator | eval.c | GWEVAL.ASM |
| Interpreter | interp.c | BINTRP.ASM |
| TUI editor | tui.c | — |
| Graphics | graphics.c | — |
| Tokens | tokens.c | IBMRES.ASM |
| Errors | error.c | GWDATA.ASM |
| Math | math_int.c, math_float.c, math_transcend.c | MATH1/2.ASM |
| Strings | strings.c | BISTRS.ASM |
| File I/O | fileio.c | BIPTRG.ASM |
| PRINT USING | print_using.c | BIPRTU.ASM |
| Sound | sound.c | — |
| Virtual memory | virmem.c | — |
| Platform | hal_posix.c | OEM*.ASM |
Key design differences from the original:
- IEEE 754 floating point (MBF conversion for CVI/CVS/CVD/MKI$/MKS$/MKD$ file compatibility)
- Dynamic memory allocation instead of 64KB segment layout
- malloc'd strings instead of compacting garbage collector
- setjmp/longjmp for error recovery
Classic Programs
The tests/classic/ directory contains classic BASIC programs from David Ahl's
BASIC Computer Games (1978) — Hamurabi, Lunar Lander, Gunner, Diamond — for
manual compatibility testing. These are interactive programs that need keyboard
input, so they're not part of the automated test suite.
Tests
64 test programs in tests/programs/, with golden-file regression testing
and CI via GitHub Actions:
bash tests/run_tests.sh
Compatibility testing against real GWBASIC.EXE under DOSBox-X:
bash tests/run_compat.sh --generate # generate .expected from GWBASIC.EXE
bash tests/run_compat.sh # compare gwbasic output against .expected
License
MIT License. See LICENSE.