image of READY prompt

Wang2200.org

The dialect of BASIC implemented in the Wang 2200 series of computers evolved over time, adding more powerful features and support for new I/O devices. With the introduction of the 2200 VP computer, Wang enhanced the syntax so much (although in a backward compatible way), it renamed the language to BASIC-2. This document only is concerned with the first generation, Wang BASIC.

Here is a brief rundown on some of the aspects of Wang BASIC that make it different (for better or for worse) than the Microsoft BASIC that was popular on most 8-bit micros just a few years later. The point of this isn't so much a competition as it is the easiest way I can think to explain Wang BASIC without explaining the "standard" stuff.

Clearly, MS BASIC is more flexible and more powerful in general, especially in regards to its string features. Wang BASIC is more geared towards computation, as evidenced by its matrix capabilities and that all computation is done in double precision. Some slack should be cut in Wang's favor as it delivered a complete computer system and a rich BASIC in May 1973, while Microsoft wasn't founded until two years later.

Wang BASIC has many features that today are pretty non-standard, and are oriented at the typical scientific/business user, not the home user. As such, the focus is more on record processing. This is especially true of some of the advanced features offered, such as the $GIO stuff, matrix sorting operations, and the disk system.

Color coding has been used in the following tables and code examples; Microsoft syntax is shown with a blue background, and Wang syntax examples are shown with a green background.

Variable names (link)

Wang BASIC allows variable names that are only a single letter or a letter and a digit. MS BASIC variable names consist of a starting letter, followed by an arbitrary number of letters or digits, although only the first two characters are significant.

Wang:
10 A = B1
20 R4 = 3 + C1
MS:
10 A = B1
20 RING = 3 + CAT


really seen as "20 RI = 3 + CA"

Numeric precision (link)

Wang BASIC supports only double precision floats, while MS allows 16-bit integers, single-precision floats, and double precision floats; the default is single-precision floats. On the other hand, code written to use the various MS data types is rather cluttered looking. The emphasis on high precision is a case where Wang's scientific calculator background is evident.

Wang:
10 A = A + 1
20 B = C
30 R = 2/3 * C

double precision
double precision
double precision
MS:
10 A% = A% + 1
20 B  = C
30 R# = 2.D0/3.D0 * C#

16 bit integer
single precision
double precision

Assignment (link)

One minor feature of Wang BASIC is that it allows multiple variables to be set by one assignment statement. In both Wang and MS BASIC, the keyword LET is optional.

Wang:
10 LET A=1
20 B=2
30 C,D,E=A+B
40 A$,B$="test"
MS:
10 LET A=1
20 B=2
30 C=A+B:D=C:E=C
40 A$="test":B$=A$

String support (link)

Wang strings are statically sized and can be a maximum of 64 characters long; the default is 16 characters. MS BASIC strings are dynamically sized and can be up to 255 characters long.

Wang:
10 DIM A$21
20 DIM B3$(50)40
30 A$ = "This is a long string"
40 B3$(4) = "Fourth one"
string 21 characters long
50 strings, each 40 characters long
MS:
20 DIM B3$(50)
30 A$ = "This is a long string"
40 B3$(4) = "Fourth one"

Also, Wang BASIC has fewer string manipulation functions, and the ones it has are typically weaker.

Wang:
10 A$ = "Here'$ a te$t"
20 N = POS(A$ = "$")


returns 6
MS:
10 A$ = "Here'$ a te$t"
20 N = INSTR(A$,"$")


returns 6

In this example, the two produce the same answer, but if the program really needed to find the string "$t", Wang's POS() won't work since it can find only a single character, whereas MS BASIC's INSTR() can find arbitrary strings.

MS BASIC has a large number of string operators, while Wang BASIC had a more limited form and outright didn't have some that MS BASIC has. In general, all Wang substring operations are handled by the STR() operator.

One feature of Wang BASIC that isn't matched by Microsoft BASIC is that STR() can be used both on the left and right side of an equation. Line 30 below shows this, although it isn't too painful in MS BASIC because dynamic string concatenation is allowed. Wang does not have string concatenation like this.

Wang:
10 A$="ABCDEFGHIJKLM"
20 PRINT STR(A$,3,3)
30 STR(A$,1,4) = "1234"
40 PRINT A$


