RustyBASIC
A QBASIC compiler written in Rust that targets the ESP32-C3 (RISC-V) microcontroller. Write embedded programs in familiar BASIC syntax instead of C/C++.
Rust
LLVM 18
ESP32-C3 RISC-V
ESP-IDF v5.4
.bas source → [Lexer] → [Parser] → [Sema] → [LLVM Codegen] → .o (RISC-V)
|
ESP-IDF link: .o + C runtime → .elf firmware
Installation
Prerequisites
- Rust toolchain (stable)
- LLVM 18 (
brew install llvm@18on macOS) - zstd library (
brew install zstdon macOS) - ESP-IDF toolchain (for firmware/flash commands)
Build from Source
1
Clone the repository
git clone https://github.com/parisxmas/RustyBASIC.git cd RustyBASIC
2
Set environment variables
export LLVM_SYS_180_PREFIX=/opt/homebrew/opt/llvm@18 export LIBRARY_PATH=/opt/homebrew/opt/zstd/lib
3
Build the compiler
cargo build --release
Usage
# Check syntax and types rustybasic program.bas check # Dump LLVM IR rustybasic program.bas dump-ir [--target host|esp32c3] # Compile to object file rustybasic program.bas build [-o output.o] [--target esp32c3|host] # Build ESP-IDF firmware rustybasic program.bas firmware [--project-dir esp-project] # Flash to device rustybasic program.bas flash --port /dev/ttyUSB0
Types
| Type | Suffix | Description |
|---|---|---|
INTEGER | % | 32-bit signed integer |
LONG | & | 32-bit signed long |
SINGLE | ! | 32-bit float |
DOUBLE | # | 64-bit float (soft-float on ESP32) |
STRING | $ | Reference-counted heap string |
| User type | — | Defined with TYPE...END TYPE |
Operators
| Category | Operators |
|---|---|
| Arithmetic | + - * / \ (int div) ^ (power) MOD |
| Comparison | = <> < > <= >= |
| Logical | AND OR NOT XOR |
Control Flow
| Statement | Description |
|---|---|
IF...THEN...ELSEIF...ELSE...END IF | Conditional branching |
SELECT CASE...CASE...END SELECT | Multi-way branch |
FOR...TO...STEP...NEXT | Counted loop |
DO WHILE/UNTIL...LOOP | Pre-condition loop |
DO...LOOP WHILE/UNTIL | Post-condition loop |
WHILE...WEND | Legacy while loop |
GOTO label | Unconditional jump |
GOSUB label / RETURN | Subroutine call/return |
ON expr GOTO l1, l2, ... | Computed GOTO |
ON expr GOSUB l1, l2, ... | Computed GOSUB |
ON ERROR GOTO label | Error handler |
EXIT FOR/DO/SUB/FUNCTION | Early exit |
I/O
| Statement | Description |
|---|---|
PRINT expr; expr | Output (; = no space, , = tab) |
PRINT USING fmt$; expr | Formatted output |
INPUT "prompt"; var | Read user input |
LINE INPUT "prompt"; var$ | Read entire line |
INCLUDE "file.bas" | Inline another source file |
Arrays
Arrays are fixed-size, heap-allocated, zero-initialized, and bounds-checked at runtime.
' 1D array (indices 0..5) DIM arr(5) AS INTEGER arr(0) = 10 arr(1) = 20 ' 2D array (indices 0..2 x 0..3) DIM matrix(2, 3) AS SINGLE matrix(1, 2) = 99.5 PRINT "Matrix(1,2) ="; matrix(1, 2)
SUB & FUNCTION
DECLARE SUB ShowResult (label AS STRING, value AS SINGLE) DECLARE FUNCTION Add! (a AS SINGLE, b AS SINGLE) DIM a AS SINGLE DIM b AS SINGLE a = 10 b = 20 CALL ShowResult("Sum", Add!(a, b)) END SUB ShowResult (label AS STRING, value AS SINGLE) PRINT label; " = "; value END SUB FUNCTION Add! (a AS SINGLE, b AS SINGLE) Add! = a + b END FUNCTION
User-Defined Types
TYPE Point x AS SINGLE y AS SINGLE END TYPE DIM cursor AS Point cursor.x = 10.5 cursor.y = 20.3 PRINT "Point("; cursor.x; ","; cursor.y; ")"
Classic BASIC
| Statement | Description |
|---|---|
SWAP var1, var2 | Exchange values of two variables |
DEF FNname(params) = expr | Define inline function |
RANDOMIZE seed | Seed the random number generator |
DATA v1, v2, ... | Declare inline data (mixed int/float/string) |
READ var1, var2, ... | Read next item(s) from data pool |
RESTORE | Reset data read pointer to beginning |
DEF FN Example
DEF FNsquare(x) = x * x DEF FNhypotenuse(a, b) = SQR(a * a + b * b) PRINT "5 squared = "; FNsquare(5) PRINT "Hypotenuse(3,4) = "; FNhypotenuse(3, 4)
DATA/READ Example
DATA "Mercury", 57.9 DATA "Venus", 108.2 DATA "Earth", 149.6 FOR i = 1 TO 3 READ planet$, distance PRINT planet$; " is "; distance; " million km from the Sun" NEXT i
String Functions
| Function | Returns | Description |
|---|---|---|
LEN(s$) | Integer | String length |
ASC(s$) | Integer | ASCII code of first character |
CHR$(n) | String | Character from ASCII code |
LEFT$(s$, n) | String | First n characters |
RIGHT$(s$, n) | String | Last n characters |
MID$(s$, start, len) | String | Substring (1-based) |
INSTR(s$, find$) | Integer | Find position (1-based, 0 if not found) |
STR$(n) | String | Number to string |
VAL(s$) | Single | String to number |
UCASE$(s$) | String | Uppercase |
LCASE$(s$) | String | Lowercase |
TRIM$(s$) | String | Strip whitespace |
STRING$(n, code) | String | n characters with ASCII code |
SPACE$(n) | String | n spaces |
Math Functions
| Function | Returns | Description |
|---|---|---|
SQR(x) | Single | Square root |
ABS(x) | Single | Absolute value |
SIN(x) | Single | Sine (radians) |
COS(x) | Single | Cosine (radians) |
TAN(x) | Single | Tangent (radians) |
ATN(x) | Single | Arctangent |
LOG(x) | Single | Natural logarithm |
EXP(x) | Single | e^x |
INT(x) | Integer | Floor |
FIX(x) | Integer | Truncate toward zero |
SGN(x) | Integer | Sign: -1, 0, or 1 |
RND | Single | Random float [0, 1) |
GPIO
| Statement | Description |
|---|---|
GPIO.MODE pin, mode | Configure GPIO (0=input, 1=output) |
GPIO.SET pin, value | Write digital output (0 or 1) |
GPIO.READ pin, var | Read digital input |
DELAY ms | Pause execution (milliseconds) |
Blink LED
CONST LED_PIN = 2 GPIO.MODE LED_PIN, 1 DO GPIO.SET LED_PIN, 1 PRINT "LED ON" DELAY 500 GPIO.SET LED_PIN, 0 PRINT "LED OFF" DELAY 500 LOOP
Peripherals
| Statement | Description |
|---|---|
I2C.SETUP bus, sda, scl, freq | Initialize I2C |
I2C.WRITE addr, data | Write to I2C device |
I2C.READ addr, len, var | Read from I2C device |
SPI.SETUP bus, clk, mosi, miso, freq | Initialize SPI |
SPI.TRANSFER data, var | SPI send/receive |
UART.SETUP port, baud, tx, rx | Initialize UART |
UART.WRITE port, data | Write byte to UART |
UART.READ port, var | Read byte from UART |
ADC.READ pin, var | Read analog input |
PWM.SETUP ch, pin, freq, res | Configure PWM channel |
PWM.DUTY ch, duty | Set PWM duty cycle |
TIMER.START | Start stopwatch timer |
TIMER.ELAPSED var | Get elapsed time (ms) |
Networking
| Statement | Description |
|---|---|
WIFI.CONNECT ssid$, pass$ | Connect to WiFi |
WIFI.STATUS var | Check WiFi status |
WIFI.DISCONNECT | Disconnect WiFi |
HTTP.GET url$, result$ | HTTP GET request |
HTTP.POST url$, body$, result$ | HTTP POST request |
MQTT.CONNECT broker$, port | Connect to MQTT broker |
MQTT.DISCONNECT | Disconnect from MQTT |
MQTT.PUBLISH topic$, msg$ | Publish message |
MQTT.SUBSCRIBE topic$ | Subscribe to topic |
MQTT.RECEIVE var$ | Receive message (blocking) |
BLE.INIT name$ | Initialize BLE |
BLE.ADVERTISE mode | Start/stop BLE advertising |
BLE.SCAN var$ | Scan for BLE devices |
BLE.SEND data$ | Send BLE data |
BLE.RECEIVE var$ | Receive BLE data |
ESPNOW.INIT | Initialize ESP-NOW |
ESPNOW.SEND peer$, data$ | Send to peer MAC |
ESPNOW.RECEIVE var$ | Receive ESP-NOW message |
UDP.INIT port | Initialize UDP socket |
UDP.SEND host$, port, data$ | Send UDP datagram |
UDP.RECEIVE var$ | Receive UDP datagram |
Display
OLED (SSD1306)
| Statement | Description |
|---|---|
OLED.INIT w, h | Initialize OLED display |
OLED.PRINT x, y, text$ | Draw text at position |
OLED.PIXEL x, y, color | Set pixel on/off |
OLED.LINE x1, y1, x2, y2, c | Draw line |
OLED.CLEAR | Clear display buffer |
OLED.SHOW | Push buffer to screen |
LCD (HD44780)
| Statement | Description |
|---|---|
LCD.INIT cols, rows | Initialize LCD |
LCD.PRINT text$ | Print text at cursor |
LCD.CLEAR | Clear display |
LCD.POS col, row | Set cursor position |
NeoPixel (WS2812)
| Statement | Description |
|---|---|
LED.SETUP pin, count | Initialize LED strip |
LED.SET index, r, g, b | Set pixel color |
LED.SHOW | Push to LED strip |
LED.CLEAR | Turn off all pixels |
Sensors & Actuators
| Statement | Description |
|---|---|
TOUCH.READ pin, var | Read capacitive touch sensor |
SERVO.ATTACH ch, pin | Attach servo to PWM channel |
SERVO.WRITE ch, angle | Set servo angle (0-180) |
TONE pin, freq, duration | Generate tone (Hz, ms) |
IRQ.ATTACH pin, mode | Attach GPIO interrupt |
IRQ.DETACH pin | Detach GPIO interrupt |
TEMP.READ var | Read internal temperature |
Servo Sweep Example
SERVO.ATTACH 0, 5 FOR angle = 0 TO 180 STEP 10 SERVO.WRITE 0, angle DELAY 100 NEXT angle
Storage & System
| Statement | Description |
|---|---|
NVS.WRITE key$, value | Write integer to flash storage |
NVS.READ key$, var | Read integer from flash storage |
JSON.GET json$, key$, var$ | Extract JSON value by key |
JSON.SET json$, key$, val$, var$ | Set key in JSON |
JSON.COUNT json$, var | Count JSON elements |
DEEPSLEEP ms | Enter deep sleep |
OTA.UPDATE url$ | Over-the-air firmware update |
Examples
Hello World
' Hello World for RustyBASIC DIM msg AS STRING msg = "Hello from RustyBASIC!" PRINT msg PRINT "Running on ESP32-C3" DIM answer AS INTEGER answer = 42 PRINT "The answer is:"; answer END
FizzBuzz
FOR i = 1 TO 100 SELECT CASE 0 CASE i MOD 15 PRINT "FizzBuzz" CASE i MOD 3 PRINT "Fizz" CASE i MOD 5 PRINT "Buzz" CASE ELSE PRINT i END SELECT NEXT i
WiFi + HTTP
DIM response$ AS STRING WIFI.CONNECT "MyNetwork", "MyPassword" DELAY 3000 HTTP.GET "http://httpbin.org/get", response$ PRINT response$
OLED Display
OLED.INIT 128, 64 OLED.CLEAR OLED.PRINT 0, 0, "Hello OLED!" OLED.PRINT 0, 16, "RustyBASIC" OLED.LINE 0, 63, 127, 63, 1 OLED.SHOW
Melody with TONE + DATA
DATA 262, 294, 330, 349, 392, 440, 494, 523 FOR i = 1 TO 8 READ freq% TONE 8, freq%, 300 DELAY 350 NEXT i
Architecture
Lexer
logos crate — zero-copy, fast, case-insensitive keywords
Parser
Hand-written recursive descent with Pratt expression parsing
Sema
Type checking, variable resolution, scope analysis
Codegen
LLVM IR via inkwell — alloca + mem2reg pattern
Runtime
C library with ESP-IDF API access, refcounted strings
Target
riscv32-unknown-none-elf (ESP32-C3 = RV32IMC)
Design Decisions
| Area | Choice | Rationale |
|---|---|---|
| Strings | Refcounted heap | Memory-efficient for ESP32-C3's 320KB RAM |
| Floats | f32 (not f64) | No hardware FPU; f32 is 2x cheaper in soft-float |
| Variables | alloca + mem2reg | Standard LLVM pattern, avoids manual phi nodes |
| Runtime | C + ESP-IDF | Direct access to all ESP-IDF APIs |