Jaap's Psion II Page

OPL Programming tips, tricks, and curiosities

ACOS ASIN Bitwise logical operators Creating a bar graph (LZ) The CLOCK Function (LZ) Current Record number EDIT - The cursor position EDIT/INPUT - input length The EOF flag Filenames without a pack name Integers - hexadecimal notation Integers - key notation The KEY Function Clearing the keyboard buffer The LOC Function Logical filenames The MENU Function The MID$ Function Pack information Percentage operators (LZ) The POSITION Command The RAISE Command String comparisons UDGs Unnamed fields The VIEW Function


On the CM/XP you can use the formula:



On the CM/XP you can use the formula:


Bitwise logical operators

Using bitwise operations can sometimes be very useful. Here are some neat examples:

Adds 1 to X% if K% equals 13, but does nothing otherwise.
X%=X%+(10 AND K%=13)
Adds 10 to X% if K% equals 13, but does nothing otherwise.
X%=X%+1-(10 AND X%=10)
Increments X% but wraps back to 1 if it has already reached 10.
Is true if X% is odd (and hence has the lowest bit set), and false otherwise.
Is true unless X% is a multiple of 8 (and hence has the 3 lowest bits clear), and false otherwise.
IF X% AND -8
Is true if X% lies outside the range 0-7, and false otherwise.
X%=(X%+1) AND 7
Increases X% modulo 8.
X%=((X%-1) OR 7)+1
Increases X% to the next multiple of 8.

Creating a bar graph (LZ)

The following program creates a bar graph on the LZ, just like in the utils/info option.

LOCAL A%(3) A%(1)=$3FA2 A%(2)=$CE21 A%(3)=$8739 P1%=10 :REM This is the percentage of the left bar P2%=20 :REM This is the percentage of the right bar AT 1,3 PRINT USR$(ADDR(A%()),P1%*256+P2%);

The program may redefine all UDG's to make the bar graph.

The CLOCK Function (LZ)

Note that CLS or any other display commands do not change the status of the clock, so if the clock is active the UDG's are still changed despite not being desplayed on the screen. This allows you to remove the clock from the standard position on the screen and display it somewhere else.

If you want to use the udg's for something else, you must issue the CLOCK(0) command first.

Current Record number

If moved out of range (except by using BACK) then the current record number becomes 1 more than the number of records in the file, and EOF is set, e.g. if POSITION is executed with a negative integer, or an integer larger than the number of record in the file.

However, a CM/XP with a ROM version 2.6 or earlier will not always change the current record number in these cases, though it will of course set EOF.

EDIT - The cursor position

One of the most irritating things about the EDIT instruction is that the cursor always starts at the beginning of the string to be edited. To go to the end of the string you then always have to press the down- arrow key.

To overcome this problem, you can put the down-arrow key into the keyboard buffer beforehand. For this the following sequence is required:

POKEW $73,$0100 POKEB $20B0,4 POKEW $73,$0001

The first poke sets the pointer into the keyboard buffer to byte 1 (the bytes $20B0 to $20BF in the buffer are numbered 0 to 15) and sets the number of keys in the buffer to zero. These measures ensure that the keypress that is about to be stored will not be overwritten by any real keypress at the same time.

The second poke stores the keypress (4 is the code for the down-arrow key) in the first byte of the buffer.

The final poke sets the pointer to the stored keypress, and sets the number of keys in the buffer to one.

Similarly, to store 2 keypresses, use

POKEW $73,$0200 POKEB $20B0,KEY1% POKEB $20B1,KEY2% POKEW $73,$0002

where KEY1% and KEY2% are the character codes for the two keypresses.

EDIT/INPUT - input length

Another feature of EDIT/INPUT is that the editor allows you to type as many characters as there is space declared in the string variable you are editing. For example, after LOCAL A$(10) the instruction, EDIT A$ or INPUT A$ will allow up to 10 characters to be given.

If you want to edit several times with different lengths, you can of course declare a different length string each time. Instead, you can also change the declared length of the variable, as the following example shows:

LOCAL A$(255),L% PRINT "Length:"; INPUT L% POKEB ADDR(A$)-1,L% PRINT "String:"; INPUT A$

Never make the new length larger than it was originally declared, or the other variables will be corrupted.

The EOF flag

This is set whenever the current record position is no longer in range, i.e. when it falls outside the current file.

BACK:When performed on the first record, nothing happens. EOF will NOT be set!
CREATE:The EOF will always be set since the first record is made current and there is no first record yet.
ERASE:The current record number doesn't change, so the EOF flag is set when ERASE is performed on the last record of the file.
FIRST:The EOF flag is set if there are no records in the file.
LAST:The EOF flag is set if there are no records in the file.
NEXT:The EOF flag is set if NEXT is performed when the last record is current.
POSITION:The EOF flag is set if the given integer is negative, or larger than the number of records in the file.
FIND:The EOF flag is set if there are no further matching records in the file.

Filenames without a pack name

If no pack name is included with the filename in OPEN, CREATE, DELETE, then the pack is assumed to be the last pack used (the same pack as the last accessed file or procedure).

On CM/XP machines with a Rom version before 3.1, you must include a pack name, because otherwise the pack chosen may be unpredictable. It is therefore recommended never to omit the pack name if your software is to run on other organisers.

Integers - hexadecimal notation