-> "CDE"

-> "1234EFGHIJKLM"
MS:
10 A$="ABCDEFGHIJKLM"
20 PRINT MID$(A$,3,3)
30 A$ = "1234" + MID$(A$,4,99)
40 PRINT A$


-> "CDE"

-> "1234EFGHIJKLM"

MS BASIC also has the RIGHT$() function, and the STRING$() function.

Actually, Wang BASIC has something akin to the MS BASIC STRING$() function.

Wang:
10 DIM A$8
20 INIT("$") A$
30 PRINT A$



->"$$$$$$$$"
MS:
10 A$=STRING(8,"$")
20 PRINT A$


->"$$$$$$$$"

Two final oddities in the WANG string length function. In MS BASIC, string length is dynamically set to be exactly as long as the string requires. But because Wang BASIC uses statically allocated strings, LEN() returns the allocated size of the string minus the trailing number of spaces.

Wang:
10 DIM A$30
20 A$ = "This is a test"
30 PRINT LEN(A$)



-> 14
MS:
20 A$ = "This is a test"
30 PRINT LEN(A$)


-> 14

So far so good. The subtle difference is that MS BASIC can have a string with trailing spaces, while Wang BASIC can't. The second oddity is that there is one caveat to Wang's definition of LEN(). If the string is entirely spaces, LEN() returns 1, not zero.

IF/THEN statement (link)

Wang BASIC's IF statement is less general than that of MS BASIC. It is weaker in two ways. Firstly, the Wang IF statement doesn't have an ELSE clause.

Wang:
100 IF A > 3 THEN 110: PRINT "NO": GOTO 120
110 PRINT "YES"
120 ...
MS:
100 IF A > 3 THEN PRINT "YES" ELSE PRINT "NO"

Secondly, Wang IF statement doesn't allow conjunctions, unlike MS BASIC. Also, the "target" of a THEN in Wang BASIC has to be a line number. MS allows it to be any legal sequence of BASIC commands.

Wang:
100 INPUT "Enter a digit";A$
110 IF A$ < "0" THEN 120: IF A$ <= "9" THEN 130
120 PRINT "Bad digit": GOTO 100
130 ...
MS:
100 INPUT "Enter a digit";A$
110 IF A$ < "0" OR A$ > "9" THEN PRINT "Bad digit": GOTO 100 
120 ...

Machine-level access (link)

Wang BASIC offers no low-level access to the machine language because its CPU was unlike that of a conventional microprocessor.

MS BASIC supports PEEK, POKE, VARPTR(), USR(), IN, OUT, and WAIT commands.

FOR/NEXT statements (link)

In Wang BASIC, the loop index of NEXT statements must be given, and only one can be given. In MS BASIC, the index variable is optional, but if it is given, a comma-separated list is allowed to close multiple loops.

Wang:
10 FOR J=1 TO 10:NEXT J
20 FOR X=1 TO 4:FOR Y=1 TO 3:PRINT X+Y:NEXT Y:NEXT X
MS:
10 FOR J=1 TO 10:NEXT
20 FOR X=1 TO 4:FOR Y=1 TO 3:PRINT X+Y:NEXT Y,X

Error handling (link)

MS has ON ERROR GOTO and RESUME commands. Wang added ON ERROR later, but it wasn't in the earlier machines. Wang's ON ERROR was different than Microsoft's. Examples are easiest:

Wang:
100 ON ERROR E$, N$ GOTO 900
...
900 REM ERROR RECOVERY
910 PRINT "Line ";N$
920 PRINT "Error ";E$
...
990 GOTO 500
If an error occurs, E$ holds a string describing the error number, and N$ contains the line that triggered the error. All FOR loops and pending RETURNS are cleared from the stack. If more than one ON ERROR appears in the program, only the first takes effect.
MS:
10 ON ERROR GOTO 900
...
900 REM ERROR RECOVERY
910 PRINT "Line "; ERL
920 PRINT "Error "; ERR
...
990 RESUME
If an error occurs, ERR contains a numeric representation of the failing line number, and ERL contains the numeric error code. The RESUME statement causes execution to return to the failing line. Optionally, RESUME NEXT resumes with the line after the failing line, and RESUME nnnn continues on at line nnnn. ON ERROR is dynamic, so the one most recently executed is the one that applies.

