OPO.FMT

PSIONICS FILE - OPO.FMT
=======================
Format of OPO and OPA files
Last modified 1998-02-28
===========================
[Much of the information in this file is supplied by Mike Rudin
<mrudin@cix.co.uk>, author of Revtran. My thanks to Mike, who will answer
questions, but asks that if you find things unclear, first try analysing
fragments of real q-code and working it out for yourself.]


File format
-----------
[Because this is somewhat different to normal, a summary is included at
the end.]

An OPO file (OPL translator output) consists of two headers, one or more
procedure bodies, and then the procedure table. An OPA file differs in
that there are one or more embedded files between the two headers.

The first header has a varying length and the following format:

  Offset  0 (cstr): "OPLObjectFile**"
  Offset 16 (word): format version (currently 1)
  Offset 18 (word): offset in file of second header
  Offset 20 (qstr): source filename

The source filename may have a trailing zero (which will be included in
the size of the qstr).

In an OPA file, the embedded files are each preceeded by a word giving
the length of the file. There are normally two embedded files: a PIC file
holding the icon, and an OPA information file (always 36 bytes):

  Offset  0 to  13: name, a dot, and then the extension
  Offset 14 to  33: path
  Offset 34 (word): type

This information is taken from the APP ... ENDA section; the first two
fields are padding out with zero bytes.

The second header is 12 bytes long and has the following format:

  Offset  0 (long): file length
  Offset  4 (word): translator version (S3t: $110F, S3a: $111F)
  Offset  6 (word): required runtime version ($110F, $111F)
  Offset  8 (long): offset of procedure table within the file

The translator and runtime versions seen are $110F for the Series 3t, and
$111F for the Series 3a.

The procedure table consists of one or more entries; the first one is
always the procedure that is executed at the start of the program. Each
entry has the format:

  Offset   0 (qstr): procedure name without trailing colon (length L)
  Offset L+1 (long): offset of procedure within the file
  Offset L+5 (word): line number in the source of the PROC line, minus 1

The table is ended by a zero-length name (always followed by another zero
byte, which will be the last byte in the file).

Each procedure body consists of a declarations block followed by the
"q-code" that is actually executed. The declarations block consists of:
  * Optimisation section (translator version $111F and above only)
  * Space control section
  * Parameter section
  * Global declaration section
  * Called procedures section
  * Global references section
  * String control section
  * Array control section

Various sections refer to a "variable type code". This is a byte describing
the type of a variable:
    bits 0 to 6: 0 = word, 1 = long, 2 = real, 3 = string
    bit 7:       set for an array, clear for a simple variable

The optimisation section consists of a single word, giving the length of
the rest of the declarations block.

The space control section has the format:
  Offset  0 (word): size of data stack frame
  Offset  2 (word): length of q-code
  Offset  4 (word): amount of dynamic stack used

The parameter section consists of a byte giving the number of parameters,
followed by one variable type code per parameter, in reverse order.

The global declaration section describes the global variables defined in
the procedure, and has the format:
  Offset  0 (word): size of rest of section (S):
  Offset  2 to S+1: zero or more per-variable blocks
Each per-variable block has the format:
  Offset   0 (qstr): name including suffix (length L)
  Offset L+1 (byte): variable type code
  Offset L+2 (word): offset within the data stack frame

The called procedure section lists all the procedures called by this one.
It consists of a word giving the length of the rest of the section, followed
by zero or more blocks, each consisting of a qstr (the name, excluding the
trailing colon) followed by a byte (the number of arguments).

The global references section lists all the global variables called but not
declared by this procedure. It consists of a list of blocks, each consisting
of a qstr (the name including the suffix) and a variable type code; the list
is ended by a zero byte (forming an empty qstr).

The string control section consists of a block for each string variable
declared by the procedure. The block has the format:
    Offset  0 (word): location in the data stack frame
    Offset  2 (byte): maximum length of the string
It is terminated by a zero word.

The array control section consists of a block for each array variable
declared by the procedure. The block has the format:
    Offset  0 (word): location in the data stack frame
    Offset  2 (word): number of elements
