BASIC (Basic Programming Language) - Nakazoto/CenturionComputer GitHub Wiki
Surprisingly, Centurion did include BASIC in some form on the OS. However, it is not like the interactive BASIC such as Microsoft BASIC provided with 1970s and 1980s microcomputers. It is instead compiled into an executable, but that executable can only be run if the Basic Monitor has been loaded. Other than that, the BASIC language does behave like fairly typical Integer BASIC, although heavily influenced by CPL and the Centurion batch file processing paradigm. It is interesting to note that Centurion BASIC does support UPSI and PUPSI, meaning it has the ability to pass values back and forth with JCL scripts. This really elevates the usefulness of BASIC on the Centurion.
INSERT SCREENSHOT HERE
The following is an example BASIC program that prints "Hellorld!" 10 times on the screen.
10 FOR I = 1 TO 10
20 PRINT "HELLORLD!"
30 NEXT I
To actually compile this program, it must first be typed up in an ASCII file. Follow the steps below to create the ASCII file, edit it, and then compile and execute the program. These steps are written with the Operating System loaded onto the fixed disk (1) and the default disk set to 1.
- .NEW ZHELLO ON 1 'A' 1S
- Can be skipped if using CED as it will create the file for you
- File name must have "Z" at the beginning
- .NEW @SCRE0 ON 1 'E' 1T
- Only needs to be done if file doesn't already exist
- S.CED
- CED is a wonderful line editor that can be used to write code into ZHELLO
- P.BASIC HELLO 1 CRT0 X
- .LOAD BASIC
- Must be run once to load the basic monitor.
- .RUN XHELLO
The compiler is a single pass translator which produces a Centurion executable consisting of a stub to call the BASIC monitor followed by the program logic converted to Reverse Polish Notation bytecode. The BASIC monitor executes the bytecode which follows the stub.
The easiest way of compiling BASIC is through the P.BASIC JCL script.
Syntax: P.BASIC job disk output [map] [lib]
job: the name of the source file without the Z prefix
disk: the disk number of the location of the source file
output: an output device (e.g., CRT0) or use LST to output to the file @LSTn where n is the partition (e.g., @LST0)
MAP: the compiled bytecode will be listed on the output device
LIB: a library filename will be requested and the preprocessor will be run.
If LST is specified as the output device, if there are compile errors S.SCAN will be executed for review of the source code and errors.
If LIB is specified the script will prompt for a library file and execute the preprocessor XBSPRP. This allows subroutine libraries to be provided and inserted into the source file before compilation.
If there are compile errors S.TXT will execute to allow correction.
If the LIB option is provided to P.BASIC then the preprocessor is executed. P.BASIC will prompt for a copy library to be used as the source file for copying subroutines into the original program.
There is a reserved statement for the preprocessor of COPY which specifies the "book" in the library.
The format of a book copy entry is as follows:
-
(as the first character of the line followed by the book name of up to six characters. The rest of the line is ignored. - One or more lines of the code to be copied.
- If the line starts with a
\followed by a four digit number then it can be used as a label for aGOSUBorGOTOin the copied code. The\and number are optional. - The entry ends with the start of the next entry indicated by
(or the end of file.
An example of a book entry for printing HELLORLD! 10 times:
(HELLO PRINT HELLORLD
\0001LET I = 0
\0002PRINT "HELLORLD!"
\0003LET I = I + 1
\0004IF I < 10 THEN \0002
An example of a BASIC program which includes this code:
10 GOSUB 1000
20 STOP
1000 COPY HELLO
1010 RETURN
The preprocessed source is saved in @SCR0 to be used as input to the compiler. The original source file is untouched
10 GOSUB 1000
20 STOP
01000 REM $COPY HELLO
01001 LET I = 0
01002 PRINT "HELLORLD!"
01003 LET I = I + 1
01004 IF I < 10 THEN 01002
1010 RETURN
This was reverse engineered using Ren's excellent disassembler.
Lines must start with a line number greater than zero and less then 32768 (or less than 32767 if the source file does not include an END statement). Lines must be in sequential order in the source file.
Multiple statements can be included on a source line, separated by :
Comments are indicated by ! which will result in the compiler ignoring the rest of the line, or * which will result in the compiler skipping to the next statement indicated by : or the end of the line.
There are three main types:
- Integer - 16 bit 2s complement integers.
- Real - 48 bit fixed point 6 decimal place real numbers.
- Strings - Any character between pairs of
'or"characters.
-
%specifies an integer value or variable/function -
$specifies a string value or variable/function
The default type for variables and numeric literals is Real.
Variables are any combination of alphanumeric characters starting with a letter and optionally including a type specifier. The identifier including the type specifier uniquely identifies the variable. Variables can't have the same identifier (excluding type specifier) as a function.
Variables can be declared as an array with the DIM statement. Up to two dimension arrays are permitted. The array size indicates the number of elements which is zero indexed, similar to C style arrays. Dimensioned arrays are determined at compile time so must have fixed sizes. The same identifier (including type specifier) cannot be used for both dimensioned and non-dimensioned variables.
There are 32 functions available in the language. Not all of them are implemented in the runtime environment.
| Function | Usage | Notes |
|---|---|---|
| ABS | ABS(arg) | Absolute value |
| ADDR | ADDR(arg) | Not implemented |
| ASCII | ASCII(arg) | The ascii code for the first character of a string |
| CHR | CHR(arg) | The character (string) from the ascii code |
| DATE/DATE$ | DATE | |
| END | END(arg) | |
| ERR | ERR(arg) | |
| EXP | EXP(arg) | The natural exponential function |
| FALSE | FALSE | The logical false value |
| FILEID | FILEID(arg) | |
| INT | INT(arg) | The integer value of arg |
| JP | JP(arg) | See FILEID |
| KEYX | KEYX(arg) | Not implemented |
| LC | LC(arg) | The lower case representation of arg |
| LEN | LEN(arg) | The string length of arg |
| LOG | LOG(arg) | The natural logarithm of arg |
| PEEK | PEEK(H$, N$) | Find the first occurence of N$ in H$. Zero indexed. Returns -1 if not found |
| POKE | POKE(arg, arg, arg) | Not implemented |
| RECLEN | RECLEN | |
| REP | REP(arg, arg) | |
| RND | RND(arg) | Returns a pseudorandom number between 0 and 1. The argument is ignored |
| SGN | SGN(arg) | Returns the sign multiplied by one of its argument |
| SHL | SHL(arg, arg) | Not implemented |
| SHR | SHR(arg, arg) | Not implemented |
| SSTR | SSTR(S$, I%, L%) | The substring with length L% starting from position I% (zero indexed) |
| SSW | SSW(S%) | Returns TRUE if the sense switch S% is set, FALSE otherwise |
| STATUS | STATUS | |
| TAB | TAB(arg) | Sets the position of the print buffer to its argument. Returns an empty string |
| TRACE | TRACE(arg) | Non zero argument initiates program tracing. Returns the previous state |
| TRUE | TRUE | The logical true value |
| UC | UC(S$) | The upper case representation of S$ |
| UPSI | UPSI(arg) |
The following operators are available, with their precedence (1 is highest):
| Operator | Precedence | Notes |
|---|---|---|
() |
1 | Parenthesis |
() |
1 | Array index |
() |
1 | Function application |
**, ^
|
2 | Exponentiation |
* |
3 | Multiplication |
/ |
3 | Division |
+ |
4 | Addition |
- |
4 | Subtraction |
// |
5 | String Concatenation |
<=, LE
|
6 | String or numeric comparison |
<>, NE
|
6 | String or numeric comparison |
<, LT
|
6 | String or numeric comparison |
=, EQ
|
6 | String or numeric comparison |
>=, GE
|
6 | String or numeric comparison |
>, GT
|
6 | String or numeric comparison |
NOT |
7 | Integer bitwise NOT |
&, AND
|
8 | Integer bitwise AND |
OR |
9 | Integer bitwise OR |
XOR |
9 | Integer bitwise XOR |
The expression syntax in EBNF-like grammar:
expr = andexpr [ { ( "OR" | "XOR" ) andexpr} ].
andexpr = notexpr [ { ( "&" | "AND" ) notexpr } ].
notexpr = [ "NOT" ] compexpr.
compexpr = catexpr [ ( "<=" | "<>" | "<" | "=" | ">=" | ">" | "EQ" | "GE" | "GT" | "LE" | "LT" | "NE" ) catexpr ].
catexpr = addexpr [ { "//" addexpr } ].
addexpr = [ ( "-" | "+" ) ] multexpr [ { ( "+" | "-" ) multexpr } ].
multexpr = expexpr [ { ( "*" | "/" ) expexpr } ].
expexpr = priexpr [ { ( "**" | "^" ) priexpr } ].
priexpr = "(" expr ")"
| numliteral
| stringliteral
| function
| dimvar
| ident.
function = fnident [ "(" expr [ { "," expr } ] ")" ].
dimvar = dimident "(" expr [ "," expr ] ")".
Notes:
- Exponentiation has the wrong associativity, it should be right associative (this is consistent with original Dartmouth BASIC).
- Operands are converted to the correct type if possible at runtime.
- Unary Plus and Minus when applied to literals are a separate operation. To create a negative numeric literal, then a string representation is required, such as
"-1234"which will be converted to a negative number at runtime.
| Statement | Usage | Notes |
|---|---|---|
| CALL | ||
| CHAIN | ||
| CLOSE | ||
| CURSOR | ||
| DATA | ||
| DECLARE | ||
| DEF | Define a user defined function | |
| DIM | Define a dimensioned variable (array) | |
| DLTKEY | ||
| END | END | Indicates the end of the program source. Any source following the END statement is ignored. The compiler will insert an END statement if none is present in the source |
| ELSE | The negative branch of an IF statement | |
| FILE | ||
| FNEND | End the definition of a user defined function | |
| FORMAT | ||
| FOR | The start of a FOR loop |
|
| FREE | ||
| GETKEY | ||
| GOSUB | ||
| GOTO | ||
| HOLD | ||
| IF | ||
| INPUT | ||
| LET | ||
| LINELENGTH | ||
| LOCAL | ||
| MOVE | ||
| NEWKEY | ||
| NEXTKEY | ||
| NEXT | The end of a FOR loop |
|
| NOTE | ||
| ON | ||
| OPEN | ||
| OUTPUT | ||
| POINT | ||
| PJP | ||
| PUPSI | ||
| RANDOMIZE | ||
| READ | ||
| RECORD | ||
| REM | ||
| RESTORE | ||
| RETURN | Return from a subroutine user defined function | |
| REWRITE | ||
| STOP | Finish execution of the program | |
| WRITE |
ABS ( expression )
10 INPUT "ENTER A NUMBER (0 TO QUIT): " A
20 PRINT "THE ABSOLUTE VALUE OF " A " IS " ABS(A)
30 IF A <> 0 THEN 10
. RUN XABS
08:53:25 .RUN XABS
ENTER A NUMBER (0 TO QUIT): 5
THE ABSOLUTE VALUE OF 5 IS 5
ENTER A NUMBER (0 TO QUIT): -12.34
THE ABSOLUTE VALUE OF 12.34- IS 12.34
ENTER A NUMBER (0 TO QUIT): 12.34-
THE ABSOLUTE VALUE OF 12.34- IS 12.34
ENTER A NUMBER (0 TO QUIT): 0
THE ABSOLUTE VALUE OF 0 IS 0
DATE
DATE$
There are two versions of DATE, a numeric version which returns a five or six digit representation of the date as MMDDYY and a string version which returns a date string in the format MM/DD/YY
10 PRINT "DATE (NUMBER VERSION) IS " DATE
20 PRINT "DATE (STRING VERSION) IS " DATE$
.run xdate
00:04:52 .RUN XDATE
DATE (NUMBER VERSION) IS 22388
DATE (STRING VERSION) IS 02/23/88
INT ( expression )
INT returns a real number with the integer value which is the next closest to zero.
10 INPUT "ENTER A NUMBER (0 TO QUIT): " A
20 PRINT "THE INTEGER VALUE OF " A " IS " INT(A)
30 IF A <> 0 THEN 10
.run xint
00:57:05 .RUN XINT
ENTER A NUMBER (0 TO QUIT): 1234.56
THE INTEGER VALUE OF 1234.56 IS 1234
ENTER A NUMBER (0 TO QUIT): -1234.56
THE INTEGER VALUE OF 1234.56- IS 1234-
ENTER A NUMBER (0 TO QUIT): 0
THE INTEGER VALUE OF 0 IS 0
SGN ( expression )
If expression is less than zero, return -1
If expression is greater the zero, return 1
If expression is equal to zero, return 0
If the real value 0.000000 is used as the argument the result is incorrect.
10 INPUT "ENTER A NUMBER (0 TO QUIT): " A
20 PRINT "THE SIGN OF " A " IS " SGN(A)
30 IF A <> 0 THEN 10
. run xsgn
19: 12:07 .RUN XSGN
ENTER A NUMBER (O TO QUIT): 5
THE SIGN OF 5 IS 1
ENTER A NUMBER (0 TO QUIT): -12.34
THE SIGN OF 12.34- IS 1-
ENTER A NUMBER (0 TO QUIT): 12.34-
THE SIGN OF 12.34- IS 1-
ENTER A NUMBER (0 TO QUIT): 0
THE SIGN OF 0 IS 32256-
DEF allows the user to define their own functions. There are two forms: a single line form where the function is calculated from an expression combining its arguments and a multi-line form where the function can be composed from multiple statements.
Functions must be defined earlier in the source than their first use.
Recursion is not permitted.
RETURN can be used for an early return of the function, returning the value of the function variable at that point.
DEF can be used to define subroutines (i.e., functions which do not return any values). Because the value of the function variable is always returned, subroutines can be invoked using CALL which will ignore any function result.
DEF identifier [(identifier {[, identifier]})]
statements
FNEND
DEF identifier [ ( identifier [ {, identifier } ] ) ] = expression
10 DEF E=EXP(1)
20 PRINT "THE MATHEMATICAL CONSTANT E IS EQUAL TO " E
.run xdef
07:44:56 .RUN XDEF
THE MATHEMATICAL CONSTANT E IS EQUAL TO 2.718282
10 DEF E
20 LET E=EXP(1)
30 FNEND
40 PRINT "THE MATHEMATICAL CONSTANT E IS EQUAL TO " E
.run xdef
08:06:32 .RUN XDEF
THE MATHEMATICAL CONSTANT E IS EQUAL TO 2.718282
FOR identifier = expression TO expression [ STEP expression] [WHILE expression]
statements
NEXT identifier
Use of WHILE is valid syntax although the bytecode is incorrect and will result in runtime errors. Possibly because the programmer made a typo and wrote 13 instead of 23.