Psion Homepage Logo.

LZ/LZ64 Programming Manual Chapter 3-8

Psion bar.

3 Loops and branches

So far we have only considered programs which run in a straight line from start to finish. They consist of a number of instructions which are executed in the order they appear in the program; if you want to carry out an instruction more than once you must repeat it.

That is clearly very inefficient. A far more efficient method is for the program to be able to loop around a particular part as many times as you require, or until a certain condition is met.

There are a number of ways of doing this in OPL.

The first two are the DO/UNTIL and the WHILE/ENDWH loops. These are known as structures. They operate in a similar way to each other, with one difference.

You can have up to eight loops nested within each other.

The DO/UNTIL loop

Here is an example of a DO/UNTIL loop:

a%=10
DO
PRINT "A=";a%
a%=a%-1
UNTIL a%=0

First 10 is assigned to a%. The loop starts on the next line, with the instruction DO. This says to OPL:

"Execute all the following instructions until an UNTIL is reached. If the condition following UNTIL is not met, repeat the same set of instructions until it is."

The next line displays "A=", followed by the value of a%. The first time through the loop, this is 10.

Next, the value of a% has 1 subtracted from it so that a% is 9. Now comes UNTIL, followed by a condition. The condition is that a% is equal to zero. It isn't yet, so the program returns to DO and the loop is repeated. Now a% decrements to 8, and again the condition fails. This process continues until a% does equal zero.

When a% equals zero the loop finishes and the program continues with the instructions after UNTIL.

The WHILE/ENDWH loop

The WHILE/ENDWH loop is similar, except that the test condition is at the beginning. For example:

a=4.1
b=2.7
WHILE a>b
PRINT "a is greater than b"
b=b+1.04
ENDWH

BREAK

BREAK can be used in conjunction with an IF statement, see below, to break out of a DO or WHILE loop. It jumps to the instruction after the end of the loop.

Labels and jumps

Another command which can direct the program out of a straight sequence is GOTO. This jumps to a label.

In this example, when the program reaches the GOTO, it jumps to the line beginning with the label exit::.

GOTO exit::
PRINT "MISS THIS LINE"
PRINT "AND THIS ONE"
exit::

Labels end in a double colon.

The label must be in the same procedure as the GOTO, and the jump is not conditional, it always happens.

Branches

GOTO is a way of branching, but it is a fairly crude tool and can make procedures difficult to read.

Better is the IF/ELSEIF/ELSE/ENDIF structure. This structure is used to perform one or more instructions IF a condition is met. If the condition is not met, you can use an ELSEIF instruction, to test for another condition. You can have any number of ELSEIF instructions within an IF/ENDIF structure.

After all likely things are catered for by the ELSEIF instructions, other possibilities can be catered for by an ELSE statement, followed at the end by the ENDIF statement. Here is an example:

whatkey:
LOCAL g%
g%=GET
PRINT "THAT KEY IS"
IF g%>64 AND g%<91
PRINT "UPPER CASE"
ELSEIF g%>96 AND g%<123
PRINT "lower case"
ELSE
PRINT "NOT A LETTER."
ENDIF
GET