It is terminated by a zero word.


Summary of file format
----------------------
A file consists of the following items:
- first header (20 bytes + qstr)
- zero (OPO) or more (OPA):
  - (word) length of embedded file
  - embedded file
- second header (12 bytes)
- one or more procedure bodies, each:
  - declarations block:
    - optimisation section (some translator versions only, 2 bytes)
    - space control section (6 bytes)
    - parameter section (length byte, then that number of bytes)
    - global declaration section:
      - (word) size of rest of section
      - zero or more per-variable blocks (qstr then 3 bytes)
    - called procedures section:
      - (word) size of rest of section
      - zero or more per-procedure blocks (qstr then 1 byte)
    - global references section:
      - zero or more per-reference blocks (qstr then 1 byte)
      - (byte) zero
    - string control section:
      - zero or more per-string blocks (3 bytes)
      - (word) zero
    - array control section:
      - zero or more per-array blocks (4 bytes)
      - (word) zero
  - q-code
- one or more procedure table entries (each qstr + 6 bytes)
- (word) zero


Organisation of memory
----------------------
When an OPL procedure is called, the run-time system allocates a block of
memory associated with the call - the "data stack frame" - which will be
freed when the procedure call returns. This block holds the values of all
of the local and global variables defined in the procedure, and also
information required to locate global variables, parameters, and other
procedures referred to by the procedure.

Space is allocated for variables defined in the procedure as follows:

    x%     2 bytes             x%(N)    2N+2 bytes
    x&     4 bytes             x&(N)    4N+2 bytes
    x      8 bytes             x(N)     8N+2 bytes
    x$(L)  L+2 bytes           x$(N,L)  3+N(L+1) bytes

The location of a variable (or, more precisely, of its value) in the stack
frame is the address of some byte within this space:

    x%     offset 0            x%(N)    offset 2
    x&     offset 0            x&(N)    offset 2
    x      offset 0            x(N)     offset 2
    x$(L)  offset 1            x$(N,L)  offset 3

and this is what is used by the q-code. Arrays have the array size N placed
at offset 0 within the space, and it is here that the array control section
points. Strings have the maximum length L placed at offset 0 (or offset 2
for string arrays) within the space, and it is here that the string control
section points. That is, suppose we declare x$(2,4), and set the two elements
to "abc" and "x" respectively, and the variable is placed at location 22
within the data stack frame. Then the memory will contain:

Location: 22  23  24  25  26  27  28  29  30  31  32  33  34
Value:     $0002   4   3  'a' 'b' 'c'  ?   1  'x'  ?   ?   ?

The array control section will contain 22 as the location, the string
control section will contain 24, and the q-code will use 25.


Q-code
------
Q-code is a series of codes of varying length, each of which is an operation.
Most operations involve pushing values on to a stack or popping them. The
first byte of each code is the opcode, with the following bytes being
arguments.

The following conventions are used in this list.

    % (suffix)   a word
    & (suffix)   a long
    $ (suffix)   a qstr
    * (suffix)   a real
    = (suffix)   an address
    ! (suffix)   a single byte
    + (suffix)   see below

    push%    push the word result on to the stack
    pop=     the address at the top of the stack, popped off
             where more than one value is popped off, pop=1 pop=2 etc
             is used to show the order; pop=1 starts at the top of the stack
    addr=&   the long word whose address is that given
    L*       two bytes giving the location of a real in the data stack frame
             the variable is referred to as LL*
    E*       two bytes giving the external reference code for a global
             variable declared elsewhere or for a procedure (in each case
             referred to as EE*); see below for details
    V*       8 bytes forming a real, referred to as VV*
    D        a byte (00 to 03) naming an open data file (A to D)
    N        a byte giving the number of arguments
    N'       a byte giving the number of arguments minus 1
    Na       a byte giving the number of arguments, or 0 meaning 2 special
             arguments: the address of an array and then a word
    Nq       a byte that is 0 or 1 for OFF or ON respectively, or else the
             number of arguments minus 1
    Nx       a byte controlling the number of arguments as described
    Q        a byte that is 0 or 1 for OFF or ON respectively
    Qn       a byte that is 0, 1, or $FF for OFF, ON, and omitted respectively
    Qa       a byte that is 0 or 1 for OFF or ON, or some other value
    J        two bytes giving a location in the Q-code relative to the
             current operation (that is, 0 means this opcode) referred to
             as JJ
    ZZ       other bytes described in the notes

