Graphics viewport and coordinate mapping: - VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]] with clipping - WINDOW [[SCREEN] (x1,y1)-(x2,y2)] with Cartesian/screen modes - PALETTE [attribute, color] with CGA 16-color remapping - PMAP(coord, func) for logical/physical coordinate conversion - All graphics statements (PSET, LINE, CIRCLE, PAINT, GET/PUT) respect viewport clipping and WINDOW coordinate mapping MBF (Microsoft Binary Format) float support: - CVS/CVD now interpret bytes as MBF format (compatible with real GW-BASIC) - MKS$/MKD$ now produce MBF-encoded bytes - Fixed shift errors in MBF↔IEEE conversion routines (single: 1→0, double: 4→3) - Random-access file I/O now byte-compatible with original GWBASIC.EXE 66 tests (2 new), 61 compat matches (up from 58).
240 lines
7.8 KiB
Markdown
240 lines
7.8 KiB
Markdown
# GW-BASIC 2026
|
||
|
||
A portable C reimplementation of Microsoft GW-BASIC, using the
|
||
[original 8088 assembly source](https://github.com/microsoft/GW-BASIC)
|
||
(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
|
||
|
||
```bash
|
||
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.13.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):
|
||
|
||
```bash
|
||
./gwbasic tests/programs/prime_sieve.bas
|
||
```
|
||
|
||
Pipe input:
|
||
|
||
```bash
|
||
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
|
||
- `--full` flag 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.TXT` in the current directory
|
||
- **Real hardware:** use `--lpt /dev/lp0` (Linux) or `--lpt LPT1` (FreeDOS) to send output to a physical parallel port printer
|
||
|
||
```bash
|
||
./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](https://en.wikipedia.org/wiki/Sixel),
|
||
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
|
||
bash tests/run_tests.sh
|
||
```
|
||
|
||
Compatibility testing against real GWBASIC.EXE under DOSBox-X:
|
||
|
||
```bash
|
||
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](LICENSE).
|