
PROGRAMMING LANGUAGE MANUAL
VERSION 2.5.1

All rights to the XPL0 software and its documentation are reserved by the authors. Copyright 2006 software: P. Boyle; manual: L. Fish; revisions: L. Blaney.
This manual is for the small group of individuals who, despite massive support behind other programming languages, continue to use XPL0. It is also for anyone who wonders what all the fuss is about.
XPL0 was developed in the mid 70s and has been surprisingly long-lived. This is because the authors added features to solve problems as they arose. For such a small language, XPL0 competes surprisingly well with major languages like C, Pascal, and BASIC. This is probably because of the marginal usefulness of the extra features in these other languages. A feature you don't use just adds to your confusion.
The major objections to XPL0 are that it is not a widely supported standard and it cannot be used for Microsoft Windows applications. The major reason for using XPL0 is that the source code for the compiler is provided and you can modify it to solve your particular problem. Also, XPL0 is less cryptic than C, less formal than Pascal, and more powerful than BASIC.
C O N T E N T S
0: INTRODUCTION . . . . . . . . 1
0.0 Example Program: GUESS . . 1
0.1 Compiling and Running . . . 4
0.2 Syntax . . . . . . . . 5
1: FACTORS . . . . . . . . . . 8
1.0 Integer Constants . . . . 8
1.1 Hex Constants . . . . . 8
1.2 ASCII Constants . . . . . 9
1.3 Real Constants . . . . . 9
1.4 Variables . . . . . . . 9
1.5 Declarations . . . . . . 10
1.6 Declared Constants * . . . 10
1.7 Example Program . . . . . 12
1.8 Free Format . . . . . . 13
2: EXPRESSIONS . . . . . . . . 15
2.0 Arithmetic Expressions . . 15
2.1 Mixed Mode . . . . . . 16
2.2 Unary Operators . . . . . 16
2.3 Comparisons . . . . . . 17
2.4 True and False * . . . . 18
2.5 Boolean Expressions * . . . 19
2.6 Example Program: SETS * . . 21
2.7 Shift Operators * . . . . 22
2.8 If Expression * . . . . 22
2.9 Constant Expressions * . . 23
2.10 Conditional Compile * . . . 23
2.11 Hazards of Real Numbers * . 24
3: STATEMENTS . . . . . . . . . 26
3.0 Assignments . . . . . . 26
3.1 Begin - end . . . . . . 26
3.2 If - then - else . . . . 27
3.3 Case - of - other * . . . 28
3.4 While - do . . . . . . 30
3.5 Repeat - until . . . . . 30
3.6 Loop - quit . . . . . . 31
3.7 For - do . . . . . . . 32
3.8 Exit . . . . . . . . 32
3.9 Subroutine Calls . . . . 33
3.10 Comments . . . . . . . 33
3.11 Null Statements . . . . . 34
3.12 Example Program: THERMO . . 34
4: SUBROUTINES . . . . . . . . 36
4.0 Procedures . . . . . . 36
4.1 Local and Global . . . . 37
4.2 Arguments . . . . . . . 37
4.3 Nesting . . . . . . . 39
4.4 Return . . . . . . . . 39
4.5 Functions . . . . . . . 40
4.6 Intrinsics . . . . . . 42
4.7 Scope * . . . . . . . 43
4.8 Recursion * . . . . . . 45
4.9 Forward Procedures * . . . 46
4.10 Forward Functions * . . . 46
4.11 Include * . . . . . . . 46
4.12 External Procedures * . . . 47
4.13 Assembly-Language Externals * 50
4.14 External .I2L Procedures * . 52
5: ARRAYS * . . . . . . . . . 54
5.0 Example Program: DICE . . . 55
5.1 How arrays work * . . . . 56
5.2 Strings * . . . . . . . 57
5.3 Multidimensional Arrays * . 59
5.4 Complex Data Structures * . 60
5.5 Constant Arrays * . . . . 63
5.6 Example Program: RECORDS * . 65
5.7 Address Operator * . . . . 67
5.8 Returning Multiple Values * . 68
5.9 Segment Arrays * . . . . 70
6: INPUT AND OUTPUT . . . . . . . 75
6.0 Device 0 . . . . . . . 77
6.1 Device 1 . . . . . . . 77
6.2 Device 2 . . . . . . . 78
6.3 Device 3 . . . . . . . 78
6.4 Device 4 . . . . . . . 81
6.5 Device 5 . . . . . . . 81
6.6 Device 6 . . . . . . . 81
6.7 Device 7 . . . . . . . 82
6.8 Device 8 . . . . . . . 82
APPENDIX . . . . . . . . . . 84
A.0 Intrinsics . . . . . . 84
A.1 Compile Errors . . . . . 110
A.2 Run-time Errors . . . . . 115
A.3 Common Errors . . . . . 117
A.4 Keyboard Scan Codes . . . 119
A.5 Syntax Summary . . . . . 120
INDEX . . . . . . . . . . . 122
ADDENDUM . . . . . . . . . . 130
* Advanced section
0 : I N T R O D U C T I O N
Welcome to XPL0!
XPL0 bridges the gap between assembly language and high-level language. It has the speed and flexibility of assembly language, yet is easy to use. It's a block-structured language that can do floating-point calculations such as trig functions.
Programs that have been written in XPL0 include: compilers, operating systems, word processors, video games, and medical instrument controllers. These programs might have been written in assembly language, but because they were written in XPL0 they were written quickly, and they can be easily modified.
If XPL0 is your first high-level language, or if you are familiar with other block-structured languages such as C or Pascal, you will find XPL0 logical and easy to learn. If your programming experience is with a non- structured language such as BASIC, a structured language might seem awkward at first. This is because it requires more setup. However, as your programs grow and your skill increases, you'll begin to appreciate the power of a block-structured language.
XPL0 was created in the mid seventies by P.J.R. Boyle and the 6502 Group, a computer club at the Colorado School of Mines in Golden. Since then, there have been many versions of XPL0 running on many different computers (6502, PDP-10, IBM-360, homebrews, 8080, 6800, 65802, 680x0 and 80x86). This manual describes the (16-bit) versions that run on IBM-style PCs.
This manual is both a tutorial and a reference. The information is in a logical order for reference, but in some cases this makes it more difficult when first learning the language. It is best to skip the sections marked "Advanced" when reading the manual the first time.
0.0 EXAMPLE PROGRAM: GUESS
A good way to learn a language is to simply jump in and get your feet wet. So let's write a small program in XPL0. We begin by describing the task in plain English.
0: Introduction 2
This program is a guessing game where the computer thinks of a number between 1 and 100, and we try to guess it. After each guess, the program tells us whether we are high or low.The program goes through these steps:
1. Think of a number between 1 and 100.
2. Get a guess from the keyboard.
3. Test the guess against the number.
4. Repeat steps 2 and 3 until the guess is the number.
Here are the same steps translated into XPL0:
begin
MakeNumber;
repeat InputGuess;
TestGuess
until Guess = Number
endNote that the program is almost word for word the same as the description of the task. First we "make a number" then we repeatedly "input a guess" and "test the guess" until it is the number.
There needs to be more to this program since it does not tell how to make a number, input a guess, or test the guess. Each of these operations is a subroutine to the main program. In XPL0, these subroutines are called procedures. We are now going to write each of these procedures.
procedure MakeNumber;
begin
Number:= Ran(100) + 1
end
This procedure generates a random number between 1 and 100 and puts that
number into the variable called "Number".
procedure InputGuess;
begin
Text(0, "Input guess: ");
Guess:= IntIn(0)
end
This procedure displays the message: "Input guess: " on the monitor (output device 0) and gets a number (INTeger IN) from the keyboard (input device 0). In XPL0 nine different input and output devices can be called from the program. This enables direct access to the monitor, keyboard, printer, disk files, and so forth.
0: Introduction 3
procedure TestGuess;
begin
if Guess = Number then Text(0, "Correct!")
else
if Guess > Number then Text(0, "Too high")
else Text(0, "Too low");
CrLf(0)
end
This procedure is more complicated but still easy to understand. If the computer's number is equal to the guess then we execute one statement; if it is not equal then we execute another statement. If the numbers are equal, we tell the user that the guess is correct; if they are not equal, we test if the guess is high or low and tell the user. CrLf(0) starts a new line on the monitor (Carriage Return and Line Feed).
Here is the complete program:
code Ran=1, CrLf=9, IntIn=10, Text=12;
integer Guess, Number;
procedure MakeNumber;
begin
Number:= Ran(100) + 1
end;
procedure InputGuess;
begin
Text(0, "Input guess: ");
Guess:= IntIn(0)
end;
procedure TestGuess;
begin
if Guess = Number then Text(0, "Correct!")
else
if Guess > Number then Text(0, "Too high")
else Text(0, "Too low");
CrLf(0)
end;
begin
MakeNumber;
repeat InputGuess;
TestGuess
until Guess = Number
end
Two new items are shown here. The command word "code" is used to give names to intrinsics. Intrinsics are built-in subroutines that do common operations. For example, "Ran" is the name of the random-number intrinsic, and "Ran" is used to call this random-number generator as a subroutine. The second item is the command word "integer". This declares
0: Introduction 4
a name and allocates memory space for each variable that follows it.
Note that the main procedure is the last block in the program. An XPL0 program is read starting at the bottom to get the main flow and working upward to get the details in the procedures.
Here is an example of what this program does when it runs:
Input guess: 50
Too high
Input guess: 25
Too high
Input guess: 9
Too low
Input guess: 18
Correct!
0.1 COMPILING AND RUNNING
After you create a program using a text editor, you compile, assemble, and link it to produce an executable .EXE file. For example, to run the number guessing program, GUESS.XPL, type the following:
XN GUESS
GUESS
XN is a batch file (XN.BAT) that does these three steps:
1. Run the compiler (XPLNQ) to convert the .XPL source to an .ASM file.
2. Run the assembler (MASM) to convert the .ASM to an .OBJ file.
3. Run the linker (LINK) to combine the .OBJ file with the run-time
code (NATIVE.OBJ) and produce an .EXE file.
![]()
You can make your program run faster by using the optimizing compiler, XPLX. To do this substitute XX for XN. Also, if your program does many floating-point calculations and is going to run on a computer that has an 80387 math coprocessor (or 486DX or Pentium), you can make it run much faster by linking in NATIVE7 instead of NATIVE.
0: Introduction 5
The above describes how to use the "native" versions of the compiler, but there is another version that compiles "interpreted" code. The native versions produce code in 8086 assembly language. The interpreted version produces code that runs with a program called an "interpreter". Each version has advantages, but the optimizing, native version (XX) is the one that is normally used. Programs are written the same way no matter which version of the compiler is used.
To run the number guessing program using the interpreted version, type:
X GUESS
GUESS
X is a batch file that does these steps:
1. Run the compiler (XPLIQ) to convert the .XPL source to an .I2L file.
2. Run the interpreter (I2L.COM) to load the .I2L file, combine it with
the run-time code, and produce a .COM file.
It is usually preferable to use one of the native versions of the compiler because they produce code that runs about ten times faster than interpreted code. Also, native programs can be much larger than interpreted programs because they are .EXE files instead of .COM files. COM files are limited to 64K bytes in size.
On the other hand, the interpreted version does have some advantages. No assembler or linker is required (which can save a significant amount of space on a floppy diskette). Since the code is not assembled and linked, it can be compiled and run quicker, which is useful when testing. The interpreted code is also more compact; thus programs require less memory and disk space. In some applications interpreted code is essential because it's being cross-compiled to run on a processor other than the 8086.
0.2 SYNTAX
A program consists of a bunch of characters. The rules that organize these characters into meaningful patterns are called the syntax of a language. Beginning with the most detailed level, the syntax of XPL0 is broken down as follows:
0: Introduction 6
Factors
Expressions
Statements
Blocks
Subroutines
A factor is the smallest part of a program that can have a numeric value. A factor is usually a constant or a variable. Constants are numbers such as 100, 5280, and 3.14. Variables are places to store numbers. They are given names by the programmer such as "Number", "Percent", and "FEET".
Factors are combined using operators to form expressions. An operator is usually one of the familiar arithmetic operators such as add, subtract, multiply, or divide. An expression calculates to a single value. Here are some examples of expressions:
Percent - 10
12.0 * FEET
(Frog + 20.5) / 0.23
A statement is a request to do something. A typical statement combines
expressions and commands. Here are two statements:
Number:= Ran(100) + 1;
if Guess = Number then Text(0, "Correct!")
Several statements can be combined into a single statement called a block. A block must start with a "begin" and terminate with an "end". Statements within a block must be separated by a semicolon (;). Here is an example of a block:
begin
Number:= 52 + 6;
InputGuess;
if Guess > Number then Text(0, "Too high");
CrLf(0)
end
XPL0 is very flexible in the way it allows statements and blocks to be combined. For example, blocks can be placed inside statements:
if Guess < Number then
begin
Text(0, "Too low");
InputGuess;
if Guess < Number then Text(0, "Still too low")
end
0: Introduction 7
Here we have an "if" statement containing a block. The block itself consists of three statements separated by semicolons.
Subroutines are the highest level of organization. In XPL0 there are several different types of subroutines; the most common is the procedure. A procedure is a block of statements that does a specific job. A program can contain any number of procedures. Procedures are given names and called as subroutines from other parts of the program. Here is an example of a procedure:
procedure InputGuess;
begin
Text(0, "Input guess: ");
Guess:= IntIn(0)
end
This gives a you quick idea of what XPL0 is about. In the next sections we will examine each of these levels of syntactic organization in detail.