This just waits for a key to be pressed, and then prints that it is either a lower or upper case letter. (If you don't realise the significance of the numbers 64 and 91, see Appendix A.) If it is not a letter at all, then that is printed, as allowed for by the ELSE statement.

ELSEIF and ELSE statements are optional, but for every IF there must be a corresponding ENDIF.

Endless Loops

ESCAPE OFF, GET, KEY and INPUT must be used with caution in loops for the following reasons:

To halt the execution of a procedure, you normally press ON/CLEAR then Q:

Organiser bar.

4 Operators

Arithmetic operators
+ add
- subtract
* multiply
/ divide
** raise to a power
- unary minus (make negative)
% percent

Comparison operators
> greater than
>= greater than or equal to
< less than
<= less than or equal to
= equal to
<> not equal to

Logical operators

AND
OR
NOT

Operator precedence
Highest: **
- (Unary minus) NOT
* /
+ -
= > < <> >= <=
Lowest: AND OR

The percent operator % is rather different as its effect depends on the operator it is combined with. The way % works is described in the calculator chapter of the operating manual.

Using Brackets

An expression such as a+b+c presents no problems, as the result is the same whichever addition is done first. However, you may want to enforce precedence with brackets. For example:
a+b*c/d

is evaluated in the order: b multiplied by c, then divided by d, then added to a. To perform the addition and the division before the multiplication, use brackets:
(a+b)*(c/d)

b% will first be raised to the power of c% and the resulting value will be used as the power of a%.

When in doubt, simply use brackets.

Precedence of integer and floating point values

You are free to mix floating point and integer values, in expressions, but be aware how OPL handles the mix:

For example, your procedure might include the expression:
a%=b%+c

This is handled like this: b% is converted to floating point and added to c. The resulting floating point value is then automatically converted to an integer in order to be assigned to the integer variable a%.

These conversions may produce odd results, so be careful. For example, a%=3.0*(7/2) assigns 9 to a%, but a%=(3.0*7)/2 assigns 10 to a%.

Logical expressions

The comparison operators and logical operators are based on the idea that a certain situation can be evaluated as either true or false. For example, if a%=6 and b%=8, a%>b% would be false. They are useful for setting up alternative paths in your procedures. For example you could say:

IF salary<expenses
PRINT "bad"
ELSEIF salary>expenses
PRINT "good"
ENDIF

You can also make use of the fact that the result of these logical expressions is represented by an integer:

These integers can be returned to a variable or printed out to the screen to tell you whether a particular condition is true or false, or used in an IF statement.

For example, in a procedure you might arrive at two sub-totals, a and b. You want to find out which is the greater. So include the statement, PRINT a>b. If zero is displayed, a and b are equal or b is the larger number but if - 1 is displayed, 'a>b' is true - a is the larger.
ExampleResultInteger Returned
< a<b True if a less than b -1
False if a greater than or equal to b 0
> a>b True if a greater than b -1
False if a less than or equal to b 0
<= a<=b True if a less than or equal b -1
False if a greater than b 0
>= a>=b True if a greater than or equal to b -1
False if a less than b 0
<> a<>b True if a not equal to b -1
False if a equal to b 0
= a=b True if a equal to b -1
False if a not equal to b 0

You can use these-operators with a mixture of floating point or integer values. However, if one side of the comparison is floating point, and the other is an integer, the integer is converted to a floating point. So if a%=1 and b=1.2, b>a% is true. You can't use a mix of string and numeric values, so a$<b is invalid.

Few programmers need the following information, so skip the rest of the chapter if it seems daunting.

The logical operators AND, OR and NOT have different effects depending on whether they are used with floating point numbers or integers:

1 When used with floating point numbers only, the logical operators have the following effects:
ExampleResultInteger Returned
a AND b True if both a and b are non-zero -1
False if either a or b are zero 0
a OR b True if either a or b are non-zero -1
False if both a and b are zero 0
NOT a True if a is zero -1
False if a is non-zero 0

2 When used with integer values only, AND, OR and NOT are bitwise logical operators.

The way the Organiser holds integer numbers internally is as a 16-bit binary code. So, 7 looks like this: 0000000000000111 The Organiser's numerical range is +32767 to -32768. 32767 is the largest number that can be represented with 15 binary bits. The 16th bit is used for the + or -.

As the operators are bitwise they perform the operation on first the 1st bit, then the 2nd, up to the 16th bit.

AND The statement PRINT 12 AND 10 prints 8. To understand this, write 12 and 10 in binary:

  12    0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
  10    0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0

AND acts on each pair of bits. Thus, working from left to right - discounting the first 12 bits:
1 AND 1 --> 1
1 AND 0 --> 0
0 AND 1 --> 0
0 AND 0 --> 0

The result is therefore the binary number 1000, or 8.

OR What result would the statement PRINT 12 OR 10 give? Again, write down the numbers in binary and apply the operator to each pair of digits:
1 OR 1 --> 1
1 OR 0 --> 1
0 OR 1 --> 1
0 OR 0 --> 0

The result is the binary number 1110, or 14 in decimal.

NOT NOT works on only one number. It returns the one's complement, i.e. it replaces 0s with 1s and 1s with 0s.

So if 7 looks like this: 0000000000000111
NOT 7 will look like this: 1111111111111000

This is the binary representation of -8.

Hint. A quick way of calculating NOT for integers is to add 1 to the original number and reverse its sign. Thus, NOT 23 is -24, NOT 0 is -1 and NOT -1 is 0. The last two results are the same for floating points.

Psion bar.

5 Handling data files

When you use the SAVE option from the main menu your records are saved in a file called MAIN. You can also create extra data files in the Xfiles option. You can access these files and the records in them from OPL. You can also create new files and then manipulate them. This chapter explains how to do this.

Record    Dr John Wood
          0982 23456
          44 Broughton Rd
          Broughton

[image]

There are also three examples of data file handling programs at the end of the example procedures chapter. It may be a good idea to refer to those programs while you read this chapter.

Files, records and fields

The data file MAIN and any extra ones you create contain records which are divided into fields. When you save a record from the main menu with the SAVE option, you start a new field every time you start a new line by pressing DOWN.

In a name and address file, in each record, there might be a name field, a telephone number field, and separate fields for each line of the address.

[image]

Records     Dr John Wood
            0982 23456
            44 Broughton Rd
            Broughton

              Dr John Wood      --> Field 1
Fields        098223456         --> Field 2
              44 Broughton Rd   --> Field 3
              Broughton         --> Field 4

Creating a data file

Before you can start to enter data into a new data file, you must create the file on one of the devices using New in Xfiles or the CREATE command in OPL.

With CREATE in OPL you have more control, as you specify how many fields there can be in each record, and what type of data can go in each field. The CREATE command has this syntax:
CREATE "<dev>fname",logname,fldnm1,fldnm2

"<dev>fname", is the device (A:, B: or C:) which the file is to be on, and then the file name. This all goes in quotes as a string, e.g. "a:clients". You can also assign this device and file name string to a string variable (e.g. cl$="a:clients") and then use the variable name (cl$) as the parameter. The file name may be up to 8 characters long.

logname is the logical file name. This may be A, B, C or D. You use this logical file name to refer to the file from within the program.

fldnm1, fldnm2 are the field names. There may be up to 16 fields in any record, and these may be given a qualifier, either % or $, to signify integer data or string data respectively. Fields containing floating point data need no qualifier. Field names may be up to 8 characters long including any qualifier used.

An example of a CREATE command might be:

CREATE "a:clients",B,nm$,tel$,ad1$,ad2$,ad3$

When a file has been created, it is automatically open. This means records can be saved to it immediately. It also becomes the current file which means that when you use one of the commands for manipulating records, they operate on this file.

Adding records to a file

You add records to a data file field by field. First you assign some values to the current field names, then you use the APPEND command to add them to the file.

Assigning values to field names

The field names act in a similar way to variables, and can be assigned values and used in INPUT statements. The field name must be used with the logical file name like this:
INPUT B.name$
or this
B.name$="MR Bruno"

where B is the logical file name and name$ is the name of the field. These are separated by a full stop.

Appending the fields

When you have assigned values to the fields, you add them to the open file with the APPEND command. They are always added as the last record in the data file. If the file is a new one, this will be the first record.

The APPEND command has no parameters - the field values are automatically added to the file in the correct order and format.

If you try to assign a text string to a numeric field name, an error will be reported.

See the second procedure (insert:) in program no. 6 of Chapter 8 for an example of adding records to a data file.

At any time while a data file is open, the field names currently in use can be used like any other variable - for example, in a PRINT statement, or a string or numeric expression. However, in order to operate on a particular field, you must make the record containing it the current one.

Changing the current record

Before you can erase a record or operate on the fields in it you must make it current. You can change the current record by using any of the six commands and functions below.

FIRST moves to the first record in a file.

NEXT moves to the following record in a file. If the end of the file is passed, NEXT does not report an error. The current record will then be null. This condition can be tested for with the EOF function.

BACK moves to the previous record in the file. If the current record is the first record in the file then the current record does not change.

LAST moves to the last record in the file.

POSITION moves to a particular record. For example, the instruction POSITION 3 makes record 3 (the first record is record 1) the current record. You can find the current record number by using the POS function which returns the number of the current record.

FIND moves to the record which contains a search string you specify.

Finding a record

The FIND function acts like the main menu Find, but without wild cards. The difference is that whereas the main menu Find prints the record on the screen, this function makes it the current record so that you can operate on it: editing it, erasing it, or just displaying it.

For example, the line
r%=FIND("HOLMES")
would make the first record. containing the string "HOLMES" the current record and return the number of that record to the variable r%. If the number returned is zero, the string was not found.

There is another function, FINDW, which is the same as FIND but does allow wild cards. So, to find the first record containing ORDER and 89 use this instruction:
r%=FINDW("*ORDER*89*")

The procedure called search: in program no. 6 in Chapter 8 is an example of how to find and edit records.

Erasing a record

To erase a record, make that record current by use of one of the commands FIRST, NEXT, BACK, LAST, POSITION or FIND and then use the ERASE command. This removes the current record from the file and renumbers the ones that follow.

The final procedure (erase:) in program no. 6 in Chapter 8 is an example of how to find and erase records.

Opening a file

When you first CREATE a data file it is automatically open but it closes again when the program ends. To open the file again in another program, you use the OPEN command.

The syntax of the OPEN command is the same as for the CREATE command. You must use the same device and file name as when you first created it, but when opening it in a different program, you can give it a different logical file name and give the fields different names. For example, a file which was created in one program with the command:
CREATE "c:address",D,name$,num$,add$,add2$
can be opened in another program by the command:
OPEN "c:address",A,a$,b$,c$,d$

Up to four files may be open at any one time, and these may be spread over any of the three devices. Each must be referred to in the program by a different logical name. So, if you have 4 files open. one is A one is B one is C and the other is D.

If you are going to edit or erase records in the file. you need to include all the fields you are going to operate on in the OPEN command. However, if you are just going to search for strings and display records. you only need to include the first field in the OPEN command, like this:
OPEN "c:address",A,a$

Changing the current file

When a file is created or opened, that file is then automatically the current file and all access is to that file until you say you want to USE a different one. The USE command makes a file current. You refer to the file by its logical file name. For example:
USE B

In this example the file with the logical file name B (as specified in the OPEN or CREATE command which opened it) becomes the current file. All access is now to this file until you change the current file with another USE command or OPEN or CREATE another file. If you attempt to use a file which has not previously been opened, an error is reported.

Closing and deleting a file

Data files close automatically when programs end. However, when you have finished accessing a particular file, you can close it with the CLOSE command which closes the current file.

You might want to close a file if you already have 4 files open in a program, and you want to open another, or if you want to delete the file. There is a command DELETE for deleting data files, but the file to be deleted must be closed first.

More information on data file handling

See the beginning of Chapter 9 for a summary of all the data file handling commands and functions. Then look up each one for more detail.

Look at programs 6, 7 and 8 in Chapter 8 for examples of data file handling programs.

Organiser bar.

6 Handling any type of file

Most of the file handling commands and functions are only for data files.

However there are three, COPYW, DELETEW and DIRW$, which operate on any type of file. You indicate the type of file with these extensions:
Data file and diary file
(ODB stands for Organiser Data Base.)
.ODB
OPL procedure .OPL
Notepad file .NTS
Comms Link setup file .COM
Spreadsheet file .PLN
Pager setup file .PAG
Diary file from XP or CM .DIA

There are also two extra extensions for COPYW only. Use these to copy only one part of an OPL procedure.
OPL procedure (text only) .OPT
OPL procedure (object only) .OPO

You can also use wild cards with COPYW, DELETEW and DIRW$. The wild cards are the normal ones: + is any character, * is any group of characters.

Examples

COPYW "A:*88.OPL","B:"
Copies all the OPL procedures whose names end in 88 from A: to B:

DELETEW "C:*.NTS"
Deletes an the notepad files on C:

DIRW$ ("A: R*")
Returns the name of the first file of any type on A: which starts with the letter R.

Diary files

Diary files are saved as data files. Each entry is a record with this two field format:
1989042712000100    
JAMES BIRTHDAY

in this example, the first field carries this information:
1989 The year
04 The month
27 The date
1200 The start time
01 The duration - one 15 minute interval
00 The alarm pre-warning time. In this case there is no alarm so it's zero.

You can open a saved diary file in Xfiles, and then find and update records in it. For example, you could find all your annual entries such as birthdays and change 1989 to 1990. If you then restored the diary and merged it with the current one you wouldn't need to put in all the birthday entries again at the end of the year. However, be careful to use the right format, or you won't be able to restore the diary.

You could also do this in OPL using the data file handling and string handling commands. There is a program in Chapter 8 which does this.

Psion bar.

7 Error handling

This chapter covers commonly made errors, then error trapping. There is a list of the OPL error messages in Appendix D.

Common errors

All programming languages are very particular about the way commands and functions are used, especially in the way program statements are laid out.

Below are a number of errors which are easy to make in OPL. The incorrect statements are in bold and the correct versions are on the right.

Punctuation errors

Omitting the colon between statements on a multi-statement line:
Incorrect Correct
a$="text" PRINT a$ a$="text" :PRINT a$

Omitting the colon after a called procedure name:
Incorrect Correct
proc1:
GLOBAL a,b,c
.
.
proc2
proc1:
GLOBAL a,b,c
.
.
proc2:

Omitting one or more of the colons after a label:
Incorrect Correct
proc1:
GOTO below:
.
.
below::
proc1:
GOTO below::
.
.
below::

Omitting the space before the colon between statements on a multi-statement line:
Incorrect Correct
proc1:
a$=b$:PRINT a$
proc1:
a$=b$: PRINT a$

Parameter errors Passing a floating point value to a procedure which requires an integer - here the procedure proc2:(x%).
Incorrect Correct
2*6+proc2:(PI) 2*6+proc2:(INT(PI))

This may also happen when a procedure is called from the calculator. The calculator converts all numbers to floating point, so:
Incorrect Correct
Calc:proc2:(3) Calc:proc2:(INT(3))

Passing an integer to a procedure which requires a floating point value - here the procedure proc3:(x)
Incorrect Correct
proc1:
.
.
proc3:(2/3)
proc1:
.
.
proc3:(2.0/3)

Passing the wrong number of parameters to a procedure - here, the procedure proc4:(x,y)
Incorrect Correct
proc1:
.
.
proc4:(3.7)
proc1:
.
.
proc4:(3.7,2.5)

Integer size error

OPL only allows numbers between minus 32768 and plus 32767 to be assigned to integer variables, so any expression which exceeds these limits will cause an error:
Incorrect Correct
proc1:
LOCAL a%
a%=100*2468
proc1:
LOCAL a
a=100.0*2468

Structure errors

The structures allowed within OPL are DO/UNTIL, WHILE/ENDWH and IF/ELSEIF/ELSE/ENDIF. These may all be nested within one another to up to eight structures deep. Attempting to nest to a greater depth than this will cause an error. Mixing up the three structures e.g. by matching up DO with WHILE, will also cause an error:
Incorrect Correct
proc1:
.
DO
.
WHILE a<2
proc1:
.
DO
.
UNTIL a>=2

Run-time errors

If an error occurs when you run a program, the program stops and an error message is displayed.

If you are running the procedure from the main menu, just press SPACE to return to the menu.

However if you are running it from the Prog menu, you get a chance to edit it. Here a call has been made to a procedure called subproc: which does not exist:
    MISSING PROC
      SUBPROC
....................
Edit A:MAINPROC Y/N

Press Y if you want to edit the procedure. If the source code is available, you are returned to the OPL editor to correct the offending line. Press N or ON/CLEAR if you don't want to edit it.

When you have done this, press MODE to get the editor menu, and either translate, save or quit the procedure. If you quit, the edits you have made this session are abandoned.

Error trapping

In the case of the run time errors described above, the program stops to display the error message. There are ways of avoiding this by trapping errors and dealing with them yourself within the program - but they put you in full control and must be used carefully. The tools used to control errors are ONERR, TRAP, ERR, ERR$ and RAISE:

ONERR label:: and ONERR OFF

ONERR, is used to redirect program control to a label if an error occurs. This is useful if you want to provide your own error handling routine, such as printing out a message for an error you anticipate. ONERR is followed by the label name which ends in two colons. The label itself can be either in the same procedure or in a procedure before it in the same program.

In the example below, LPRINT is being used to print to an attached printer. Normally, when no printer is connected, the message DEVICE MISSING is displayed. Here a more precise message, CONNECT PRINTER, is supplied by the programmer:

ONERR noprint::
LPRINT "Dear Sir"
RETURN
noprint::
ONERR OFF :IF ERR=194 :RAISE ERR :ENDIF
PRINT "CONNECT PRINTER"
GET

The first line causes a jump to the label noprint:: if an error occurs. If no printer is connected, the LPRINT command causes such a jump and the message "CONNECT PRINTER" is displayed. If there is one connected, "Dear Sir" is printed and the lines after the label are never run, because of the RETURN before it.

ONERR OFF

Notice that the first instruction after the label noprint:: is ONERR OFF. This is very important because if you don't do this after the ONERR label:: command is used, all subsequent program errors - even in other procedures called - result in the program being directed to the same label. This diagram illustrates how two completely different errors cause a jump to the same label, and cause the same explanatory message to be printed out:

proc1:
onerr label::
....
a=log(- 1)
....
label::
PRINT "Explanation of log error"
....

Risks of ONERR label::

As all errors go back to the same label unless you switch ONERR OFF, it is very easy to create an endless loop by mistake. If this happens, you cannot press ON/CLEAR and Q to break out, as this just makes control go to the label as any other error would - so, you have to take the battery out of the Organiser, and lose everything in the internal memory. To avoid this:

TRAP

TRAP traps errors on a specified command only, so it doesn't need to be cancelled like ONERR does. It can be used with any of the commands listed below:

APPEND BACK CLOSE COPY COPYW CREATE DELETE DELETEW ERASE EDIT FIRST INPUT LAST NEXT OPEN POSITION RENAME UPDATE USE

The trap command immediately precedes any of these commands, separated from it by a space - e.g:
TRAP INPUT a%

When INPUT is used without TRAP and a text string is entered when a number is required, the display just scrolls up and a question mark is shown, prompting for another - valid - entry. When you put TRAP in front of INPUT, the command is executed in the usual way, but if an error occurs the next line of the program is executed as if there had been no error. The next line in the example below is a helpful message.

proc:
LOCAL a%
start::
PRINT "ENTER AGE",
TRAP INPUT a%
IF ERR=252
PRINT "NUMBER NOT WORD"
GOTO start::
ENDIF

The example above also uses the ERR function.

The ERR function

When errors occur in a program, the number of the error is accessible by using the ERR function. This means that you can be absolutely sure which error you are dealing with.

The anticipated error in the lines below is 246 (NO PACK). If 246 occurs when trying to open the MAIN file on pack B: a helpful error message is printed out. However, just in case a different error occurs, the next lines make sure that the standard error message for that error is printed.

TRAP OPEN "B:MAIN",A,a$
IF ERR=246
PRINT "NO PACK IN B:"
ELSEIF ERR
PRINT ERR$(ERR)
ENDIF

The OPL error messages are listed in Appendix D.

RAISE

If you are using commands to trap errors and handle them yourself, then at some during the development stage of your program you need to test your error handling routines. An easy way to do this is with the RAISE command. You can generate an error that you think might occur when the program is in use, and see if the error handling routine takes care of it in the way you anticipate. For example, this statement causes the NO PACK error to be generated:

RAISE 246

Organiser bar.

8 Example programs

This chapter contains example programs written in OPL. The programs are not intended to demonstrate all the features of OPL, but they should give you a few hints. To find out more about a particular command or function, refer to Chapter 9. Each of the procedures must be entered separately, you can't enter two procedures in one block. Chapter 1 explains how to type in, translate, save and run a procedure.

Uppercase, lowercase

In the listings here, variables are shown in lowercase and commands and functions in uppercase. However, it doesn't matter which you use when you enter procedures into the Organiser:

Spaces

Be careful to type in the necessary spaces:

Remarks

Lines beginning with the command REM are remarks; they are there to explain things. They do not affect the way a procedure runs and you don't have to type them in if you don't want to.

1 Days (Version 1)

This procedure works out the number of days you've been alive. Substitute your own date of birth for the one supplied here.

days1:
LOCAL birth%,now%,answer%
birth%=DAYS(14,6,1957)
now%=DAYS(DAY,MONTH,YEAR)
answer%=now%-birth%
PRINT answer% :GET

Variables
birth% is your date of birth.
now% is the current date.
answer% is the current date minus your date of birth.

Date functions
All the OPL date functions return values accessed from the Organiser clock and calendar. The function DAYS returns the number of days since the beginning of the calendar on a particular date.

2 Days (Version 2)

This procedure is a more flexible one which works out the number of days between boy two dates that you input. When you run the procedure you are prompted to enter the day, month and year of the first date then the second date.

days2:
LOCAL d1%, m1%, y1%, d2%, m2% y2%
PRINT "ENTER FIRST DAY"
INPUT d1%
PRINT "ENTER FIRST MONTH"
INPUT m1%
PRINT "ENTER FIRST YEAR"
INPUT y1%
PRINT "ENTER SECOND DAY"
INPUT d2%
PRINT "ENTER SECOND MONTH"
INPUT m2%
PRINT "ENTER SECOND YEAR"
INPUT y2%
PRINT DAYS(d2%,m2%,y2%)-DAYS(d1%,m1%,y1%)
GET

Variables
d1%, m1% and y1% are the day month and year of the first date.
d2%, m2% and y2% are the day month and year of the second date.

3 Dice

This procedure turns the Organiser into a dice. When the program is run, a message is displayed saying that the dice is rolling. You then press S to stop it. A random number from one to six is displayed and you choose whether to roll again or not.

dice:
LOCAL dice%,key%
KSTAT 1
top::
CLS :PRINT "****DICE ROLLING****"
PRINT "PRESS S TO STOP"
DO
 dice%=(RND*6+1)
UNTIL KEY$="S"
CLS
PRINT "********* ";dice%;" ********"
BEEP 50,100
AT 1,4 :PRINT "ROLL AGAIN Y/N"
label::
key%=GET
IF key%=%Y
 GOTO top::
ELSEIF key%=%N
 RETURN
ELSE
 GOTO label::
ENDIF

Variables
dice% is a random number from 1 to 6.
key% is the ASCII value of the keypress read by the GET function.

Random numbers
This is how dice: displays 1, 2, 3, 4, 5 or 6 randomly. The RND function returns a random floating point number, between 0 and 1 (not including 1). It is then multiplied by 6 and 1 is added (so that you get numbers from 1 to 6 instead of from 0 to 5). It is rounded down to a whole number by assigning to the integer dice%.

Identifying keypresses of Y and N
The ASCII value of the character on the key you press is returned by the GET function and assigned to key%. In OPL you get the ASCII value of any character by putting % in front of it, so %Y is the ASCII value of Y and %N is the ASCII value of N. In case you had the keyboard set to lowercase, KSTAT 1 is used at the start of the procedure to set it to uppercase, so that you are not pressing y and n.

4 Mortgage calculator

This program calculates monthly mortgage payments. When you run it, you have to enter the amount of the loan, the interest rate and the term in years. Then you specify the source of the loan by selecting from a menu. The program does not allow for tax relief.

The program is made up to two procedures - mortgage: and q: - a general input routine. You must type each one in separately. The input routine could be called by any procedure which needs to prompt the user to enter a floating point number.

mortgage:
LOCAL num%,loan,x,term,rate,pay,ques$(2)
CLS
PRINT "EVALUATE MONTHLY"
PRINT "MORTGAGE PAYMENT"
PAUSE 30
ques$=CHR$(63)+" "
REM CHR$(63) is a "?" - See Appendix A
loan=q:("ENTER LOAN"+ques$)
DO
 rate=q:("INTEREST RATE % "+ques$)
UNTIL rate>0 AND rate<99
DO
 term=q:("ENTER TERM (YRS)"+ques$)
UNTIL term>.5 AND term<100
num%=MENU("BUILDING-SOCIETY,BANK,OTHER")
IF num%=0 :RETURN :ENDIF
rate=rate/100 :x=1+11*(num%/2)
pay=loan*rate/12/(1-((1+rate/x)**(-x*term)))
CLS :PRINT "MONTHLY PAYMENT"
PRINT FIX$(pay, 2, -8)
GET
RETURN

q:(a$)
LOCAL z
CLS :PRINT a$,CHR$(16);
INPUT z
CLS
RETURN(z)

Variables
loan, term, rate and pay are the amounts of the loan, the term in years, the interest rate and the monthly payments.
ques$ is "? ".
num% is the number of the menu item you choose.
x is a variable used in the calculation at the end.
z is the variable used for the values you input when q: is running.

Calling q:
The main procedure mortgage: calls q: three times, passing it a string to be printed out as a prompt. The text string is passed to q: as a parameter. The values returned are assigned to the variables loan, rate, and term.

How "?" is printed
The CHR$ function converts the ASCII value 63 to the question mark character.

How the input routine beeps
The CHR$ function converts the value 16 to the beep control character. This is then "printed" out.

5 Chase Game

The next two procedures make up a game which demonstrates the use of user defined graphics (UDG's). Each procedure must be typed in separately.

The object of the game is to avoid being caught by the pursuers. Your movable man can jump up and down: press the X key to jump down and the S key to jump up. At the end of the game your score will be displayed on the screen.

To pause the game, press ON/CLEAR and to restart press it again. To quit out of the game press ON/CLEAR then Q.

game:
LOCAL e$ (2)
LOCAL a%,b%,b1%,c%,c1%,x%,y%,i%,sc%
graphic: :CURSOR OFF
e$=CHR$(4)+CHR$(6)
b%=20 :c%=12 :x%=3 :y%=1
DO
 CLS :PRINT REPT$(CHR$(158),80)
 AT x%,y% :PRINT CHR$(7)
 a%=1 :c%=1+RND*4
 DO :c1%=1+RND*4 :UNTIL c%<>c1%
 DO
  AT a%,c% :PRINT CHR$(0)
  AT a%,c1% :PRINT CHR$(0) :BEEP b%,10*b%
  AT a%,c% :PRINT CHR$(1)
  AT a%,c1% :PRINT CHR$(1) :BEEP b%,10*b%
  AT a%,c% :PRINT CHR$(2)
  AT a%,c1% :PRINT CHR$(2) :BEEP b%,10*b%
  AT a%,c% :PRINT CHR$(3)
  AT a%,c1% :PRINT CHR$(3) :BEEP b%,10*b%
  AT a%,c% :PRINT e$
  AT a%,c1% :PRINT e$ :BEEP b%,10*b%
  AT a%,c% :PRINT CHR$(5)
  AT a%,c1% :PRINT CHR$(5) :BEEP b%,10*b%
  AT a%,c% :PRINT " " :AT a%,c1% :PRINT " "
  i%=KEY
  IF i%
   IF i%=%S AND y%>1
    AT x%,y% :PRINT CHR$(158)
    y%=y%-1 :AT x%,y% :PRINT CHR$(7)
   ENDIF
   IF i%=%X AND y%<4
    AT x%,y% :PRINT CHR$(158)
    y%=y%+1 :AT x%,y% :PRINT CHR$(7)
   ENDIF
  ENDIF
  a%=a%+1
  IF a%=x% AND (c%=y% OR c1%=y%) :REM Hit
   i%=0
   DO
    AT x%,y% :PRINT CHR$(170+i%)
    BEEP 10,100+i%
    i%=i%+1
    BEEP 10,100-i%
   UNTIL i%=30
   b%=b%+5 :a%=20 :x%=x%+2
   IF x%>20
    CLS :PRINT "GAME OVER"
    PRINT "SCORE:",sc% :PAUSE 40
    WHILE KEY :ENDWH :REM Drain buffer
    GET :RETURN
   ENDIF
  ENDIF
 UNTIL a%=20
 sc%=sc%+1
 IF b%>12
  b%=b%-2
 ELSEIF b%<6
  IF b1%
   b%=b%-1 :b1%=0
  ELSE
   b1%=1
  ENDIF
 ELSE
  b%=b%-1
 ENDIF
UNTIL 0

graphic:
UDG 0,0,0,28,30,30,30,28,0
UDG 1,0,0,14,31,30,31,14,0
UDG 2,0,0,7,14,12,14,7,0
UDG 3,0,0,3,7,6,7,3,0
UDG 4,0,0,1,3,3,1,0,0
UDG 5,0,0,0,1,1,1,0,0
UDG 6,0,0,24,16,4,16,24,0
UDG 7,30,14,4,14,30,14,11,25

User defined graphics
The main procedure, game:, calls graphic: which then uses the UDG command 8 times to set up the graphics characters used in the game. The last UDG command is the one which sets up the little man. User defined characters are explained in Appendix A.

6 Data file handling procedures

The main procedure below creates a data file called addr on device A, to contain names, addresses, post codes and telephone numbers. It is followed by 4 other procedures which allow you to insert. search for, alter and erase records in the file. When you run files:, a menu giving you a choice of these options is displayed.

Each of these 5 procedures must be typed in separately.

files:
LOCAL m%
IF NOT EXIST("A:addr")
 CREATE "A:ADDR",A,n$,ad1$,ad2$,ad3$,pc$,tel$
ELSE
 OPEN " A:ADDR",A,n$,ad1$,ad2$,ad3$,pc$,tel$
ENDIF
DO
 m%=MENU("INSERT,SEARCH,ALTER,ERASE,QUIT")
 IF m%=0 or m%=5 :STOP
 ELSEIF m%=1 :insert:
 ELSEIF m%=2 :search:
 ELSEIF m%=3 :alter:
 ELSEIF m%=4 :erase:
 ENDIF
UNTIL 0 :REM do loop forever

insert:
PRINT "ENTER NAME" :INPUT A.n$
CLS :PRINT "ENTER STREET" :INPUT A.adl$
CLS :PRINT "ENTER TOWN" :INPUT A.ad2$
CLS :PRINT "ENTER COUNTY" :INPUT A.ad3$
CLS :PRINT "ENTER PCODE" :INPUT A.pc$
CLS :PRINT "ENTER TELNUM" :INPUT A.tel$
APPEND

search:
LOCAL recnum%,search$(30)
top::
FIRST :CLS :PRINT "FIND:";
TRAP INPUT search$
IF ERR=206
 RETURN
ENDIF
recnum%=FIND(search$)
IF recnum%=0
 CLS :PRINT "NOT FOUND" :PAUSE 20
 GOTO top::
ENDIF
DO
 DISP(-1,"") :NEXT :recnum%=FIND(search$)
 IF recnum%=0 :CLS
  PRINT " NO MORE ENTRIES"
  PAUSE 20 :RETURN
 ENDIF
UNTIL 0

alter:
LOCAL recnum%,search$(30),k%
DO
 FIRST :CLS
 PRINT "ALTER:"; :TRAP INPUT search$
 IF ERR=206 :RETURN :ENDIF
 recnum%=FIND(search$)
 IF recnum%=0
  CLS :PRINT "NOT FOUND"
  PAUSE 20 :CONTINUE
 ENDIF
 DO
  KSTAT 1 :CLS :AT 1,2 :PRINT "EDIT Y/N"
  k%=VIEW(1,A.n$)
  IF k%=%Y :CLS
   EDIT A.n$ :EDIT A.ad1$
   EDIT A.ad2$ :EDIT A.ad3$
   EDIT A.pc$ :EDIT A.tel$ :UPDATE :RETURN
  ELSEIF k%=%N :NEXT :recnum%=FIND(search$)
   IF recnum%=0
    CLS :PRINT "NOT FOUND" :PAUSE 20 :BREAK
   ENDIF
  ENDIF
 UNTIL 0
UNTIL 0

erase:
LOCAL recnum%,search$(30),k%
FIRST :CLS :PRINT "ERASE:";
TRAP INPUT search$
IF ERR=206 :RETURN :ENDIF
recnum%=FIND(search$)
DO
 IF recnum%=0
  CLS :PRINT "NOT FOUND" :PAUSE 20 :RETURN
 ENDIF
 ask::
 KSTAT 1 :AT 1,2 :PRINT "ERASE Y/N"
 k%=VIEW(1,A.n$)
 IF k%<>%Y AND k%<>%N
  GOTO ask::
 ELSEIF k%=%Y
  ERASE
 ELSEIF k%=%N
  NEXT :recnum%=FIND(search$)
 ENDIF
UNTIL EOF

Variables
m% is the number of the menu item you select
recnum% is the record number returned by FIND.
search$ is the search clue you enter.
k% is the ASCII value of the key you press whilst the found record is displayed. (%Y is the ASCII value of Y, %N is the ASCII value of N.)

Creating the data file
The first procedure, files:, creates or opens a file called addr on device A: with 6 fields for each record. The six field names are n$ for the name, ad1$, ad2$, and ad$3 for each line of the address, pc$ for the post code and tel$ for the phone number. The file is given the logical name A.

Inserting records
Notice how in insert: the 6 fields of the record are input one by one. The field names are used like variables and preceded by the logical file name (A) and a full stop. Then the APPEND command is used; this is necessary to actually add the record to the end of the file.

Displaying the current record
When a record containing a particular string has been found by FIND it becomes the current record. DISP with -1 as the first parameter, displays it.

In the procedure alter:, VIEW is used to display just the first field of the record, while you decide whether to edit it. If you choose to, each field is then displayed by the EDIT function, which allows you to alter what is on the screen.

7 Telephone logging (data file handling)

These 3 procedures make up a program which allows you to log telephone calls. It stores their duration with your comments in a data file. It could easily be adapted to record the duration of any other activity.

The first procedure displays a menu like this with a phone UDG and the clock. Because the clock uses UDGs 3, 4, 5, 6, 7 and 1, UDGs 0 and 2 been used for the phone and the underline:
X             11:19a
Logcall   Viewcalls

 

logger:
LOCAL m%
UDG 0,31,21,14,10,31,31,0,31
UDG 2,0,0,0,0,0,0,0,31
PRINT CHR$(0);REPT$(CHR$(2),14)
CLOCK(1)
m%=MENUN(2,"Logcall,Viewcalls")
IF m%=0
  RETURN
ELSEIF m%=1
  logcall:
ELSE m%=2
  viewcall:
ENDIF

logcall:
LOCAL k%,h%,m%,s%,sec%,start$(8),swof%
ESCAPE OFF
IF NOT EXIST("A:LOG")
 CREATE "A:LOG",B,date$,t$,comment$
ELSE
 OPEN "A:LOG",B,date$,t$,comment$
ENDIF
swof%=PEEKB($7C)
POKEB $007C,0 :REM no auto turn off
B.t$="00:00:00"
B.date$=DATIM$
start$=RIGHT$(DATIM$,8)
h%=0 :m%=0 :s%=0 :sec%=SECOND
DO
 IF sec%<>SECOND
  BEEP 1,100
  sec%=SECOND :s%=s%+1
  IF s%=60 :s%=0 :m%=m%+1
   IF m%=60 :m%=0 :h%=h%+1
   ENDIF
  ENDIF
  B.t$=REPT$("0",-(h%<10))+NUM$(h%,2)+":"
  B.t$=B.t$+REPT$("0",-(M%<10))+NUM$(m%,2)+
  B.t$=B.t$+REPT$("0",-(s%<10))+NUM$(s%,2)
 ENDIF
 AT 1,1 :PRINT "Started:";start$
 PRINT "Time:";B.t$
 PAUSE -1 :REM save battery
 k%=KEY
 IF k%=1
  GOTO exit::
 ENDIF
UNTIL k%=13
PRINT "Comments "+CHR$(63)
TRAP INPUT B.comment$
IF ERR=206
 GOTO exit::
ENDIF
CLS :PRINT "Saving" :APPEND
exit::
CLOSE
POKEB $007C,swof% :REM restore auto turn off
ESCAPE ON

viewcall:
LOCAL k%
TRAP OPEN "A:LOG",B,date$,t$,comment$
IF ERR :RETURN :ENDIF
DO
 k%=DISP (-1,"")
 NEXT
 IF k%=1
  RETURN
 ENDIF
UNTIL EOF
AT 1,4 :PRINT "  NO MORE ENTRIES"
PAUSE 25
RETURN

Variables
k% is used for the keypresses read by KEY.
sec% is the actual number of seconds past the minute at the start of the procedure.
s% is the seconds shown counting up.
m% is the minutes shown counting up.
h% is the minutes shown counting up.
start$ is the time the call started.
swof$ is the initial setting of auto-switch off.

The data file
A data file called LOG is created on device A: with three fields in each record: one for the date, one for the time which the call took, and one for your comments. When you run the program and log a call the date, time and comment are appended to the data file as a record. Select Viewcalls to look through all the appended records.
You could also open the file A LOG in Xfiles, and search for the records using Find.

How the stopwatch counts up the elapsed time
The actual number of seconds past the minute is read from the system clock by the SECOND function and assigned to sec% at the start. Then the SECOND function is used again, and if it is no longer equal to sec% - i.e. when a second has elapsed - 1 is added to s% and sec% is assigned the actual number of seconds again. Whenever s% is 60, it is reset to 0 and 1 is added to m% and so on.

ESCAPE OFF
Normally ON/CLEAR pauses the execution of a procedure so that Q can be pressed to quit. At the start of this procedure ESCAPE OFF is used. This means that ON/CLEAR does not pause the program, and can thus be read by the KEY and DISP functions and cause a RETURN to the PROG menu instead of an ESCAPE error.

Displaying the records
When A:LOG is opened in viewcall, the first record is made current then displayed by DISP with - 1 as the first parameter. Then NEXT is used to make each record current in turn.

8 Diary file handling procedure

If you save your diary to a file, you can use this program to copy all the birthdays from one year to the next year. It could easily be adapted to deal with other annual entries too.

A saved diary file called "olddia" is opened. All the records containing 1989 and birthday have the 1989 changed to 1990 and are put into a file called "newdia". You can then restore "newdia" into your current diary and merge the two.

Remember to substitute the name of your saved diary for "olddia" when you type the procedure in.

birthday:
CREATE "a:newdia",B,date$,text$
OPEN "a:olddia",A,date$,text$
WHILE FINDW("1989*birthday")
 PRINT a.text$
 b.date$="1990"+mid$(a.date$,5,255)
 b.text$=a.text$
 USE B :APPEND
 USE A :NEXT
ENDWH
GET

Diary files
A saved diary file is just like any other data file. However, for more information about the format of records in diary files, see Chapter 6.

Psion bar.

These pages should be viewed using Netscape 4.03 or Microsoft Internet Explorer 3.02 at 800x600 pixels.
Left arrow LZ Prog. Manual Chpt. 1-2 ~ Homepage ~ LZ Prog. Manual Chpt. 9 Right arrow