Certain opcodes differ only in the type of some operands. In this case, the
opcode is given as a range (e.g. $00-3 or $4C-E), and the affected operands
are shown by a + suffix. This should be replaced by % & * and $ respectively
for the four opcodes (omitting $ if there are only three).

Unless stated otherwise, keywords take their arguments from the stack in
reverse order. For example, opcode B9 is actually "RANDOMIZE pop&" and
opcode 9E is actually "AT pop%2, pop%1".

For certain keywords and builtin functions, some of the arguments are
required to be variables (for example, the first argument of IOOPEN). In
this case, the appropriate argument is handled by pushing the address of
the variable as a word, not as an address (an address is converted to a
word using the ADDR opcode - 57 00). This is indicated in the following list
by putting [v2] (indicating that the second argument is treated in this way)
after the meaning.


External references
-------------------
Global variables, parameters, and procedures called by a procedure are each
given an external reference code and a code size. The code of the first item
is always 18, and the codes of all other items are found by adding the code
size to the code of the previous item. The codes are allocated in the
following order:
* The global variables defined by the procedure; the code size for a
  variable is 4 more than the length of the name including the type suffix,
  so the code size for "abc&" is 8. These external references do not appear
  in the Q-code.
* The procedures called by this procedure, in the order given in the called
  procedure section; the code size for each procedure is 1 more than the
  length of the name including the colon, so the code size for "xx:" is 4.
  These external references are only used by opcode 53.
* The parameters of the procedure in order; the code size is 2 per parameter.
  These external references are used by opcodes 08 to 0B and 18 to 1B.
* The global variables referenced, but not defined, by the procedure, in the
  order given in the global references section; the code size is 2 per
  variable. These external references are used by opcodes 08 to 0F and 18
  to 1F.