DEFFN (link)

The Wang define function DEFFNx() syntax allows only one argument variable, whereas MS BASIC allows multiple arguments. However, Wang BASIC is more liberal in that it permits the function name to be a single digit as well.

10 DEF FNA(X) = X*X-1
20 DEF FN3(A) = SIN(A)*COS(A/2)

Array dimensioning (link)

The maximum value of an array index is 255 in Wang BASIC, and the total number of elements must be less than 4096. Arrays may have only one or two indexes, not more. On the positive side, arrays could be redimensioned after they were created. But on the negative side of that positive side, the redimensioned size can't be larger than the original dimensions of the array.

MS BASIC has no limit other than not exceeding available memory, and supports arrays with more than two dimensions. Arrays can not be redimensioned.

Error checking (link)

Wang BASIC checks the each line of the program as they are entered and immediately reports any syntax errors. When a program is run, the interpreter cross-checks for consistency, reporting syntax errors anywhere in the source, errors in dimensioning variables, exceeding memory limits, and any references to undefined functions or line numbers.

MS BASIC reports the error only as it attempts to execute the command in error. This is a drawback because errors can lurk in the code for a long time undetected, unless the bad line is executed or some data dependent behavior is triggered, exposing the problem.

Matrix operations (link)

Wang BASIC has a full complement of matrix operations. According to the Wang Matrix Statements Reference Manual, the built-in matrix operations operate at roughly 8x to 10x the speed of doing the function via FOR loops.

Example command String Version? Explanation
MAT REDIM M(5,3) n dynamically redimension an array
MAT M = ZER n zero an array
MAT M = IDN n fill with identity array
MAT M = CON n fill with constant 1.0
MAT M = TRN(B) n transpose
MAT M = INV(B),D n invert matrix, returning determinant
MAT M = A + B n matrix addition
MAT M = A - B n matrix subtraction
MAT M = A * B n matrix multiplication
MAT M = (2) + A n scalar addition
MAT M = (2) * A n scalar multiplication
MAT PRINT M y print matrix
MAT INPUT M y input matrix
MAT READ M y fill with DATA elements

String matrices can be used for the non-arithmetic commands above, e.g. MAT READ M$().

Later versions of Wang BASIC (2200T) had even more MAT operations, which are too complicated to explain here. Instead, an example of each is given. As you can see, they are quite arcane. Personally, I never used them except experimenting with them to see how they worked. MAT SEARCH did give all the functionality of the MS Basic INSTR() function, and then some!

MAT Sort command examples:

Program chaining (link)

Wang has a COM statement, which allows defining variables that won't be cleared when the next program is run. There are also mechanisms for clearing a section of the current program, loading new program lines, then continuing on.

Chaining in MS BASIC has no equivalent (AFAIK).

Wang:
10 COM A, B3, C(10,10), D$
...
1000 LOAD "MORE" 50,100

When line 1000 is executed, it will erase all program variables except those listed in the COM statement; it will then delete program lines 50 to 100, inclusive, then merge in the lines from the program named "MORE" before resuming operation.

Number/string conversion (link)

Wang BASIC's CONVERT statement comes in two flavors. Conversion from a string to a number is similar to MS BASIC's VAL statement.

Wang:
10 A$="-1.2345E+3"
20 CONVERT A$ TO A
MS:
10 A$="-1.2345E+3"
20 A=VAL(A$)

Wang's CONVERT command can also be used for converting from number to string representations. Wang's CONVERT from number to string was like C's sprintf() function. Or, another way to look at it, it was kind of like PRINTUSING with the output going to a string. E.g.

