An Altair-style BASIC interpreter written in AlexScript. Inspired by the Ruby BASIC tutorial, ported to AlexScript language and extended into a more modular shape.
You write line-numbered BASIC programs at the prompt, type RUN, and
the interpreter executes them top to bottom (or wherever GOTO and
GOSUB send you). Programs can be saved to disk, loaded back, edited
line by line — the usual Altair vibe.
basic.as entry point — start the REPL
repl.as interactive shell, command dispatcher, SAVE/LOAD
interpreter.as statement execution (every BASIC statement lives here)
parser.as expression parser, built-in functions, FN call dispatch
lexer.as tokenizer
runtime.as shared interpreter state (program buffer, variables,
arrays, FOR/GOSUB stacks, DATA pool, flags)
The Runtime class holds all mutable state — there are no globals
floating around between modules. Every other component takes a
Runtime instance and operates on it.
From the directory containing the files:
alexscript basic.asYou should see:
AlexScript Altair Basic 0.0.1
Type 'HELP' for available commands
>
| Command | What it does |
|---|---|
RUN |
execute the stored program |
LIST |
display the stored program |
NEW |
clear the stored program |
CLEAR |
clear the screen |
SAVE "file" |
save program to a text file |
LOAD "file" |
load program from a text file |
HELP |
show the command list |
QUIT |
exit |
A line that starts with a number is stored in the program buffer. Typing just the number deletes that line. Anything else is treated as an immediate command and executed on the spot.
Comma-separated values get a space between them. A trailing semicolon suppresses the newline. Strings can be mixed with expressions freely.
PRINT "Hello, world!"
PRINT "X =", X, "Y =", Y
PRINT "no newline";
TAB(n) inside a PRINT jumps to column n.
LET X = 42
LET NAME$ = "Alice"
X = X + 1 ' LET is optional once X is defined
Array elements work the same way:
LET A(3) = 99
A(I, J) = I * J
Prompt is optional. Multiple variables can be read from one line of input by separating them with commas.
INPUT N
INPUT "Enter your name: "; NAME$
INPUT "Coordinates: "; X, Y
$-suffixed names get the raw string, everything else is parsed as a
number (with 0 as fallback if parsing fails). Array targets are
supported too: INPUT A(I).
IF X > 10 THEN PRINT "big" ELSE PRINT "small"
IF READY = 1 THEN GOTO 200
GOTO 50
The target of GOTO can be any expression that evaluates to a line
number, not just a literal.
Counter variable, end value, and optional STEP. Works for nested
loops — NEXT without a variable closes the innermost loop.
FOR I = 1 TO 10
PRINT I
NEXT I
FOR X = 10 TO 0 STEP -2
PRINT X
NEXT X
GOSUB pushes the next line onto a return stack and jumps; RETURN
pops it. END terminates the program.
10 GOSUB 100
20 PRINT "back"
30 END
100 PRINT "subroutine"
110 RETURN
Declares an array. Indices are 0-based and inclusive — DIM A(10)
gives you 11 slots from A(0) to A(10). Multi-dimensional arrays
are supported.
DIM A(10)
DIM GRID(5, 5)
User-defined one-liner functions. Parameters shadow same-named globals during the call and are restored afterwards. Functions can call other functions in their body.
DEF FN SQUARE(X) = X * X
DEF FN HYP(A, B) = SQR(FN SQUARE(A) + FN SQUARE(B))
PRINT FN HYP(3, 4) ' prints 5
DATA declarations are collected into one big pool during a pre-pass
before the program runs, regardless of where they appear in the
source. READ consumes from the pool sequentially; RESTORE rewinds
the pointer back to the start.
10 DATA "Alice", 30, "Bob", 25
20 READ NAME$, AGE
30 PRINT NAME$, AGE
40 READ NAME$, AGE
50 PRINT NAME$, AGE
60 RESTORE
70 READ NAME$
80 PRINT NAME$ ' Alice again
REM is a comment — works on its own line or as a trailing
annotation. Multiple statements can share a line by separating them
with colons.
10 REM this is a header comment
20 LET X = 5 : REM initialise
30 PRINT X : PRINT X*2 : PRINT X*3
- parentheses, unary
-,NOT,~ ^(exponent, right-associative)*/+-=<><><=>=&,AND|,OR
Note:
&/|/AND/ORare treated uniformly as logical operators, not bitwise. This is a deliberate simplification from the Ruby original.
Math: ABS(x), SGN(x), SQR(x), INT(x), LOG(x), EXP(x),
SIN(x), COS(x), TAN(x), ATN(x), RND (random float in [0,1))
Strings: LEN(s), STR$(n), CHR$(code), VAL(s), LEFT$(s, n),
RIGHT$(s, n), MID$(s, start, length) — note MID$ is 1-indexed
the way classic BASIC does it.
Uppercase letters followed by uppercase letters or digits, with an
optional type suffix $, %, #, or !. Examples: X, A1,
NAME$, COUNT%. The suffix is part of the name, so A and A$
are different variables.
10 PRINT "Calculate factorial"
20 INPUT "Enter a number: "; N
30 LET FACT = 1
40 FOR I = 1 TO N
50 LET FACT = FACT * I
60 NEXT I
70 PRINT N; "! ="; FACT> RUN
Calculate factorial
Enter a number: ? 5
5! = 120
10 LET N = 5
20 GOSUB 100
30 PRINT N; "! ="; F
40 LET N = 7
50 GOSUB 100
60 PRINT N; "! ="; F
70 END
100 LET F = 1
110 FOR I = 1 TO N
120 LET F = F * I
130 NEXT I
140 RETURN> RUN
5! = 120
7! = 5040
10 DATA "Alice", 30, 165
20 DATA "Bob", 25, 175
30 DATA "Carol", 35, 160
40 LET TOTALAGE = 0
50 FOR I = 1 TO 3
60 READ NAME$, AGE, HEIGHT
70 PRINT "NAME:"; NAME$; " AGE:"; AGE; " HEIGHT:"; HEIGHT
80 LET TOTALAGE = TOTALAGE + AGE
90 NEXT I
100 PRINT "Average age ="; TOTALAGE / 3> RUN
NAME: Alice AGE: 30 HEIGHT: 165
NAME: Bob AGE: 25 HEIGHT: 175
NAME: Carol AGE: 35 HEIGHT: 160
Average age = 30
10 DEF FN SQUARE(X) = X * X
20 DEF FN CUBE(X) = X * X * X
30 FOR X = 1 TO 5
40 PRINT "x="; X; " sq="; FN SQUARE(X); " cube="; FN CUBE(X)
50 NEXT X> RUN
x=1 sq=1 cube=1
x=2 sq=4 cube=8
x=3 sq=9 cube=27
x=4 sq=16 cube=64
x=5 sq=25 cube=125
Runtimeclass instead of globals. A single instance is threaded through the interpreter and parser. Easier to reason about, no shared mutable state across files.- Sorted line index. A parallel sorted array of integer line
numbers lives next to the program buffer, so
RUNandGOTOdon't pay for a re-sort on every iteration. - Index-based lexer. The original Ruby version mutates a
character array via
shift. This port keeps apositioncursor on the source string — same semantics, no per-token allocation. assign_to_targetshared byLETandINPUT. Whether the target is a scalar, an array element, or a multi-dim slot, both statements go through one assignment routine. Bug fixes on either side benefit both.- Two-pass program execution. First pass scans for
DATAdeclarations and builds the data pool; second pass runs the program normally.DATAlines are no-ops during normal flow.