A compiler for classic BASIC: build a native executable, or compile into memory and run at once. x86-64 and aarch64, on Linux, Windows and macOS.
xbasic64 compiles 1980s-era BASIC dialects (Tandy Color BASIC, GW-BASIC, QuickBASIC) to native machine code with Cranelift. It does so two ways, from one code generator:
- Ahead of time:
xbasic64 prog.baswrites a standalone executable. - Just in time:
xbasic64 run prog.bascompiles the program into memory and runs it at once, with no executable and no linker -- the immediacy of an interpreter, at native speed.
Either way the whole program is compiled before it starts. Nothing is interpreted, and there is no bytecode.
Why xbasic64?
- Nostalgia: Write and run classic BASIC programs on modern hardware
- Education: Learn compiler design with a simple, readable Rust codebase
- Simplicity: The AST is lowered straight to Cranelift IR, which emits native code
- Classic BASIC syntax with line numbers, named labels, or structured code
- Numeric types: Integer, Long, Single, Double (with type suffixes)
- Strings with the standard function set (
LEFT$,MID$,UCASE$,INSTR, ...), includingMID$as an assignment target - Control flow:
IF/THEN/ELSE,FOR/NEXT,WHILE/WEND,DO/LOOP,SELECT CASEwith ranges, lists andIScomparisons, andEXIT - Procedures:
SUBandFUNCTIONwith recursion;DEF FNfor one-liners - Arrays with
REDIM,REDIM PRESERVE,OPTION BASE, andLBOUND/UBOUND - User-defined record types with
TYPE, including nesting and arrays of records - File I/O: sequential reading and writing, with
EOFandLOF; random-access records withFIELD,LSET/RSET,GET/PUT,LOCKand theMKI$/CVIconversion family DATA/READ/RESTOREfor inline data- Formatted output with
PRINT USING - Runtime checks for out-of-range subscripts, division by zero and arithmetic
overflow, with
--unsafeto remove them - Diagnostics that name the file, line and problem rather than failing at link time, including a reason for every GW-BASIC keyword the compiler does not provide, so a program using one is refused rather than quietly misbehaving
cargo build --release# Run a BASIC program at once (compiled into memory, then run)
xbasic64 run program.bas
# Compile a BASIC program to an executable
xbasic64 program.bas
# Specify output file
xbasic64 program.bas -o myprogram
# Write the program's Cranelift IR to program.clif instead of compiling it
xbasic64 -S program.bas
# Omit the runtime safety checks (for either)
xbasic64 --unsafe program.bas
xbasic64 run --unsafe program.bas' Fibonacci sequence
A = 0
B = 1
FOR I = 1 TO 10
PRINT A
C = A + B
A = B
B = C
NEXT ISave as fib.bas and run it with xbasic64 run fib.bas -- or compile it
with xbasic64 fib.bas and run ./fib.
- Language Reference - Complete guide to the supported BASIC dialect
The compiler is a four-stage pipeline, ending in one of two places:
Source → Lexer → Parser → Semantic Analysis → Code Generator ─┬→ Object → Executable
(tokens) (AST) (symbols) │ (xbasic64 prog.bas)
(Cranelift IR)
└→ Memory → Run now
(xbasic64 run prog.bas)
- Lexer - Tokenizes BASIC source (case-insensitive keywords, line numbers, type suffixes)
- Parser - Recursive descent parser producing an AST
- Semantic analysis - Resolves names, checks types and argument counts, and reports problems with a source line
- Code Generator - Lowers the AST to Cranelift IR; Cranelift does instruction
selection and register allocation and writes the object file -- or, for
xbasic64 run, puts the code in memory (a JIT) and runs it in the compiler's own process. The whole program is compiled first either way, from the same lowering; nothing is interpreted
The runtime library (I/O, strings, number formatting, math) is a no_std Rust crate in rt/ that calls the C library. It is built once, when the compiler is built, and linked beside each program; xbasic64 run uses the copy linked into the compiler.
Key design choices:
- No IR of its own: the AST is lowered directly to Cranelift's
- One code generator for both executables and the JIT, so the two cannot disagree; the test suite runs every program both ways
- A portable
no_stdRust runtime over the C library - GW-BASIC type semantics (division always returns Double)
- Rust 1.96 or later
- To build executables, the platform's linker:
ccwith the C library on Linux and macOS, the MSVC linker (link.exe) and its C runtime on Windows.xbasic64 runneeds no linker
| OS | Architecture | Executables | xbasic64 run |
|---|---|---|---|
| Linux | x86-64, aarch64 | yes | yes |
| Windows | x86-64 | yes | yes |
| macOS | arm64 | yes | yes |
The compiler targets the machine it runs on.