Relax constraints in the language where it is easy to do so
without compromising compatibility.

Vintage BASIC is written in Haskell.
Haskell has a feature called monads, which aid in constructing custom
control structures. The interpreter handles BASIC's dynamic
control structures by using an advanced form of exception, rather than
the traditional
stack.

Early BASICs used an interactive prompt for editing
program text. Vintage BASIC allows you to use your own text editor.
However, it still supports the line numbers of traditional BASIC, as
targets for control flow.

System
Requirements and Installation

Vintage BASIC requires minimal resources, and will run on Windows, Mac, and Linux.
(It can be compiled from source to run on these or other platforms, too.)
Please see the downloads page
for installation instructions.

Running
Vintage BASIC

From the command line

The interpreter consists of one command-line tool, vintbas.
It takes one filename parameter, the name of a BASIC source file.
By convention (but not fiat), it should end in .bas. So, if you've written
BASIC code and saved it in miracle.bas, you'd
run it by typing vintbas miracle.bas.

On Windows

If you have used the Windows installer, you can open Vintage BASIC using the
Vintage BASIC Prompt item in the Vintage BASIC folder on the Start Menu. Or, you
can just open Command Prompt. If you have a BASIC program saved with a .bas
extension, you can double-click on it in Explorer to run it.

BASIC
Program Lines

Each line in your BASIC source file should start with a BASIC
line number (also called a label).
These numbers are part of the text at the start of the
line, and not just a count of lines from the start of the file. They
are used as labels for GOTO, GOSUB,
and THEN targets, as well as in RESTORE
statements. Here is a sample BASIC program:

Barring any flow control changes, the lines are executed in
order by line number, regardless of the sorting of the input file. If
two lines in the input file have the same line number, the later one
will take precedence (you will get a warning when lines are
superseded). A space is not required following the line number, but it
aids in readability.

Control flow will end when your program reaches the end of the
lines, when an END or STOP
statement is encountered, or when an error occurs. Errors may occur
during parsing or execution, and always being with an exclamation point
(!).

BASIC
Syntax

Each line of BASIC consists of a series of statements, separated by
colons. Statements are executed in order, unless an error or
control flow statement is encountered.

BASIC keywords are not case sensitive. You may
freely use
lowercase, uppercase, or mix both. Examples will be shown in caps since
that is traditional for the language. Literal strings in quotes are
case sensitive. They will be printed in the case used in the source.

Spaces are not required between keywords. This is a violation
of the ANSI standard, but was implemented on some early microcomputers
in order to save memory. It will aid in compatibility with running
programs written for such computers. Spaces are encouraged because the
code can be harder to read without them. For example, FORK=1TON
appears to set the value of a variable FORK
to a weight of 1 ton. In reality it begins a FOR
loop with control variable K, ranging in
value from 1 to N.
Ignoring spaces means that keywords must be tokenized by the parser
before the parsing phase. It also makes it difficult to use long
variable names, which might accidentally contain keywords. For example,
FACTOR contains the keyword TO.
Experience shows that people can get used to reading code without
spaces.

BASIC
Types

Vintage BASIC has a simple type system. In expressions, all values are
either floating-point or string. Variables can additionally hold
integers, which are converted to/from floating-point to be used in
expressions. Floating-point values are also used for boolean logic. The
canonial true value is -1, and the canonical
false value is 0. These are the values that
will be generated by comparison operators. However, any nonzero value
will be treated as true by the boolean operations.

BASIC
Expressions

Expressions in BASIC, like in most languages, are used to
compute a value. They can be used in many statements, such as LET,
DIM, ON-GOTO, ON-GOSUB,
IF-THEN, FOR, PRINT,
and DEF FN. They consist of literal values
and variable references, composed by operators and function calls.
Let's look at each of these in turn.

literal

Literals include floating-point numbers and strings.
Floating-point literals are of the form [+|-]999[.999][E[+|-]99]: a
sign, a mantissa, and an exponent indicated by the letter E. The signs
and exponent are optional. The decimal point is optional if you don't
need to put any digits after it. Examples: 0,
2, 5., 5.6,
90000000, -.01, 1E-20.
Precision depends upon your platform, but is generally IEEE bin32
(single precision). There are no special IEEE values, like +Inf or NaN.
String literals are enclosed in quotes, e.g. "HELLO".
There are no escape sequences. To generate a quote character, append a CHR$(34).

varname

