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

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

TypeSuffixDescription
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 typeDefined with TYPE...END TYPE

Operators

CategoryOperators
Arithmetic+ - * / \ (int div) ^ (power) MOD
Comparison= <> < > <= >=
LogicalAND OR NOT XOR

Control Flow

StatementDescription
IF...THEN...ELSEIF...ELSE...END IFConditional branching
SELECT CASE...CASE...END SELECTMulti-way branch
FOR...TO...STEP...NEXTCounted loop
DO WHILE/UNTIL...LOOPPre-condition loop
DO...LOOP WHILE/UNTILPost-condition loop
WHILE...WENDLegacy while loop
GOTO labelUnconditional jump
GOSUB label / RETURNSubroutine call/return
ON expr GOTO l1, l2, ...Computed GOTO
ON expr GOSUB l1, l2, ...Computed GOSUB
ON ERROR GOTO labelError handler
EXIT FOR/DO/SUB/FUNCTIONEarly exit

I/O

StatementDescription
PRINT expr; exprOutput (; = no space, , = tab)
PRINT USING fmt$; exprFormatted output
INPUT "prompt"; varRead 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

StatementDescription
SWAP var1, var2Exchange values of two variables
DEF FNname(params) = exprDefine inline function
RANDOMIZE seedSeed the random number generator
DATA v1, v2, ...Declare inline data (mixed int/float/string)
READ var1, var2, ...Read next item(s) from data pool
RESTOREReset 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

FunctionReturnsDescription
LEN(s$)IntegerString length
ASC(s$)IntegerASCII code of first character
CHR$(n)StringCharacter from ASCII code
LEFT$(s$, n)StringFirst n characters
RIGHT$(s$, n)StringLast n characters
MID$(s$, start, len)StringSubstring (1-based)
INSTR(s$, find$)IntegerFind position (1-based, 0 if not found)
STR$(n)StringNumber to string
VAL(s$)SingleString to number
UCASE$(s$)StringUppercase
LCASE$(s$)StringLowercase
TRIM$(s$)StringStrip whitespace
STRING$(n, code)Stringn characters with ASCII code
SPACE$(n)Stringn spaces

Math Functions

FunctionReturnsDescription
SQR(x)SingleSquare root
ABS(x)SingleAbsolute value
SIN(x)SingleSine (radians)
COS(x)SingleCosine (radians)
TAN(x)SingleTangent (radians)
ATN(x)SingleArctangent
LOG(x)SingleNatural logarithm
EXP(x)Singlee^x
INT(x)IntegerFloor
FIX(x)IntegerTruncate toward zero
SGN(x)IntegerSign: -1, 0, or 1
RNDSingleRandom float [0, 1)

GPIO

StatementDescription
GPIO.MODE pin, modeConfigure GPIO (0=input, 1=output)
GPIO.SET pin, valueWrite digital output (0 or 1)
GPIO.READ pin, varRead digital input
DELAY msPause 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

StatementDescription
I2C.SETUP bus, sda, scl, freqInitialize I2C
I2C.WRITE addr, dataWrite to I2C device
I2C.READ addr, len, varRead from I2C device
SPI.SETUP bus, clk, mosi, miso, freqInitialize SPI
SPI.TRANSFER data, varSPI send/receive
UART.SETUP port, baud, tx, rxInitialize UART
UART.WRITE port, dataWrite byte to UART
UART.READ port, varRead byte from UART
ADC.READ pin, varRead analog input
PWM.SETUP ch, pin, freq, resConfigure PWM channel
PWM.DUTY ch, dutySet PWM duty cycle
TIMER.STARTStart stopwatch timer
TIMER.ELAPSED varGet elapsed time (ms)

Networking

StatementDescription
WIFI.CONNECT ssid$, pass$Connect to WiFi
WIFI.STATUS varCheck WiFi status
WIFI.DISCONNECTDisconnect WiFi
HTTP.GET url$, result$HTTP GET request
HTTP.POST url$, body$, result$HTTP POST request
MQTT.CONNECT broker$, portConnect to MQTT broker
MQTT.DISCONNECTDisconnect 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 modeStart/stop BLE advertising
BLE.SCAN var$Scan for BLE devices
BLE.SEND data$Send BLE data
BLE.RECEIVE var$Receive BLE data
ESPNOW.INITInitialize ESP-NOW
ESPNOW.SEND peer$, data$Send to peer MAC
ESPNOW.RECEIVE var$Receive ESP-NOW message
UDP.INIT portInitialize UDP socket
UDP.SEND host$, port, data$Send UDP datagram
UDP.RECEIVE var$Receive UDP datagram

Display

OLED (SSD1306)

StatementDescription
OLED.INIT w, hInitialize OLED display
OLED.PRINT x, y, text$Draw text at position
OLED.PIXEL x, y, colorSet pixel on/off
OLED.LINE x1, y1, x2, y2, cDraw line
OLED.CLEARClear display buffer
OLED.SHOWPush buffer to screen

LCD (HD44780)

StatementDescription
LCD.INIT cols, rowsInitialize LCD
LCD.PRINT text$Print text at cursor
LCD.CLEARClear display
LCD.POS col, rowSet cursor position

NeoPixel (WS2812)

StatementDescription
LED.SETUP pin, countInitialize LED strip
LED.SET index, r, g, bSet pixel color
LED.SHOWPush to LED strip
LED.CLEARTurn off all pixels

Sensors & Actuators

StatementDescription
TOUCH.READ pin, varRead capacitive touch sensor
SERVO.ATTACH ch, pinAttach servo to PWM channel
SERVO.WRITE ch, angleSet servo angle (0-180)
TONE pin, freq, durationGenerate tone (Hz, ms)
IRQ.ATTACH pin, modeAttach GPIO interrupt
IRQ.DETACH pinDetach GPIO interrupt
TEMP.READ varRead 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

StatementDescription
NVS.WRITE key$, valueWrite integer to flash storage
NVS.READ key$, varRead 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$, varCount JSON elements
DEEPSLEEP msEnter 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

AreaChoiceRationale
StringsRefcounted heapMemory-efficient for ESP32-C3's 320KB RAM
Floatsf32 (not f64)No hardware FPU; f32 is 2x cheaper in soft-float
Variablesalloca + mem2regStandard LLVM pattern, avoids manual phi nodes
RuntimeC + ESP-IDFDirect access to all ESP-IDF APIs