10 CONVERT A TO A$,(+#.#####^^^^)

converts A to A$ in a 12 character field, with leading sign, in exponential format, with five fraction digits. MS BASIC does not have anything like it.

Wang also has the NUM(string) function, which returns the leading number of characters in the specified string that form a legal number. E.g., NUM("123+6") would return 3. It is useful for validating user input before attempting a conversion.

Trig functions (link)

Wang BASIC allows setting whether trig functions deal in degrees, radians, or grads via the SELECT statement.

SELECT D
SELECT R
SELECT G
degrees
radians
grads

MS BASIC only operates in radians.

Besides SIN(), COS(), and TAN(), Wang BASIC supports the ARCSIN(), ARCCOS(), and ARCTAN() functions. MS BASIC lacks the equivalent of ARCSIN() and ARCCOS().

Line renumber (link)

Wang BASIC supports the RENUM command to renumber the program. If it can't successfully complete the job (line number collisions or line number too big), it gives an error without changing any of the line numbers.

Some versions of MS BASIC have this as well, but it wasn't standard on typical 70's 8-bit micros (e.g. it was a utility function, not something burned into ROM).

Debugging support (link)

While Wang BASIC and MS BASIC both have a TRACE statements, Wang's TRACE is more informative. MS BASIC only shows the line number of each line as it is executed, while Wang BASIC shows each variable assignment and change of control flow.

Wang:
10 A=0
20 TRACE
30 FOR I=1 TO 10: A=A+I:NEXT I
40 TRACE OFF
MS:
10 A=0
20 TRON
30 FOR I=1 TO 10: A=A+I:NEXT I
40 TROFF

Also, the Wang keyboard has a STEP/RUN key; after each step it lists the remainder of the current line still to be executed.

Although the SELECT Pn statement has a few uses, it is particularly useful for slowing down the output while TRACE mode was on, otherwise all the information would scroll past before the programmer had a chance to absorb anything. SELECT Pn allowed specifying an automatic delay after each carriage return, in sixths of a second.

SELECT P0
SELECT P1
SELECT P6
no pause
pause 1/6-th of a second after each carriage return
pause one second after each carriage return

Named subroutines, special function keys (link)

Wang has an odd "marked subroutine" feature. Perhaps an example is best:

100 GOSUB'10
110 GOSUB'5("this", 4)
120 STOP
200 DEFFN'10
210 PRINT "Hi":RETURN
300 DEFFN'5(A$, N)
310 FOR I=1 TO N:PRINT A$:NEXT I:RETURN
400 DEFFN'6(A, B)
410 PRINT SQR(A*A+B*B):RETURN
500 DEFFN'8 "PRINT A*3.14159"

When the GOSUB' command is seen, a DEFFN' with a matching value (0 to 255) is sought out. Note that arguments may be passed, but they aren't true dummy variables -- they change the global variable of the same name just like an assignment.

One cool feature is that the Wang has 16 "special function" keys along the top, which can be treated as 32 in combination with the shift key. When special function key N is pushed, the marked subroutine with the matching N is performed. If the command line has a list of arguments, they are passed in as the arguments to the marked subroutine. For instance, if the command line reads

 :5 6

and then the special function key 6 is pressed, the routine at line 400 would be executed with A set to 5 and B set to 6. This feature certainly derives from Wang's calculator heritage.

As a special case, line 500 shows how a key can be defined as a keyboard macro. Pressing special function key 8 results in the specified string being entered into the keyboard input buffer as if the user had typed the string directly.

MS BASIC had nothing similar to either feature, but I don't think anybody but a Wang BASIC user would miss them.

RETURN CLEAR (link)

The RETURN CLEAR command allows a subroutine to pop the pending return frame off the call stack. It wasn't used often, but when it was, it was very handy. A typical use is for error recovery inside a subroutine. Rather than passing some error status back to the caller and having each caller check the return flag, instead the subroutine would clear the return and jump to the error handler (e.g., print a message and ask for input again).

10 GOSUB 100
...
100 IF N>0 THEN 130
110 REM OOPS -- aborting to error handler
120 RETURN CLEAR: GOTO 200
130 PRINT "OK": A=5/N: RETURN

Boolean vector operators (link)

Wang BASIC has a large number of boolean operator statements that operate on bit vectors much faster than it can be done in MS BASIC. In all the functions below, the B$ argument may be replaced by two hex digits (e.g. 01), and that one byte value is applied to each byte of A$.

Command Explanation
ADD(A$,B$) adds corresponding bytes of B$ to A$
ADDC(A$,B$) like ADD(A$,B$) but with interbyte carry
AND(A$,B$) ANDs each byte of A$ with corresponding byte of B$
OR(A$,B$) ORs each byte of A$ with corresponding byte of B$
XOR(A$,B$) XORs each byte of A$ with corresponding byte of B$
BOOLn(A$,B$) perform boolean operation n on A$ with B$
INIT(B$)A$ fill A$ with the first byte of B$
INIT("x")A$ fill A$ with the byte "x"

There are some other statements and functions that deal with bit vectors.

Command Explanation
HEX(012345) represents a 3 byte string of 01,23,45 hex
ROTATE(A$,N) rotate left each byte N bits (N=1 to 7)
HEXPRINT A$ print contents of A$ in hex
HEXPRINT B$() print contents of array B$() in hex

Data packing (link)

Wang BASIC has a flexible and succinct way of packing a collection of data into a string, plus a corresponding way of unpacking it. Typical MS BASICs don't have it, although some versions of their disk basic have a more limited ability.

10 DIM M(10,10), A$6, B$(10)30
20 PACK(####) A$ FROM X,Y,Z
30 PACK(+#.##^^^^) B$() FROM M()

Line 20 specifies that each of X, Y and Z should be converted to a four digit BCD number, occupying two bytes each, for a total of six bytes. This fits into A$ perfectly (A$ could have been larger), and the 6 byte storage cost is much less than the native 24 byte cost of the X, Y, and Z variables.

Line 30 specifies that the 100 values in M() are to be converted to a signed exponential format with three digits; since each number requires three bytes of storage in packed format, we need a total of 300 bytes. We can't dimension a single string that large, so instead B$ is declared as 10 strings of 30 bytes each, and this is large enough to contain the packed image of M().

UNPACK performed the inverse operation:

120 UNPACK(##.##) A$ TO X,Y,Z
130 UNPACK(+#.##^^^^) B$() TO M()

Note that typically the pack and unpack formats would be identical, but nothing says it must be so. It is simply a collection of nibbles when in the packed state, with no explicit indication of the decimal point nor of which nibbles are exponents. Line 20 and 120 use different formats, which would result in the values in X, Y, and Z being scaled by 1/100.

These functions are probably most commonly used for packing and unpacking data when transferred between memory and a storage device, but they can also be used to advantage by compressing arrays of low precision numbers during run time.

I/O redirection (link)

Wang has various standard I/O devices, each of which could be remapped to some other I/O device. The standard I/O devices are:

SELECT Name Device
CI console input
CO console output
LIST list output
PRINT print output
TAPE tape device
PLOT plotter device

E.g.:

10 SELECT CO 215 (80)

selects device 215 for console output, 80 columns wide. It isn't very mnemonic, but Wang supports a whole family of peripherals and had a scheme for numbering them all that at least had a pattern. There is no problem putting multiple keyboards, printers, tape drives, plotters, card readers, disk drives, etc. all on one machine and switching I/O between them.

MS BASIC can do a very limited version of this, but it requires doing some magic POKEs and such. Some disk BASICs have more robust OS support for device table mapping.

Cassette operation (link)

Non-disk based MS BASIC only has CLOAD and CSAVE commands for programs. Files could be OPENed and data PRINT #n'd to them or INPUT #n'd from them, but due to the low cost cassette interface and cassette interface, it wasn't very functional and was slow and often unreliable.

Wang BASIC's cassette I/O is richly supported with a dizzying number of I/O commands for rewinding, skipping, reading, writing, verifying, etc. The cassette interface is a high speed, high quality recording mechanism. Besides being faster than the audio cassette players adopted by most 8-bit micros, the Wang cassette drive is under direct computer control and can move the tape forwards and backwards. Tape speed was 7.5 ips and data is transferred at approximately 326 bytes/second. Each block is recorded twice for error tolerance, and each block is checksummed. Compare this to most 70's micros that used 1.825 ips cassette tapes and transferred 30-120 bytes/sec.

Because of this ability, any tape read that is in error will cause the tape mechanism to rewind a bit and retry the failing operation a few times before declaring an error. This "washing machine" noise as the tape mechanism ran forward, then backwards, then forwards, then backwards again very quickly is a greatly feared sound, anxiety mounting with each retry.

There are three concepts to the Wang tape format: the physical record, the logical record, and the file. A physical record is a 256 byte indivisible block transfer, although only 253 byte are used as data payload, with the other three used for control purposes. A logical record spans one or more physical records and corresponded to all the data specified in a single DATASAVE command. A file is a collection of logical/physical records starting with a special header record and a special trailer record.

Example command Explanation
LOAD load a program
SAVE "FOO" save program named as "FOO"
SAVE "FOO" 50,100 save just program lines 50 to 100
SAVE P "FOO" save program "FOO", protected mode
REWIND high speed rewind to beginning of tape
SKIP 2 skip two logical records
SKIP 2F skip two files
SKIP END skip to end of current file
BACKSPACE 3 backspace three logical records
BACKSPACE 3F backspace three files
BACKSPACE BEG backspace to start of current file
DATASAVE OPEN "FOO" open a data file for writing (header record)
DATASAVE A,B,C$,D() save logical record
DATASAVE END terminate a file (trailer record)
DATARESAVE A,B,C$,D() rewrite a logical record
DATALOAD OPEN "SAM" open a data file for reading
DATALOAD A,B,C$,D() load data elements

When reading data from a file, a program often has to determine when the last record has been read. In Wang BASIC, the technique is to to attempt a DATALOAD operation and then perform an end of file test using this syntax:

40 DATALOAD A,B,C
50 IF END THEN 110

Multiple tapes can be operated on by specifying the device I/O address (if not specified, the default SELECT TAPE device is assumed).

20 REWIND/10B
30 DATASAVE/10C, A(), B$()

There is a mapping of physical device to one of six logical I/O devices via the SELECT statement:

10 SELECT #1, 10B, #2, 10C
20 REWIND #1
30 DATASAVE #2, A(), B$()

Minor differences (link)

Assorted other commands (link)

Wang continually enhanced Wang BASIC by adding more commands. In the last incarnation before BASIC-2, a bizarre assortment of "General I/O Statements" was added. Only a brief hint of the purpose is given here.

Group Command Explanation
General I/O:
$GIO ... general I/O microcommand
$IF ... test ready condition of I/O device
$TRAN ... translate characters
$PACK ... pack data
$UNPACK ... unpack data
Plotter:
PLOT <X,Y,"Title">, <40,60,A$> Besides a string literal, the third argument of each 3-tuple could also be one of U, D, S, C, R (pen up, pen down, set horizontal and vertical spaces, set character size, reset to zero).
Plotter, card reader, tape punch:
DATASAVE BT ... block-level transfer
DATALOAD BT ... block-level transfer

A further word on the $GIO instruction. This instruction defines a pseudo-microcode format for performing high-speed, low-level operations. These instructions are actually interpreted and aren't directly executed, but it is still many times faster than if such a thing were to be done using BASIC. For example, via small microcode programs one can control I/O devices that were released after the BASIC was frozen, such as reel-to-real tape devices, modems, and data loggers.

This idea is in some ways very similar to the SWEET-16 interpreter embedded into the original Apple-II ROMs, although Apple did it for reasons of code density, for neither speed nor flexibility.

Disk commands (link)

The disk commands of Wang BASIC are rather awkward. The file structure of the disk is exposed, and the programmer had the choice of operating at the sector level or using a "cataloged" disk approach. Many disk commands come in two flavors, one to operate through the catalog structure, the other specified direct sector-level requests.

Command Explanation
COPY copy sectors from one platter to another
DATALOAD BA read one sector into the specified array
DATALOAD DA read a logical record from a given sector
DATALOAD DC read logical record from cataloged disk file
DATALOAD DC OPEN F open a cataloged disk file
DATASAVE BA write one sector
DATASAVE DA write a logical record to a given sector
DATASAVE DC save logical record to cataloged disk file
DATASAVE DC CLOSE close cataloged file
DATASAVE DC OPEN open cataloged file
DBACKSPACE backspace a number of records or sectors
DSKIP skip the number of logical records or sectors
LIMITS obtain start & end addresses, number of sectors in file
LIST DC list contents of catalog index
LOAD DA load program starting from specified sector
LOAD DC load program from cataloged file
MOVE catalog manipulation
SCRATCH change the file status of the named disk files
SCRATCH DISK reserve space on disk
VERIFY Verify data in a given range of sectors