Variable names can include any number of letters followed
by any number
of digits, and a type indicator. However, only the first two letters
and first digit are considered significant to distinguish variable
names. Since computers historically did not have enough
memory to handle long variable names, variable names were kept short.
The ANSI Minimal BASIC standard, and some early BASICs, allowed only
one letter and one digit in the variable name. In some later BASICs,
the first two letters and the first digit of the variable name are
considered significant. That is the choice made in Vintage BASIC. So,
the variables MILK123 and MINT145
are considered the same.

Variables can store integers, floating-point
numbers or strings. Integer variables are mainly there to save storage
memory; in calculations they are converted to floats. String variable
names are ended with a $, and integer
variables with a %, while floating-point
variables have no type indicator. Variable names with the same
significant letters and digits but different type indicators are
distinct. Thus, you can store a string in A$,
a floating-point value in A, and an integer
value in A% without a conflict.

var

Variables can also be scalar or array. Scalar variables are
indicated by an variable name alone. Array variables are accessed by
following the variable name with parentheses enclosing a
comma-separated list of expressions whose values are indices into the
array. For example, A(3) is the value from
index 3 in the floating-point array named A,
and FR$(2,3) is a string value from index 2
(in the first dimension) and 3 (in the second dimension) in an array
named FR$. Scalar and array variables are
also distinct from each other, so it is possible to have a scalar
variable A with value 3,
so that A(A) would be A(3)
and might contain some other value.

FNvarname(expr1,expr2,
...)

Another sort of variable is a user-defined function.
Defined using DEF FN statements, they are
accessed in expressions by preceding the function name with the FN
keyword. Example: FN A(4) calls the
floating-point user-defined function A with
argument value 4.

builtin(expr1,expr2,
...)

Calls a builtin function. The names of the builtins are
keywords, so they cannot be used in variable names. See the list of
builtin functions below.

(expr)

A parenthesized expression has the same value of
expression. Parentheses can be used to override operator precedence.

-expr

Negates a floating-point expression.

+expr

No effect. This is included for parity with the negation
operator.

expr1^expr2

Exponentiation. Raises the value of the first expression to
the power of the value of the second expression.

expr1*expr2

Multiplication. Multiplies the value of the first
expression by the value of the second expression.

expr1/expr2

Division. Divides the value of the first expression by the
value of the second expression. Division by zero is an error.

expr1+expr2

Addition. Adds the value of the first expression to the
value of the second expression.

expr1-expr2

Subtraction. Subtracts the value of the second expression
from the value of the first expression.

expr1=expr2

Equality test. Evaluates to 0
if the two strings or numbers are equal, -1
if they differ.

expr1<>expr2

Inequality test. Evaluates to -1
if the two strings or numbers are equal, 0 if
they differ.

expr1<expr2

Less than. Evaluates to -1 if the
first string or number is less than the second, 0
otherwise. (Strings are compared alphabetically.)

expr1<=expr2

Less than or equal. Evaluates to -1 if
the first string or number is less than or equal to the second, 0
otherwise. (Strings are compared alphabetically.)

expr1>expr2

Greater than. Evaluates to -1 if
the first string or number is less than the second, 0
otherwise. (Strings are compared alphabetically.)

expr1>=expr2

Greater than or equal. Evaluates to -1
if the first string or number is less than or equal to the second, 0
otherwise. (Strings are compared alphabetically.)

Logical conjunction. If neither expression evaluates to 0,
then the result is the value of expr1.
Otherwise, the result is 0.

expr1ORexpr2

Logical disjunction. If expr1 evaluates to
a nonzero value, that value is the result. Otherwise, the value of expr2 is the result.

Operator
Precedence and Associativity

Operators are listed in precedence groups, from highest to lowest.

Operators

Associativity

unary -, unary +

N/A

^

right

*, /

left

+, -

left

=, <>,
<, <=,
>, >=

left

NOT

N/A

AND

left

OR

left

Builtin
Functions

The builtin functions each take a particular number of arguments of
particular types. Passing the wrong number of arguments or types will
result in a runtime error. Each argument can be an expression. The
expressions are evaluated before being passed to the builtin function.

ABS(float)

Returns the absolute value of an expression. (I.e., its
negation if it is negative.)

ASC(string)

Returns the ASCII value of the first character of the
string.

ATN(float)

Returns the arctangent of the value. The range is from -pi/2 to pi/2.

CHR$(float)

Returns a single-character string with the specified ASCII
value.

COS(float)

Returns the cosine of the value.

EXP(float)

Returns the transcendental number e raised to the
power of the value.

INT(float)