8
1 : F A C T O R S
A factor is the smallest part of a program that has a value. Most factors in XPL0 are either constants or variables. A constant is a value that remains unchanged throughout the execution of a program, while a variable is a value that can be changed. Factors are classified as integer or real. An integer is a 16-bit value that represents a whole number. A real number is a floating-point value that is not limited to a whole number and can cover a very large range of values. Thus there are basically four kinds of factors: integer constants, real constants, integer variables, and real variables.
1.0 INTEGER CONSTANTS
In XPL0 an integer constant is a whole number in the range -32768 through 32767. Here are some examples:
10 0
-10000 1975
1.1 HEX CONSTANTS
Integers can also be written in hexadecimal form. A hex number is an integer in base 16. Hex numbers are indicated by a dollar sign ($). They range from $0000 through $FFFF. Hex is very useful when programming at the machine level. Here are some examples:
$123 $1e0
$FFC0 $00ff
Note that both upper and lower case letters (A-F and a-f) can be used.
1: Factors 9
1.2 ASCII CONSTANTS
ASCII characters are often used as constants. A caret (^) converts a character to its ASCII value. For example:
^A = $41 = 65
^z = $7A = 122
^$ = $24 = 36
^^ = $5E = 94
1.3 REAL CONSTANTS
Real constants are distinguished from integer constants by having either a decimal point or an exponent. The exponent is indicated by an "E". For instance, "3E14" means 3 times 10 raised to the 14th power, or 3 followed by 14 zeros. The following are examples of real constants:
2.5 .2
-1000000. 05.e-1
1E6 -0.00000000000000707
6.63E-34 6.023e+023
In XPL0 a real number represents values ranging between ±2.23E-308 and ±1.79E+308 with 16 decimal digits (53 bits) of precision.
Expressions containing reals execute slower than corresponding expressions containing integers. Also, a real number requires four times as much memory as an integer. Thus when an integer is sufficient, it is preferable to a real.
1.4 VARIABLES
Variables are temporary storage places for values. These storage places are given names by the programmer that can be single letters or whole words. Usually names are chosen to describe what the variable contains. For example, if you were calculating interest rates, the interest could be stored in a variable called "Interest". Since XPL0 is a compiled language, long names do not slow execution speed or take up extra memory space at run time (unlike an interpreted language like BASIC).
Variable names contain letters (A-Z, a-z), numbers (0-9), and underlines (_), but the first character must be an uppercase letter or an underline. Here are some examples:
X RATE12 _drawLine
Guess I_AM_A_NAME IAmAName
1: Factors 10
Names can be as long as you want, but only the first 16 characters are recognized by the compiler. Upper and lower case letters are equivalent. For example, the following all refer to the same name:
Guess GUESS GueSS
1.5 DECLARATIONS
Before a variable can be used, it must be declared. The integer variable declaration has the general form:
integer NAME, NAME, ... NAME;
For example:
integer Guess, Number, Frog;
This declaration tells the compiler that the variables Guess, Number, and Frog are used later in the program.
The word "integer" is a command word. Command words are words that have special meaning to the compiler. They are in lowercase letters. This, for instance, allows you to also use the word "Integer" as a variable name.
Since the compiler looks at only the first three characters of a command word, they can be abbreviated. For example, these are equivalent:
integer int
Variables that contain real numbers are declared similar to the way integers are declared:
real NAME, NAME, ... NAME;
In XPL0 all named things, such as variables, procedures, and intrinsics, must be declared before they can be used. The rules for creating variable names, such as starting with a capital letter, apply to all names.
1.6 DECLARED CONSTANTS (Advanced)
Names can also be declared for constants. Constants are different from variables because once they are defined they cannot be changed. Using a
1: Factors 11
constant is more efficient than using a variable. Giving names to numbers can add clarity to a program. For instance, the name "Highest" might be more meaningful than the number 29028.
Declared constants have the form:
define NAME = CONSTANT, ... NAME = CONSTANT;
For example:
define Summit = 14210, Highest = 29028, Median = 13489.72;
In this example Summit and Highest are integer constants, and Median is a real constant.
Any constant can be used in a "define", for example:
define A = $41, B = 66, C = -^C, LETTER = B, Number= -3.1E-3;
Note that B, once it is defined, can be used to define other constants. Also note that a constant can be signed (- or +).
Sometimes it is useful to have distinct names for things, but the actual value is irrelevant. In fact sometimes we do not want to know the value so that we cannot come to depend on it. For example, we might be working with a set of colors that we just want to distinguish by name. If we come to depend on the particular numerical value of a color, later changes in the program might be difficult. XPL0 has a simple scheme for defining sets of things:
define Red, Blue, Green;Here, all you need to know is that these constants have distinct values.
The values actually assigned by the compiler are integers beginning with zero and stepping by one up to the last item in the set. In the example, Red equals 0, Blue equals 1, and Green equals 2.
1: Factors 12
1.7 EXAMPLE PROGRAM
This program shows some relationships between the various types of integer factors.
code ChOut=8, CrLf=9, IntOut=11, Text=12;
integer Counter;
define Tab=$09;
begin
Counter:= $41;
repeat ChOut(0, Counter);
ChOut(0, Tab);
IntOut(0, Counter);
CrLf(0);
Counter:= Counter + 1
until Counter = ^G;
Text(0, "That's all folks!"); CrLf(0)
end
When run, this program displays:
A 65
B 66
C 67
D 68
E 69
F 70
That's all folks!
The program begins by declaring the things that are needed to run it. The first line tells which intrinsic subroutines are needed and gives each a name. The second line declares a single variable called "Counter" that will hold integer values. The last declaration tells us that the word "Tab" can be used as a direct replacement for the hex number $09. This replacement is convenient because the ASCII value of the tab character is equal to $09. These three lines of declarations can be in any order. It is conventional that "code" declarations are first.
The rest of the program describes the actions it performs when it runs. Since this executable part of the program is a block, consisting of several statements, it is enclosed within a begin-end pair.
The first statement in our program block puts the value $41 in the variable called Counter. $41 is the value of the ASCII character A.
Now we are going to repeatedly execute a sub-block until Counter contains a value equal to the ASCII character G.
1: Factors 13
We begin the sub-block by calling the intrinsic subroutine ChOut, which we send 0 and the value in Counter (initially $41). ChOut (CHaracter OUT) sends a value to a specified output device. Here we are specifying device number 0, which is the monitor. When the monitor driver receives a value, it displays the ASCII character that corresponds to the value. So the first time we call ChOut, an "A" is displayed.
The next line calls ChOut again and sends the ASCII value for a tab character. This moves over to the next tab stop on the monitor.
Now we call IntOut. IntOut (INTeger OUT) is similar to ChOut, but rather than the value being displayed as a character, it is displayed as a decimal integer. The first time we call IntOut, "65" (= $41) is displayed.
The next statement, CrLf(0) (Carriage Return Line Feed), is an intrinsic that moves to the beginning of a new line on the monitor.
Now, 1 is added to the value in Counter, and the result is stored back into Counter. On the next line we test the value in Counter to see if it is equal to ASCII G. If it is not then we go back to the beginning of the repeat block and repeat the statements starting with ChOut. If Counter has incremented up to G then we fall through to the next line, which is the Text statement.
Text is an intrinsic similar to ChOut, but it sends out a whole string of characters rather than just one.
Note the overall logic of the program. We started at A and counted up to G. For each count we displayed the character and its decimal value. When we got to G, we broke the repeat loop and displayed the message "That's all folks!"
1.8 FREE FORMAT
In the examples shown so far, a certain formatting has been implied. Statements, for instance, have been written one to a line. XPL0 is a free-format language, which means that the compiler ignores formatting characters such as spaces, tabs, carriage returns, and form feeds. These characters are only used to make the structure of the program more apparent to the reader.
The example program shown above could be rewritten as follows without changing the way it compiles or runs:
1: Factors 14
code ChOut=8, CrLf=9,IntOut=11,Text=12;integer
Counter; define Tab = $09; begin Counter := $41;
repeat ChOut ( 0,Counter);ChOut(0,Tab);IntOut( 0
,Counter ) ; CrLf(0);Counter := Counter + 1 until
Counter =^G;Text(0,"That's all folks!" );CrLf(0 ) end
However this hides the structure, making it more difficult to see what the program does.
Formatting characters can be left out, but they cannot be used everywhere. Just as with normal English, words cannot be split apart. For example, this causes a compile error:
Count er:=$41;
15
2 : E X P R E S S I O N S
XPL0, like many computer languages, is a mathematical language. It does arithmetic and other operations on numbers. Expressions consist of factors and operators. Operators perform on anything that has a value, such as constants, variables, and sub-expressions. An expression calculates to a single value. In XPL0, an expression can be used anywhere a value is used and vice versa.
2.0 ARITHMETIC EXPRESSIONS
The common arithmetic operations are done using familiar symbols:
+ Addition
- Subtraction
* Multiplication
/ Division
An arithmetic expression is evaluated from left to right, with multiplication and division done first followed by addition and subtraction. The order of evaluation is important because it affects the result of a calculation.
Sometimes it is necessary to evaluate an expression in a different order. The part of an expression within parentheses is evaluated first.
Here are some examples of arithmetic expressions:
6 + 4/2 equals 8 (6+4)/2 equals 5
6 - 4*2 equals -2 (6-4)*2 equals 4
6/2*3 equals 9 6/(2*3) equals 1
6-4+2 equals 4 6-(4+2) equals 0
3*(6-(4-1)) equals 9
Integer division gives a quotient and a remainder. The remainder of the most recent division is gotten from the intrinsic "Rem". For example, 19/5 evaluates to 3, and Rem has the remainder 4.
Note that integer division does not work the same way as division using real numbers. For example, these three expressions are not necessarily equal:
2: Expressions 16
X/10 * 5
X*5 / 10
X * (5/10)
For instance, if X is 15 then the first expression evaluates to 5, the second to 7, and the third to 0.
Integer operations do not give an error if they overflow. Overflowing values wrap around. For example, if you add 32767 + 1, the result is -32768. This is desirable because $7FFF + 1 = $8000, and so forth.
2.1 MIXED MODE
XPL0 does not allow integer and real factors to be used directly together in the same expression. For instance:
2 + 3.5
This causes a compile error. It should be changed to:
2. + 3.5
To do calculations on a mixture of reals and integers, convert the factors to a single data type using the Fix and Float intrinsics. Fix changes reals to integers, and Float changes integers to reals. For example, if the variable X is a real and I is an integer then calculations are done as follows:
Fix(X) + I
X + Float(I)
2.2 UNARY OPERATORS
Since a constant can be negative, we could have an expression like:
2 * -3
Do not confuse the minus sign shown here for the minus sign used to do subtraction. This minus sign is called a unary operator because it operates only on the 3 and indicates that the 3 is negative.
Any factor (or sub-expression) can have the unary operators "-" and "+". Because the "+" operator really does not do anything, it can always be left out. It is sometimes used to emphasize that a number is positive.
2: Expressions 17
When unary operators are used in expressions with other operators, the unary operations are done first unless parentheses are used to force a different order of evaluation.
Here are some expressions using unary operators:
2 * -3 equals -6 +2 +2 equals 4
6+ -4 equals 2 -$40/16 equals -4
-4 - -6 equals 2 -^A + $41 equals 0
-(4+6) equals -10 2*--3 equals 6
2.3 COMPARISONS
It is often necessary to compare one value to another and make a decision based on the result. XPL0 uses the following symbols to make comparisons:
= Tests for equal values.
# Tests for not equal values.
< Tests if the first value is less than the second.
> Tests if the first value is greater than the second.
>= Tests if the first value is greater than or equal
to the second.
<= Tests if the first value is less than or equal to
the second.Here are some expressions containing comparison operators:
X = 3
A < 0.91
(X+1) >= YWe have already seen an example of how XPL0 uses comparisons to make decisions. In the number guessing program, one of two statements were executed depending on a comparison:
if Guess > Number then Text(0, "Too high")
else Text(0, "Too low")
If the Guess was greater than the Number then it was "Too high"; otherwise it was "Too low".
XPL0 evaluates a comparison to true or false. These expressions evaluate to true:
2: Expressions 18
55 > 23
(3*4) # (3+4)
These expressions evaluate to false:
(2+2) = 5
-33.3 > -4.5
WARNING: Since XPL0 treats all 16-bit integers as signed,
$F000 > $A000 is true, but
$F000 > $7000 is false.
Converting the hex to decimal makes the reason apparent:
-4096 > -24576 is true, and
-4096 > 28672 is false.
2.4 TRUE and FALSE (Advanced)
When a comparison is made, it produces a true or false value, like 2 + 3 produces the value 5. The reserved word "false" is just another way to represent the integer 0, and likewise "true" is equal to -1 (=$FFFF).
Using these concepts and adding the new variable High, the previous example from the GUESS program can be rewritten as:
High:= Guess > Number;
if High = true then Text(0, "Too high")
else Text(0, "Too low")
Going one step further, since High is assigned either true or false and since:
true = true is true
and:
false = true is false, the "if" statement can be simplified as:
if High then Text(0, "Too high")
else Text(0, "Too low")
2: Expressions 19
2.5 BOOLEAN EXPRESSIONS (Advanced)
A boolean is a value that has two states: true or false. In XPL0, integers are used to represent booleans. Boolean expressions are formed by combining booleans with boolean operators.
XPL0 has four boolean operators: "not", "and", "or", and "exclusive or". The following symbols perform these operations:
~ "Not" operator (the word "not" can also be used)
& "And" operator
! "Or" operator
| "Exclusive or" operator
"Not" operates on a single value--it is another unary operator like the minus sign. It simply changes the value to its opposite. For instance, "not true" evaluates to "false". The "and" operator requires two values. If either value is false then the result is false. If both are true then the result is true. The "or" operator also requires two values. If both values are false then the result is false. If either value is true then the result is true. "Exclusive or" requires two values. If both values are the same then the result is false. If the values are different, the result is true. Here are some examples:
if Pig = ~true then Text(0, "Still ok");
if Guess<20 & Number>70 then Text(0, "Way too low");
if Pig ! Bombed then Text(0, "Blew it!")
Boolean operators actually perform on all 16 bits of an integer at once. Here are some examples, using 4-bit values for simplicity:
~ 1100 1100 1100 1100
= 0011 & 1010 ! 1010 | 1010
= 1000 = 1110 = 0110
Boolean operations are used to set and clear specific bits. A common operation is masking, which uses the "and" operator to clear all the bits except the ones we are interested in. For example, Number & 1 would tell us if Number is even or odd by masking off all but the least significant bit.
The value "true" is not limited to just $FFFF, but is defined as any non-zero value. Thus "anding" the odd number above with 1 is 1, which is "true". However be careful when using values other than $FFFF for "true". There can be instances when the "not" of a true value is not false. For example, ~$33 is $FFCC, both of which are non-zero, and thus both are "true".
2: Expressions 20
Expressions can contain boolean operations, comparisons, and mathematical operations. In mixed expressions, arithmetic operations are done first, then comparisons, then boolean "not", then "and", "or", and "exclusive or". Therefore, the following expressions are the same:
(A = 1) & (B = 2) is the same as A=1 & B=2
(X & Y) ! Z is the same as X&Y ! Z
But these are different:
(A & $80) = 0 is different than A & $80=0
~(X ! Y) is different than ~X ! Y
A common mistake is to forget to use parenthesis when masking an expression such as this:
Number & 7 = 3 is different than (Number & 7) = 3
Boolean operations cannot be done on real numbers. For example, this gives a compile error:
Frog & 3.2
However the following example is legal because the comparisons are done first, which produce true or false values for the "and" operator:
Frog<3.2 & Toad>=6.3E3
Here are some more expressions using boolean operators:
true & false equals false
$A ! 1 equals $B
false & false ! true equals true
false & (false ! true) equals false
~$55AA & $F0F0 equals $A050
~($F0F ! $33) equals $F0C0
3+1 = 4 equals true
3=4 & true equals false
(1 ! $80) = $81 equals true (or $FFFF)
1 ! $80 = $81 equals 1 (or true)
4+1=6-1 & not 10>12 equals true
17/3=5 & Rem(0)=2 equals true
(A&~B ! ~A&B) = (A|B) equals true
2: Expressions 21
2.6 EXAMPLE PROGRAM: SETS (Advanced)
This program shows how boolean operators are used to operate on sets. A single integer can represent a set containing up to 16 elements. The elements are either present or absent, as indicated by set or cleared bits (1 or 0).
The elements that are common to two or more sets are determined by "anding" the sets using the boolean "&" operator. These common elements are called the "intersection" of the sets. Similarly, the "union" of the sets is determined by the "!" operator.
\SETS.XPL code ChOut=8, CrLf=9, Text=12;
int Week, Work, Free; \Sets of days
int Day;
def \Day\ Mon=1, Tue=2, Wed=4, Thr=8, Fri=$10, Sat=$20, Sun=$40;
\Assign days of the week to the individual bits of an integer
proc Show(SET); \Graphically show the set of days int Set, Day;
begin
Day:= Mon;
while Day & Week do \For all of the days of the week do:
begin
if Day & SET then ChOut(0, ^X) else ChOut(0, ^-);
Day:= Day * 2; \Next day--shift bit left
end;
CrLf(0);
end; \Show
begin \Main
\Initialize work days and free days to empty sets:
Work:= 0; Free:= 0;
\There are 7 days in a week, so set the first 7 bits:
Week:= $7F;
\Saturday and Sunday are free days:
Day:= Sat;
Free:= Day ! Free ! Sun; Show(Free);
\The rest of the week are work days:
Work:= Week & ~Free; Show(Work);
\Free is a subset of Week:p; \Free is a subset of Week:
if (Free & Week) = Free then ChOut(0, ^O);
\Week is a superset of Work:
if (Week & Work) = Work then ChOut(0, ^K);
\Work and Free are mutually exclusive:
if ~(Work & Free) then Text(0, " PETER?");
\We won't work on Sunday!
if Sun & Work then Text(0, " FORGET IT!");
CrLf(0);
end; \Main
2: Expressions 22
This program produces the following output:
-----XX
XXXXX--
OK PETER?
2.7 SHIFT OPERATORS (Advanced)
Those familiar with assembly language will recognize the shift operation. The general form of the shift expression is:
EXPR << EXPR or EXPR >> EXPR
EXPR is an integer sub-expression--a 16-bit value. "<<" means shift to the left, and ">>" means shift to the right. The value of the first sub-expression is shifted the number of bits specified by the second sub-expression. The value of the second sub-expression should range from 0 through 15. Beware that only the low five bits are used (except for the 8086). This means that attempting to shift 33 places shifts only one place, and attempting to shift -1 places shifts 31 places.
Here are some examples:
1 << 1 = 2
$30 << 2 = $C0
$50 >> 4 = $05
$FF5A >> 4 = $0FF5
The shift operator's precedence (priority) is between the unary operators and the multiplication and division operators. The following expressions show this:
-1>>8 * 2 = (-1 >> 8) * 2 = $01FE
2 + 1<<4 = 2 + (1 << 4) = $0012
Multiplying and dividing by powers of two is similar to doing a shift operation. However note that dividing a negative number gives a negative result, which is not the same as shifting the negative number to the right. Shift operations are faster than multiplying or dividing.
2.8 IF EXPRESSION (Advanced)
Sometimes, rather than calculate a value, we simply want to choose between two values. This can be done using an "if" expression. Do not confuse "if" expressions with the much more common "if" statements that are described later.
2: Expressions 23
The general form of an "if" expression is:
if BOOLEAN EXPRESSION then EXPRESSION else EXPRESSION
For example:
if Guess > Number then 75 else 20+5
The "if" expression evaluates to either 75 or 25 depending on the outcome of the comparison. If the comparison is true, that is, if Guess is greater than Number then the entire expression is 75; otherwise it is 25.
Like all expressions, an "if" expression can be used anywhere a value is used. For instance:
Text(0, if Guess = Number then "Correct!" else "Incorrect")
2.9 CONSTANT EXPRESSIONS (Advanced)
An expression that consists entirely of constants can be used in place of any constant such as in a "define" declaration (or constant array). The compiler calculates the required constant. For example:
def SEC_PER_HR = 60.0 * 60.0;
def SEC_PER_DAY = SEC_PER_HR * 24.0;
def HI = ^I<<8 ! ^H;
All expression operators can be used. Unfortunately, function calls, such as Rem(17/5), cannot be used. This means that integers and reals cannot be mixed in an expression since the intrinsics Fix and Float cannot be used.
2.10 CONDITIONAL COMPILE (Advanced)
The command word "condition" is used to conditionally compile sections of code. "condition" must be followed by an expression that evaluates to true or false. If this expression is false then the following code is treated as a comment. This commented-out code is terminated by a second "condition" that evaluates to true. "condition" works everywhere except in comments and strings. It can be used to change declarations as well as executable code. For example:
2: Expressions 24
def Debug = true;
condition Debug;
int X;
condition not Debug;
real X;
condition true;
begin
cond not Debug;
X:= 3.0;
cond Debug;
X:= 3;
cond true;
. . .
"Condition" is intended for commenting out code--not for comments in general. Even though the condition is false, the code that follows is not completely ignored. The compiler is scanning for a lowercase word that starts: "con". Also, some minimal syntax checking is done. For instance, a dollar sign ($) must still be followed by a hex digit, otherwise an error is flagged.
2.11 HAZARDS OF REAL NUMBERS (Advanced)
Calculations with real numbers must be done carefully. Unlike integers, there are many instances where a real number is only an approximation of the desired value. For example, just as the value 1/3 cannot be exactly represented by a decimal number (only approximated by 0.333333333333...), it also cannot be exactly represented by an XPL0 real number. The discrepancy is called a rounding error. A real must round the true value to the nearest value it can represent.
Because of rounding errors an expression like:
9.0 * (1.0 / 3.0)
does not evaluate to exactly 3.0. The intermediate result, 0.33333333333, is not 1/3, and 0.3333333333333333 times 9.0 is 2.9999999999999997. Yet if the order of this calculation is changed, the result is exactly 3.0:
(9.0 * 1.0) / 3.0
These two expressions are not exactly equal. Thus the first hazard of real numbers is testing for equality. Usually, it is only a coincidence if a real expression evaluates to an exact value. This problem is obscured because if we were to output the values of the two preceding expressions using the RlOut intrinsic, we would get 3.000000000000000000 in both cases. The reason is RlOut itself rounds to compensate for slight rounding errors.
2: Expressions 25
The second hazard of rounding errors is that they can accumulate to cause big errors. For example, if an expression such as:
3.0 * (1.0 / 3.0)
is multiplied by itself 1000 times, the result might be something like 1.000000000000220.
Another hazard to be wary of is loss of accuracy caused by subtracting. For example, the expression
1234567890123456. - 1234567890123454. + 1.25
equals 3.25, but the same expression evaluated in a different order
1234567890123456. - (1234567890123454. + 1.25)
equals 1.0.
The discrepancy is caused by not having more than 16 digits of accuracy. When 1234567890123454 is added to 1.25, the result is rounded to 1234567890123455. This discrepancy can be seen two ways. Certainly the difference between 3.25 and 1.0 seems significant, but 2.25 compared to 1234567890123456 is really quite small.
26
3 : S T A T E M E N T S
Expressions, command words, and sub-statements combine to form XPL0 statements. A statement is a request to do something.
3.0 ASSIGNMENTS
The most fundamental statement is the assignment. It specifies that a value is to be stored into a variable. Assignments have the general form:
VARIABLE:= EXPRESSION
An assignment uses a colon-equal symbol (:=) to make a distinction between comparing two values for equality and storing a value into a variable. The ":=" symbol is pronounced "gets". For instance, the statement X:= 5 + 1 is read: "X gets five plus one."
Here are some assignment statements:
Number:= 23;
Time:= Time + 1;
Pig:= Fish = 0
In the first statement, 23 is stored into the variable named "Number". The second statement adds 1 to whatever is contained in Time and stores the result back into Time. In the last statement, Pig gets the value "true" or "false" depending on whether Fish is a zero.
3.1 BEGIN - END
"Begin" and "end" are used to designate blocks of code. A block consists of one or more statements that are combined to form a single new statement. This statement has the form:
begin STATEMENT; STATEMENT; ... STATEMENT end
3: Statements 27
Note that statements within the block are separated by semicolons.
Each "begin" must have a matching "end". A common programming error is mismatched "begin-end" pairs.
Square brackets ([ ]) can be used instead of "begin" and "end". For example, this is a block:
[X:= 12; Y:= 5]
3.2 IF - THEN - ELSE
A characteristic that makes programs seem intelligent is the ability to select alternative courses of action. The "if" statement enables alternatives to be selected based on a condition.
The "if" statement has two forms:
if BOOLEAN EXPRESSION then STATEMENT
if BOOLEAN EXPRESSION then STATEMENT else STATEMENT
The "if" statement is used to execute statements or blocks of code conditionally. For example:
if Number = Guess then Correct:= true else Correct:= false
This statement tests to see if Number is equal to Guess. If it is equal, the variable Correct gets the value "true"; if it is not equal then Correct gets "false".
Usually the condition is based on a comparison, but any expression that evaluates to true or false can be used. Here are some examples:
if A/B+C-D = (Time+1)/45 then Pig:= true;
if Pig then [X:= 3; Y:= 4] else [X:= 4; Y:= 3];
if A=B & C=D then Frog:= 1 else Frog:= 0
Two of the examples shown in this section can be simplified:
Correct:= Number = Guess;
Frog:= if A=B & C=D then 1 else 0
The first simplification is an often overlooked use of boolean expressions. The second simplification uses an "if" expression instead of an "if" statement. Note the difference between the two uses of "if".
3: Statements 28
3.3 CASE - OF - OTHER (Advanced)
Often a program must decide between more than the two alternatives offered by an "if" statement. Since an "if" statement can contain other statements, "if" statements can be nested. For example:
if Guess = Number then Text(0, "Correct!!")
else if Guess < Number then Text(0, "Too low")
else if Guess > 100 then Text(0, "Way too high")
else Text(0, "Too high")However many levels of nested "if" statements can be inefficient and confusing, so XPL0 has the "case" statement.
The "case" statement has two forms, the first is:
case of
BOOLEAN EXPRESSION: STATEMENT;
BOOLEAN EXPRESSION: STATEMENT;
...
BOOLEAN EXPRESSION: STATEMENT
other STATEMENTIn this form the "case" statement is like the nested "if"s shown above. The first expression that evaluates to true causes the corresponding statement to be executed. If no expression is true then the "other" statement is executed. Note that there is no semicolon before "other". The nested "if" example translates as follows:
case of
Guess = Number: Text(0, "Correct!!");
Guess < Number: Text(0, "Too low");
Guess > 100: Text(0, "Way too high")
other Text(0, "Too high")The "other" cannot be left out, but it can have a null statement:
case of
Number = 1: DoOne;
Number = 2: DoTwo
other [];
The second form of the "case" statement is used for efficiency. The expressions must all have a common component and must be a comparison for equality, like in the last example. This form is:
3: Statements 29
case EXPRESSION of
EXPRESSION: STATEMENT;
EXPRESSION: STATEMENT;
...
EXPRESSION: STATEMENT
other STATEMENT
The last example, in this form, looks like this:
case Number of
1: DoOne;
2: DoTwo
other [];Sometimes several different expressions are associated with a single statement. For example:
case Number of
1: DoOdd;
2: DoEven;
3: DoOdd;
4: DoEven;
5: DoOdd
other [];
Here, if Number equals 1, 3, or 5 then the subroutine DoOdd is executed; if Number equals 2 or 4 then DoEven is executed. The "case" allows any number of expressions to select a statement. The form is:
EXPRESSION, EXPRESSION, ... EXPRESSION: STATEMENT
So, the example above could be rewritten:
case Number of
1,3,5: DoOdd;
2,4: DoEven
other [];
"Case" expressions must evaluate to integers. Reals cannot be used since it is generally a coincidence when two reals are exactly equal. However, a comparison containing reals, such as 2.3 > X, evaluates to true or false, which is an integer expression that can be used by the first "case-of" form.
Note that "case" selectors are not limited to simple constants; they can be any integer expression.
3: Statements 30
3.4 WHILE - DO
Much of the power of a computer is its ability to do repetitive tasks. In programming it is frequently necessary to make tasks execute over and over. This is called looping. XPL0 has four kinds of looping statements each of which repeatedly execute a block of code until certain conditions are met.
The "while" statement is a conditional looping structure. As long as the condition is met, the following statement or block is repeatedly executed. This statement has the form:
while BOOLEAN EXPRESSION do STATEMENT
For example:
while Guess # Number do
begin
InputGuess;
TestGuess
end
As long as the variables Guess and Number are not equal, the code within the begin-end block is repeated. The program tests the condition at the beginning of the "while" statement. If the condition is false, the block in the loop is ignored. If the condition is true, the block is executed and the code loops back to retest the condition. The condition must eventually become false, otherwise the loop continues forever.
3.5 REPEAT - UNTIL
The "repeat" statement has the form:
repeat STATEMENT; ... STATEMENT until BOOLEAN EXPRESSION
The "repeat" loop is similar to the "while" loop except that the decision to continue the loop is made after the block.
3: Statements 31
These flow diagrams show the difference between the "while" and "repeat" statements:

An example of a repeat loop is:
repeat InputGuess;
TestGuess
until Guess = Number
Note that the command words "repeat" and "until" also act as "begin" and "end" for the block in the loop.
3.6 LOOP - QUIT
The "loop" statement has the form:
loop STATEMENT
A "loop" command repeatedly executes the following statement or block. A "quit" statement is used to exit from any point (or points) within the loop. Usually a "quit" is used in an "if" statement so that the loop exits under certain conditions. For example:
loop begin
InputGuess;
if Guess = Number then quit;
TestGuess
end
3: Statements 32
3.7 FOR - DO
A "for" loop is a powerful looping statement. It counts upward one at a time, and for each count it executes a block. The starting and ending values of the count are specified, and the count is stored in a variable so that it can be used by the block. This statement has the form:
for VARIABLE:= EXPRESSION, EXPRESSION do STATEMENT
For example:
for Guess:= 1, 100 do TestGuess
Guess starts with a value of 1 and steps one at a time up to and including 100. TestGuess is executed 100 times.
In XPL0, the steps are always ascending, and the increment is always one. The loop control variable cannot be a real (or have a subscript). Negative loop limits can be used, but descending loops and other increments must be created using the other looping statements.
If the starting and ending limits are expressions, they are evaluated one time before the looping begins. The starting value is assigned to the control variable. This variable is compared to the ending limit before each pass through the loop. If it is greater, the loop is exited. Otherwise, the block is executed, and then the control variable is incremented.
Note that a "for" loop is not executed if the limits are not in ascending order, as in:
X:= -10;
for Guess:= 1, X do Text(0, "Way too low")
Also note that 32767 cannot be used as the ending limit because there is not a larger signed number that can be represented with 16 bits. Writing "for I:= 32000, 32767 do" is an infinite loop.
3.8 EXIT
Perhaps the simplest statement is "exit". It terminates the execution of a program at the point it is encountered. This statement is used to halt execution at a point other than the normal end of a program. It is not necessary to put "exit" at the end of a program.
The "exit" statement can also return a code to DOS. The low byte of the value (0-255) of an optional expression following "exit" is returned to DOS interrupt $21 function $4C. This return code can be tested in a batch
3: Statements 33
file with an IF ERRORLEVEL statement. For example, the batch file used to run the compiler (XN.BAT) uses this feature to skip the assembly and link steps if there is an compile error. By convention, a returned value of 0 indicates no errors.
3.9 SUBROUTINE CALLS
Another simple statement is a call to a subroutine. It merely consists of the name of the subroutine, which can be a procedure, an intrinsic, or an external. (This is explained further in 4: SUBROUTINES.)
A call can send some values, known as arguments, to the subroutine. In this case the call has the form:
NAME(EXPRESSION, EXPRESSION, ... EXPRESSION)
Here are some examples of subroutine calls:
MakeNumber;
CrLf(0);
Text(0, "Too low")
The first example is a procedure call. The second example calls the new-line intrinsic and passes the argument 0. The last example is an intrinsic call with two arguments.
3.10 COMMENTS
Comments are used by the programmer to make notes to himself and others. They are important because they make a program easier to understand. A comment can go almost anywhere (except in the middle of a name, inside a string, or in an "include" file name). A comment must be enclosed in backslash (\) characters, unless it is the last item on a line, in which case only the leading backslash is needed. A comment can contain any character except a backslash. Here are some examples:
begin \Move down the page
for X:= -10, 10 \Twenty-one times\ do CrLf(0);
3: Statements 34
3.11 NULL STATEMENTS
The null statement does nothing. It consists of nothing, and it compiles into nothing. It is useful because in some circumstances we want to do nothing. An example of this was shown with the "other" part of a "case" statement. Here are some more examples:
for I:= 1, 1000 do []; \Kill some time
while not Strobe do; \Wait for Strobe to be "true"
repeat until KeyStruck \Another form of wait
Each of these statements contains a null sub-statement.
Null statements are frequently used as a coding convenience--a kind of XPL0 slang. For example, these two blocks compile into exactly the same code:
begin begin
X:= X + 1; X:= X + 1;
Y:= Y - 1 Y:= Y - 1;
end end
Note that the block on the right actually contains three statements: the two assignments and a null statement after the second semicolon.
This is convenient because now we can simply insert or delete statements by inserting or deleting lines and not worry about a semicolon on the previous line. Here you might think of semicolons as statement terminators, but they are actually statement separators.
Unless you understand the concept of null statements, you can become confused by semicolons, especially in if-then-else statements. A semicolon is used to separate statements and procedures and to terminate declarations.
3.12 EXAMPLE PROGRAM: THERMO
The following program uses real numbers to convert degrees Fahrenheit to degrees Celsius.
3: Statements 35
\THERMO.XPL 01-AUG-2005
\This program prints a table of Fahrenheit temperatures
\ and their Celsius equivalents.
code CrLf=9, Text=12;
code real RlOut=48, Format=52;
real Fahr, \Fahrenheit temperature
Cel; \Celsius temperature
begin
\Print table heading:
Text(0, "FAHRENHEIT CELSIUS");
CrLf(0);
Format(3, 1); \Define real-number format
Fahr:= -40.0;
while Fahr <= 100.0 do
begin
Cel:= 5.0/9.0 * (Fahr - 32.0); \Calculate Celsius
RlOut(0, Fahr); \Print out results
Text(0, " "); \(2 tabs)
RlOut(0, Cel);
CrLf(0);
Fahr:= Fahr + 20.0; \Next step
end;
end;
When THERMO executes, it displays the following:
FAHRENHEIT CELSIUS
-40.0 -40.0
-20.0 -28.9
0.0 -17.8
20.0 -6.7
40.0 4.4
60.0 15.6
80.0 26.7
100.0 37.8
CrLf and Text are intrinsics we have used before, but RlOut and Format are new. RlOut (ReaL OUT) outputs real numbers in a format specified by Format. Here we are specifying a format of three places (including the minus sign) before the decimal point and one place after it.
36
4 : S U B R O U T I N E S
One of the most important constructs in programming is the subroutine. XPL0 has four different kinds of subroutines:
Procedures
Functions
Intrinsics
Externals
4.0 PROCEDURES
Scattered throughout most programs are certain operations that must be done over and over. To avoid writing the same code over and over, a programmer puts the common code into a single routine that is called whenever the operation is needed. After the common code is executed, the program resumes at the point following the call. Such a routine in XPL0 is called a procedure.
Any block of code can become a procedure simply by giving it a name. The process of naming a procedure is a declaration. Procedure declarations have the general form:
procedure NAME(COMMENT);
DECLARATIONS;
STATEMENT;For example, here is a simple procedure:
procedure MakeNumber;
begin
Number:= Ran(100) + 1;
end;Once a procedure is declared it can be executed simply by calling its name. For instance, here is a block that calls three procedures:
4: Subroutines 37
begin
MakeNumber;
InputGuess;
TestGuess;
end;
A block of code does not necessarily need to be called more than once to justify making it into a procedure. An important use of procedures is to make a program more understandable by breaking it down into smaller, simpler pieces. By making a piece of code into a procedure, you can name it according to its use, test it separately, and keep the main body of code uncluttered.
4.1 LOCAL AND GLOBAL
Names are active only in certain areas of a program. These areas are defined by the rules of scope (see: 4.7 Scope). A name that is declared within a procedure is said to be local to that procedure. A name that is defined for several procedures is global to those procedures.
A procedure is an independent piece of code that can contain its own declarations. For example:
code Ran=1;
integer Number;
procedure MakeNumber;
integer Times, X; \Local variables
begin \Randomly pick a random number
Times:= Ran(10);
for X:= 0, Times do Number:= Ran(100) + 1;
end;
begin
MakeNumber;
end;In this example Times and X are local names while Number, Ran, and MakeNumber are global names.
4.2 ARGUMENTS
It is often necessary to send information to a procedure. Values to be sent are separated by commas and placed between parentheses immediately after the procedure call. These values are the arguments of the procedure. When the procedure is called, these arguments are copied into the first local variables of the procedure. Here is an example:
4: Subroutines 38
integer A, B, C, Result;
procedure AddTen; \Subroutine
integer X, Y, Z; \Arguments
begin
X:= X + 10;
Y:= Y + 10;
Z:= Z + 10;
Result:= X + Y + Z;
end;
begin \Start of the program
A:= 1;
B:= 2;
C:= 3;
AddTen(A, B, C); \Procedure call with arguments
end;In this example the second block calls the first. In the process it sends the values of the variables A, B, and C, which are 1, 2, and 3 respectively. When AddTen is called, the values in A, B, and C are copied into X, Y, and Z. The procedure adds 10 to these values, sums them into Result (= 36), and returns. The original A, B, and C are not changed by the procedure call.
XPL0 allows a special comment to be placed after the name of a procedure and before the semicolon in the declaration. This helps the programmer keep track of which variables are arguments and which are normal locals. Use the comment to list the arguments in the order they are sent when the procedure is called.
Here is an example of an argument list as a comment:
procedure Check(Area, Perimeter);
integer Area, Perimeter; \Arguments
integer Side; \Normal local variable
begin
Side:= Perimeter / 4;
if Side*Side = Area then Text(0, "square")
else Text(0, "rectangle");
end;Writing Area and Perimeter in parenthesis on the first line shows that this procedure has these two values passed to it as arguments, while Side is simply a normal local variable.
Real values can also be passed as arguments. Be sure to declare the local variables in the same order as they are passed. "Real" and "integer" declarations can be mixed in any order to accomplish this.
4: Subroutines 39
The ability to pass values to procedures, with the ability to declare in each procedure just those variables it needs, enables each procedure to be a complete and independent piece of code. This enables it to be debugged separately and copied from program to program.
4.3 NESTING
Since a procedure is an independent piece of code, it can itself contain procedures. Procedures can be nested inside each other. For example:
procedure ONE;
procedure TWO;
procedure THREE;
begin
...
end;
begin \TWO
...
end;
begin \ONE
...
end;
Look at how these procedures are nested. Procedure THREE is nested inside procedure TWO, which in turn is nested inside procedure ONE.
Procedures can be nested up to eight levels deep. Here ONE is at the highest level, and THREE is at the lowest level. Note that the block for the highest level routine is last, but is executed first.
The same order applies to an entire program. The code for the main routine is always the last block in the program, and this highest-level block is always executed first. In fact, a program is just one big procedure.
4.4 RETURN
Occasionally it is desirable to return from a procedure at a point other than its normal end. This is done using a "return" statement. "Return" forces a procedure to immediately return to its caller. At the end of a procedure, a "return" is implied and need not be written.
4: Subroutines 40
The TestGuess procedure used in the number guessing program can be rewritten using a "return" statement:
procedure TestGuess;
begin
if Guess = Number then [Text(0, "Correct!"); return];
if Guess > Number then Text(0, "Too high")
else Text(0, "Too low");
CrLf(0);
end;
4.5 FUNCTIONS
The "return" statement is also used to "return" a value from a subroutine to the calling routine. A subroutine that returns a value is called a function. A function is similar to a procedure except that it returns a value and is used as a value. A procedure call is a statement, but a function call represents a value and is therefore a factor. The general form of a function is:
function TYPE NAME(COMMENT);
DECLARATIONS;
STATEMENT;
Since all factors must be distinguished as either integers or reals, the function declaration includes a type specifier. This specifier is either "integer", "real", or none. If the type is not specified (none), the function defaults to "integer".
The value to be returned by the function is placed immediately following the "return" command. The general form is:
return EXPRESSION;
Here is an example of how a function is used:
integer X, Y;
function integer Increment(A);
integer A;
begin
return A + 1;
end;
begin
X:= 3;
Y:= Increment(X); \Function call
end;
4: Subroutines 41
This function increments a value. When the function is called, the value in X is sent to it. This value is incremented and passed back to the caller by the "return" statement. The result (4) is then stored into the variable Y.
Here is an example of a function that returns a real value:
real Angle;
func real Deg(X);
real X;
return 57.2957795 * X;
begin
Angle:= Deg(3.141592654);
end;This function converts radians to degrees. Angle gets 180.0.
Here is an example of a function that returns a boolean:
code ChIn=7, ChOut=8, Text=12, OpenI=13;
integer Ch;
function Affirmative;
begin
OpenI(0);
return ChIn(0) = ^y;
end;
begin
Text(0, "Do you want to see the ASCII character set? ");
if Affirmative then for Ch:= $20, $7E do ChOut(0, Ch);
end;
This function returns "true" if the first character typed on the keyboard is a "y" (as in "yes"), otherwise it returns "false". The OpenI (OPEN Input) intrinsic discards any characters that might already be in the keyboard's buffer, thus assuring that the intended character is used.
If a "return" is used in the main (highest-level) procedure, it has the same effect as an "exit" statement. If an expression follows such a "return", it also has the same effect as an expression following an "exit" statement. (See: 3.8 Exit.)
4: Subroutines 42
Intrinsics are built-in subroutines that do a variety of operations, such as input and output, and math functions. There are 79 intrinsics in the run-time code (NATIVE).
An intrinsic, like any named thing, must be declared before it can be used. When an intrinsic is declared, a name is given to its number. The general form of an intrinsic declaration is:
code TYPE NAME(COMMENT) = INTEGER, ... NAME(COMMENT) = INTEGER;
Here are some examples:
code Ran=1, Text=12;
code real Sin=56, Cos=60;
Intrinsics can be given any name, but the established names are usually preferred.
Since some intrinsics are used as functions, and since the compiler must distinguish between integer and real functions, an intrinsic declaration includes an optional type specifier. This specifier works the same way as for function declarations except that it defines the data type of all the names following the declaration. In the example, Sin and Cos are trig functions that return real values.
An intrinsic call is identical to a procedure or function call. Arguments, if any, are placed between parentheses immediately following the intrinsic name.
Here are some examples of intrinsic calls:
Cursor(20, 12);
Number:= Ran(100);
Height:= Sin(Angle) * 10.0;
The first example sends the values 20 and 12 to the cursor positioning intrinsic. In the second example, a random number between 0 and 99 (inclusive) is assigned to the variable "Number". The last example computes the sine of Angle, multiplies it by 10, and stores the result in Height.
Some intrinsics return a value while others do not. Intrinsics that return a value must be used as functions (factors), not as statements, otherwise a run-time error occurs. Conversely, an intrinsic that does not return a value must not be used as a function.
4: Subroutines 43
The following is an example of the incorrect use of intrinsics. This statement is illegal and causes a run-time error when it executes:
for I:= 10, 100 do Ran(I); \A bad statement
The error occurs because the random-number intrinsic returns a value that is not used.
See appendix A.0 for a list of the intrinsics and a description of what they do.
4.7 SCOPE (Advanced)
Scope is the feature that makes names active only in certain parts of a program. A name declared in one part does not necessarily conflict with the same name declared in another part. Scope is what makes a program modular.
When a name is active, it is in scope. At any point in the program certain names are in scope and available, while others are out of scope and nonexistent. A name is in scope from the point it is declared to the end of the procedure in which its declaration appears. It is active in any sub-procedures that might be nested in the procedure. Usually we think of scope applying to variable names, but it applies to procedure names, as well as all other names.
Here are some nested procedures with a variable declared in each one:
procedure ONE;
integer X;
procedure TWO;
integer Y;
procedure THREE;
integer Z;
begin
. . .
end;
begin \TWO
. . .
end;
begin \ONE
. . .
end;
4: Subroutines 44
Here is another way of looking at these same nested procedures:

The statements inside procedure ONE can call procedure TWO because both the call and procedure TWO are within procedure ONE. However, the statements inside procedure ONE cannot call procedure THREE because the scope of THREE ends at the end of procedure TWO.
For similar reasons, only the variable X is in scope for the statements inside procedure ONE. Procedure TWO can access variables X and Y, and it can call procedures ONE, TWO, and THREE. Procedure THREE can access all the variables, X, Y, and Z, and can call procedures, ONE, TWO, and THREE.
Note that a procedure is in scope during its own body code, so a procedure can call itself. (See: 4.8 Recursion.)
Two procedures at the same level, but nested inside different procedures, cannot call each other. For example:

Procedures B and TWO cannot call each other because they are not in scope with each other. The scope of B ends at the end of procedure A. However, statements in procedures ONE and TWO can call procedure A, and conversely, statements in A and B can call procedure ONE. (See: 4.9 Forward Procedures.)
4: Subroutines 45
In XPL0, names in scope with each other and at the same level must be unique in their first 16 characters, otherwise a compile error occurs (ERROR 11: NAME ALREADY DECLARED). However, there is no conflict if the identical names are declared in different scopes or in the same scope but in procedures nested at different levels. For example, "integer Frog" can be declared in all four of the procedures: A, B, ONE, and TWO, without conflict. Each declaration creates a separate variable, so there are four unique variables that have the same name.
When the same name is declared at different levels in nested procedures, the most local declaration is used. In the last example suppose the nested procedures A and B both have "integer Frog" declared in them. When a statement in procedure B refers to Frog, it refers to the local Frog declared in B, not the global one in A. Statements in procedure A use the Frog declared in A.
4.8 RECURSION (Advanced)
Recursion is a powerful programming technique. It is the ability of a routine to call itself. Recursion provides another approach to solving problems. Some things can be easily defined in a recursive way. For example, an ancestor is a person's father or mother or one of their ancestors. In programming, recursion is used for sorting, searching trees, parsing languages, and so on.
XPL0 is designed to facilitate recursive programming. Any procedure (or function) can call itself. A procedure can also call itself indirectly. For instance, a procedure P could call a second procedure Q that calls the original procedure P. Each time a procedure calls itself, the current set of local variables for the procedure is saved and a new set is created.
Here is an example using recursion to compute factorials:
code IntOut=11;
function Factorial(N); \Returns N!
integer N;
begin
if N = 0 then return 1 \(0! = 1)
else return N * Factorial(N-1);
end;
begin \Main
IntOut(0, Factorial(7));
end;
Seven factorial (7!) is 7*6*5*4*3*2*1, which is equal to 5040.
4: Subroutines 46
4.9 FORWARD PROCEDURES (Advanced)
In XPL0, all names must be declared before they can be used. Procedures, in particular, must be declared before they are called. Occasionally, a situation arises in recursive programs where a procedure must be called before it is declared. The forward-procedure declaration solves this problem. It has the form:
fprocedure NAME(COMMENT), ... NAME(COMMENT);
For example:
fprocedure MakeNumber, TestGuess, Break, Repair;
This declaration tells the compiler that the four names listed are procedures that occur within the present procedure and at the current level. Now that these procedures are declared, they can recursively call each other without regard to the order that they are written.
"Fprocedure" declarations must occur immediately before "procedure" declarations; there cannot be any intervening variable declarations.
4.10 FORWARD FUNCTIONS (Advanced)
Forward declarations can also be made for functions. The form is:
ffunction TYPE NAME(COMMENT), ... NAME(COMMENT);
Forward-function declarations are similar to forward-procedure declarations with the exception that functions must be typed. The type is either "integer", "real", or none. (See: 4.5 Functions.) For example:
ffunction real Sinh, Cosh, Tanh;
4.11 INCLUDE (Advanced)
Large programs can be broken into smaller, more manageable pieces in several ways. One way is to use the "include" command word to automatically insert another file when you compile your program. For example, it is convenient to "include" the file CODESI.XPL that declares all the intrinsics:
include C:\CXPL\CODESI;
4: Subroutines 47
Note that backslashes specify the path name in the normal DOS manner, and do not indicate a comment in this situation. The default extension is .XPL, so it does not need to be written, and other extensions can be used. Only one file name can follow "include", and it must be terminated by a semicolon.
Any number of files can be included in a program. An included file can itself include other files. Included files can be nested in this fashion up to eight levels.
4.12 EXTERNAL PROCEDURES (Advanced)
When developing a large program, it is inefficient to repeatedly edit, list, and compile the entire program when all the changes are concentrated in one small area. To avoid this, procedures are broken off the main program and put into separate files. These are called "external procedures". They are compiled and assembled separately from the main program, and the resulting .OBJ files are combined using the linker.
Like all named things, external procedures must be declared before they can be called. The form is:
eprocedure NAME(COMMENT), ... NAME(COMMENT);
For example:
eprocedure Baker, Charlie;
This means that Baker and Charlie are procedures that are called from the present file, but they exist in another, external file.
The actual procedure in the external file must be prefixed by the command word "public". For example:
public procedure Baker;
begin
. . .
end;
"Eprocedure" and "public" declarations must be in scope with each other. This means that "eprocedure" declarations must be made at the beginning of the main program, typically right after the "code" declarations, and that "public procedure" declarations must not be nested inside other procedures (except, of course, the main procedure).
4: Subroutines 48
Functions also can be external. They are handled like procedures, but since they return a value, they must be identified as "integer", "real", or none. The form of the declaration is:
efunction TYPE NAME(COMMENT), ... NAME(COMMENT);
External procedures and functions handle local variables and argument passing just as you would expect, but global variables require special consideration. WARNING: Each file must declare global variables in the exact same order. This way the global variables correspond to the same memory locations for each file. A convenient way to make sure that each file has the exact same variable declarations is to use the "include" command.
Here is an example of a program that is divided into three files plus a common global variable file:
\GLOBALS.XPL -- COMMON GLOBALS --
code CrLf=9, Text=12;
code real RlOut=48;
int Flag;
real X;
\PARENT.XPL -- MAIN PROGRAM --
include GLOBALS;
efunc real Able; \External procedures & functions
eproc Baker;
begin \Main
X:= 0.0;
Flag:= false;
Text(0, "EXTERNAL EXAMPLE"); CrLf(0);
RlOut(0, Able(2.0)); CrLf(0);
Text(0, "Global X = "); RlOut(0, X); CrLf(0);
X:= X + 1.0;
Baker;
Text(0, "Global X = "); RlOut(0, X); CrLf(0);
end; \Main
4: Subroutines 49
\FILE1.XPL -- SECONDARY FILE --
include GLOBALS;
public func real Able(X);
real X; \Local variable
begin
Text(0, "This is Able"); CrLf(0);
if Flag then X:= X + 1.0;
Flag:= true;
return X * X;
end; \Able
\FILE2.XPL -- SECONDARY FILE --
include GLOBALS;
efunc real Able; \External function
public proc Baker;
begin
Text(0, "This is Baker"); CrLf(0);
RlOut(0, Able(3.0)); CrLf(0);
Text(0, "Baker's global X = "); RlOut(0, X); CrLf(0);
X:= X + 1.0;
end; \Baker
After these files are compiled and assembled, they are linked by the command:
LINK /SE:256 PARENT+FILE1+FILE2+NATIVE;
The program is executed by typing "PARENT", and it displays the following:
EXTERNAL EXAMPLE
This is Able
4.00000
Global X = 0.00000
This is Baker
This is Able
16.00000
Baker's global X = 1.00000
Global X = 2.00000
Several public procedures can be combined into a single file and used as a library. This is like having your own set of intrinsics, and it keeps you from compiling and debugging the same subroutines over and over.
4: Subroutines 50
4.13 ASSEMBLY-LANGUAGE EXTERNALS (Advanced)
External subroutines can also be written in assembly language when you want maximum speed or need total control. Assembly-language subroutines are declared using the command word "external". The form is:
external NAME(COMMENT), ... NAME(COMMENT);
"External" can be declared at any level of procedure nesting, unlike "eprocedure" and "efunction".
In the assembly code, the entry point label must be declared public. For example:
CSEG SEGMENT DWORD PUBLIC 'CODE'
ASSUME CS:CSEG
PUBLIC DOADD
DOADD: POP CX ;Save return address
POP DX ;Save return segment
POP AX ;Get second argument
POP BX ;Get first argument
ADD AX,BX ;Add arguments
PUSH AX ;Return result
PUSH DX ;Restore return address
PUSH CX
RETF ;Far return to caller
CSEG ENDS
END
This example takes two arguments that are passed on the stack, adds them and returns the result on the stack.
Like intrinsics, it is essential to keep the stack balanced by popping and pushing the correct number of arguments. Also, if you change the stack pointer (SP) or segment registers (CS, DS, SS, ES), they must be restored to their original values before you return. The other registers (AX, BX, CX, DX, SI, DI, BP) need not be preserved. The direction flag bit (D) also does not need to be preserved.
The last example shows one way of getting arguments on and off the stack using PUSH and POP instructions. Another way is to use BP to access all the arguments directly:
DOADD: MOV BP,SP ;Get stack pointer
MOV AX,[BP+4] ;Get second argument
ADD [BP+6],AX ;Add to first argument
RETF 2 ;Drop one argument
4: Subroutines 51
Local variables can be created by putting them on the stack. This makes a subroutine reentrant, which enables it to be called by an interrupt routine in addition to the XPL0 program (it also enables it to call itself--recursively). For example:
SUB SP,4 ;Reserve space for two integers
MOV BP,SP ;Get stack pointer
MOV AX,[BP] ;Access one variable
ADD AX,[BP+2] ;Access the other
ADD SP,4 ;Drop the local variables
You can also create local variables by defining blocks of data using DB, DW, DQ, and so forth, but this makes the subroutine non-reentrant. These variables must be in a segment declared like this:
DSEG SEGMENT WORD PUBLIC 'DATA'
COLOR DB 0
PIXEL DW 0
DSEG ENDS
This declaration tells the linker to group your data with the rest of the variables in the program. If you leave the DSEG directive out, your local variables will collide with variables in the XPL0 code. It is also important to link NATIVE last, otherwise similar problems occur.
Accessing global variables is a little more complicated. Generally it is best to pass any variables as arguments. You can even pass the address of a global variable the way arrays are passed. However, global variables can also be accessed using the public label "HEAPLO".
HEAPLO is the bottom of the heap memory space, which is where global variables start. HEAPLO is a public label defined in NATIVE. An assembly-language subroutine can use HEAPLO if HEAPLO is declared external (EXTRN). For example:
\MAIN XPL0 PROGRAM
\Start of global declarations
integer Frog, Pig, Cow;
. . .
;EXTERNAL ASSEMBLY-LANGUAGE SUBROUTINE
EXTRN HEAPLO:WORD ;Declare HEAPLO as an external
FROG EQU HEAPLO+8 ;Define global variables
PIG EQU HEAPLO+10
COW EQU HEAPLO+12
CSEG SEGMENT DWORD PUBLIC 'CODE'
MOV AX,FROG ;Accessing global FROG
MOV AX,PIG ;Accessing global PIG
MOV COW,AX ;Accessing global COW
. . .
4: Subroutines 52
Note that global variables actually start at HEAPLO+8. The first eight bytes are used for a special variable called "global zero". This is used by functions to return values. Eight bytes are used so that reals can be returned as well as integers.
WARNING: Do not give your external assembly-language file the same name as an .XPL file, otherwise when the .XPL file is compiled, you will lose your .ASM file.
4.14 EXTERNAL .I2L PROCEDURES (Advanced)
Up to this point we have discussed externals for the native versions of XPL0. In the native versions .XPL code is converted to assembly language, and after being assembled the resulting .OBJ files are combined using the standard linker (LINK). However, the interpreted version does not compile into assembly language, so a different linker is used. XLINK combines the main program with files containing external procedures and produces a .C2L file, which is loaded and run like a normal .I2L file. For example:
XLINK PARENT.I2L+FILE1.I2L+FILE2.I2L
I2L PARENT.C2L
The first file after "XLINK" must be the main program, but the other files can be in any order. The linker allows up to 200 external procedures and 1000 calls to those procedures. If two external procedures have the same name, the first one is always called.
The interpreted version can also have external subroutines written in assembly language. An assembly-language subroutine is made into a .COM file, then combined with the main program using XLINK. For example:
MASM DOADD;
LINK DOADD;
EXE2BIN DOADD.EXE DOADD.COM
XPLIQ PARENT
XLINK PARENT.I2L+DOADD.COM
I2L PARENT.C2L
Note the .COM extension in the XLINK command. This is how the linker distinguishes assembly subroutines from .I2L code.
Assembly-language subroutines used with the interpreted version of XPL0 have several restrictions compared to the native version. Since "public"
4: Subroutines 53
names are not used in these .COM files, XLINK uses the name of the file as the name of the subroutine. Because of this, each subroutine must be in a separate file and have its entry point as the first instruction of the file. Also, the name is restricted to eight characters.
Another restriction involves local variables. Subroutines called from the interpreted version must use the stack for any local variables. They cannot be declared using DW or DB because these are not relocated by XLINK.
To get around the limitations imposed by ".COM" files, XLINK uses a special type of library file called a "supervisor" file. The supervisor file contains the names of files to be linked. XLINK handles these files just as though their names had been typed on the command line.
The supervisor file does not contain the name of the main program, this must be entered on the command line. All file names in the supervisor file must have extensions, even the .I2L files. Paths can be used. File names are separated by either a plus sign, comma, space, tab, or a carriage return. The supervisor file itself must have the extension ".XLB". Here is an example of a supervisor file and its usage:
FILE1.I2L+FILE2.I2L
C:\LIBRARY\DOADD.COM
XLINK PARENT.I2L+SUPER.XLB
The simplest use of supervisor files saves the trouble of typing many names on the command line. However, supervisor files can also include other supervisor files to form complex trees of library routines.
54
5 : A R R A Y S
It is often useful to handle variables as a group when the variables have something in common--like points on a graph or dollars in accounts. In XPL0 variables can be grouped using a single name with each item having a separate number. Such a group is called an array. For example:
Account(11)
This refers to the 12th item in the array named "Account". If there are 20 items in an array, they are numbered 0 through 19.
In XPL0 there are three types of arrays: integer, real, and character.
Integer arrays are groups of variables where each variable is an integer. Each variable in the array can store a 2-byte value in the range -32768 through 32767 (or $0000 through $FFFF).
The name of an array must be declared before it can be used. Integer array declarations have the general form:
integer NAME(DIMENSIONS), ... NAME(DIMENSIONS);
For example:
integer Account(20);
This sets aside memory space for 20 integers and gives this space the name "Account". Now, values can be moved in and out of the elements of this array. For example:
begin
Account(19):= 2050;
I:= Account(9) + 100;
. . .
Array variables are normally used with an item number in parentheses. This number is called a "subscript", and it can be any integer expression as long as it evaluates to an item number that is in the array.
5: Arrays 55
Account(I+2):= J;
if Account(0)=$0C then FormFeed;
Arrays that contain real numbers are similar to integer arrays. Here is an example:
real Dollars(70), X;
int I;
begin
for I:= 0, 70-1 do Dollars(I):= 0.00;
Dollars(7):= 1.25;
X:= Dollars(7) - 1.00;
end;
Note that subscripts are always integers, or integer expressions, even for a real array.
Array elements can also be single bytes. Since a byte is often used to store an ASCII character, these arrays are called character arrays. Here are some examples:
character Name(20), Address(20), City(10), State(2);
Character arrays can have subscripts larger than 32767 (or $7FFF). It is logical to use hex numbers in this case (although negative decimal numbers can be used).
5.0 EXAMPLE PROGRAM: DICE
This little program uses an integer array to represent the six sides of a die. The program simulates throwing the die 10000 times and counts the number of times each side lands up. The sides are numbered 0 through 5 in the array.