Operation  Meaning
------------------
00-3 L+    push+ the value of LL+
04-7 L+    push= the address of LL+
08-B E+    push+ the value of EE+
0C-F E+    push= the address of EE+ [cannot be a parameter]
10-3 L+    push+ the value of LL+(pop%)
14-7 L+    push= the address of LL+(pop%)
18-B E+    push+ the value of EE+(pop%)
1C-F E+    push= the address of EE+(pop%) [cannot be a parameter]
20-3 D     push+ the value of field pop$+ of data file D
24-7 D     push= the address of field pop$+ of data file D
28-B V+    push+ VV+
2C-F       [[apparently not used]]
30-3       push% pop+2 <  pop+1
34-7       push% pop+2 <= pop+1
38-B       push% pop+2 >  pop+1
3C-F       push% pop+2 >= pop+1
40-3       push% pop+2 =  pop+1 (compare, not assign)
44-7       push% pop+2 <> pop+1
48-B       push+ pop+2 +  pop+1
4C-E       push+ pop+2 -  pop+1
4F V!      push% VV! ($80 to $FF convert to $FF80 to $FFFF)
50-2       push+ pop+2 * pop+1
53 E:      call procedure EE: (see below)
54-6       push+ pop+2 / pop+1
57         see below
58-A       push+ pop+2 ** pop+1
5B J       if pop% is zero, jump to JJ
5C-E       push% pop+2 AND pop+1
5F V!      push& VV! ($80 to $FF convert to &FFFFFF80 to &FFFFFFFF)
60-2       push% pop+2 OR pop+1
63 V%      push& VV% ($8000 to $FFFF convert to &FFFF8000 to &FFFFFFFF)
64-6       push% NOT pop+
67         [[apparently not used]]
68-A       push+ - pop+
6B ZZ      @ operator (see below)
6C         push* pop*2 < pop*1 %  [These are the "x+y%" operators]
6D         push* pop*2 > pop*1 %
6E         push* pop*2 + pop*1 %
6F         push* pop*2 - pop*1 %
70         push* pop*2 * pop*1 %
71         push* pop*2 / pop*1 %
72         [[apparently not used]]
73         [[apparently not used]]
74-7       RETURN no value from type + procedure
78         push% value of pop&
79         push% value of pop*
7A         push& value of pop*
7B         push& value of pop%
7C         push* value of pop%
7D         push* value of pop&
7E         [[apparently not used]]
7F         [[apparently not used]]
80-3       pop+ and discard value
84-7       store pop+1 in location with address pop=2
88-B       PRINT pop+ ;    (i.e. with no following space or newline)
8C-F       LPRINT pop+ ;   (i.e. with no following space or newline)
90         PRINT ,         (i.e. with no following newline)
91         LPRINT ,        (i.e. with no following newline)
92         PRINT           (i.e. newline at end of printing)
93         LPRINT          (i.e. newline at end of printing)
94-7       INPUT+ into location with address pop=
98-B       POKE+ pop+1 in location with address pop=2
9C         POKEB pop%1 in location with address pop=2
9D         APPEND
9E         AT
9F         BACK
A0         BEEP
A1         CLOSE
A2         CLS
A3         COMPRESS
A4         COPY
A5 D ZZ    CREATE pop$ (see below for details)
A6 Qa      CURSOR (Qa is 2, 3, or 4 for 1, 4, or 5 parameters respectively)
A7         DELETE
A8         ERASE
A9 Q       ESCAPE
AA         FIRST
AB V! ZZ   VECTOR pop% (see below for details)
AC         LAST
AD         LCLOSE
AE         LOADM
AF         LOPEN
B0         NEXT
B1 J       ONERR JJ (0 means ONERR OFF)
B2         OFF
B3         OFF pop%
B4 D ZZ    OPEN pop$ (see below for details)
B5         PAUSE
B6         POSITION
B7         IOSIGNAL
B8         RAISE
B9         RANDOMIZE
BA         RENAME
BB         STOP
BC         TRAP action immediately following
BD         UPDATE
BE D       USE
BF J       GOTO JJ
C0         RETURN pop+  (type popped depends on procedure type)
C1         UNLOADM
C2         EDIT
C3         SCREEN pop%2, pop%1
C4 D ZZ    OPENR pop$ (see below for details)
C5 Nx      gSAVEBIT (Nx: 0 = 1 argument, 1 = 3 arguments)
C6         gCLOSE
C7         gUSE
C8 N       gSETWIN
C9 Q       gVISIBLE
CA         gFONT
CB         gUNLOADFONT
CC         gGMODE
CD         gTMODE
CE         gSTYLE
CF         gORDER
D0         gINFO [v1]
D1         gCLS
D2         gAT
D3         gMOVE
D4-7       gPRINT pop+ ;    (i.e. with no following space or newline)
D8         gPRINT ,         (i.e. with no following newline)
D9 N'      gPRINTB
DA         gLINEBY
DB         gBOX
DC         [[apparently not used]]
DD         [[apparently not used]]
DE         gPOLY [v1]
DF         gFILL
E0         gPATT
E1         gCOPY
E2 N       gSCROLL
E3 Qn      gUPDATE
E4         GETEVENT [v1]
E5         gLINETO
E6         gPEEKLINE [v4]
E7         SCREEN pop%4, pop%3, pop%2, pop%1
E8         IOWAITSTAT [v1]
E9         IOYIELD
EA         mINIT
EB Nx      mCARD (there are Nx menu items, and 2Nx+1 arguments)
EC N       dINIT
ED         see below
EE         SETNAME
EF Nq      STATUSWIN
F0 N       BUSY (0 arguments means OFF)
F1 Q       LOCK
F2         gINVERT
F3         gXPRINT
F4 N       gBORDER
F5 Nq      gCLOCK
F6 V!      push* value of calculator memory VV
F7 V!      push= address of calculator memory VV
F8         MKDIR
F9         RMDIR
FA         SETPATH
FB         SECSTODATE [v2, v3, v4, v5, v6, v7, v8]
FC N'      GIPRINT
FD         [[apparently not used]]
FE         [[apparently not used]]
FF         see below