Rounds the number down to the next lower integer.

LEFT$(string,
float)

Returns a prefix of the string, with the number of
characters specified by the second argument. Negative numbers are an
error, while numbers too large will result in the whole string being
returned.

LEN(string)

Returns the length of the string.

LOG(float)

Returns the logarithm, to base e, of the number.
Argument must be positive.

MID$(string,
float)

MID$(string,
float,
float)

In its first form, returns a substring starting from the
specified index (1-based), through the end of the string. In the second
form, returns a substring starting an index specified by the second
argument, with the number of characters specified by the third
argument. Indexes less than one or string lengths less than zero will
result in a runtime error. Any attempt to extract characters beyond the
length of the string will be cut off at the end of the string with no
error.

RIGHT$(string,
float)

Returns a suffix of the string, with the number of
characters specified by the second argument. Negative numbers are an
error, while numbers too large will result in the whole string being
returned.

RND(float)

Psuedorandom number generator. The behavior is different
depending on the value passed. If the value is positive, the result
will be a new random value between 0 and 1 (including 0 but not 1). If
the value is zero, the result will be a repeat of the last random
number generated. If the value is negative, it will be rounded down to
the nearest integer and used to reseed the random number generator.
Pseudorandom sequences can be repeated by reseeding with the same
number.

SGN(float)

Sign. Returns -1 if the value is negative, 0 if it is zero,
or 1 if it is positive.

SIN(float)

Returns the sine of the specified value.

SPC(float)

Returns a string containing the specified number of spaces.
This is useful for formatting text.

SQR(float)

Returns the square root of the value. Argument must be
non-negative.

STR(float)

Returns a string representation of the floating-point
value, as it would be printed by PRINT, but
without the trailing space.

TAB(float)

In most BASICs this is a special command available only in PRINT
statements, but in Vintage BASIC, it is just another builtin function.
Vintage BASIC keeps track of the output column as text is printed. The TAB
function generates a string with the number of spaces required
to advance the cursor to the column number specified by the
argument. Column numbers start with zero, which is consistent with
Microsoft BASIC but not the ANSI standard, which starts with one
instead. If the cursor is already past the desired column, the result
string will be empty. This behavior is consistent with
Microsoft BASIC but not the ANSI standard, which specifies that the
cursor should move to a new line before tabbing to the specified column.

TAN(float)

Returns the tangent of the value.

VAL(string)

Attempts to read the string as a floating-point number. If
it fails, the result is zero.

BASIC
Statements

DATAliteral1,literal2,
...

Has no effect when executed, but supplies data for the READ
statement. Each value can be a string or floating-point literal (not an
expression). Whitespace is ignored around values. Double quotes can be
placed around a string to escape whitespace and commas between the
quotes. DATA statements can occur on
the same line as other statements, but, due to its special parsing
rules, it must be the last statement on the line. The line on which the
DATA statement occurs can be used as the
target of a RESTORE statement. Example: DATA
January, 31, "Martian History Month".

DEF FNvarname(arg1,arg2,
...)=expr

Defines a user-defined function. The arguments are
variable names that may be used in the expression.
While the standard allows only for numeric functions of a single
floating-point variable, Vintage BASIC permits string, integer or
floating-point functions ofany number and type of arguments. Example: DEF
FN F(X) = X^2 + 3*X - 4. The argument variables shadow
the original variables; that is, the value of the original variable
with the same name will be restored after the function is evaluated.
Like scalar variables but unlike arrays, user-defined functions may be
repeatedly redefined. Warning: Recursive functions are definable, but
will always result in an infinite loop.

DIM varname(bound1, bound2, ...)

Dimensions an array. That is, specifies the number of
dimensions and upper index bounds for an array. The lower bound is
always zero. Unlike most BASICs, the boundaries are not limited to
literal values; they can be any expression. Attempting to re-dimension
an array will produce an error. Arrays can be referenced without
dimensioning; in that case they are automatically created as
one-dimenional arrays with an upper bound of 10. Example: DIM
RX$(20, 5) creates a two-dimensional, 20x5 array of
strings.

END

Terminates execution with no error condition. This
statement is not required at the end of a program because control flow
will end automatically there.

FORvarname = startTOend [STEPincrement]