The prefix $ is used for hexadecimal numbers. For example $100 is 256 and $FFFF is -1. This is useful for machine code programming for example when loading a short program into an integer array. Also, by writing all negative numbers in this way, the object code of the procedure will be shorter because normally negative numbers are stored as positive ones which are then negated, and using this notation removes that negate instruction. This will also make it run very slightly faster.

Integers - key notation

The prefix % before any character denotes the ASCII value of that character. For example %A is denotes 65, %% denotes 37. This conversion is done during the translation of a procedure, so it will be shorter and run faster than when you use ASC("A") because the latter is computed during the running of the procedure.

The KEY Function

If the ESCAPE OFF command has been issued, then KEY will detect the ON/Clear key, and return its value 1. If not, then KEY will only detect the ON/Clear key if it is pressed at the exact moment that KEY is being executed.

Clearing the keyboard buffer

To clear the keyboard buffer, use something like this:


The pause statement ensures that the user stops what he is doing to read the message, after which the WHILE loop clears the keyboard buffer, and the GET waits for the first keypress after the user has read the message.

The LOC Function

This is case independent: LOC("Hello world","L") is 3

Logical filenames

After the CLOSE command, if there are any other files open, the one with the lowest logical filename becomes the current file.

On a CM/XP with Rom version 2.6 or earlier, logical filename D should be avoided due to bugs.

The MENU Function

The screen is cleared both before and after the menu is displayed.

To have spaces in a menu item, use the ASCII 254 character which is also displayed as blank. For example:

M%=MENU("PACK"+CHR$(254)+"B: PACK"+CHR$(254)+"C:")

On LZ's, menu items are displayed in lower case with initial capital letters. To avoid this, set the system variable mnb_cptz ($209C) to 1.

On a CM/XP with Rom version 2.6 or earlier, MENU will hang the machine if there is a menu item with more than 16 characters.

The MID$ Function

Length can be longer than given string, e.g. MID$("Hello world",3,255) is "llo world" Since no string can be longer than 255 chars, this is useful as an alternative to RIGHT$ if you don't want to calculate the length.

Pack information

Every organiser pack automatically has the MAIN file placed on it during sizing, so to test whether a slot contains a pack, you can use the following simple method:

IF EXIST("B:MAIN") print "Slot B: contains a pack."; ELSE print "Slot B: has no a pack."; ENDIF

Once a you open a file on a pack, the SPACE function will return the number of bytes that you still have available. For example:


Any opened file will do, and if you have several files open the current file is used (see the USE command). It is interesting to note that you can use pack D: as well, which gives the unused space in the Comms link ROM (or other peripheral device).

The sizes of packs B:, C: and D: are given (in Kbytes) by the expressions
8*PEEKB($20EC), and
respectively. A value of 0 means that no pack is inserted in that slot, or at least that it hasn't been detected/booted yet. These expressions can be typed into the calculator for a quick size check, but do make sure that the pack has been detected by first pressing ON in the main menu to boot the devices.

Peripherals like the comms link, always have a ROM software version number. This is encoded in the same way as the Psions own ROM version number, viz. as a binary coded decimal. It can be found at address $20F9, so the following OPL fragment will show it properly:

PRINT "Device ROM" PRINT "Version:"; PRINT LEFT$(HEX$(PEEKB($20F9)),1);".";RIGHT$(HEX$(PEEKB($20F9)),1);

Percentage operators (LZ)

The LZ operators -% +% *% /% <% >% do not have a fixed priority. It seems that these are performed at whatever moment they are encountered, reading from left to right.

The POSITION Command

The commands POSITION 0, POSITION 1 and FIRST have the same effect.

The RAISE Command

A strange feature of the RAISE instruction occurs when you try RAISE 0. This stops the execution of the program, but since 0 means that no error has occured it behaves just like the STOP instruction. All this is assuming that there is no error trapping active. If there was an ON ERROR instruction beforehand then, unlike STOP, the error routine is called but with ERR equal to zero of course.

String comparisons

On CM models with Rom version below 2.6 strings are compared case independently.


On an LZ it is easy to define a udg, by using the UDG command. The POKE method is nevertheless still useful on the LZ because it is possible to change only part of a UDG. For example:

POKEB $180,64+3*8+4 :REM This is halfway down udg 3. POKEB $181,31 POKEB $181,31 POKEB $181,31 POKEB $181,31

This blacks out the bottom half of udg 3.

Another example is to use a POKEB in a loop to update all the udg's from an array:

A%=1 POKEB $180,64 DO POKEB $181,U%(A%) A%=A%+1 UNTIL A%>64

This is much shorter than if you use UDG commands.

Unnamed fields

When a file is opened, then you need not name all the fields. If fields in the current record are used but are not named, then you cannot access or change them directly, but they will be saved if you use an UPDATE or APPEND command.

For example, suppose A:MAIN has the following first record:


Now execute these lines:


Now a new record has been appended:


With the UPDATE command this is usually desirable, but not so with the APPEND command. To overcome this, you can do the following instead:


The position command places the current record out of range, and therefore clears the field variables, whether they are named or not.

The VIEW Function

On a CM with a version 2.4 Rom, VIEW(1,"") does not work properly, and a string of length 255 will not scroll.

On an LZ with a version 4.2 Rom, VIEW(1,"") will crash the machine.

The VIEW instruction contains a minor bug, or rather an oversight. When PRINTing to the screen, the ROM normally intercepts characters with codes between 8 and 31 and performs whatever function is associated with them. The VIEW instruction doesn't do this, and passes these codes directly to the LCD. If you do this with the codes between 8 and 15, they will show on screen as the UDG's 0 to 7.