READ into array elements: parse subscripts after variable name in READ
handler, call gwrt_array_elem + gwrt_data_read. Unlocks matrix_mult,
roman_numerals (partial).
PRINT USING colon-in-string: scan past quoted strings when finding
statement-end colon for token embedding. Fixes truncated format strings
like "Pi estimate: #.####".
Array element assignment: don't zero element before RHS evaluation.
Previous code did `*_elem = {.type=4}` which zeroed fval before
reading C(I,J) in `C(I,J) = C(I,J) + A(I,K)*B(K,J)`, making
self-referencing assignments always read 0.
DEF FN call fix: skip past TOK_FN byte before calling gw_eval_fn_call.
DEFINT pre-scan: analysis pass processes DEFINT/DEFSNG/DEFDBL/DEFSTR
before variable type resolution.
Integer assignment rounding: use gw_cint() (rint) instead of (int16_t)
C truncation.
41/72 tests pass. 0 compile errors.
New: hundred_doors, matrix_mult, pascal_triangle, stats_calc.
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.