Marks the start of a FOR-NEXT
loop, and its termination conditions. The control variable must be a
floating-point variable. It is initialized to the value of the start expression.
Each time a NEXT is encountered, the control
variable is incremented by one, or by increment if the
optional STEP clause is specified. If the
value of the control variable has not exceeded the value
of the end
expression, control will continue with the statement following the FOR.
But if the value of the control value exceeds end (greater than
end if increment
is positive, less than end if increment
is negative), control will pass to the statement following the NEXT.
The expressions start,
end,
and increment
are evaluated only once at the start of the loop. The termination
condition is not evaluated before entering the loop, as it is not
always possible to determine where the corresponding NEXT
might be, should the loop meet the termination condition. (This
contradicts the ANSI standard, but the ANSI standard is unenforceable.)
Example: FOR I = 1 TO 20 STEP 2: PRINT I, I*2: NEXT I.

GOSUBlabel

Transfers control to the line numbered label. Upon
encountering a RETURN statement, control will
return to the statement following the GOSUB.
This is the cornerstone of structured programming in BASIC. Note: GO
and SUB are separate keywords and may be
separated by space.

GOTOlabel

Transfers control to the line numbered label. Note: GO
and TO are separate keywords and may be
separated by space.

IFexprTHENstatement1:statement2:
...IFexprTHENlabel

Evaluates the truth of the supplied expression. If it is
true, it executes the statements following it on the line. If it is
false, control skips to the following line. The form with a label is a
shorthand for IFexprTHEN
GOTOlabel.
Expressions are expected to have floating-point values. They are
considered false if zero and true otherwise. Note: if statements follow
the label
form on the same line, they are ignored. Example: IF
A>0 THEN Y=Y+3:GOTO 80.

INPUT [prompt;]
var1,var2,
...

Reads input from an interactive user prompt. The optional
prompt string is followed by a question mark and a space. The
user may then enter a series of values separated by commas. The format
is the same as in the DATA statement, so
quotes can be used to escape spaces or commas. Value types entered must
match the variable types, or an error will be generated and the user
will be re-prompted. The user will also be re-prompted (with a double
question mark) if the user enters insufficient values to fill the
variables. The variables may be scalar or (indexed) array variables.
Examples: INPUT "NAME AND AGE";NA$(I), AG(I).

LETvar = expr

Evaluates the expression and assigns the value to the
variable. The LET keyword is optional.

NEXT [var]

Returns control to a FOR
statement to determine whether the loop should be repeated. If the
termination condition has not been met, control will proceed with the
line following the FOR statement. If the
termination condition has been met, control will proceed with the
statement following the NEXT. How does the
interpreter determine which FOR goes with the
NEXT? It is determined dynamically. If the
NEXT has no variable, then it is the most
recently encountered non-terminated FOR. If
the NEXT indicates a variable, then it is the
most recently encountered non-terminated FOR
with that control variable. Usually, it is simple to glance at code and
see which pairs of FOR and NEXT
statements go together. But due to the dynamic nature of the connection
between them, it is possible to write NEXT
statements that are sometimes connected with one FOR,
sometimes another. For an example, see FOR.

ONexprGOSUBlabel1, label2,
...

Like GOSUB, but makes a decision
about the target line based on the result of evaluating an expression.
The value of expr
is rounded down to the nearest integer, and is used as an index into
the list of labels, starting with 1. If the index is less than 1 or
greater than the number of labels, control falls through to the next
statement (as in Microsoft BASIC but in violation of the ANSI standard,
which prescribes a runtime error).

ONexprGOTOlabel1, label2,
...

Like GOTO, but makes a decision
about the target line based on the result of evaluating an expression.
The value of expr
is rounded down to the nearest integer, and is used as an index into
the list of labels, starting with 1. If the index is less than 1 or
greater than the number of labels, control falls through to the next
statement (as in Microsoft BASIC but in violation of the ANSI standard,
which prescribes a runtime error).

PRINTexpr1[;|,]
expr2 [;|,]
...

Outputs text to the user. Multiple expressions can be
separated by semicolons or commas. Semicolons leave no space between
printed expressions. A comma is used to align text neatly in columns;
it forces the next output column to be the start of the next print
zone. Print zones are always 14 characters wide. A newline will be
automatically printed at the end of the line, unless the PRINT
statement ends in a semicolon or comma. Floating-point values are
automatically
padded with one trailing space, and positive values are also padded
with a leading space, so that they take up the same amount of space as
negative numbers. Example: PRINT "TURN";TU, "YOU
HAVE";LI;"LIVES LEFT".

RANDOMIZE

Re-seeds the random number generator used for the RND
function, based on the number of seconds that have elapsed since
midnight, local time.

READvar1,var2,
...

Reads data from DATA statements
into variables. A pointer is maintained into the DATA
values, which could be anywhere within the program. Values are read in
order into the variables, and the pointer is advanced. A runtime error
occurs if there are not enough DATA values to
fill the variables. The DATA pointer can be
reset using a RESTORE statement. Example: READ
A$, B.

REMtext

A comment (remark). All characters through the end of the
line are considered to be part of the comment. Example: REM
BRIDGE BY J. Q. PROGRAMMER.

RESTORE [label]

Adjusts the DATA value pointer.
Without a line number, the pointer is reset to the first DATA
statement of the program. With a line number, the
pointer is moved to the first DATA statement
on or following that line.

RETURN

Returns control to the statement following the most
recently executed GOSUB to which control has
not already been RETURNed. GOSUB-RETURN
pairs can be nested to any depth. If there is no such GOSUB,
a runtime error occurs. As with FOR-NEXT, association of a RETURN
with a GOSUB is dynamic; it cannot be
statically determined in all cases.

STOP

In early BASICs, the END
statement could only appear at the end of the program. The STOP
statement was designed to send control to that solitary END
statement. Vintage BASIC imitates this behavior by treating the STOP
statement as an END. This is unlike Microsoft
version of BASIC, in which STOP is used to
terminate the program without clearing variables, so that they can be
inspected for debugging purpose, and the CONT
statement can resume control where it left off. That wouldn't be very
useful in Vintage BASIC, since it does not have a read-eval-print loop.

Error
Messages

!BAD GOSUB TARGET label1
in line label2

There is no line number corresponding to the target
specified for the GOSUB.

!BAD GOTO TARGET label1
in line label2

There is no line number corresponding to the target
specified for the GOTO.

!BAD RESTORE TARGET label1
in line label2

There is no line number corresponding to the target
specified for the RESTORE.

!BREAK IN LINE label

A STOP statement was encountered.

!DIVISION BY ZERO IN LINE label

An attempt was made to divide by zero.

!END OF INPUT IN LINE label

An attempt to INPUT characters
failed because the end of the standard input stream was reached.

!INVALID ARGUMENT IN LINE label

The argument supplied to a builtin function was outside of
the allowed range.

!LINE NUMBERING ERROR IN RAW LINE nn

The line scanner could not properly read line numbers, or
the file did not end in a newline.

!MISMATCHED ARRAY DIMENSIONS IN LINE label

The
number of indices used in an array reference does not match the number
of bounds supplied in the DIM statement (1 if it was not dimensioned).

!NEGATIVE ARRAY DIM IN LINE label

A negative value was supplied as an array index, or bound
in a DIM statement.

!NEXT WITHOUT FOR ERROR IN LINE label

A NEXT statement was encountered,
but a matching FOR statement could not be
found. Either a FOR statement was never
encountered, or all FOR statements have
reached their termination conditions.

!NEXT WITHOUT FOR ERROR (VAR X) IN
LINE label

A NEXT statement was encountered,
but a matching FOR statement could not be
found. Either a FOR statement with that
control variable was never encountered, or all FOR
statements with that control variable have reached their termination
conditions.

!OUT OF ARRAY BOUNDS IN LINE label

A supplied array index exceeded the bound for that
dimension.

!OUT OF DATA IN LINE label

A READ statement in this line
tried to read data, but the DATA pointer has
already reached the end of data strings in the program. You may have
too few DATA statements, or a bad loop
termination condition that causes too many READs.

!REDIM'D ARRAY IN LINE label

A DIM statement was encountered,
but the array has already been dimensioned, either explicitly (through
anorther DIM statement), or automatically
(through reference to the array).

!RETURN WITHOUT GOSUB ERROR IN LINE label

A RETURN statement was
encountered, but a matching GOSUB statement
could not be found. Either a GOSUB statement
was never encountered, or all GOSUB
statements have already been RETURNed to.

!SYNTAX ERROR IN LINE label

The program did not meet valid syntax
requirements. The error may contain more details as to what
symbols were found or expected.

!TYPE MISMATCH IN LINE label

The
type of an expression was not the type expected by the surrounding
context, whether it be an operator, builtin function, user-defined
function, array, or statement that requires an expression. In a READ
statement, the type of the variable did not match the value read.

!UNDEFINED FUNCTION X IN
LINE label

The user-defined function was called via FN
but has never been defined by DEF FN.

!WRONG NUMBER OF ARGUMENTS IN LINE label

The wrong number of arguments were passed to the builtin or
user-defined function.