Each of the opcodes 57, ED, and FF are followed by a second opcode byte.
Opcode 57 is used for builtin functions; unless stated otherwise, these
take their arguments from the stack in reverse order and push the result.
For example, opcode 57 33 is actually "push% gPRINTCLIP pop$2, pop%1".


Operation  Meaning
------------------
57 00      push% ADDR pop= (address of a word, long, or real)
57 01      ASC
57 02 N    CALL
57 03      COUNT
57 04      DAY
57 05      DOW
57 06      EOF
57 07      ERR
57 08      EXIST
57 09      FIND
57 0A      GET
57 0B      IOA [v3, v4, v5]
57 0C      IOW [v3, v4]
57 0D      IOOPEN [v1] (second argument is a string)
57 0E      IOWRITE
57 0F      IOREAD
57 10      IOCLOSE
57 11      IOWAIT
57 12      HOUR
57 13      KEY
57 14      LEN
57 15      LOC
57 16      MINUTE
57 17      MONTH
57 18      PEEKB
57 19      PEEKW
57 1A      POS
57 1B      RECSIZE
57 1C      SECOND
57 1D      USR
57 1E      YEAR
57 1F      push% ADDR pop= (address of a string)
57 20      WEEK
57 21      IOSEEK [v3]
57 22      KMOD
57 23      KEYA [v1, v2]
57 24      KEYC [v1]
57 25      IOOPEN [v1] (second argument is a word)
57 26      gCREATE (5 arguments)
57 27      gCREATEBIT
57 28 N    gLOADBIT
57 29      gLOADFONT
57 2A      gRANK
57 2B      gIDENTITY
57 2C      gX
57 2D      gY
57 2E      gWIDTH
57 2F      gHEIGHT
57 30      gORIGINX
57 31      gORIGINY
57 32      gTWIDTH
57 33      gPRINTCLIP
57 34      TESTEVENT
57 35 N    OS
57 36      MENU without argument
57 37      DIALOG
57 38 N    ALERT
57 39      gCREATE (6 arguments)
57 3A      MENU with argument [v1]
57 3B      CREATESPRITE
57 3C      LOADLIB [v1]
57 3D      UNLOADLIB
57 3E      FINDLIB [v1]
57 3F      GETLIBH
57 40      DAYS
57 41      IABS
57 42      INT
57 43      PEEKL
57 44      SPACE
57 45      DATETOSECS
57 46      NEWOBJ
57 47      NEWOBJH
57 48 N    SEND [v3, v4, v5] (arguments 3 to 5 are optional)
57 49 N    ENTERSEND [v3, v4, v5] (arguments 3 to 5 are optional)
57 4A N    ENTERSEND0 [v3, v4, v5] (arguments 3 to 5 are optional)
57 4B      ALLOC
57 4C      REALLOC
57 4D      ADJUSTALLOC
57 4E      LENALLOC
57 4F N    IOC [v3, v4, v5]
57 50      UADD
57 51      USUB
57 52      IOCANCEL
57 53      STATWININFO [v2]
57 54      FINDFIELD
57 58      POPUP @@@@S5@@@@ ?
57 80      ABS
57 81      ACOS
57 82      ASIN
57 83      ATAN
57 84      COS
57 85      DEG
57 86      EXP
57 87      FLT
57 88      INTF
57 89      LN
57 8A      LOG
57 8B      PEEKF
57 8C      PI
57 8D      RAD
57 8E      RND
57 8F      SIN
57 90      SQR
57 91      TAN
57 92      VAL
57 93 Na   MAX
57 94 Na   MEAN
57 95 Na   MIN
57 96 Na   STD
57 97 Na   SUM
57 98 Na   VAR
57 99      EVAL
57 C0      CHR$
57 C1      DATIM$
57 C2      DAYNAME$
57 C3      DIR$
57 C4      ERR$
57 C5      FIX$
57 C6      GEN$
57 C7      GET$
57 C8      HEX$
57 C9      KEY$
57 CA      LEFT$
57 CB      LOWER$
57 CC      MID$
57 CD      MONTH$
57 CE      NUM$
57 CF      PEEK$
57 D0      REPT$
57 D1      RIGHT$
57 D2      SCI$
57 D3      UPPER$
57 D4      USR$
57 D5      GETCMD$
57 D6      CMD$
57 D7      PARSE$ [v3]
57 D8      ERRX$   @@@@S5@@@@

ED 00 Nx   dTEXT (Nx is 2 less than the number of arguments)
ED 01      dCHOICE pop=3, pop$2, pop$1
ED 02      dLONG pop=4, pop$3, pop&2, pop&1
ED 03      dFLOAT pop=4, pop$3, pop*2, pop*1
ED 04      dTIME pop=5, pop$4, pop%3, pop&2, pop&1
ED 05      dDATE pop=4, pop$3, pop&2, pop&1
ED 06      dEDIT pop=2, pop$1
ED 07      dEDIT pop=3, pop$2, pop%1
ED 08      dXINPUT pop=2, pop$1
ED 09      dFILE pop=3, pop$2, pop%1
ED 0A Nx   dBUTTONS (Nx is the number of buttons, there are 2Nx arguments)
ED 0B      dPOSITION pop%2, pop$1

FF 00      gGREY
FF 01      DEFAULTWIN
FF 02 N    DIAMINIT
FF 03      DIAMPOS
FF 04      FONT
FF 05      STYLE
FF 06      USESPRITE
FF 07      APPENDSPRITE (Nx: 0 = 2 arguments, 1 = 4 arguments)
FF 08      DRAWSPRITE
FF 09      CHANGESPRITE (Nx: 0 = 3 arguments, 1 = 5 arguments)
FF 0A      POSSPRITE
FF 0B      CLOSESPRITE
FF 0C      FREEALLOC
FF 0D      LINKLIB
FF 0E Qa   CACHE (Qa is the number of parameters if 2 or more)
FF 0F      gBUTTON
FF 10 N    gXBORDER
FF 11      gDRAWOBJECT
FF 12      ODBINFO [v1]
FF 13      CACHETIDY
FF 14      SCREENINFO [v1]
FF 15      CACHEHDR pop%
FF 16      CACHEREC pop%2, pop%1
FF 17 N    dINITS
FF 18      CALLOPX      @@@@S5@@@@
FF 22      GETEVENT32   @@@@S5@@@@
FF 23      GETEVENTA32  @@@@S5@@@@
FF 24      GCOLOR       @@@@S5@@@@
FF 26      SETDOC       @@@@S5@@@@
FF 29      IOWAITSTAT32 @@@@S5@@@@
FF 30      MCASC        @@@@S5@@@@
FF 33      DCHECK       @@@@S5@@@@


Notes
-----

Procedures expect two values on the stack for each parameter: a value and
a type code. The top of the stack is the variable type code for the last
parameter.

The @ operator expects the procedure name to be below the other values on
the stack. The opcode is followed by two bytes: the first is the number of
arguments to the call, and the second is one of %%, %&, 0, or %$. So
    @$(x$):(y%,z$)
will appear as
    [code to push x$]
    [code to push y%]
    [code to push% 0 (type code for word)]
    [code to push z$]
    [code to push% 3 (type code for string)]
    6B 02 24

The opcodes for CREATE, OPEN, and OPENR are followed by a D byte and then
information describing the field names. For each field in turn there is
a variable type code followed by the name; the list is ended by $FF. So
    OPEN x$,c,AAA$,B%
will appear as
    [code to push x$]
    B4 02 03 04 41 41 41 24 00 02 42 25 FF
       C     <--- AAA$ --->    <- B$ ->

The VECTOR keyword is followed by a byte giving the number of labels, and
then that number of relative addresses J1, J2, etc.