Vintage BASIC User's Guide 1.0.3

© 2009–2017, Lyle Kopnicky

Congratulations on installing Vintage BASIC! You are now the proud owner of a copy of a versatile tool for BASIC development.

Vintage BASIC is a language derived from the Microsoft's original Altair BASIC. It would feel right at home on a Commodore 64. The design tensions of Vintage BASIC are:

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.

Table of Contents

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 GOTOGOSUB, and THEN targets, as well as in RESTORE statements. Here is a sample BASIC program:

10 INPUT"YES OR NO";A$
20 IF A$ = "YES" THEN 50
30 IF A$ = "NO" THEN 60
40 PRINT"YOU MUST ENTER YES OR NO.":GOTO 10
50 PRINT"GREAT!":END
60 PRINT"WHY NOT?":END

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 canonical 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.
FN varname(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.)
NOT expr
Negates a boolean expression. Specifically, nonzero values become 0 and 0 becomes -1.
expr1 AND expr2
Logical conjunction. If neither expression evaluates to 0, then the result is the value of expr1. Otherwise, the result is 0.
expr1 OR expr2
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

DATA literal1, 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 FN varname(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.
FOR varname = start TO end [STEP increment]
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.
GOSUB label
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.
GOTO label
Transfers control to the line numbered label. Note: GO and TO are separate keywords and may be separated by space.
IF expr THEN statement1: statement2: ...
IF expr THEN label
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 IF expr THEN GOTO label. 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).
LET var = 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.
ON expr GOSUB label1, 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).
ON expr GOTO label1, 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).
PRINT expr1 [;|,] 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.
READ var1, 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.
REM text
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.