\DICE.XPL
\This program simulates dice throwing
code Ran=1, ChOut=8, CrLf=9, IntOut=11;
integer Side(6), I, N;
begin
for I:= 0, 5 do Side(I):= 0; \Initialize array with zeros
for I:= 1, 10000 do \Throw the die 10000 times
begin
N:= Ran(6); \Randomly pick a side
Side(N):= Side(N) + 1; \Increment counter for side
end;
\Show the results
for I:= 0, 5 do [IntOut(0, Side(I)); ChOut(0, \tab\$09)];
CrLf(0);
end;
5: Arrays 56
Running this program produced the following output:
1701 1715 1711 1665 1601 1607
5.1 HOW ARRAYS WORK (Advanced)
When an array name is declared with a dimension in parentheses, memory space is set aside for the items that will be in the array. Memory space is also set aside for the name of the array, just like space is set aside for any variable name. However, the array name is automatically set up with the address in memory where the array items start. The only difference between an array name and an ordinary variable name is that the array name has a value automatically stored into it. This starting address points to the items in the array, and it is called a "pointer".
For example, the declaration
integer Account(20);
reserves memory space for 20 integers plus space for one more integer, the variable called Account. The variable called Account is set to point to the start of the space reserved for the 20 integers. Account is normally used with a subscript that refers to one of the items in the array. Account without a subscript refers to the starting address of the array. Here is what this array looks like:

The starting address of an array declared as "real" is handled as a real variable even though it contains a 16-bit address pointing to its data. The address is in the first two bytes, low byte first.
5: Arrays 57
When an array is passed to a procedure, only the starting address is passed, not the actual items in the array. Thus an array passed to a procedure should never have its dimensions declared in the procedure. In other words, the local variable name of the array argument should never have parenthesis showing its size.
Memory used for arrays, as well as variables, comes from an area known as the "heap". The heap has about 60000 bytes and works like a stack but is a little more versatile. When a procedure returns, any arrays and variables that were declared in it are no longer needed. The heap space used by these arrays and variables is released so that it can be used by other arrays and variables in other procedures. This efficient method of using memory is called "dynamic memory allocation". The amount of unused space available in the heap can be determined by calling the Free intrinsic (18). If you have large arrays and need more space, see: 5.9 Segment Arrays.
Declared array dimensions must be constants; they cannot be variables. This is rarely a limitation because any constant expression can be used. For example:
def Size=20;
int Array(Size);
char Name(Size*3);
If a variable must be used to define the size of an array at run time, it can be done using the method described in: 5.4 Complex Data Structures.
5.2 STRINGS (Advanced)
Another way to set up a character array is to make a text string. For example:
"This is a string"
This allocates some memory space, fills it with the ASCII for each character, and returns the starting address. If this address is assigned to the character variable S then S is like any other character array except that the contents are already set.
We can read the individual bytes, as in:
5: Arrays 58
character S;
begin
S:= "This is a string";
if S(3)=$73 then Text(0, "It's an s");
. . .
Or we can store bytes into this array, as in:
S(3):= ^n; S(5):= ^a;
We can output the string to any device using the Text intrinsic. For example:
Text(0, S);
now displays:
Thin as a string
on the monitor (device 0).
Note that the quoted string itself allocates the memory space; there is no dimension after the S in the declaration. Writing: "character S(16);" would allocate another 16 bytes that would not be used.
The end of a string is marked by setting the high bit of the last character. This adds $80 (128) to the ASCII value of this character. In the example above, S(15) has the value $E7, which is $80 more than the ASCII for the letter g ($67).
The caret character (^), besides indicating ASCII values (see: 1.2 ASCII Constants), enables quotes (") and carets to be in strings. For example:
Text(0, "^"^^^" is called a ^"caret^"");
displays:
"^" is called a "caret"
A string can contain any printable character. It can also contain control characters like tab, carriage return, bell, and form feed. However, putting a form feed in a string can mess up a program listing, and a control character, such as a bell ($07), does not show in the listing. For these reasons, it is better to use the caret character to put a control character in a string.
Inside a string, ^A means control-A, ^Z means control-Z, and so forth. Do
5: Arrays 59
not confuse this use of the caret character with the way it is used to represent an ASCII character outside a string. ^G in a string means control-G ($07, the bell character), but outside a string it means the letter G ($47).
Characters in addition to A-Z can be used with the caret to get the complete range of control characters. The symbols ^@, ^A...^Z, ^[, ^\, ^], and ^_ correspond to the values $00, $01...$1A, $1B, $1C, $1D, and $1F. Note the exception: ^^, which is not $1E but the caret character ($5E) described above. Lowercase letters and characters can also be used. ^`, ^a...^z, ^{, ^|, ^}, and ^~ correspond to the values $00, $01...$1A, $1B, $1C, $1D, and $1E.
5.3 MULTIDIMENSIONAL ARRAYS (Advanced)
Arrays can have more than one dimension. A multidimensional array has multiple subscripts to select an individual element.
A 2-dimensional array can be visualized as a grid of rows and columns that contain data. For example, a 3-by-5 array named "Data" would look like this:

Notice that the order of the subscripts is row followed by column. The rows increase going down, and the columns increase going to the right. (You can reverse this order and think of a 3-by-5 array as having 3 columns and 5 rows, but this is not the order used by matrices and constant arrays.) This kind of data structure is used for many things, such as board games, matrix calculations, and pixel coordinates.
The 2-dimensional array shown above can be set up and used as follows:
integer Data(3,5), I, J;
begin
for I:= 0, 3-1 do
for J:= 0, 5-1 do
Data(I,J):= 0;
Data(1,3):= 42;
. . .
More dimensions can be easily added. Here is a 3-by-5-by-8 array, this time using a real variable:
5: Arrays 60
real Data(3,5,8);
int I, J, K;
begin
for I:= 0, 3-1 do
for J:= 0, 5-1 do
for K:= 0, 8-1 do
Data(I,J,K):= 0.0;
Data(1,3,7):= 42.0;
. . .
Character arrays can also be multidimensional. For example:
character String(100,80);
This reserves space for 100 strings that are each 80 bytes long. Note that the number of bytes is specified by the last dimension. Single bytes are accessed using a subscript:
String(I,J):= ^A;
ChOut(0, String(99,3));
5.4 COMPLEX DATA STRUCTURES (Advanced)
XPL0 implements arrays in a flexible way that lets you build complex data structures that are not limited to the uniform arrays that have been discussed so far.
Each element in an integer array is a 16-bit value. This value can be an integer or the address of another integer array. When a 2-dimensional array is declared, XPL0 reserves the space and sets up pointers to the first and second dimensions. Here is how a 4-by-3 array works:
integer Frog(4,3);
5: Arrays 61
Like the variable Frog, the elements Frog(0) through Frog(3) contain addresses that point to arrays. These arrays are the second dimension of the original array, Frog.
Normally an element of the array Frog would be accessed like this:
I:= Frog(1,2);
But note that this is equivalent to these two steps:
I:= Frog(1);
I:= I(2);
When XPL0 sets up a multidimensional array, it must be uniform. That is, the rows must all be the same length. But you can set up an array yourself and make it any shape you want. The above 2-dimensional array can be set up as follows:
integer Frog, I;
begin
Frog:= Reserve(4*2);
for I:= 0, 4-1 do Frog(I):= Reserve(3*2);
. . .
The Reserve intrinsic reserves the specified number of bytes and returns the starting address of the reserved memory space. The first statement reserves eight bytes of memory (four integers) and stores the address of this memory space into Frog. Thus the pointer to the first dimension is set. The second statement does the same thing but reserves three integers for the four elements in the first dimension of the array.
You could make the first row of the second dimension larger than the others by adding a statement like this:
Frog(0):= Reserve(100);
Or you could add a third dimension to one of the elements in a row with a statement like this:
Frog(1,1):= Reserve(17);
Using the Reserve intrinsic, you can make linked lists; you can make trees; you can make any shape data structure you want.
5: Arrays 62
Character arrays and arrays containing real values are set up similar to integer arrays. The only difference for a character array is that the number of bytes is reserved in the last dimension rather than the number of integers (bytes * 2). For example:
character Frog(4,3);
is equivalent to:
character Frog;
int I;
begin
Frog:= Reserve(4*2);
for I:= 0, 4-1 do Frog(I):= Reserve(3);
Setting up real arrays uses the intrinsic RlRes instead of Reserve. The argument for RlRes (an integer) reserves enough memory to hold a real number instead of a byte. A 20-element array would use RlRes(20). For example:
real Frog(4,3);
is equivalent to:
real Frog;
int I;
begin
Frog:= RlRes(4);
for I:= 0, 4-1 do Frog(I):= RlRes(3);
Be careful where you put calls to Reserve and RlRes. Note that the Reserve in the "for" loop reserves more memory each time it is called. Normally reserves are made at the beginning of a procedure to set up a data structure used by the procedure.
Reserved space is allocated dynamically (like any local variable or array space). This means that when a procedure that calls Reserve (or RlRes) returns, the allocated space is released so that other routines can use it. If the procedure is called again, the space is allocated again, but usually the former contents are gone.
A common mistake is to reserve a data structure and use it outside the scope of the procedure that reserves it. A data structure should be reserved in the same procedure that declares the name of the structure. If the name is a global variable then the reserve must be done in the main procedure. Do not call an initialization procedure to reserve this space because the space goes away when the initialization procedure returns.
5: Arrays 63
5.5 CONSTANT ARRAYS (Advanced)
Sometimes what we need is a fixed table of values. It is possible to assign values to each element of an array, but a better way is to use a constant array. The general form is:
[CONSTANT, CONSTANT, ... CONSTANT]
For example:
integer Data;
begin
Data:= [2, 22, 222, 2222, 22222];
. . .
This array is similar to a text string. The difference is that the elements are 16-bit integer constants instead of 8-bit ASCII characters. In this example, Data(2) contains the value 222. The assignment (:= ) stores the address of the array into Data. The elements of a constant array can be used just like other array elements.
Constant arrays can contain real numbers as well as integers and have multiple dimensions. However, reals and integers cannot both be used in a single array. Here is a 2-dimensional, 3-by-5 array:
real Data;
begin
Data:= [[70.0, 70.1, 70.2, 70.3, 70.4],
[71.0, 71.1, 71.2, 71.3, 71.4],
[72.0, 72.1, 72.2, 72.3, 72.4]];
. . .
Data(0,0) contains 70.0, and Data(1,4) contains 71.4. Note that the rows are the first dimension.
A constant array can contain other constant arrays and text strings to make complex data structures. For example:
Info:= [70, 71, [+720, ^A, [true, -7221] ], $73, "HELLO"];
5: Arrays 64
This array has a structure that looks like this:

Here, Info(0) contains 70, Info(2,0) contains 720, and Info(2,2,0) contains "true". Also, after we store Info(4) into a character variable, we can use it as a character array and access the individual bytes in the string "HELLO". For example: