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
        end

Note 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) >= Y

We 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 STATEMENT

In 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:

        character C;
        integer   Info;
        begin
        Info:= [70, 71, [+720, ^A, [true, -7221] ], $73, "HELLO"];
        C:= Info(4);
        ChOut(0, C(1));
        . . .

This displays the character "E", and

        Text(0, Info(4));

displays the string "HELLO".

Variables local to a procedure normally do not keep their values from the previous time that the procedure was called. Usually this does not matter, but occasionally the value of a variable is needed the next time the procedure is called. A simple way to code this is to make the variable global. However if the variable is not used by any other procedure, it is best to keep the procedure modular by keeping its variables local. Constant arrays can be used to do this. (Other languages call these "static variables".) Here is an example:

5: Arrays 65

        proc    MakeNumber;
        int     Counter;
        begin
        Number:= Ran(100) + 1;
        Counter:= [0];
        Counter(0):= Counter(0) + 1;
        if Counter(0) >= 3 then
                begin
                Number:= 50;
                Counter(0):= 0;         \Reset the counter
                end;
        end;

This procedure sets Number (a global) to 50 every third time it is called. Counter could be declared and initialized in the main procedure, but this way it is kept local to the only procedure that uses it. This makes the overall program more modular and less confusing.

5.6 EXAMPLE PROGRAM: RECORDS (Advanced)

Because of the flexibility of XPL0 arrays, record structures can be made. A record structure is an array that contains elements of different types. In XPL0, integers and reals cannot both appear in a single array. However, integer values can be used to represent such diverse things as numbers, addresses of strings, and elements of a set.

Here is a program that combines the concept of sets with constant arrays and complex data structures.

        \RECORDS.XPL
        code    ChOut=8, CrLf=9, Text=12;

        int     File, Person;

        def \Person\    Name, SS, Sex, Birth, Dependents, Status;

        def \Name\      Last, First;
        def \Sex\       Male, Female;
        def \Birth\     Month, Day, Year;
        def \Status\    Married, Widowed, Divorced, Single;

        def \Month\     Jan, Feb, Mar, Apr, May, Jun,
                        Jul, Aug, Sep, Oct, Nov, Dec;

5: Arrays 66


        begin   \Main
        File:=[ [ ["WIRTH", "NIKLAUS"],
                   "701-25-9412",
                   Male,
                   [Aug, 30, 1944],
                   4,
                   Married              ],

                [ ["BOREAL", "LENNY"],
                   "521-54-1657",
                   Male,
                   [Oct, 22, 1948],
                   1,
                   Single               ],

                [ ["MUPPET", "PIGGY"],
                   "345-51-7734",
                   Female,
                   [Feb, 25, 1955],
                   1,
                   Single               ] ];

        for Person:= 0, 2 do
            if File(Person,Sex)=Female & File(Person,Status)=Single then
                begin
                Text(0, "MISS ");
                Text(0, File(Person,Name,First));
                ChOut(0 ,^ );
                Text(0, File(Person,Name,Last));
                CrLf(0);
                end;
        end;    \Main

This program scans File for nubile females (and old maids) and produces the following output:

        MISS PIGGY MUPPET

The program begins by defining the elements of the set Person. The elements that describe Person are: Name, social security number (SS), Sex, date of Birth, number of Dependents, and marital Status. Some of these elements are in turn defined as consisting of sub-elements. Name, for instance, consists of a Last name and a First name.

All these elements are mapped into the locations of the constant array called "File". The "def" declaration provides names for these locations (subscripts): Name=0, SS=1, Sex=2, etc. File consists of three major elements, or records, of "data type" Person.

5: Arrays 67

5.7 ADDRESS OPERATOR (Advanced)

The "address" operator gives the address where a variable is stored. It has the form:

        address VARIABLE

When "address" is written in front of a variable name, the value is no longer the contents of the variable, but the address in memory where the variable contents are stored. Because variable space is dynamically allocated, this address is not determined until a program executes. The variable can be an integer, real, or character, and it can be an array with a subscript. The "address" of a real variable is a 16-bit integer.

The "address" operation on a segment array (see: 5.9 Segment Arrays) is not supported because segment arrays do not have simple 16-bit addresses.

"Address" is the reverse operation of subscripting an array name with zero. For example:

        integer Frog, Pointer;
        begin
        Pointer:= address Frog;
        if Pointer(0) = Frog then Text(0, "INVERSE OPERATORS");
        . . .

"INVERSE OPERATORS" is displayed despite the value initially contained in Frog because Pointer(0) and Frog both access the same memory location.

The address operator can be used to solve a problem with multidimensional character arrays. Recall that a character array with a subscript always accesses a single byte. However, sometimes we want to access a 16-bit address. Look at this program:

        char    S;
        begin
        S:= ["one", "two", "three", "four"];
        ChOut(0, S(2,1));
        S(1,1):= ^W;
        Text(0, addr S(1,0));  \Caution: Text(0, S(1)); will not work
        end;

5: Arrays 68

When this runs, it displays:

        htWo

Note that "addr S(1,0)" is used in the Text statement rather than "S(1)". This is because S(1) fetches a single byte rather than the entire word that holds the address of the string "tWo". Another solution would be to copy S into a temporary integer variable, for instance I, then I(1) would also fetch the desired address, but this is not as efficient.

5.8 RETURNING MULTIPLE VALUES (Advanced)

An "address" operator can be used to return more than one value from a function. Values can always be returned by passing them through global variables, but a better way in some cases is to return them using pointers. For example:

        code    ChOut=8, CrLf=9, IntOut=11;
        int     Frog, Pig(11);
        int     Low1, High1, Low2, High2, I;

                proc    MinMax(Array, Size, Min, Max);
                \Returns the minimum and maximum values of the array
                int     Array, Size, Min, Max;
                int     I;
                begin
                Min(0):= Array(0);   Max(0):= Array(0);
                for I:= 1, Size-1 do
                        begin
                        if Array(I) < Min(0) then Min(0):= Array(I);
                        if Array(I) > Max(0) then Max(0):= Array(I);
                        end;
                end;    \MinMax

        begin   \Main
        Frog:= [16, 23, 127, -33, 0];
        MinMax(Frog, 5, addr High1, addr Low1);
        for I:= 0, 10 do Pig(I):= 2*I*I - 16*I + 20;
        MinMax(Pig, 11, addr High2, addr Low2);
        IntOut(0, High1);   ChOut(0, $09);   IntOut(0, Low1);   CrLf(0);
        IntOut(0, High2);   ChOut(0, $09);   IntOut(0, Low2);   CrLf(0);
        end;    \Main

This program displays the following:

        -33     127
        -12     60

The program displays the minimum and maximum values for two arrays. The

5: Arrays 69

calls to MinMax pass the addresses of the High and Low variables, which get values returned to them. The MinMax procedure uses a zero subscript with Min and Max to access the original variables in the calling routine. Compare this to the normal way arguments are passed, where only the value in a variable is passed to a procedure and the procedure does not have access to the variable.

Here is another example of using the address operator to pass values to and from a procedure, this time using reals. This is tricky since the address of a real variable is a 2-byte integer. Three dummy integers (the 0's) are passed along with the address to fill out an 8-byte real (AX). Actually these 0's are not needed here; they are only needed when another argument follows the address argument.

        code    CrLf=9;
        code real RlOut=48;
        real    X, Y, Z;        \(Must be in X-Y-Z order)

                proc    Sqr3(AX);    \Square 3 reals pointed to by AX
                real    AX;          \Address of X
                int     I;
                begin
                for I:= 0, 2 do      \Return the square of the arguments
                        AX(I):= AX(I) * AX(I);
                end;    \Sqr3

        begin   \Main
        X:= 1.0;   Y:= 2.0;   Z:= 3.0;
        Sqr3(addr X,0,0,0);     \Pass address into a real
        RlOut(0, X);   RlOut(0, Y);   RlOut(0, Z);   CrLf(0);
        end;    \Main

This program displays:

        1.00000    4.00000    9.00000

Here the address of the X, Y and Z variables is passed to Sqr3, where a subscript is used to access X, Y and Z directly. The net result is the same as passing and returning three real arguments.

Passing an integer into a real is a little improper, but it works, and it is less awkward than passing values in a temporary array or through globals. Be aware that the number of pad integers (0's) can change if the number of bytes in either a real or an integer change. This code might need to be modified to work with other versions of XPL0, where something other than two bytes are used for integers and eight bytes are used for reals. Finally, don't be tempted to do the following; it doesn't work.

        AX:= Float(addr X);

5: Arrays 70

5.9 SEGMENT ARRAYS (Advanced)

Segment arrays solve the problem of the limited 60K heap space. You can have arrays as large as one megabyte.

The 8088 microprocessor used in the original IBM PC can address one megabyte of memory. Unfortunately, this memory is divided into 64K-byte blocks called segments. Memory is addressed by a combination of two 16-bit values called a "segment" and an "offset". The value in a segment register is multiplied by 16 and added to the offset to give a 20-bit number that can address one megabyte. This method of accessing memory does not work well for high-level languages because each variable must be addressed using both a segment and an offset. This slows every memory access and complicates pointers.

Other languages deal with this problem by using several different memory models. Each model addresses memory differently. For example, the "tiny memory model" is used by programs that run in less than 64K. In this case addressing is simple: The segment register is set once, and only the offset part of the address is used. If a program needs more than 64K, the "large memory model" might be chosen, which uses both a segment and an offset.

The native versions of the XPL0 compilers (those that produce .EXE instead of .COM files) allow code up to one megabyte, and programs that use segment arrays can have up to one megabyte of data. Of course the actual memory space available is typically less than 640K, the size of conventional memory. If your code needs more memory, you can divide it into modules and use the Chain intrinsic to run a piece at a time. If your data needs more memory, you can use EMS (Expanded Memory Specification) BIOS interrupt $67 or XMS (eXtended Memory Specification) interrupt $15.

Segment arrays are like other arrays except that they can reside in any segment of memory. They can be integer, real, or character arrays. Segment arrays are declared using the form:

        segment TYPE NAME(DIMENSION), ... NAME(DIMENSION);

For example:

        seg int  Length, Angle, Depth;
        seg real Rain, Snow;
        seg char BitMask, OrMask, AndMask;

Segment arrays are always 2-dimensional and are normally used with two subscripts. For example:

        Depth(X, Y):= 1530;

5: Arrays 71

The first dimension contains a list of 16-bit "segment addresses". The second dimension contains a 16-bit offset. When an element of a segment array is accessed, the segment address and offset are combined to form a 20-bit address. Since the microprocessor automatically combines segments and offsets, this operation is relatively fast.

ALLOCATING MEMORY (Advanced)

Segment arrays differ from normal arrays in the way they are reserved. Since segment arrays use memory outside the heap, size declarations (DIMENSIONs) and the Reserve intrinsic are not used for the second dimension. Instead, MAlloc, which stands for "memory allocation", is used. This intrinsic calls DOS and requests some memory. MAlloc allocates memory in 16-byte quantities called "paragraphs". For example, here is how to allocate 64000 bytes for a graphic image:

        segment char Pixel(1);
        int     I;
        begin
        Pixel(0):= MAlloc(4000);        \4000 paragraphs = 64000 bytes
        I:= 0;                          \(A "for" loop won't work here)
        repeat  Pixel(0, I):= 0;        \Clear the array
                I:= I + 1;
        until I = 64*1000;
        . . .

This provides a segment array that is 1 by 64000. Note that the first dimension is reserved like a normal integer array, and that integers, not bytes, are reserved. The second dimension uses MAlloc, which returns a segment address that points to the start of a 64000-byte block of memory.

If we needed a 320K array, a similar process could be used:

        segment char Pixel(20);
        int     S, I;
        begin
        for S:= 0, 19 do
                begin
                Pixel(S):= MAlloc(1024);        \1024 * 16 = 16K bytes
                for I:= 0, 16384-1 do Pixel(S, I):= 0;
                end;
        . . .

This provides a 20-by-16K array for a total of 320K bytes. Although you could make this a 16K-by-20 array, it is usually better to reserve the large dimension with MAlloc, since this makes better use of the limited (60K) heap space.

5: Arrays 72

Segment arrays also can be used for integer and real arrays. For example, here is how a 10-by-4K array of reals is built:

        segment real Data(10);
        int     S, I;
        begin
        for S:= 0, 9 do
                begin
                Data(S):= MAlloc((4096*8)/16);
                for I:= 0, 4096-1 do Data(S, I):= 0.0;
                end;
        . . .

In the MAlloc statement, 4096 is multiplied by 8 because there are eight bytes in a real number. Note that the total memory allocation is 4096 * 8 * 10 = 320K. This is a large block of memory, and if you have other programs loaded or a limited amount of memory installed, you might not have enough for this array. If DOS is unable to allocate the requested memory, an "OUT OF MEMORY" run-time error occurs.

The largest offset that can be used for each type of segment array is:

        seg char   $FFFF
        seg int    $7FFF
        seg real   $1FFF

SHORT REALS (Advanced)

To conserve memory, reals can also be stored in segment arrays in a 4-byte short form. Short reals have a range of ±1.2E-38 to ±3.4E+38 with seven decimal digits of precision. Short reals are only used for storage; they are automatically converted to normal 8-byte reals when fetched. As a result, short reals can be used just like normal reals. Segment arrays of short reals are declared as:

        segment short NAME(DIMENSION);
For example:

        segment short Data(10);
        int     S, I;
        begin
        for S:= 0, 9 do
                begin
                Data(S):= MAlloc((4096*4)/16);
                for I:= 0, 4096-1 do Data(S, I):= 0.0;
                end;
        . . .

5: Arrays 73

RELEASING MEMORY (Advanced)

A normal array dimensioned or reserved in a procedure only exists as long as the procedure is active. When the procedure returns, the memory used by the array is automatically released. However, a segment array that uses MAlloc does not release memory when the procedure returns. If the procedure is called a second time, more memory is allocated. The Release intrinsic is used to release memory allocated by MAlloc. It requires an argument that is the segment address returned by MAlloc. For example:

        proc    Demo;
        segment char Pixel(1);
        begin
        Pixel(0):= MAlloc(4000);
        . . .
        Release(Pixel(0));
        end;

It is unnecessary to release memory that is allocated at the global level since the memory is automatically released when the program exits. If you release a block of memory that was not allocated by MAlloc, you get a run-time error. If you write beyond the end of an array, the memory control blocks used by DOS can be corrupted, and you can get a run-time error when you release the memory block. If this occurs, you can find the exact DOS error code by examining the registers in the array returned by GetReg. See DOS call $21, function $49 for details.

DIRECTLY ACCESSING MEMORY (Advanced)

A segment array can be used to directly access anything in the first megabyte of RAM. It can be used, for instance, to directly access the video memory, or the program segment prefix (PSP) set up by DOS, or the system interrupt vectors. Any segment address can be used, not just the one provided by MAlloc. Here is an example of a segment array used to clear the video text screen (for modes 0-3):

        seg int Video(1);
        int     I;
        begin   \Set up for bright white characters on a blue background
        Video(0):= $B800;
        for I:= 0, 2000-1 do Video(0, I):= $1F20;  \attribute:space
        . . .

74

75

6 : I N P U T   A N D   O U T P U T

Everything XPL0 can do is useless without a way to communicate with the outside world. Input and output (I/O) is done through intrinsics, most of which call I/O device drivers in DOS or BIOS.

The fundamental I/O intrinsics are:

        variable:= ChIn(device) Input a byte (or character) from device
        ChOut(device, byte)     Output a byte to the device
        OpenI(device)           Make the device ready for input
        OpenO(device)           Make the device ready for output
        Close(device)           Close the device (flush output buffer)

An input device, such as the keyboard, sends bytes (or characters) that are read in by ChIn. Each time ChIn is called, it returns with the next byte. An output device, such as the monitor, receives bytes (or characters) that are sent by ChOut. ChOut sends a single byte each time it is called. Some devices must be made ready, or "opened", before they can be used. For instance, a disk file has pointers that indicate where to start filling or emptying its buffer, and these pointers must be set to the beginning of the buffer. Bytes sent to an output file pass through an output buffer, and after the last byte has been sent, this buffer is "closed" so that any bytes remaining in it are written to the disk.

There are other intrinsics that use the fundamental capabilities provided by ChIn and ChOut to input and output integers and reals. For example:

        variable:= IntIn(device)        Input an integer
        IntOut(device,expression)       Output an integer
        variable:= RlIn(device)         Input a real
        RlOut(device,expression)        Output a real

IntIn and RlIn are similar to ChIn, but they input a number consisting of one or more digits instead of just a single character. If a series of numbers are typed on the keyboard and separated by spaces then each time IntIn(0) is called, it returns with the value of the next number. Any non-numeric character (or series of characters) are used to separate the numbers, such as space, comma, or a carriage return and line feed. If the numbers come from device 3, we have a numeric data file.

6: Input & Output 76

Integers and reals are normally represented outside a program as strings of ASCII characters. For example, IntOut(0,35) converts the integer 35 from its 16-bit binary form into an ASCII "3" character followed by an ASCII "5". Conversely, when numbers are input, strings of ASCII characters are converted into binary form.

Unlike some other languages, XPL0 has simple output commands. The advantage is that output can be formatted in a straightforward way. For example, when an integer is output, only the digits of the integer (and possibly a minus sign) are sent out. There are no "helpful" spaces or carriage returns sent that might not be wanted in some cases, and that might be confusing to eliminate. In XPL0 if you want formatting, you do it yourself.

Intrinsics used for I/O specify a device number. Device numbers are assigned to physical devices as follows:

        DEVICE NUMBER    OUTPUT DEVICE    INPUT DEVICE
             0           Monitor          Buffered Keyboard
             1           Monitor          Unbuffered Keyboard
             2           Printer             --
             3           Disk File        Disk File
             4           COM Port         COM Port
             5           Printer          Printer Status
             6           Monitor          Unbuffered keyboard
             7           Null             Null
             8           Buffer           Buffer

Most intrinsics used for I/O call DOS and BIOS routines. You can learn more about how these intrinsics work by looking up these routines in a book such as "Advanced MS-DOS Programming" by Ray Duncan. This table shows the interrupt and function calls (in hex) used by each device:

        Device    OpenI   OpenO   ChIn    ChOut   Close
          0       21 0C    --     21 0A   21 02    --
          1       16 00    --     21 08   10 0E    --
          2        --      --      --     21 05    --
          3       21 42   21 42   21 3F   21 40   21 40
          4        --      --     14 02   14 01    --
          5       17 01   17 01   17 02   17 00    --
          6        --     10 02   16 00   10 09*   --
          7        --      --      --      --      --
          8        *       *       *       *       --

        -- Does nothing; simply returns.
        *  Calls routines that are not in DOS or BIOS (see below).

6: Input & Output 77

6.0 DEVICE 0

Output device 0 is the monitor. It displays ASCII characters and handles certain control characters such as tab, form feed (clears screen), bell, carriage return, line feed, and backspace. Text reaching the end of a line automatically wraps to the beginning of the next line. Text written beyond the bottom line scrolls the entire screen up one line. Tab stops are every eighth column. DOS interrupt $21 function $02 is called.

Input device 0 is a buffered keyboard. Characters are echoed on the monitor as they are typed in, but the buffer holds them until the "Enter" (or Carriage Return) key is struck. This enables errors to be corrected, using the "Backspace" key (and other editing keys such as left arrow and F3), before the characters are sent to the program. The buffer holds up to 128 characters including the carriage return ($0D) at the end. Typing an "Esc" deletes all the characters in the buffer (Esc cannot be entered as a character). Typing a control-C aborts the program.

Output and input can be redirected using the DOS commands ">" and "<" on the command line when starting your program. The "<" command is useful because it provides a way to make a type of batch file that works inside a program (.BAT files only perform DOS commands).

OpenI(0) initializes the keyboard, which discards any characters that were previously struck and still residing in its buffers. It is a good idea to do an OpenI(0) before getting a reply to a critical question like: "Format Hard Drive?". OpenO(0) and Close(0) do nothing.

6.1 DEVICE 1

Device 1 is similar to device 0 for output, but it calls BIOS instead of DOS. This gives it the following differences: Form feeds and tabs are not handled (they are displayed as characters instead); output cannot be redirected with ">"; and attributes (such as color and flashing) are not changed to white characters on a black background.

For input, keystrokes are not echoed on the monitor, although a flashing cursor is displayed. There is no buffer, so keystrokes are sent to the program as soon as they are struck. Of course, calling ChIn(1) waits until a key is struck. If a non-ASCII key is struck, such as "F1", a zero is returned. ChIn(1) must be called a second time to get the key's scan code (see: A.4: Keyboard Scan Codes). Typing a control-C aborts the program. [A "^C<CR><LF>" is echoed to the display even with TrapC(true)].

OpenI(1) discards any pending keystrokes.

6: Input & Output 78

6.2 DEVICE 2

Device 2 is the printer (PRN or LPT1). If the printer is busy, ChOut(2) waits until the printer is ready to accept the character (there is no timeout). Output can be redirected to another printer port or to a serial port using the DOS "MODE" command. For example, to select LPT2 type: "MODE LPT2".

Beware, if the printer is out of paper or powered off, an "Abort, Retry, Ignore?" error can occur. If the user types "A" for Abort, it aborts your program immediately, not giving it a chance to clean up such things as open output files (resulting in lost allocation units) or to restore the text video mode. You can prevent this by changing DOS's critical-error-handler vector, interrupt $24, to point to your own routine; but an easier way is to use device 5 (see below).

6.3 DEVICE 3

Device 3 is a disk file. Opening, reading, writing, and closing device 3 is more complicated than the other devices. The usual operations are:

\Read an input file
Hand:= FOpen("C:\DIR\FILENAME.EXT", 0); \Get handle for in file
FSet(Hand, ^I);                         \Set device 3 to handle
OpenI(3);                               \Initialize input buffer
repeat until ChIn(3) = $1A;             \Read some characters
FClose(Hand);                           \Close out this handle


\Write an output file
Hand:= FOpen("C:\DIR\FILENAME.EXT", 1); \Get handle for out file
FSet(Hand, ^o);                         \Set device 3 to handle
OpenO(3);                               \Initialize output buffer
for Ch:= $20, $7E do ChOut(3, Ch);      \Write some characters
ChOut(3, $1A);                          \Write end-of-file Ctrl-Z
Close(3);                               \Flush output buffer
FClose(Hand);                           \Close out this handle

FOpen opens a file and returns a "handle", which is an integer used to refer to the file. FOpen has two arguments: the address of a string giving the name of the file; and the mode, which is either 0 for input or 1 for output. The file name can include the drive and subdirectory path names. If these are omitted, the current drive and subdirectory are used. If you output to device 3 without opening a file, DOS sends this information to the monitor screen, and no error is detected.

FSet assigns the handle to be used by device 3. It also selects a large or small buffer for input or output. The following modes can be selected:

6: Input & Output 79

        ^i = Input using small buffer
        ^I = Input using large buffer
        ^o = Output using small buffer
        ^O = Output using large buffer

The large buffers are faster than the small ones, but there are only two of them, one for input and one for output. Several files can be open simultaneously if the small buffers are used.

OpenI(3) and OpenO(3) reset the file pointers to the beginning of the file. Close(3) flushes any characters that might be remaining in the large output buffer out to the disk file.

FClose calls DOS interrupt $21 function $3E, which flushes all internal buffers associated with the file handle. If the file was created or changed then the time, date, and size are updated in the DOS directory.

END OF FILE

Character files are usually terminated by a control-Z ($1A). This is merely a programming aid since the file-handling intrinsics and DOS pay no attention to control-Z's. This enables them to handle any kind of data files (such as binary files), not just character files.

Some character files are not terminated by a control-Z, so a control-Z is automatically generated if a program attempts to read beyond the end of the file. If the program attempts this a second time, a run-time I/O error occurs.

When reading binary files, the program must know when to stop. It can get the size of the file from DOS (interrupt $21, function $4E), but an easier way is to use the error trapping intrinsics Trap (17) and GetErr (22) and read until an error is detected. If you use this method, note that an extra control-Z is returned at the end, and it is not part of the file.

OPENING FILES FROM THE COMMAND LINE (Advanced)

The command tail in the program segment prefix (PSP) can be used to specify input and output files. The PSP is 256 bytes of memory that is loaded at the beginning of an .EXE file. It contains useful information such as the command tail, which is the rest of the line typed after the program name when starting the program. For example, the following command line starts the program called LOWCASE and opens FILE1 for input and FILE2 for output:

6: Input & Output 80

        LOWCASE FILE1, FILE2

Here is the program:

\LOWCASE.XPL 01-AUG-2005 \This copies a file, shifting all characters to lowercase. code Reserve=3, ChIn=7, ChOut=8, OpenI=13, OpenO=14, Close=15, FSet=24, FOpen=29, FClose=32, GetReg=35, Blit=36; int CpuReg, \Register array from GetReg HandIn, HandOut, \File handles I; \Scratch char CmdTail; \Copy of command tail begin CpuReg:= GetReg; \Get DOS PSP and data segment CmdTail:= Reserve($80); \Get copy of command tail Blit(CpuReg(11), $81, CpuReg(12), CmdTail, $7F); HandIn:= FOpen(CmdTail, 0); \Open first file name for input FSet(HandIn, ^I); OpenI(3); loop for I:= 1, $7F do \Scan to second file name if CmdTail(I) = ^, then quit; HandOut:= FOpen(CmdTail+I+1, 1); \Open second file name for output FSet(HandOut, ^O); OpenO(3); repeat I:= ChIn(3); \Copy and shift to lowercase if I>=^A & I<=^Z then I:= I !$20; ChOut(3, I); until I = \EOF\ $1A; Close(3); FClose(HandIn); FClose(HandOut); end;

6: Input & Output 81

6.4 DEVICE 4

Device 4 is the COM RS-232 port. The baud rate etc. can be set from DOS using the "MODE" command. For example: "MODE COM1:9600,N,8,1" sets COM1 to 9600 baud, no parity, 8 data bits, and 1 stop bit. The high byte of the device number is used to specify ports other than COM1:

        COM1    $0004
        COM2    $0104
        COM3    $0204
        COM4    $0304

The COM ports are configured as data terminal equipment (DTE). They send data out pin 2 and receive data on pin 3. When data is sent, input pins 5 (CTS) and 6 (DSR) must be high, and output pins 4 (RTS) and 20 (DTR) are driven high. When data is received, pin 6 (DSR) must be high, and pin 20 (DTR) is set high. To make this all work, it is often convenient to jumper pin 6 to 20 and pin 4 to 5. The program waits until these signals are correct (timeouts are not used).

6.5 DEVICE 5

Device 5 sends characters to the printer like device 2, but it is much faster because it calls BIOS routines instead of DOS routines. The faster speed is useful, for instance, when sending graphic images to a laser printer. A consequence of calling BIOS routines is that output cannot be redirected using the DOS "MODE" command. Also, there is no "Abort, Retry, Ignore?" error (which might be desirable). Output can be sent to ports other than LPT1 by using the high byte of the device number:

        LPT1    $0005
        LPT2    $0105
        LPT3    $0205

6.6 DEVICE 6

Device 6 is similar to devices 0 and 1, but it has colors and windows. The foreground and background colors used for characters can be defined using the Attrib intrinsic (69), and a window size and location can be defined using the SetWind intrinsic (70). Device 6 is faster than devices 0 and 1 for display modes 0 through 3 and 7 because it writes directly to video memory. Output for the other display modes is done using BIOS interrupt $10, function $09.

6: Input & Output 82

Here is a table showing how the different devices handle control characters on the monitor:

   DEVICE   BEL (07)   BS (08)   TAB (09)   LF (0A)   FF (0C)   CR (0D)
     0         x         x          x         x         x         x
     1         x         x          -         x         -         x
     6         -         -          -         x         -         x

An "x" means that the control function is done, while "-" means that a character is displayed instead. Control characters not shown are all displayed as characters, including DEL ($7F). All of the extended characters ($80-FF) are displayed. ($00, $20 and $FF are displayed as space characters.)

Input from device 6 is similar to device 1 in that keystrokes are sent to the program as soon as they are struck (there is no line buffer). It differs from device 1 in that keystrokes are echoed to the display. Also, typing a control-C (or control-Break) does not abort the program; it is handled like any other keystroke. If a non-ASCII key is struck, such as "F1", a zero is returned (and echoed--looks like a space character), but the scan code is not available. Use ChIn(1) to handle these special keys.

OpenO(6) resets any window set up by SetWind to the size of the full screen, selects an attribute with white characters on a black background, enables normal scrolling and cursor movement, and moves the cursor to the upper-left corner of the screen.

6.7 DEVICE 7

Device 7 is the null device. It is used to discard unwanted output. For example, the compiler sends its output to a disk file, but if it detects an error, it diverts the output to the null device.

6.8 DEVICE 8

Device 8 is a 256-byte circular buffer. It has a variety of uses. For example, the following routine displays the number in X, replacing the decimal point with a comma, which is the format used in some European countries. Note that a control-Z (EOF) is returned when reading beyond the last character written, and it is used to detect the end of the number.

6: Input & Output 83

        OpenO(8);               \Start writing at the beginning of buffer
        RlOut(8, X);            \Write the number to the buffer
        OpenI(8);               \Start reading at the beginning of buffer
        loop    begin
                Ch:= ChIn(8);               \Read character from buffer
                if Ch = ^. then Ch:= ^,;    \Change decimal point
                if Ch = $1A then quit;      \Quit if EOF character
                ChOut(0, Ch);               \Display the character
                end;

OpenO(8) and OpenI(8) reset their respective output and input pointers to the start of the buffer.

84

APPENDIX

A . 0 :    I N T R I N S I C S

Here is a list of the intrinsics in both numeric and alphabetic order:


code     Abs=0,          Ran=1,          Rem=2,          Reserve=3,
         Swap=4,         Extend=5,       Restart=6,      ChIn=7,
         ChOut=8,        CrLf=9,         IntIn=10,       IntOut=11,
         Text=12,        OpenI=13,       OpenO=14,       Close=15,
         Abort=16,       Trap=17,        Free=18,        Rerun=19,
         GetHp=20,       SetHp=21,       GetErr=22,      Cursor=23,
         FSet=24,        SetRun=25,      HexIn=26,       HexOut=27,
         Chain=28,       FOpen=29,       Write=30,       Read=31,
         FClose=32,      ChkKey=33,      SoftInt=34,     GetReg=35,
         Blit=36,        Peek=37,        Poke=38,        Sound=39,
         Clear=40,       Point=41,       Line=42,        Move=43,
         ReadPix=44,     SetVid=45       Fix=50,         POut=64,
         PIn=65,         IntRet=66,      ExtJmp=67,      ExtCal=68,
         Attrib=69,      SetWind=70,     RawText=71,     Hilight=72,
         MAlloc=73,      Release=74,     TrapC=75,       TestC=76,
         Equip=77,       Shrink=78;
 code real
         RlRes=46,       RlIn=47,        RlOut=48,       Float=49,
         RlAbs=51,       Format=52,
         Sqrt=53,        Ln=54,          Exp=55,         Sin=56,
         ATan2=57,       Mod=58,         Log=59,         Cos=60,
         Tan=61,         ASin=62,        ACos=63;


                            ___________________

        Abort=16        Abs=0           ACos=63         ASin=62
        ATan2=57        Attrib=69       Blit=36         Chain=28
        ChIn=7          ChkKey=33       ChOut=8         Clear=40
        Close=15        Cos=60          CrLf=9          Cursor=23
        Equip=77        Exp=55          ExtCal=68       Extend=5
        ExtJmp=67       FClose=32       Fix=50          Float=49
        FOpen=29        Format=52       Free=18         FSet=24
        GetErr=22       GetHp=20        GetReg=35       HexIn=26
        HexOut=27       Hilight=72      IntIn=10        IntOut=11
        IntRet=66       Line=42         Ln=54           Log=59
        MAlloc=73       Mod=58          Move=43         OpenI=13
        OpenO=14        Peek=37         PIn=65          Point=41
        Poke=38         POut=64         Ran=1           RawText=71
        Read=31         ReadPix=44      Release=74      Rem=2
        Rerun=19        Reserve=3       Restart=6       RlAbs=51
        RlIn=47         RlOut=48        RlRes=46        SetHp=21
        SetRun=25       SetVid=45       SetWind=70      Shrink=78
        Sin=56          SoftInt=34      Sound=39        Sqrt=53
        Swap=4          Tan=61          TestC=76        Text=12
        Trap=17         TrapC=75        Write=30

A.0: Intrinsics 85

This list has evolved over several years. The result is that the intrinsics tend to be grouped, with the fundamental ones first. For instance, intrinsics 40 through 45 all pertain to graphics.

In the descriptions that follow, each heading shows the intrinsic's number and an example call. An assignment such as "variable:=" indicates that the intrinsic is a function that returns a value. All of the values and arguments are integers unless "real" is shown.

0: variable:= Abs(value);

This intrinsic returns the absolute value of the argument. If the value is negative, the sign is removed. For example:

        X:= Abs(X);

WARNING: There is one exception:

        Abs(-32768) = -32768, or Abs($8000) = $8000

1: variable:= Ran(value);

This intrinsic returns a random number between zero and the argument minus one. For example:

        X:= Ran(100);           \Range is 0 through 99
        X:= Ran(0);             \Resets seed for repeatable sequence
        X:= Ran(-4);            \Randomizes then returns Ran(4)

The random number generator produces a repeatable sequence of random numbers from a particular seed. Each time a program starts, this seed is "randomized" using a counter that is incremented, about 18 times per second, by the system timer.

2: variable:= Rem(expression);

This intrinsic is used with integer division. It returns the value of the remainder of the division in the argument expression. If a zero argument

A.0: Intrinsics 86

is used, the intrinsic returns the remainder of the last division performed. For example:

        X:= Rem(7/3);           \X gets 1
        Y:= Rem(0);             \Y gets 1
        Z:= Rem(-18/-5);        \Z gets -3

The remainder gets the sign of the dividend (numerator), which is not necessarily the same sign as the quotient.

3: address:= Reserve(value);

This intrinsic sets aside some memory space, which is usually used for an array, and returns the starting address of this space. The argument specifies the number of bytes to be reserved. For example:

        Data:= Reserve(1000);   \1000 bytes or 500 integers

Space reserved in a procedure is released when the procedure returns.

4: variable:= Swap(value);

This intrinsic returns the value obtained by swapping the bytes of the argument. For example:

        X:= Swap($1234);        \X gets $3412

5: variable:= Extend(value);

This intrinsic extends the sign bit of the low byte to a 16-bit integer. It is useful when fetching signed numbers from a character array. For example:

        X:= Extend($FD);        \X gets $FFFD (= -3)
        X:= Extend(3);          \X gets $0003

6: Restart;

This intrinsic immediately terminates execution of the program, sets the Rerun flag to "true", and restarts the program from the beginning. This intrinsic is rarely used. Sometimes when procedure calls are nested many levels down and an error condition is detected that a high-level procedure must handle, it is easier to start the program over than to pass

A.0: Intrinsics 87

the error back through many levels of procedure calls. See intrinsics Rerun (19) and SetRun (25).

7: variable:= ChIn(device);

This intrinsic reads in one byte from the specified input device. The byte is usually an ASCII character (hence: CHaracter IN), but it can be any 8-bit value. After the character is read in, ChIn is ready to read the next character. For example:

        X:= ChIn(0);            \Get byte from keyboard buffer

8: ChOut(device, byte);

This intrinsic sends a byte to the specified output device. For example:

        ChOut(0, ^=);           \Display "=" on the monitor
        ChOut(3, $FF);          \Send $FF to the output file

9: CrLf(device);

This intrinsic sends a carriage return ($0D) and line feed ($0A) to the specified output device. It begins a new line.

10: variable:= IntIn(device);

This intrinsic gets a decimal integer from the specified input device. It converts the integer from ASCII digits into a 16-bit binary value. Integers should be in the range: -32768 through 32767. For example:

        X:= IntIn(0);           \Get an integer from the keyboard buffer

After the integer is read in, IntIn is ready to read the next integer. Non-numeric characters, such as spaces and commas, are skipped. This intrinsic does not return until an integer is read. The integer must be terminated by a non-numeric character.

A.0: Intrinsics 88

11: IntOut(device, value);

This intrinsic sends a decimal integer to the specified output device. It converts the integer from its signed 16-bit binary value into ASCII digits. For example:

        IntOut(0, X);           \Display the value in X on the monitor

12: Text(device, address);

This intrinsic outputs an ASCII text string, beginning at the specified address, to the specified output device. The string is terminated by a byte with its high bit set. For example:

        Text(0, "This is a string");
        String:= "HELLO";
        Text(2, String);        \Print HELLO on the printer

13: OpenI(device);

This intrinsic executes the initialization routine for the specified input device. For example:

        OpenI(0);               \Clear the keyboard buffer

14: OpenO(device);

This intrinsic executes the initialization routine for the specified output device. For example:

        OpenO(3);               \Get ready to write to the disk

15: Close(device);

This intrinsic executes the close routine for the specified output device. For example:

        Close(3);               \Flush output buffer to disk

A.0: Intrinsics 89

16: Abort;

This intrinsic aborts the program. It does the same thing as the "exit" statement except that it cannot return a value. It is included here for compatibility with other versions of XPL0. New code should use "exit" instead.

17: Trap(integer);

This intrinsic determines which run-time errors stop the program and display error messages. The default is to trap all errors, but they can be individually disabled. The argument is an integer, each bit of which turns on or off a run-time error:

    bit 0: Integer division by 0     bit 7: Real underflow out of range
        1: Out of memory space           8: Fix argument out of range
        2: I/O error                     9: Square root error
        3: Invalid opcode               10: Logarithm error
        4: Invalid intrinsic            11: Exponential error
        5: Real division by 0.0         12: --
        6: Real overflow                13: ATan2(0.0, 0.0)

For example, sometimes you do not care if you divide by zero and you certainly do not want your program to stop if you do. Trap($FFFE) will disable this error trap, and the divide will give the best answer it can (32767).

WARNING: These bit assignments are different than those used by the non-PC versions of XPL0, such as on the 6502 and 68000.

18: variable:= Free;

This intrinsic returns the number of bytes of available heap space. Since variables and arrays are dynamically allocated space, the number of bytes returned varies depending on where and when Free is called. The largest possible Reserve is usually this value minus a few hundred bytes of working space. For example:

        Buffer:= Reserve(Free-300);     \A big buffer

WARNING: If the free space is greater than 32767 ($7FFF), the number returned will appear to be negative.

A.0: Intrinsics 90

19: boolean:= Rerun;

This intrinsic returns the value of the Rerun flag, either true or false. The Rerun flag is false when a program starts. It is set "true" by the intrinsic Restart (6), and it can be set "true" or "false" by the intrinsic SetRun (25). These intrinsics are rarely used.

20: address:= GetHp;

This intrinsic returns the current value of the heap pointer. GetHp does the same thing as Reserve(0). For example:

        X:= GetHp;

This intrinsic is rarely used.

21: SetHp(address);

This intrinsic sets the heap pointer to the specified memory address. This intrinsic is very rarely used.

22: integer:= GetErr;

This intrinsic returns the number of the most recently detected, untrapped error. If this number is 0 then no error was detected. After returning the error number, GetErr is internally reset to 0, ready for the next call. See the Trap intrinsic (17). For example:

        if GetErr # 0 then Text(0, "TROUBLE!");

When a program terminates, a run-time error message appears if the internal error number is not 0.

23: Cursor(X, Y);

This intrinsic sets the position of the cursor on the monitor screen. The next character output appears at this location. X is horizontal, 0 through 79 (left to right--other screen dimensions are also supported), and Y is vertical, 0 through 24 (top to bottom). For example:

        Cursor(3, 4);

A.0: Intrinsics 91

WARNING: After calling this intrinsic, tabs can stop in the wrong position.

24: FSet(handle, mode);

This intrinsic assigns the file handle that is to be used by device 3. "Handle" is normally gotten from FOpen (29). "Mode" is one of the following:

        ^i = Input using small buffer
        ^I = Input using large buffer
        ^o = Output using small buffer
        ^O = Output using large buffer

There is only one large buffer for input and one large buffer for output, but several small buffers can be open at the same time. The large buffers hold 1024 bytes and are much faster than the small buffers, which hold a single byte each.

25: SetRun(boolean);

This intrinsic sets the Rerun flag directly. This intrinsic is rarely used. See intrinsics Restart (6) and Rerun (19).

26: variable:= HexIn(device);

This intrinsic gets a hex integer from the specified input device. Hex values should be in the range: $0000 through $FFFF. For example:

        X:= HexIn(0);           \Get hex value from keyboard buffer

This intrinsic skips any non-hex characters until a hex character is found, thus the dollar sign is optional. Hex numbers are unsigned, and any minus sign is ignored. Hex digits are read until a non-hex character is found, thus numbers must be terminated by a non-hex character. If more than four hex digits are read, only the last four are used.

27: HexOut(device, value);

This intrinsic outputs a hex integer to the specified output device. For example:

        HexOut(0, $123);        \Displays: "0123" on the monitor

A.0: Intrinsics 92

28: Chain("path\filename.ext");

This intrinsic executes another program as a subroutine. The called program is specified by a string containing the file and path name. No wild cards (* or ?) are allowed, and the extension (.EXE or .COM) must be given. The string must be less than 80 characters long, and must be terminated by one of four methods (see 29: FOpen). For example:

        Chain("C:\WORK\XDEMO.EXE");

When an XPL0 program begins, it returns unused memory to DOS. This unused memory can be used by a subprogram called by Chain.

If there is not enough memory or if the execution fails, this intrinsic returns with the carry flag set and error information in the CPU register array (see 35: GetReg). If the memory allocation fails, the DOS function is $4A, and if the execution fails, the DOS function is $4B.

        CPU Array          No Error   Memory Error  Chain Error

         7 -  Carry flag    False       True          True
        14 - DOS function     -         $4A           $4B

If there is an error, the DOS return code (GetReg item 15) gives detailed information:

                15 - DOS Return Code
              
        Memory Error ($4A) $7 = Memory control blocks destroyed
                           $8 = Insufficient memory
                           $9 = Incorrect segment in ES

        Chain Error ($4B)  $1 = Invalid function
                           $2 = File not found
                           $5 = Access denied
                           $8 = Insufficient memory
                           $A = Environment invalid
                           $B = Format invalid

Large blocks of data can be passed to and from the chained program through the environment block. Every program has an environment block that contains system information. Normally it contains text strings that specify things like the default path and batch file information. For example, if you type "PATH" at a DOS prompt, the information displayed comes from the environment block. If you do not care about this information, or if you save and restore it, you can use the environment block to transfer other information.

The segment address of the environment block is specified at location $2C in the program segment prefix (PSP). The Chain intrinsic automatically

A.0: Intrinsics 93

passes the environment block address of the main program to the sub-program. By overwriting $2C with a segment address of your choosing before the Chain call, you can pass a large block of data to the sub-program. Since only the segment address is passed, the data must be aligned to a segment boundary. For example:

        \MAIN PROGRAM PASSING DATA TO SUBPROGRAM
        CpuReg:= GetReg;                \Get info array
        PspSeg:= CpuReg(11);            \Get PSP segment

        Data:= Reserve(1024+16);        \Reserve data block
        Data:= ((Data/16)+1)*16;        \Align block with segment

        ThisSeg:= CpuReg(12);           \Get current segment
        DSeg:= ThisSeg+(Data/16);       \Calculate data segment
        Poke(PspSeg, $2C, DSeg);        \Set environment block
        Poke(PspSeg, $2D, Swap(DSeg));

        Chain("FROG.EXE");

The subprogram can read the segment address of the data block and, using the Blit intrinsic (36), copy the data into an array of its own. Up to 32K of data can be transferred this way.

29: handle:= FOpen("drive:path\filename.ext", mode);

This intrinsic opens a file and returns its handle. The file is specified by a string containing the file name and an optional drive and path name. No wild cards (* or ?) are allowed. Any leading or trailing spaces are ignored. The string must be less than 80 characters long and must be terminated by one of four methods:

        - Bit 7 set on the last character
        - A zero byte after the last character
        - A carriage return after the last character
        - A comma after the last character

"Mode" is 0 for read and 1 for write.

FOpen is typically used with other intrinsics as follows:

        Hand:= FOpen("C:\WORK\FILENAME.EXT", 1);
        FSet(Hand, ^O);
        OpenO(3);

A.0: Intrinsics 94

When a file is opened for writing, if it already exists, its contents are discarded; if it does not exist, a new one is created. If you send characters to device 3 without opening a file with FOpen, DOS sends them to the monitor screen and no error is detected. DOS interrupt $21 function $3C is called for writing output files, and function $3D is called for reading input files. (See: 6.3 Device 3).

30: Write(drive, sector, buffer, size);

This intrinsic writes data from memory to disk. "Drive" is 0=A:, 1=B: 2=C:, and so forth. "Sector" is the starting logical sector number. Logical sectors start at 0 (the boot sector). "Buffer" is the address of the data to write. "Size" is the number of sectors to write. There are 512 bytes per logical sector (even on a hard drive in this situation). DOS interrupt $26 is called.

WARNING: Writing to a hard drive (C:) is a very dangerous operation (in fact Windows XP will not let you do it).

31: Read(drive, sector, buffer, size);

This intrinsic is the counterpart to the Write intrinsic described above.

32: FClose(handle);

This intrinsic closes a file handle. All internal buffers associated with the file are flushed, and the handle is released for possible reuse. If the file was modified, the time, date, and size are updated in the directory. DOS interrupt $21 function $3E is called.

When a handle is closed, it ceases to exist. If additional operations need to be made to the file a new handle must be obtained using FOpen (29).

WARNING: If you close handle 0, which DOS uses for the console, your program cannot input from device 0, which is the keyboard.

33: boolean:= ChkKey;

This intrinsic returns a "true" if a key was struck on the keyboard. Interrupt $16 function $01 is called.

A.0: Intrinsics 95

34: SoftInt(interrupt);

This intrinsic is used to call a DOS or BIOS "interrupt" routine. Values are passed to and from interrupt routines using the hardware registers of the processor. These values are accessed using the GetReg intrinsic (35). The following example uses SoftInt and GetReg to get the time of day:

        code    Swap=4, SoftInt=34, GetReg=35;
        int     Hour, Minute, Second;
        int     CpuReg;
        begin                           \Get system time
        CpuReg:= GetReg;                \Copy of hardware registers
        CpuReg(0):= $2C00;              \Set AH to $2C
        SoftInt($21);                   \Call DOS function
        Hour:= Swap(CpuReg(2)) & $FF;   \Read returned values
        Minute:= CpuReg(2) & $FF;
        Second:= Swap(CpuReg(3)) & $FF;
        end;

DOS and BIOS provide many useful routines. These are documented in "Advanced MS-DOS Programming" by Ray Duncan.

35: address:= GetReg;

This intrinsic provides access to the hardware registers in the processor. It returns the address of an integer array that contains a copy of these registers. Before SoftInt calls an interrupt routine, it copies the contents of this array into the hardware registers. After the interrupt routine returns, SoftInt copies the registers back into the array before returning to your program.

The array also contains additional information. The state of the carry flag is returned to aid in error checking. DOS error codes are also returned, which enables more precise error messages than those given by the run-time error traps (22: GetErr).

Some useful values are in the array when an XPL0 program is started by DOS. The array is arranged as follows:

A.0: Intrinsics 96


         INDEX   CONTENTS         VALUE SET BY DOS 
      
           0    AX register
           1    BX register
           2    CX register
           3    DX register
           4    DI register
           5    SI register
           6    BP register

           7    Carry flag ("true" or "false")

           8    CS register     Points to program's code segment
           9    DS register     Points to program's PSP
          10    SS register
          11    ES register     Points to program's PSP

          12    Data segment    Points to program's data segment
          13    DOS interrupt   ($21, $25, $26)
          14    DOS function
          15    DOS return code

          16    Flags register

Flags Register Bits:


        15 14 13 12 |11 10  9  8 | 7  6  5  4 | 3  2  1  0
         0 NT IOPL  | O  D  I  T | S  Z  0  A | 0  P  1  C

              NesTed                    Sign
              I/O Privilege Level       Zero
              Overflow                  Auxiliary carry
              Direction                 Parity
              Interrupt enable          Carry
              Trap

This array is also filled by the Chain intrinsic (28). This enables values to be passed between programs just like values are passed with interrupt routines.

The run-time code copies the array into the processor's registers when starting intrinsics ExtJmp and ExtCal, and it copies the registers back into the array when finishing the IntRet intrinsic.

36: Blit(source seg, source off, destination seg, destination off, size);

This intrinsic quickly copies a block of memory from one location to another. Overlapping blocks of memory can be copied without damaging the data, and up to 65535 bytes can be copied at a time. The source and destination locations are given by segment and offset addresses. "Size" is the number of bytes to copy.

A.0: Intrinsics 97

37: variable:= Peek(segment, offset);

This intrinsic fetches a byte using segment and offset addressing. It can fetch any location in the first megabyte of memory.

38: Poke(segment, offset, value);

This is the counterpart to the Peek intrinsic. It can be used to write a byte to any location in the first megabyte of memory. For example, this writes directly to video memory and displays an "A" (assuming the video mode is 2 or 3):

        Poke($B800, 160, ^A);   \Display "A" at line 1 column 0

39: Sound(volume, duration, frequency);

This intrinsic sounds the speaker. "Volume" is zero for no sound and non-zero for full sound. "Duration" is seconds times 18.2. "Frequency" is 1190000 divided by the actual frequency. This intrinsic can also be used as a time delay by setting "volume" to zero. For example:

        Sound(1, 18, 4542);     \One second of Middle C (262 Hz)

40: Clear;

This intrinsic clears the screen when in graphics (or text) mode.

41: Point(X, Y, color);

When in a graphics mode, this intrinsic plots a point (pixel) located at the X and Y coordinates. The upper-left corner of the display is coordinate 0,0. X increases to the right, and Y increases downward. The ranges of X, Y, and "color" vary with the video mode (see 45: SetVid). If the mode has 16 colors, they are the foreground colors shown for the Attrib intrinsic (69). If bit seven of "color" is set, the low four bits of "color" are exclusive-ORed with the pixel on the screen. If the mode has 256 colors, there is no exclusive-or. The colors can be adjusted using several subfunctions of BIOS interrupt $10 function $10.

A.0: Intrinsics 98

42: Line(X, Y, color);

This intrinsic draws a straight line from the last point plotted (or moved to with the Move intrinsic) to the specified X and Y coordinates. "Color" is the same as for Point (41), but bits 8 through 15 are used to specify various patterns of dotted and dashed lines. Pixels are not drawn at locations corresponding to set bits. For example, to draw a line with widely space dots, "color" could be $7F01. Horizontal lines are drawn much faster than other lines. This can be exploited when filling areas. For example:

      Point(0, 0, 1);                 \Set the start of the line
      Line(160, 100, 1);              \Draw a solid blue line
      Line(319, 199, $AA04);          \Continue with a dotted red line

43: Move(X, Y);

This intrinsic moves to the beginning of a line specified by the X and Y coordinates.

44: color:= ReadPix(X, Y);

This intrinsic returns the color of the pixel (point) at the specified coordinates.

45: SetVid(mode);

This intrinsic sets the video display mode. It also clears the screen (unless bit 7 of "mode" is set) and reinitializes the colors and fonts to their defaults.

Here is an example program that plots a sine wave:

      code ChIn=7, Point=41, Line=42, Move=43, SetVid=45, Fix=50;
      code real Float=49, Sin=56;
      int X;
      begin
      SetVid($12);                            \640x480x16 colors (VGA)
      Move(320, 0);   Line(320, 479, 1);      \Draw axes in blue
      Move(0, 240);   Line(639, 240, 1);
      for X:= 0, 639 do                       \Plot in light red
              Point(X, 240 - Fix(180.0 *Sin(Float(X-320) /60.0)), $C);
      X:= ChIn(1);                            \Wait for keystroke
      SetVid(3);                              \Restore text mode
      end;

A.0: Intrinsics 99


        MODE RESOLUTION COLORS  TYPE    ADDRESS  MDA CGA EGA MCGA VGA
        $00    40x25      16    text    $B8000        x   x    x   x
               color burst off
        $01    40x25      16    text    $B8000        x   x    x   x
        $02    80x25      16    text    $B8000        x   x    x   x
               color burst off
        $03    80x25      16    text    $B8000        x   x    x   x

        $04    320x200     4    graph   $B8000        x   x    x   x
        $05    320x200     4    graph   $B8000        x   x    x   x
               color burst off
        $06    640x200     2    graph   $B8000        x   x    x   x
        $07    80x25       2    text    $B0000    x       x        x

        $08    160x200    16    graph           PC JR
        $09    320x200    16    graph           PC JR
        $0A    640x200     4    graph           PC JR
        $0B                                     EGA BIOS
        $0C                                     EGA BIOS

        $0D    320x200    16    graph   $A0000            x        x
        $0E    640x200    16    graph   $A0000            x        x
        $0F    640x350     2    graph   $A0000           64K       x
        $10    640x350    16    graph   $A0000          128K       x

        $11    640x480     2    graph   $A0000                 x   x
        $12    640x480    16    graph   $A0000                     x
        $13    320x200   256    graph   $A0000                 x   x

        $6A    800x600    16    graph   $A0000  VESA

Characters can be displayed in graphic as well as text modes.

46: real:= RlRes(integer);

This intrinsic reserves space for real arrays. RlRes(3) reserves enough memory to hold three real numbers. For example:

        Array:= RlRes(3);      \Reserve elements 0 through 2

47: real:= RlIn(device);

This intrinsic gets a real number from the specified input device. It converts the number from ASCII digits into binary form. After a number is read in, RlIn is ready to read the next number. Non-numeric characters, such as spaces and commas, are skipped. This intrinsic does not return

A.0: Intrinsics 100

until a number is read. The number must be terminated by a non-numeric character. For example:

        Array(2):= RlIn(0);     \Get real number from buffered keyboard

48: RlOut(device, real);

This intrinsic outputs a real number to the specified device. It converts the real number from its binary form into ASCII digits. For example:

        RlOut(2, 3600.0*24.0*365.25);   \Print seconds in a year

The number of decimal places shown (etc.) can be specified by the Format intrinsic (52).

49: real:= Float(integer);

This intrinsic converts an integer into its equivalent real value. (See: 2.1 Mixed Mode.) For example:

        RlOut(0, (Float(35));   \Display "35.00000"

50: integer:= Fix(real);

This intrinsic converts a real to its nearest integer value. (See: 2.1 Mixed Mode.) For example:

        IntOut(0, Fix(13.002)); \Display "13"

Converting a value outside the range -65535.0 through 65535.0 causes a run-time error.

51: real:= RlAbs(real);

This intrinsic takes the absolute value of a real. For example:

        X:= RlAbs(X);           \Remove the minus sign from X

52: Format(integer, integer);

This intrinsic specifies the format of reals that RlOut (48) outputs. The

A.0: Intrinsics 101

first integer is the number of characters before the decimal point, and the second integer specifies the number of characters after the decimal point. If the first integer is 0 then scientific notation is used instead. If the first integer is -1 (or any value less than zero) then engineering notation is used. For example:

        Format(5,2)  gives 12345.67
                               3.14
                           -1000000.00
        Format(0,1)  gives  1.2E+004
        Format(-1,4) gives   12.3457E+003

One purpose of Format is to align decimal points. However, if the number is too large to fit in the indicated space, all of the digits are still sent out, and the decimal point is not aligned. If the format is not specified then RlOut uses the default: Format(5,5). If the number of characters specified after the decimal point is 0, there is no decimal point.

53: real:= Sqrt(real);

This intrinsic returns the square root of the argument. The argument must be >= 0.0, otherwise a run-time error occurs. For example:

        Root2:= Sqrt(2.0);        \1.414213562

54: real:= Ln(real);

This intrinsic returns the natural logarithm (base e) of the argument. The argument must be > 0.0, otherwise a run-time error occurs.

55: real:= Exp(real);

This intrinsic computes the exponential function (e**X). This is the inverse operation of Ln.

56: real:= Sin(real);

This intrinsic computes the sine function. All of the trig functions use angles measured in radians. To convert from radians to degrees, multiply by 57.2957795 degrees/radian (= 180 deg/pi). To convert degrees to radians, divide by 57.2957795. For example:

        X:= Sin(30.0/57.2957795); \Sine of 30 degrees (=0.5)

A.0: Intrinsics 102

57: real:= ATan2(real Y, real X);

This intrinsic computes the arc-tangent of Y divided by X in radians. If the computed angle is in the range ±pi/2 (±90 degrees) then X can be 1.0. However, if an angle over the entire range of a circle (±pi or ±180°) is to be computed then the signed values of the Y and X coordinates are used. This converts rectangular coordinates to polar coordinates. For example:

      Angle:= ATan2(0.5, 1.0);        \Angle:= ATan(0.5) (= 26.56505°)
      Angle:= ATan2(13.0, -13.0);     \Angle:= 3/4 pi (= 135°)
      Angle:= ATan2(-5.0, -5.0);      \Angle:= -3/4 pi (= -135°)

58: real:= Mod(real, real);

This intrinsic computes the modulo function. This is the real counterpart to the Rem intrinsic (2). Mod(A, B) is defined as A modulo B, which is defined as A - Int(A/B) * B. Where Int(A/B) extracts the largest integer <= Abs(A/B) and attaches the sign of A/B (i.e. it truncates toward zero). For example:

        X:= Mod(10.2, 3.0);     \X:= 1.2
        X:= Mod(-10.2, 3.0);    \X:= -1.2
        X:= Mod(123.456, 1.0);  \Get the fractional part (0.456)

59: real:= Log(real);

This intrinsic computes the common-logarithm function (base 10). The argument must be > 0.0, otherwise a run-time error occurs.

60: real:= Cos(real);

This intrinsic computes the cosine function. The argument is the angle in radians.

61: real:= Tan(real);

This intrinsic computes the tangent function. The argument is the angle in radians.

A.0: Intrinsics 103

62: real:= ASin(real);

This intrinsic computes the arc-sine function in radians. It loses accuracy when arguments are very close to ±1.0 because of an internal subtraction from 1.0.

63: real:= ACos(real);

This intrinsic computes the arc-cosine function in radians. It loses accuracy when arguments are very close to ±1.0 because of an internal subtraction from 1.0.

64: POut(value, port, size);

This intrinsic writes to an output port. "Port" specifies the port address. "Value" is the value written. If "size" is 0 then eight bits are written; if "size" is not zero then 16 bits are written.

65: variable:= PIn(port, size);

This intrinsic reads an input port. "Port" specifies the port address. If "size" is 0 then eight bits are read; if "size" is not zero then 16 bits are read. For example:

        POut(PIn($61,0)&$FC, $61, 0);   \Disable speaker

66: IntRet;

This intrinsic executes an interrupt return (IRET) opcode. This enables an XPL0 program to be used as an interrupt service routine. Just before returning, the processor's registers are restored to the values they had when the XPL0 program was started (unless SoftInt was executed).

67: ExtJmp(segment, offset);

This intrinsic executes a far jump to an external routine. This is useful when XPL0 code is inserted in front of an existing interrupt service routine and control must be passed on to this routine. Just before jumping, the processor's registers are restored to the values they had when the XPL0 program was started (unless SoftInt was executed).

A.0: Intrinsics 104

68: ExtCal(segment, offset);

This intrinsic executes a far call to an external routine. The GetReg register array is loaded into the processor's registers just before this call is executed, and the processor's registers are saved into the GetReg array immediately after the call returns. This provides a way to pass arguments to and from the external routine.

69: Attrib(colors);

This intrinsic specifies the colors (attribute) used when sending characters to device 6. The high nibble of the argument sets the background color, and the low nibble sets the foreground color. For example, Attrib($17) displays white characters on a blue background.

           BACKGROUND              FOREGROUND
           $00: Black              $00: Black
           $10: Blue               $01: Blue
           $20: Green              $02: Green
           $30: Cyan               $03: Cyan
           $40: Red                $04: Red
           $50: Magenta            $05: Magenta
           $60: Brown              $06: Brown
           $70: White              $07: White
           $80: Flashing Black     $08: Gray
           $90: Flashing Blue      $09: Light Blue
           $A0: Flashing Green     $0A: Light Green
           $B0: Flashing Cyan      $0B: Light Cyan
           $C0: Flashing Red       $0C: Light Red
           $D0: Flashing Magenta   $0D: Light Magenta
           $E0: Flashing Brown     $0E: Yellow
           $F0: Flashing White     $0F: Bright White

On a monochrome display (mode 7) the attribute "colors" are as follows:

               VGA                            HERCULES
    $00:   No display (black)                 Same
    $01:   Underlined                         Same
    $07:   Normal                             Same
    $09:   Underlined intense                 Same
    $0F:   Intense                            Same
    $70:   Reverse (black on white)           Same
    $77:   No display (white)                 Normal
    $7F:   Intense on white                   Intense on black

    $81:   Flashing underlined                Same
    $87:   Flashing                           Same
    $89:   Flashing underlined intense        Same
    $8F:   Flashing intense                   Same
    $F0:   Flashing black on white            Same
    $FF:   Flashing intense on white          Flashing intense on black

A.0: Intrinsics 105

The underline is continuous on a Hercules display, is dashed on a VGA display, and is nonexistent on a Compaq portable.

Attributes are handled a little differently when the display is in a graphics mode rather than a text mode. The graphics hardware cannot flash characters so bit 7 from the Attrib intrinsic is used to specify intense colors for the background instead. This gives 16 background colors identical to the 16 foreground colors. Characters with a black background (Attrib($0X)) are written almost twice as fast as characters with colored backgrounds. If the foreground color is the same as the background color and the color is not black (0) then the character is written by XORing it with the screen. This makes the background transparent. That is, characters can be drawn on top of a pattern without a surrounding background box. If the character is XORed a second time, it is erased, and the original background pattern is restored.

Graphics mode $13 is different because it has 256 colors. The high byte of the argument (Attrib($XX00)) sets the background color, and the low byte sets the foreground color. There is no XOR.

OpenO(6) sets the attribute to white on black ($07) (and also sets the cursor to the upper-left corner of the display).

70: SetWind(X0, Y0, X1, Y1, mode, fill);

This intrinsic specifies the window used when sending characters out to device 6. X0, Y0 sets the upper-left corner of the window, and X1, Y1 sets the lower-right corner.

"Mode" specifies how the window behaves. The high byte controls the visible cursor:

    = 0: Visible cursor moves normally.

    # 0: Visible cursor does not move (but the invisible character
         insertion point moves normally).

The low byte of "mode" controls the text:

    0 = Scroll: Text scrolls up when the cursor reaches the bottom of
    the window, and an automatic CR and LF is done at the right edge.
    (Writing to the bottom-rightmost character cell scrolls.)

    1 = Wrap: Text does an automatic CR and LF at the right edge, but
    it wraps to the top of the window when it exceeds the bottom edge.

A.0: Intrinsics 106

    2 = Clip: Text is clipped beyond the right edge and beyond the
    bottom of the window.

The visible cursor moves when sending a character out to device 0, even if visible cursor movement is turned off for device 6. Visible cursor movement can only be turned off for video modes 0, 1, 2, 3, and 7. When the visible cursor is off, text can be output about twice as fast.

If the "fill" flag is "true", the window is erased and filled with the background color specified by Attrib (69). If the "fill" flag is "false", the window is set up without changing any characters on the screen. When in a graphic video mode, the background is not filled with a color as you would expect, but instead it is filled with a pattern.

Opening device 6 for output resets the window to the full screen size and enables normal scrolling and cursor movement.

WARNING: The Cursor intrinsic (23) is not affected by the position of a window; it always uses the upper-left corner of the entire screen as position 0,0.

71: RawText(device, address);

This intrinsic is the same as the Text intrinsic except that strings are terminated by a space character with its most significant bit set ($A0). This enables the extended ASCII codes to be used. The terminating space is not sent out. For example:


72: Hilight(X0, Y0, X1, Y1, attribute);

This intrinsic changes the colors in a specified area on the text screen without changing the characters. The area is defined by the corners of a rectangle. X0, Y0 is the upper-left corner, and X1, Y1 is the lower-right corner. "Attribute" defines the background and foreground colors (see 69: Attrib).

Hilight is typically used to highlight selected menu items, but it can also be used to make such things as drop shadows for windows. It is intended for text modes only; it does not work for graphic modes. (Use XOR lines for graphic modes--other than mode $13.)

A.0: Intrinsics 107

If the foreground color is the same as the background color, characters are invisible (there is no XOR).

73: segment address:= MAlloc(paragraphs);

This intrinsic returns the starting segment address of a block of memory. (A segment address is a physical address divided by 16.) Memory is allocated in 16-byte quantities called "paragraphs". For example:

        Seg:= MAlloc(4000);     \Allocate 64000 bytes

Unlike Reserve, MAlloc does not automatically release memory when returning from a procedure. If MAlloc is called at the beginning of a procedure, and the procedure is repeatedly called, more memory is allocated each time. Allocated memory is automatically released when the program terminates. DOS interrupt $21 function $48 is called.

74: Release(segment address);

This intrinsic deallocates a block of memory that was allocated by MAlloc. The segment address of the block is passed to indicate which block to deallocate. For example, this would deallocate the 64000 bytes allocated above:

        Release(Seg);

75: TrapC(boolean);

This intrinsic turns control-C trapping on and off. "True" is passed to turn on control-C trapping, which prevents the control-C and control-Break keys from aborting a program. Control-C trapping is normally off. Any change to the way control-C and control-Break are handled is restored when a program terminates.

76: boolean:= TestC;

When control-C trapping is on, TestC is used to determine if the control-C or control-Break keys were struck. If either one was then TestC returns "true".

Each time the control-C or control-Break key is struck, a status flag is set. When TestC is called, it returns the state of this status flag and then resets it to "false".

A.0: Intrinsics 108

TestC can give unexpected results. A control-C is not checked for until some I/O is done through DOS or BIOS. Also, the keyboard hardware is buffered and a control-C is not detected until it is actually read in. Control-Break, on the other hand, is detected immediately.

77: address:= Equip;

This intrinsic returns the address of an array that describes the equipment that the program is running on. The array contains the following information:

0: Processor type
1: Math coprocessor type
2: Video configuration
3: Processor speed
4: Run-time code version
5: Run-time code type

0: Processor Type. The type of processor is indicated by one of the following integers: 86, 286, 386, 486, or 586.

1: Math Coprocessor Type. The type of math coprocessor is indicated by one of the following integers: 0, 87, or 387. The 80287 is indistinguishable from the 8087, so 87 refers to both the 8087 and 80287. Zero means there is no math coprocessor. A DX386 or Pentium (586) effectively has a 387 math coprocessor built into it.

2: Video Configuration. This gives the type of video adapter and monitor. The possible configurations are:

        VIDEO ADAPTER (low byte)
        $01: MDA  Monochrome Display Adapter
        $02: CGA  Color Graphics Adapter
        $04: EGA  Enhanced Graphics Adapter
        $08: VGA  Video Graphics Array
        $10: MCGA Multi-Color Graphics Array
        $20: HGC  Hercules Graphics Card

        MONITOR (high byte)
        $01: Monochrome
        $02: Color (or enhanced monochrome emulating color)
        $04: Enhanced color
        $08: Analog monochrome only
        $10: Analog color only
        $18: Analog monochrome and color

3: Processor Speed. This gives an indication of the processor's speed. The original 4.77 MHz IBM PC-XT returns a value of 5. Interrupts are

A.0: Intrinsics 109

left enabled so the value reflects the actual running environment. Interrupt-intensive environments, such as Microsoft Windows, give lower speeds. The speed test takes 55 to 110 milliseconds to run no matter how fast the processor.

4: Run-Time Code Version. This gives the run-time code (NATIVE or I2L) version number. The value is the version number times 10. For example, if the current version is 2.4.7, the value returned is 24.

5: Run-Time Code Type. This gives the type of run-time code used.

        ^N: NATIVE
        ^7: NATIVE7
        ^S: NAT
        ^I: I2L

For example, this displays the processor type:

        int     EqList;
        begin
        EqList:= Equip;
        IntOut(0, EqList(0));
        . . .

78: Shrink(value);

This intrinsic returns unused heap space to DOS. The argument is the number of bytes to keep (above the heap pointer). A minimum of a couple hundred bytes are usually kept for local variables and small, temporary arrays.

When an XPL0 program starts, it is given about 60K of memory space for its heap, which is where variables and arrays are stored. Usually this is more than what is needed. In situations where memory is scarce, any excess can be given back to DOS and used by other programs that are also loaded in memory at the same time. This can occur, for instance, when using the Chain intrinsic (28).

Shrink is normally called from the main procedure after reserving global arrays. This sets a new limit for the heap space. You will get a run-time error if you attempt to reserve past this limit.

110

A . 1 :   C O M P I L E   E R R O R S

XPL0 has two different types of error messages. The first type, called compile errors, occur when a program is being compiled; and the second type, called run-time errors, occur when the program runs.

If the compiler detects an error, it stops and asks if it should attempt to continue. A "Y" (or just hitting the Enter key) continues; an "N" aborts the compile. The ASM output file is discarded if any error is detected.

For example, if we try to compile:

        Frog:= 2 + 3.5 + Frog;

the compiler stops and displays:

        Frog:= 2 + 3.5 + F
        ***** ERROR NO. 46 *****
        MIXED MODE
        ATTEMPT TO CONTINUE (Y/N)?

Compile error messages can sometimes be misleading because the actual error might have occurred prior to the point that the compiler flags as the error. The reason these two points do not always coincide is because the compiler finds the code at the actual error to be syntactically correct, but it interprets it in a way other than what was intended. This alternate interpretation can go for several lines before an error is finally flagged. An extreme example of this is failing to terminate a string with a close quote. In this case the compiler simply interprets the following code as being part of the string, and an error is not detected until either a quote mark or the end-of-file is encountered. Particularly misleading error messages can result from unpaired begin-ends.

All of the compile error messages are listed below together with some helpful comments and suggestions.

1: Too Many Variables. Procedures with too many variables should be broken into smaller procedures. Perhaps the variables are more global than necessary. Perhaps several variables could be combined into an array.

A.1: Compile Errors 111

2: Too Many Real Constant Names. There are too many constants "define"d as real values in scope at one time. The maximum number is 160. Perhaps they are more global than necessary.

3: Too Many Names. There are too many names (variables, procedures, intrinsics, constants, etc.) in scope at one time causing the symbol table to overflow. The maximum number is 1600. Perhaps some intrinsics are declared but not used. Perhaps some names are more global than necessary. Perhaps several variables could be combined into an array.

4: Too Many 'Quits'. There cannot be more than 160 total "quit" statements inside a "loop". This total includes "quit"s for other "loop"s that are nested inside the "loop".

5: Too Many Static Levels. Procedures can be nested to a maximum depth of eight levels.

6: Number Out Of Range. Integers are limited to the range of -32768 through +32767.

7: Number Out Of Range. Intrinsic "code" declarations are limited to 0 through 127.

10: Undeclared Name. The name is undefined here. It might be out of scope or be forward referenced. A procedure declaration that is missing a semicolon causes the rest of the line to not be seen (it is taken as a comment).

11: Name Already Declared. This name conflicts with a previous declaration at this level. Only the first 16 characters are significant to the compiler.

20: Illegal Start Of A Statement. Missing an "end"? Unpaired "begin-end"s? If "procedure" is flagged then there is a missing "end" in the previous procedure.

21: ":=" Expected But Not Found. Illegal variable in a "for" or an assignment statement? The control variable in a "for" loop cannot have a subscript.

22: 'Then' Expected But Not Found. Illegal expression in an "if" statement?

A.1: Compile Errors 112

23: 'Do' Expected But Not Found. Illegal expression in a "for" or "while" statement?

24: "," Expected But Not Found. Illegal expression in a "for" statement?

26: Illegal Factor. Incomplete expression or an illegal operator? Semicolon or "of" before an "other" in a "case" statement? Perhaps parentheses are needed around an "if" expression.

27: Statement Starting With A Constant. The name is declared as a constant, which cannot be assigned a value.

28: 'Until' Expected But Not Found. Perhaps the previous statement is missing a semicolon. Unpaired "begin" "end"s within a "repeat" block?

29: 'Other' Expected But Not Found. A "case" statement must be terminated with an "other" statement. Perhaps the previous statement is missing a semicolon.

30: 'Else' Expected But Not Found. An "if" expression must have the "else" clause. Illegal expression after the "then"? Do not confuse an "if" expression for the more common "if" statement.

31: Digit Expected But Not Found. Either the exponent of a real number or a hex digit is missing.

33: Integer Variable Expected But Not Found. The control variable in a "for" statement must be an integer or character variable.

39: "(" Expected But Not Found. Parentheses must enclose arguments.

40: "=" Expected But Not Found. In a "code" declaration every name must be set equal to an integer.

41: ";" Expected But Not Found. A semicolon must be at the end of a declaration, must separate procedures, and must separate statements within a "begin-end" (or a "repeat-until") block.

42: Constant Expected But Not Found. In a "define" or a constant array the values must be previously declared constants or be integer or real constants; they cannot be variables.

A.1: Compile Errors 113

43: Variable Expected But Not Found. The "address" operator can only return the address of a variable.

44: ")" Expected But Not Found. Parentheses must be balanced. Even though balanced, extra sets of parentheses around arguments and subscripts are illegal.

45: Name Expected But Not Found. There must be a name in a declaration.

46: Mixed Mode. Reals and integers cannot be mixed within an expression without explicitly doing the type conversions using the intrinsics Fix and Float. This message can be triggered if a variable is undefined. Also, a forward-function declaration and its function must be the same data type.

47: Integer Expected But Not Found. The indicated value or expression is not of type integer. Subscripts, the control variable in a "for" loop, and "case" expressions cannot be reals.

48: 'Of' Expected But Not Found. Illegal expression in a "case" statement?

49: ":" Expected But Not Found. Illegal expression in a "case" statement?

50: "]" Expected But Not Found. Constant-array brackets must be balanced. Perhaps a comma is missing.

51: No Arguments Declared. The called procedure has no local variables declared and therefore cannot have arguments passed to it.

52: Statement Starting With 'Else'. An "else" is never preceded by a semicolon.

53: Statement Starting With 'Other'. An "other" is never preceded by a semicolon.

60: 'Quit' Not In A 'Loop'. The "quit" statement is legal only inside a "loop" block.

61: EOF Expected But Not Found. More code after the apparent end of the program. Unpaired "begin" "end"s? Too many "end"s or missing a "begin"?

62: EOF Inside A Block. End-of file (CTRL-Z, $1A) is inside a block statement. Too many "begin"s or not enough "end"s? Incomplete or missing statement?

A.1: Compile Errors 114

63: EOF Inside A String. Unpaired double quote (")? A caret (^) can cause a quote to not be seen.

65: 'FProc' & Its 'Proc' Not At Same Level. A forward procedure declaration and its corresponding procedure declaration must be at the same level and must be in scope with each other.

66: 'FProc' Reference Not Found. Unresolved forward procedure or function reference. Perhaps it is out of scope. "fproc" and its corresponding "proc" must be at the same static level. Maybe a "begin" is missing.

67: 'Proc' Or 'Func' Expected But Not Found. "public" must be followed by "procedure" or "function".

68: 'EProc's And 'Public's Must Be Global. "eproc"s, "efunc"s, and "public"s must be at level zero; they cannot be inside a procedure (except the main routine).

69: 'Include's Nested Too Deep. A file that is included can itself include other files. These files also can include files, but the chain of includes is limited to eight levels. Perhaps a file is including itself, or is including a file that includes the original file.

70: Bad File Spec. The specification should be: C:\path\filename.ext; Everything but the file name is optional. The semicolon is required. Backslashes do not designate comments in a file name.

71: File Not Found. Perhaps the file is not in the current directory.

72: 'Int', 'Real', 'Char', or 'Addr' Expected But Not Found.

73: Divide By Zero In A Constant Expression.

74: Math Error In A Constant Expression. Floating point overflow or underflow occurred.

75: Expression Must Be Enclosed In Parentheses. Exclusive-or operations (|) and "if" expressions must be enclosed in parentheses when the short-circuit boolean command-line switch (/b) is used.

L2002: Fixup Overflow. This error is generated by the linker (LINK). There is more than 64K of code in external routines that are declared after a dimensioned array declaration. Rearrange your declarations, putting "external" "eproc" and "efunc" ahead of any dimensioned arrays. This moves the EXTRN declarations in the assembly code ahead of the code segment.

115

A . 2 :   R U N - T I M E   E R R O R S

If an error is detected while a program is running, it aborts and a run-time error message is displayed.

Aborting points out errors in the code, but sometimes it is more a nuisance than a help. The Trap intrinsic (17) is used to disable the abort for selected run-time errors.

This is a list of all of the run-time error messages:

1: Div By 0. Illegal division by zero for an integer. If this is untrapped, 32767 is returned.

2: Out Of Memory. No more memory space. An array declaration, a Reserve, or the I2L loader tried to exceed the allotted memory bounds.

3: I/O Error. Some device driver returned with an error. The most common I/O errors are caused by forgetting to specify an input or output file on the command line, or mistyping the name of an input file.

4: Bad Opcode. Invalid opcode encountered. When this occurs, the stack is out of balance and the program has probably blown-up. It's not a bad idea to reboot your computer to be absolutely safe, although this is unnecessary if running under a protected operating system such as Windows XP. The common causes of this error are an array subscript was incorrectly computed or an intrinsic was incorrectly used.

5: Bad Intrinsic. Invalid intrinsic number used. This is usually due to an incorrect "code" declaration, but it could be caused in the same way as error 4. Some versions of the run-time code (NAT) do not support floating-point calculations and their related intrinsics (such as Fix).

6: Div By 0.0. Floating-point divide by zero. If untrapped, the largest possible real value is returned.

7: Overflow. Floating-point overflow. Some calculation exceeded the upper limit of ±1.79E+308. If untrapped, the largest possible real value is returned.

A.2: Run-Time Errors 116

8: Underflow. Floating-point underflow. A calculation exceeded the lower limit of ±2.23E-308. If untrapped, 0.0 is returned.

9: Fix Overflow. Fixed-point overflow. Attempted to Fix too large or too small a number (greater than 65535.0 or less than -65535.0). If untrapped 32767 is returned with the appropriate sign. This error can also be caused by the Mod intrinsic if an internal calculation exceeds 15 bits of precision.

10: Sqrt < 0. Square-root error. Attempted to take the square root of a negative number. This error is also trapped by the ASin and ACos intrinsics if the argument is outside of its legal range (-1.0 <= arg <= 1.0). If untrapped, the square root of the absolute value is returned.

11: Log <= 0. Logarithm error. Attempted to take the logarithm of a number that is less than or equal to zero. If untrapped and the argument is 0.0, the smallest possible negative value is returned (minus infinity). If the argument is less than 0.0, the logarithm of the absolute value is returned.

12: Exp Overflow. Exponential error. Exp intrinsic caused an overflow. If untrapped, the largest possible value is returned.

13: Unused.

14: Atan2(0.0, 0.0). ATan2(0.0, 0.0) is undefined. Returns 0.0 if untrapped.

117

A . 3 :   C O M M O N   E R R O R S

There are some errors that seem to catch everyone when they first start programming in XPL0. Here is a list of these errors beginning with the most common.

1. There are several commands or symbols that must be used in pairs. Many newtimers omit one of the pairs. The most likely place that you might do this is with "begin-end"s. It is easy to get the wrong number of "end"s at the end of a complex procedure. The easiest way to keep track of these is to use indentation:

        begin
        . . .
                begin
                . . .
                        begin
                        . . .
                        end;
                end;
        end;

Each indentation must have a "begin" and a corresponding "end".

Here are some other pairs to watch for:

        " . . . "       Double quotes around text strings
        ( . . . )       Parentheses
        [ . . . ]       Brackets (same as  "begin-end"s)
        \ . . . \       Comments (when not the last item on the line)

2. A semicolon can catch you in two ways. One is that there must be a semicolon between all statements in the program. The other is that you must not place a semicolon before the "else" of an "if" statement or the "other" of a "case" statement. For example:

A.3: Common Errors 118

        if N = Guess then
                begin
                Restart;
                MakeNumber;
                end  <----------  semicolon is illegal here
        else    begin
                N:= N + 1; <----- semicolon is required here
                Restart; <------- semicolon is optional here
                end;

3. Intrinsics require various numbers of arguments. A common error is to pass the wrong number of arguments or the wrong type of arguments (integer versus real). This causes a stack imbalance and your program blows up.

        WRONG                       CORRECT
        Text("message");        Text(0, "message");
        ChIn(0);                I:= ChIn(0);
        I:= ChOut(0,^A);        ChOut(0, ^A);
        X:= Sqrt(100);          X:= Sqrt(100.);

4. When arguments are passed to a procedure, the values passed are stored into the first variables declared and in the same order that they are passed. As a program is written, it is easy to add new variables to the declarations, which shift their order and change which arguments are passed into which variables. "Integer", "real", and "character" declarations can be mixed in any way necessary to properly pass values into the correct variables. It is often useful to have completely separate declarations for arguments and local variables. For example:

        procedure Oink(I, X, Ch);
        integer I;              \Arguments
        real X;
        integer Ch;
        integer A, B, C;        \Local variables
        begin
        . . .

5. XPL0 does not do run-time array bounds checking. Thus it is possible to store something in an incorrect place in memory. Almost always, this is due to an error in the calculation of a subscript for an array.

6. Avoid using the same name both locally and globally. You can easily get confused as to which is which, and this can be a difficult error to find. If you use a local variable with the same name as a global variable, the compiler does not give a NAME ALREADY DECLARED error; the local variable is used instead of the global variable. As a consequence you should make global names longer and more formal than local names. For example, avoid using a name like "I" for a global. At the very least call it "II".

119

A . 4 :   K E Y B O A R D   S C A N   C O D E S


      3B   F1               68   Alt-F1           1E   Alt-A
      3C   F2               69   Alt-F2           1F   Alt-S
      3D   F3               6A   Alt-F3           20   Alt-D
      3E   F4               6B   Alt-F4           21   Alt-F
      3F   F5               6C   Alt-F5           22   Alt-G
      40   F6               6D   Alt-F6           23   Alt-H
      41   F7               6E   Alt-F7           24   Alt-J
      42   F8               6F   Alt-F8           25   Alt-K
      43   F9               70   Alt-F9           26   Alt-L
      44   F10              71   Alt-F10
                                                  2C   Alt-Z
      54   Shift-F1         78   Alt-1            2D   Alt-X
      55   Shift-F2         79   Alt-2            2E   Alt-C
      56   Shift-F3         7A   Alt-3            2F   Alt-V
      57   Shift-F4         7B   Alt-4            30   Alt-B
      58   Shift-F5         7C   Alt-5            31   Alt-N
      59   Shift-F6         7D   Alt-6            32   Alt-M
      5A   Shift-F7         7E   Alt-7
      5B   Shift-F8         7F   Alt-8            03   Ctrl-2
      5C   Shift-F9         80   Alt-9            0F   Shift-Tab
      5D   Shift-F10        81   Alt-0            47   Home
                            82   Alt-Hyphen       48   Up arrow
      5E   Ctrl-F1          83   Alt-=            49   PgUp
      5F   Ctrl-F2                                4B   Left arrow
      60   Ctrl-F3          10   Alt-Q            4D   Right arrow
      61   Ctrl-F4          11   Alt-W            4F   End
      62   Ctrl-F5          12   Alt-E            50   Down arrow
      63   Ctrl-F6          13   Alt-R            51   PgDn
      64   Ctrl-F7          14   Alt-T            52   Insert
      65   Ctrl-F8          15   Alt-Y            53   Delete
      66   Ctrl-F9          16   Alt-U            73   Ctrl-Left arrow
      67   Ctrl-F10         17   Alt-I            74   Ctrl-Right arrow
                            18   Alt-O            75   Ctrl-End
                            19   Alt-P            76   Ctrl-PgDn
                                                  77   Ctrl-Home
                                                  84   Ctrl-PgUp

120

A . 5 :   S Y N T A X   S U M M A R Y


FACTORS                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 SECTION
                                                        
CONSTANTS:      Decimal integers: 123    .  .  .  .  .  .  .  .   1.0
                Hexadecimal integers: $FE00    .  .  .  .  .  .   1.1
                ASCII characters: ^A     .  .  .  .  .  .  .  .   1.2
                Real numbers: 6.023E23   .  .  .  .  .  .  .  .   1.3
                Declared constants:  define  Pi=3.14;   .  . 1.5, 2.9
                True and false     .  .  .  .  .  .  .  .  .  .   2.4
VARIABLES:      Integers     .  .  .  .  .  .  .  .  .  .  .  .   1.4
                Reals     .  .  .  .  .  .  .  .  .  .  .  .  .   1.4
                Array elements  .  .  .  .  .  .  .  .  .  .  .   5
FUNCTIONS     .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   4.5
INTRINSICS that return a value     .  .  .  .  .  .  .  .  .  .   4.6
EXTERNALS that return a value   .  .  .  .  .  .  .  .  .  .  .   4.13
TEXT STRINGS: "..."    .  .  .  .  .  .  .  .  .  .  .  .  .  .   5.2
CONSTANT ARRAYS: [CONSTANT, ... CONSTANT]      .  .  .  .  .  .   5.5
ADDRESS of a variable or array: addr Frog      .  .  .  .  .  .   5.7

OPERATORS

The operator precedence (priority) is shown in parentheses; 1 is highest.
Unary minus (or plus):  - (+)   (1)   .  .  .  .  .  .  .  .  .   2.2
Shift left:             <<      (2)   .  .  .  .  .  .  .  .  .   2.7
Shift right:            >>      (2)   .  .  .  .  .  .  .  .  .   2.7
Multiplication:         *       (3)   .  .  .  .  .  .  .  .  .   2.0
Division:               /       (3)   .  .  .  .  .  .  .  .  .   2.0
Addition:               +       (4)   .  .  .  .  .  .  .  .  .   2.0
Subtraction:            -       (4)   .  .  .  .  .  .  .  .  .   2.0
Equal:                  =       (5)   .  .  .  .  .  .  .  .  .   2.3
Not equal:              #       (5)   .  .  .  .  .  .  .  .  .   2.3
Less than:              <       (5)   .  .  .  .  .  .  .  .  .   2.3
Less than or equal:     <=      (5)   .  .  .  .  .  .  .  .  .   2.3
Greater than:           >       (5)   .  .  .  .  .  .  .  .  .   2.3
Greater than or equal:  >=      (5)   .  .  .  .  .  .  .  .  .   2.3
Boolean "not":          ~       (6)   .  .  .  .  .  .  .  .  .   2.5
Boolean "and":          &       (7)   .  .  .  .  .  .  .  .  .   2.5
Boolean "or":           !       (8)   .  .  .  .  .  .  .  .  .   2.5
Boolean "exclusive or": |       (8)   .  .  .  .  .  .  .  .  .   2.5
If expression:          if      (9)   .  .  .  .  .  .  .  .  .   2.8

SPECIAL CHARACTERS

Space, tab, carriage return, and form feed are formatters     .   1.8
()      Expression evaluation priority, arguments, and subscripts.
                                                   2.0, 3.9, 4.2, 5.0
;       Statement and procedure separator and declaration terminator
                                                             3.1, 3.11
\       Comment (except in strings and "include" path names)      3.10
^       ASCII constants, and ", ^ and ctrl chars in strings  1.2, 5.2
_       Underline is a legal character in a name     .  .  .  .   1.4

A.5: Syntax Summary 121


STATEMENTS

VARIABLE:= EXPRESSION;    .  .  .  .  .  .  .  .  .  .  .  .  .   3.0
begin STATEMENT; STATEMENT; ... STATEMENT end;    .  .  .  .  .   3.1
[STATEMENT; STATEMENT; ... STATEMENT];   .  .  .  .  .  .  .  .   3.1
if BOOLEAN EXPRESSION then STATEMENT;    .  .  .  .  .  .  .  .   3.2
if BOOLEAN EXPRESSION then STATEMENT else STATEMENT;    .  .  .   3.2
case of    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.3
        BOOLEAN EXPRESSION, ... BOOLEAN EXPRESSION: STATEMENT;
        ...
        BOOLEAN EXPRESSION, ... BOOLEAN EXPRESSION: STATEMENT
        other STATEMENT;
case INTEGER EXPRESSION of   .  .  .  .  .  .  .  .  .  .  .  .   3.3
        INTEGER EXPRESSION, ... INTEGER EXPRESSION: STATEMENT;
        ...
        INTEGER EXPRESSION, ... INTEGER EXPRESSION: STATEMENT
        other STATEMENT;
while BOOLEAN EXPRESSION do STATEMENT;   .  .  .  .  .  .  .  .   3.4
repeat STATEMENT; ... STATEMENT until BOOLEAN EXPRESSION;  .  .   3.5
loop STATEMENT;  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.6
quit;   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.6
for VARIABLE:= INTEGER EXPRESSION, INTEGER EXPRESSION   .  .  .   3.7
        do STATEMENT;
exit;   .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.8
exit BYTE EXPRESSION;  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.8
SUBROUTINE NAME(EXPRESSION, ... EXPRESSION);   .  .  .  .  . 3.9, 4.0
return;    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   4.4
return EXPRESSION;  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .   4.5
;    (null statement)  .  .  .  .  .  .  .  .  .  .  .  .  .  .   3.11

DECLARATIONS

integer NAME, NAME, ... NAME;   .  .  .  .  .  .  .  .  .  .  .   1.5
real NAME, NAME, ... NAME;   .  .  .  .  .  .  .  .  .  .  .  .   1.5
define NAME=CONSTANT, ... NAME=CONSTANT;    .  .  .  .  .  .  .   1.6
define NAME, NAME, ... NAME;    .  .  .  .  .  .  .  .  .  .  .   1.6
procedure NAME(COMMENT);     .  .  .  .  .  .  .  .  .  .  .  .   4.0
function TYPE NAME(COMMENT);    .  .  .  .  .  .  .  .  .  .  .   4.5
code TYPE NAME(COMMENT)=INTEGER, ... NAME(COMMENT)=INTEGER;   .   4.6
fprocedure NAME(COMMENT), NAME(COMMENT), ... NAME(COMMENT);   .   4.9
ffunction TYPE NAME(COMMENT), NAME(COMMENT), ... NAME(COMMENT);   4.10
eprocedure NAME(COMMENT), NAME(COMMENT), ... NAME(COMMENT);   .   4.12
efunction TYPE NAME(COMMENT), NAME(COMMENT), ... NAME(COMMENT);   4.12
public procedure NAME(COMMENT);    .  .  .  .  .  .  .  .  .  .   4.12
public function TYPE NAME(COMMENT);   .  .  .  .  .  .  .  .  .   4.12
external TYPE NAME(COMMENT), NAME(COMMENT), ... NAME(COMMENT);    4.13
integer NAME(DIMENSIONS), ... NAME(DIMENSIONS);   .  .  .  .  .   5
real NAME(DIMENSIONS), ... NAME(DIMENSIONS);   .  .  .  .  .  .   5
character NAME(DIMENSIONS), ... NAME(DIMENSIONS);    .  .  .  .   5
segment TYPE NAME(DIMENSION), ... NAME(DIMENSION);   .  .  .  .   5.9

122

I N D E X


A                                  ASin 103
Abort 89                           assembler 4
Abort, Retry, Ignore? 78, 81       assembly language 50
Abs 85                             Assignments 26
absolute value 85, 100             ATan2 102
ACos 103                           Attrib 81, 104
Addition 15                        attribute 77
address 67, 68
address, segment 71                B
Advanced 1                         backslash 33, 47
align decimal points 101           baud rate 81
allocation, dynamic memory 57      begin 6, 26, 117
allocation, memory 71              BEL 82
and 19                             binary files 79
arc-cosine function 103            BIOS or DOS interrupts 95
arc-sine function 103              bit, sign 86
arc-tangent 102                    Blit 80, 96
arguments 33, 37, 118              block 6
argument, array 57                 block, environment 92
array 54, 86                       boolean 19, 27
array argument 57                  brackets 27
array bounds checking 118          Break, control- 107
array dimensions 57                BS 82
arrays, character 55               buffer, circular 82
arrays, constant 63                buffer, large & small 79, 91
arrays, Integer 54
ARRAYS, MULTIDIMENSIONAL 59        C
arrays, multidimensional char 67   call 104
arrays, segment 67, 70             CALLS, SUBROUTINE 33
arrays, 2-dimensional 59           cards, wild 92
.ASM 4                             caret 9, 58
ASCII 57, 87                       Carriage Return 13, 87
ASCII characters 9                 Carry flag 96
ASCII, extended 106

Index 123


case 28                            control characters 58, 82
CGA 99, 108                        control-Break 82, 107
Chain 92                           control-C 82
character 55                       control-C trapping 107
character arrays 55                control-Z 79
characters, ASCII 9                coordinates, polar 102
characters, control 58, 82         Cos 102
checking, array bounds 118         cosine function 102
ChIn 75, 87                        CR 82
ChkKey 94                          CrLf 3, 13, 87
ChOut 13, 75, 87                   CTS 81
circular buffer 82                 Cursor 90, 105
Clear 97                           .C2L 52
Close 75, 78, 88
code 3, 42                         D
code segment 96                    data segment 96
CODESI.XPL 46                      DATA STRUCTURES 60
codes, DOS error 95                date 79
CODES, KEYBOARD SCAN 77, 82, 119   day, time of 95
code, run-time 4                   decimal integer 87
color 81, 97, 98, 104, 106         decimal point 9
COM RS-232 81                      declaration 10
.COM 5, 52                         Declared constants 11
command tail 79                    define 11
command word 3, 10                 degrees 101
COMMAND LINE, OPENING FILES 79     device drivers 75
command, PATH 92                   device 6 104, 105
comments 23, 33                    device, input 87
COMMON ERRORS 117                  device, null 82
common mistake 20, 62              device, output 87
common-logarithm function 102      DICE 55
comparisons 17                     dimensions, array 54, 57
compile errors 110                 disk file 78
compiler 4                         divide by zero 89
COMPILING 4                        Division 15, 85
condition 23                       do 30, 32
configuration, Video 98, 108       dollar 8, 91
constant 6                         DOS error codes 95
constant array 63                  DOS or BIOS interrupts 95
CONSTANT EXPRESSIONS 23            drive 94
constants, Declared 10             drivers, device 75
constants, Real 9                  DSEG 51
constant, integer 8

Index 124


DSR 81
DTE 81                             F
DTR 81                             factor 6, 8
DW 53                              Factorial 45
dynamic memory allocation 57       false 17, 18
                                   FClose 78, 79, 80, 94
E                                  FF 82
E 9                                ffunction 46
efunction 48                       files, binary 79
EGA 99, 108                        file, disk 78
else 3, 27                         FILE, END OF 79
end 6, 26, 117                     file, library 53
END OF FILE (EOF) 79, 80, 82       file, supervisor 53
engineering notation 101           Fix 16, 100
environment block 92               flag, Carry 96
eprocedure 47                      flag, Rerun 86
Equip 108                          Flashing 104
error trapping 79                  Float 16, 100
ERRORS, COMMON 117                 fonts 98
errors, compile 110                FOpen 78, 80, 91, 93
errors, run-time 89, 90, 115       for 32
error, I/O 79                      form feed 13
error, rounding 24                 Format 35, 100
even 19                            Forward-function 46
exclusive or 19                    forward-procedure 46
EXE2BIN 52                         fprocedure 46
.EXE 4, 79                         Free 57, 89
exit 32, 89                        free-format 13
Exp 101                            FSet 78, 80, 91
exponent 9                         function 40
exponential function 101           function, arc-cosine 103
Expressions 6, 15                  function, arc-sine 103
EXPRESSIONS, CONSTANT 23           function, common-log 102
expression, if 22                  function, cosine 102
ExtCal 96, 104                     function, exponential 101
Extend 86                          function, modulo 102
extended ASCII 106                 function, natural log 101
external 50                        function, sine 101
external procedures 47             function, tangent 102
external routine 104
ExtJmp 96, 103                     G
                                   GetErr 79, 90

Index 125


GetHp 90                           intrinsics 3, 42, 118
GetReg 80, 92, 95                  IRET 103
global 37                          I2L 5, 52, 53, 109, 115
global zero 52                     I/O 75
graphics 97                        I/O error 79
GUESS 1                            .I2L 5, 52, 53

H                                  J
handle 78, 91                      jump 103
heap 51, 57, 71
heap pointer 90                    K
heap space 89                      keyboard 77, 94, 108
HEAPLO 51                          KEYBOARD SCAN CODES 119
HERCULES 104
hexadecimal 8                      L
HexIn 91                           large buffer 79, 91
HexOut 91                          LF 82
HGC 108                            library 49
Hilight 106                        library file 53
                                   Line 98
I                                  Line Feed 13, 87
if 3, 27                           line, new 87
if expression 22                   link 4, 49
include 46, 48                     linked lists 61
initialization 88                  linker 4, 114
INPUT AND OUTPUT 75                Ln 101
input device 87                    local 37
input port 103                     local variables 53, 64
InputGuess 2                       Log 102
int 10                             logarithm, common 102
integer 10, 54                     logarithm, natural 101
Integer arrays 54                  loop 31
integer constant 8                 LOWCASE 80
integer, decimal 87                LPT1 78
Intense 104
interpreted 5                      M
interrupt return 103               main procedure 4
intersection 21                    MakeNumber 2
IntIn 2, 75, 87                    MAlloc 71, 107
IntOut 13, 75, 88
IntRet 96, 103

Index 126


masking 19                         OpenI 75, 78, 88
MASM 4                             OPENING FILES FR COMMAND LINE 79
Math coprocessor 108               OpenO 75, 78, 88
matrices 59                        operator 6
MCGA 99, 108                       operator priority 120
MDA 99, 108                        operator, unary 16
memory allocation 71               or 19
memory model 70                    or, exclusive 19
memory, video 73                   other 28
mistake, common 20, 62             output device 87
MIXED MODE 16                      output port 103
Mod 102                            OUTPUT, INPUT AND 75
MODE 78, 81                        output, unwanted 82
modulo function 102                out-of-memory 72
monitor 77                         overflow 16
monochrome 104
Move 98                            P
MULTIDIMENSIONAL ARRAYS 59         paragraphs 71, 107
multidimensional char array 67     parentheses 15
Multiplication 15                  parity 81
MUPPET, PIGGY 66                   Passing an integer into a real 69
                                   PATH command 92
N                                  path, subdirectory 78
names 9                            Peek 97
name, same 45                      PIGGY MUPPET 66
NATIVE 4, 5                        PIn 103
natural logarithm 101              pixel 98
new line 87                        Point 97, 98
not 19                             pointers 56, 68
notation, engineering 101          Pointer(0) 67
notation, scientific 101           pointer, heap 90
null device 82                     points, align decimal 101
null statement 34                  point, decimal 9
numbers, real 24                   Poke 97
number, random 85                  polar coordinates 102
                                   port, I/O 103
O                                  POut 103
.OBJ 4                             precision 9
odd 19                             prefix, program segment 79, 92
of 28                              printer 78, 81
offset 70                          priority, operator 120
                                   PRN 78

Index 127


procedure 7, 36                    RlAbs 100
procedures, external 47            RlIn 75, 99
procedure, main 4                  RlOut 24, 35, 75, 100
Processor type 108                 RlRes 62, 99
program segment prefix 79, 92      root, square 101
PSP 79, 92, 96                     rounding error 24
public 47, 50                      routine, external 104
                                   RS-232, COM 81
Q                                  RTS 81
quit 31                            RUNNING 4
quotient 15                        run-time code 4
                                   run-time errors 89, 90, 115
R
radians 101                        S
Ran 2, 85                          same name 45
random number 85                   Scan Code 77, 82
rate, baud 81                      scientific notation 101
RawText 106                        Scope 43
Read 94                            scroll, Text 105
ReadPix 98                         sector 94
real 10, 55, 56                    segment address 71
Real constants 9                   segment arrays 67, 70
real numbers 24                    segments 70
reals, Short 72                    segment, code 96
real, Passing an integer into 69   segment, data 96
RECORDS 65                         semicolons 6, 34, 117
Recursion 45                       SetHp 90
reentrant 51                       SetRun 90, 91
registers 95                       sets 11, 21
Release 73, 107                    SetVid 98
Rem 15, 85                         SetWind 81, 105
remainder 15, 85                   shift 22
repeat 13, 30                      Short reals 72
Rerun 90, 91                       Shrink 109
Rerun flag 86                      sign bit 86
Reserve 61, 86                     Sin 98, 101
Restart 86, 90                     sine function 101
return 39, 40                      sine wave 98
RETURNING MULTIPLE VALUES 68       small buffer 79, 91
Return, Carriage 13, 87            SoftInt 95
return, interrupt 103              Sound 97
                                   spaces 13

Index 128


space, heap 89                     U
speed 85, 108                      unary operator 16
Sqrt 101                           underline 9
square root 101                    Underlined 104
statements 6, 26                   union 21
statement, null 34                 until 30
static variables 64                unwanted output 82
string 88
string, text 57                    V
STRUCTURES, DATA 60                variable 6, 9
subdirectory path 78               variables, local 53, 64
SUBROUTINE CALLS 33                variables, static 64
subroutines 7, 36                  version 108
subscript 54                       VGA 99, 104, 108
Subtraction 15                     Video configuration 98, 108
SUMMARY, SYNTAX 120                video memory 73
supervisor file 53
Swap 86                            W
syntax 5                           wave, sine 98
SYNTAX SUMMARY 120                 while 30
                                   wild cards 92
T                                  windows 81, 105, 106
Tab 12, 13, 82, 91                 word, command 3, 10
tail, command 79                   Write 94
Tan 102
tangent function 102               X
temperature 35                     X 5
TestC 107                          XLINK 52
TestGuess 2                        XN.BAT 4
Text 2, 13, 88                     .XPL 4
Text scroll 105                    XPLIQ 5
text string 57                     XPLNQ 4
then 3, 27                         XPLX 4
THERMO 34
time 79                            Z
time of day 95                     zero, divide by 89
Trap 79, 89, 90, 115               zero, global 52
TrapC 107
trapping, control-C 107
trapping, error 79
trees 61
true 17, 18, 19

Index 129


2-dimensional array 59
! 19
" 57, 117
# 17
$ 8, 91
& 19
( ) 15, 38, 54, 117
* 15, 92
+ 15, 16
- 15, 16
. 9
/ 15
: 28
:= 26
; 6
< 17, 77
<< 22
<= 17
= 11, 17, 42
> 17, 77
>= 17
>> 22
? 92
[ ] 27, 63, 117
\ 33, 46, 117
^ 9, 58
_ 9
| 19
~ 19

130

A D D E N D U M

Here are some new features of the XPL0 language.

NEW INTRINSICS

79: RanSeed(integer);

This intrinsic sets the seed for the random number generator (1: Ran). This allows up to 65536 different, repeatable random number sequences. Previously a repeatable random sequence was set by using Ran(0), but this only provided a single sequence.

80: Irq(boolean);

This intrinsic turns interrupts on if boolean is "true", or off if boolean is "false". This is useful for instance when manipulating the VGA registers and you don't want the mouse interrupt service routine to interfere. The NMI interrupt is not affected. Interrupts, of course, should not be left off for very long. Be aware that most BIOS and DOS calls will temporarily re-enable interrupts.

Some intrinsics can be generated as in-line code by the compiler, which makes them much faster. If Abs, Rem, Swap, or Extend are replaced with lowercase names (abs, rem, swap, extend) then in-line code is generated. As a bonus "abs" works equally well for integers and reals, thus RlAbs is no longer need. Since the compiler recognizes these lowercase names it can optimize some constant calculations. For example swap($1234<<8) is generated as $0034. Lowercase intrinsics are not available in the interpreted version (XPLI).

A new command word "port" has also been added. This allows accessing I/O ports as though they were character (byte) arrays. For example:

        port($27A):= port($27A) ! $01;    \(set LPT2 strobe line high)

This "port" feature is not available in the interpreted version (XPLI); the existing PIn and POut intrinsics must be used. PIn and POut must also be used to do 16-bit word I/O.

Addendum 131

SWITCHES

Command-line switches are used to modify the behavior of the compilers. Many of them are not normally used. However, for completeness of documentation they're all listed here.

The command-line switches for XPLN are:

        /L  List source code to the monitor screen
        /A  divert Assembly code to monitor instead of the output file
        /C  insert I2L Comments into the output file

XPLX also recognizes these switches:

        /D  Debug: include XPL0 source code in .ASM output file
        /2  align loops to word boundaries to speed them up
        /3  align loops to double-word boundaries to speed them up
        /S  generate near procedure calls for code Smaller than 64K
        /J  generate short conditional Jumps (to be fixed by MASM 6)
        /B  do short-circuit evaluation of Boolean expressions

XPLI only recognizes these switches:

        /L  List source code to the monitor screen
        /D  list Debug information to the monitor screen
        /W  display Warning messages

Procedure calls can be optimized if a program is less than 64K by using the /S (Small) switch. This replaces the normal far call instruction with a near call. Calls to external XPL0 procedures (eprocs) are handled the same as normal procedures. Calls to intrinsics and external assembly language routines are always far regardless of the /S switch. (These calls can be optimized a little using the /F switch in LINK.) If several modules are linked together, they must all be compiled the same way--either with /S or without it.

Conditional jumps can be optimized using the /J switch and MASM 6. The /J switch makes XPLX output short conditional jumps, such as JNE. It relies on MASM 6 to automatically replace them with jumps over long jumps if necessary. For example, MASM 6 automatically replaces JNE with JE $+5 and JMP if the target location is more than 128 bytes away. It also replaces a normal 3-byte JMP with a short JMP wherever possible.

TASM 3.1 does not support the /J switch, that is, it does not fix short jumps that are out of range. Nor does it support the environment variable INCLUDE, which tells MASM where to look for include files (such as RUNTIME.ASM) if they are not in the current directory.

Addendum 132

SHORT-CIRCUIT BOOLEANS

Some boolean expressions can be executed much more quickly by using the /B command-line switch to enable short-circuit evaluation. Short-circuit evaluation is used in conditional statements to by-pass the rest of a boolean expression when the result is already known. For example:

        if A=3 ! B=5 ! C=7 ! D=11 then Prime:= true;

In this expression if B is equal to 5 then there is no reason to compare C to 7 and D to 11 because Prime will be assigned the value "true" despite these additional comparisons.

The reason this feature uses a switch rather than being done automatically is that it can cause some errors, although they're very unlikely.

An error can occur if a term in the boolean expression contains a function call that does more than simply return a value. Such a function is said to have a "side effect", and it is generally considered bad programming. Here is an example:

        if P<10 & P/3=N then DoSomething;
        R:= Rem(0);

Rem(0) is not defined when P is >= 10 and short-circuit evaluation is enabled. The divide operation not only returns the quotient, but also sets the remainder as a side effect.

Another reason for not automatically using short-circuit evaluation is that some older programs might give a compile error unless a small modification is made. For instance, the statement: "while A | B do..." gives the new compile error 75: EXPRESSION MUST BE ENCLOSED IN PARENTHESES. Adding parentheses solves the problem: "while (A | B) do..." This problem only occurs with the exclusive-or operator and the "if" expression, and these are rarely used in the boolean expression of a conditional statement. For example: while (if A=1 then F1 else F2) do....

Since expressions are evaluated from left to right, it's faster to test for frequent conditions on the left side, for example:

        if Ch>=^0 & Ch<=^9  !  Ch>=^A & Ch<=^F then DoHex;

(Hint: There are ten digits in the range 0..9 and only six in the range A..F.) Also it's more efficient to do comparisons before testing flags. For example, this takes advantage of short-circuit evaluation:

        if Printer=Epson & Pin9 then ...

Addendum 133

This does not:

        if Pin9 & Printer=Epson then ...

Avoid using unnecessary parentheses because expressions enclosed in parentheses are not short-circuit evaluated.

IN-LINE ASSEMBLY CODE

XPL0 has the ability to handle assembly code that is inserted directly into an XPL program. The reserved word "asm" designates that the following characters on the line are assembly code, and they are to be copied to the output (.ASM) file. For example:

        asm     cli
        asm     mov     ax, 102         ;comment
        asm     mov     bx, Frog        ;Comment

Assembly code must be written in lowercase characters except when an XPL variable or constant name is used. These are written in the usual way with at least the first letter capitalized. This enables the compiler to distinguish them from the rest of the assembly code and to substitute them with their corresponding code. For instance, in the above example, "Frog" might be replaced with something like "[SI+4]". Capital letters may be used in comments because comments are ignored by the compiler.

If several lines of assembly code are needed, they may be written this way:

        asm     {
                cli
                mov     ax, 102         ;comment
                mov     bx, Frog        ;Comment
                }

The ability to insert assembly code into a high-level language program is a two-edged sword. In general it should be avoided, but there are instances when it is very useful.

The most obvious application is to replace compiled code with more efficient assembly code. For instance "Irq(false)" can be replaced with "asm cli", which is at least ten times faster (except under Windows XP, which simulates the cli). Similarly, "POut(Time, $40, 1)" could be replaced with:

        asm     mov ax, Time
        asm     out 40h, ax

Addendum 134

Assembly language provides low-level control that a high-level language can't. Consider this expression: Frog * 777 / 1000. If Frog is above 42, the calculation will overflow in 16-bit XPL. However the following will not overflow even when Frog is 32767:

        asm     {
                mov     ax, 777
                imul    Frog            ;ax:dx := ax * Frog
                mov     cx, 1000
                idiv    cx              ;ax := ax:dx / cx
                }

Here is an example of a double-precision add:

        TimeLo:= TimeLo + 143;
        asm     {jnc    tm10
                 inc    TimeHi
                tm10:};

RULES AND RESTRICTIONS

With the power of assembly language, it's easy to shoot yourself in the foot. When using XPLX, the SI and DI registers must not be altered, and of course altering DS, SS or CS is fatal.

The compiler generates the correct code for named constants such as: "def Frog=123; asm mov ax, Frog". It also generates the correct code for variables except as follows. If the variable is at an intermediate level (neither local or global), the inserted assembly code must make sure the correct BASEn is loaded into the BP register. With XPLN the correct BASEn must be loaded in the SI register for all variables other than globals.

Here is an example that fills an integer array with a pattern. It runs about seven times faster than the equivalent "for" loop in XPLX (and 24 times faster than XPLN--as measured on a Duron 850).

        \XPLN needs the following line, but it must not be in XPLX
        \asm    mov     si, base1
        asm     {
                push    ds              ;es:= ds
                pop     es
                push    di              ;save di register
                mov     di, Array
                mov     ax, Pattern
                mov     cx, Size
                cld                     ;set direction flag to increment
                rep stosw               ;es:[di++] := ax; cx--
                pop     di              ;restore di register
                }

Addendum 135

Segment variables are not supported.

Line labels are allowed if they are in lowercase and don't conflict with names generated by the compiler. Certain names are reserved and cannot be used such as: l208, ll3, cseg, dseg, base2, intr10. The assembler will flag an error if there is a conflict.

Expressions are evaluated by the assembler, not by the compiler. Thus, for instance, hex numbers are represented with a trailing "h" instead of a leading "$".

The bracket "}" ends an assembly-code section, even if it occurs inside a "; comment", but not if occurs inside a "\ comment".

WARNINGS

A separate version of the run-time code is required for XPLX (XX.BAT). Code compiled with XPLX must be linked to NATIVEX, NATIVE7X, or NATX. Code compiled with XPLN must be linked to NATIVE, NATIVE7, or NAT. This is handled automatically when using the batch files X, XX, XN and XJSB.

XJSB.BAT makes programs about 7K smaller than with XX.BAT. It accomplishes this mostly by linking in NATX.OBJ instead of NATIVEX.OBJ. This small version of the run-time code does not support floating-point calculations nor does it support high-speed line draw (intrinsic 42: Line). XJSB also enables the /J /S /B switches. This means that MASM 6 must be used to fix any short jumps, that the program code cannot be larger than 64K, and that short-circuit boolean evaluation is done. Floating point calculations will not give a compile error, but they will give the run-time errors 4 and 5 (unless they are turned off with the Trap intrinsic). Line draw still works, but it is many times slower than with the full version of the run-time code.

Beware of STDLIB.OBJ. It must be compiled with the same compiler used to make other .OBJ files. If your main program module uses XPLX then STDLIB.XPL must be compiled using XPLX, not XPLN. One critical difference between these two compilers is that XPLX returns integer values from function calls in the AX register while XPLN returns them in global 0. Another difference is that XPLX uses the DI register as the heap pointer. Also, be sure that the /S switch is used consistently on all .XPL modules that are linked together.

XPLX puts the control variable of a "for" loop in a register. Although unlikely, a possible problem is using a pointer to change this variable. For example, the following is not an infinite loop if compiled by XPLX, but it is if it's compiled by the other compilers:

        A:= addr I;
        for I:= 1, 10 do
                A(0):= 3;

XPLI's /W switch displays a warning message for this situation.

Addendum 136

Be aware that using the Reserve intrinsic to reserve an odd number of bytes in a character array can misalign the heap, which makes any variable declared from that point on (including in called procedures) misaligned, which can significantly slow a program. The Reserve intrinsic does not automatically align to a word boundary because some early programs took advantage of consecutive Reserves allocating contiguous space.

Consecutive dimensioned arrays cannot allocate contiguous space, because the pointer to the array precedes the reserved space. Thus adding a byte to maintain word alignment is not a problem.

It is more efficient to declare dimensioned arrays last, after all other variables have been declared. This allows single-byte offsets to address these variables, whereas if an array is declared with 128 bytes or more, then double-byte offsets are required.