Cornerstone Bytecode Notes
Warning: These notes are incomplete, and there's a fair amount about the Cornerstone bytecode I haven't fathomed.
Cornerstone is written in interpreted bytecode; on the PC, the interpreter is called MME.EXE. The bytecode is for a stack-based VM, with a 16-bit word.
A bytecode program is stored in two files: a .MME file and a .OBJ file. The .MME file is passed to the interpreter; the .OBJ filename is deduced from it (it's assumed to have the same name, substituting ".obj" for ".mme"). The actual bytecode is in the .OBJ file.
MME File Format
The .MME file has a 96-byte header, of which the following fields appear to be the only ones used by the interpreter. All are little-endian words:
Offset | Meaning |
---|---|
0x0E | The address of the last code byte in the .OBJ file, divided by 512 and rounded down. |
0x10 | The address of the entry point in 'far call' format — that is, the high byte is the 1-based module number, and the low byte is the number of the entry in the module header. |
0x16 | The size in words of the initial RAM image. |
0x1A | The offset in the .MME file of the module headers table, divided by 256. |
0x1E | The length of the module headers table, in words. |
Immediately following the header is a table of module offsets. Again, all entries are little-endian words:
Offset | Meaning |
---|---|
0x60 | The number of modules. PC Cornerstone supports up to 24. |
0x62 | The offset of each module in the .OBJ file, divided by 256. |
For all modules except the last, the end address of a module is the start address of the next module - 1. When the module index is read in, the header word 0x0E is used to work out the end address of the last module; this is appended to the table in memory.
The module index is followed by a 'globals' index:
Offset | Meaning |
---|---|
+0 | The number of program-wide global variables. |
+2 | The number of module-wide global variables, module 1. |
+4 | The number of module-wide global variables, module 2. |
+6 | etc. |
And this, in turn, is followed by the initial values of these variables.
DW g0,g1,... ; Initial values of program-wide globals. DW m0g0,m0g1,... ; Initial values of module 1 globals. DW m1g0,m1g1,... ; Initial values of module 2 globals. etc.
Program-wide globals are accessed with the LOADG, PUTG, STOREG, INCG and DECG instructions. PC Cornerstone supports at most 256 globals.
Module globals are accessed with LOADMG, PUTMG etc. PC Cornerstone supports at most 192 module globals. If these instructions are used with a number higher than 192, they refer to interpreter system variables such as the current date or time.
After the globals table, the loader then seeks to the module headers using header word 0x1A. The original VM does not allow the module headers to occupy more than 2k.
Each module header starts with a word; this is the number of words that follow. The subsequent words are procedure addresses relative to the start of the module.
Offset | Meaning |
---|---|
+0 | Number of entry points. |
+2 | Offset of entry point 1. |
+4 | Offset of entry point 2. |
+6 | etc. |
The module headers are followed by the initial contents of VM RAM (aligned to a 256-byte boundary). The interpreter finds offset of initial RAM by: 256 * (header word 0x1A + 1 + ((header word 0x1E * 2) / 256)). This suggests to me that if the module headers happened to be an exact multiple of 256 bytes in size, the interpreter would require 256 empty bytes before the RAM contents.
VM memory layout
To a first approximation, the virtual machine has 128k of RAM — if you were writing a Cornerstone interpreter on anything other than an extremely constrained machine, you'd just allocate 128k and have done with it.
In a situation where the RAM is constrained, it is composed of two segments of the same size, referred to here as 'low' and 'high'. The 'low' segment starts at 0x00000, and the high segment at 0x10000. For example, if only 96k was available for VM RAM, it would appear to the virtual machine as two 48k segments: 0x00000-0x0BFFF and 0x10000-0x1BFFF. Thus, RAM is only contiguous in a full-128k configuration.
Within the VM bytecode an address in RAM is represented by a 16-bit value:
- The top bit selects which half of RAM to use (set for high, clear for low).
- The bottom 15 bits should be doubled to give the address (ie, it can only address even-numbered bytes).
- 0x8001 represents FALSE (a null pointer).
Frequently, the 16-bit VM address is treated as a pointer to a "Tuple" or a "Vector/Tuple" structure. In this case, I refer to it as a "tuple ID" or "vector ID".
Low RAM is initialised from the .MME file; the header word at 0x16 gives the number of words in the initial image. Assuming that X is the first address after the initial image, the memory at X is initialised as:
Word at X: 0000h Word at X+2: X + 6 Word at X+6: X + 6 Word at X+8: End of interpreter RAM low segment - (X+8)
For example, if the word at 0x16 is 0x4A48, X will be 2 * 0x4A48 = 0x9490, and memory will be initialised as follows:
0x9490: 0000 0x9492: 9496 0x9496: 9496 0x9498: 6B68
VM RAM is split between data and stack memory. The initial split appears to be the top 1/64th of high memory is given to the stack (so 1k on a full 128k system, 512 bytes on a 64k system, etc.). To avoid confusion with the interpreter's own stack (where local variables live) I'm going to call these areas of memory 'vector data' and 'tuple stack' respectively.
'Vector data' memory starts just after the RAM image loaded from file, and has an allocator system I haven't worked out yet. Presumably the four words initialised above represent an empty arena. Since the arena is visible to the bytecode, all interpreters should use the same allocation system, whatever that is.
Strings in RAM are stored in Pascal format:
WORD length BYTE text[];
File I/O
Cornerstone allows up to 25 open files (or 'channels', as it calls them). One of these will be the .OBJ file; its handle is held in the bottom 5 bits of system variable 0xDB.
Channels can either be disk files, or devices such as a printer. In the PC Cornerstone interpreter, files are allocated channel handles 0-23 and the printer gets handle 24.
One file used internally by Cornerstone is called sysbuf.hld. This is created by the interpreter on startup and appears to be a swapfile of some kind.
OBJ File Format
A .OBJ file is a sequence of modules, each aligned to 256 bytes. Modules are a sequence of procedures.
Code is divided into 256-byte blocks. An instruction cannot be split across two different blocks. The blocks are swapped in and out of memory by the interpreter as required. The last instruction encountered in each block must be NEXTB, to instruct the interpreter to load the next block. NEXTB might be at offset 255, or it might come sooner. For example, assembling the sequence:
PUSH_L3 OPEN 8
If the PUSH_L3 happened to end up at offset 253, then there would be no room for the OPEN (a 3-byte opcode) at offset 254. So NEXTB would be inserted at offset 254, and the OPEN would be at offset 0 in the next block.
Each procedure starts with a single byte:
- Bits 0-6
- give the number of local variables used by this procedure. These are initialised to 0x8001 (FALSE).
- Bit 7
- is set if initial values for local variables follow.
If bit 7 of the intro byte is set, the procedure header is followed by a list of encoded initial values for local variables. Each entry is 2 or 3 bytes, the first one of which is:
- Bits 0-5
- Index number of local variable, 0-31
- Bit 6
- If this bit is 0, the two following bytes are the word value to assign. If it is 1, the one following byte is the value; sign-extend it to a word.
- Bit 7
- Set if this is the last initial value.
Opcodes
Explanation of the "args" column:
- "1-> "
- pops one word from the stack, pushes none.
- "2->1"
- pops two words, pushes one.
- " ->1"
- pops no words, pushes one.
Let $1 refer to the top word on the stack, $2 the one below it,
and so forth.
$B is a byte following the instruction byte.
$W is a little-endian word following the instruction byte.
Mnemonics are my own invention.
Opcode | Args | Mnemonic | Effect |
---|---|---|---|
0x00 | ->1 | BREAK | I have assumed this to be a debug breakpoint. It replaces the 0x00 in memory with a previously stored byte, pushes the current location counter onto the stack, and set the location counter to the value of the stored byte! |
0x01 | 2->1 | ADD | Pushes $2 + $1 |
0x02 | 2->1 | SUB | Pushes $2 - $1 |
0x03 | 2->1 | MUL | Pushes $2 × $1 |
0x04 | 2->1 | DIV | Divides $1 by $2 and pushes the quotient. |
0x05 | 2->1 | MOD | Divides $1 by $2 and pushes the remainder. |
0x06 | 1->1 | NEG | Changes $1 to its two's complement. |
0x07 | 2->1 | ASHIFT | Arithmetic shift $2 by $1 bits (to the left if $1 is positive,
to the right if $1 is negative). When shifting to the right, the
sign is preserved:
shift = -$1; if ($2 == 0x8000) { $2 = 0x4000; --shift; } $2 = - ((-$2) >> shift); |
0x08 | ->1 | INCL | Increment local variable $B, and push its new value onto the stack. |
0x09 | ->1 | PUSH8 | Push constant 8. |
0x0A | ->1 | PUSH4 | Push constant 4. |
0x0B | ->1 | DECL | Decrement local variable $B, and push its new value onto the stack. |
0x0C | ->1 | PUSHm1 | Push constant -1 (0xFFFF). |
0x0D | ->1 | PUSH3 | Push constant 3. |
0x0E | 2->1 | AND | Pushes the bitwise AND of $1 and $2. |
0x0F | 2->1 | OR | Pushes the bitwise OR of $1 and $2. |
0x10 | 2->1 | SHIFT | Shift $2 by $1 bits (to the left if $1 is positive, right if $1 is negative). Unlike ASHIFT, this uses the 8086 SAR instruction. |
0x11 | 1->1 | VALLOC | Allocates a vector in interpreter RAM; $1 = size in words. Pushes the new vector ID. |
0x12 | ?->1 | VALLOCI | As VALLOC, but also populates the newly-allocated vector with $2,$3,$4... up to $1 words. Pushes the new vector ID. |
0x13 | 2-> | VSIZE | Resize a vector? $1 is new size; $2 is vector ID, which cannot be FALSE. |
0x14 | 1->1 | TALLOC | Allocate a tuple of $1 words on the tuple stack. Returns the new tuple ID. Interpreter terminates if allocation fails. |
0x15 | ?->1 | TALLOCI | As TALLOC, but also populates the newly-allocated tuple with $2,$3,$4... up to $1 words. Pushes the new tuple ID. |
0x16 | 2->1 | VLOADW | Read a word from a vector / tuple. $2 is the vector ID; this is converted to the actual address, then an offset of ($1 * 2) - 2 bytes is added. |
0x17 | 2->1 | VLOADB | Read a byte from a vector / tuple. $2 is the vector ID; this is converted to the actual address, then an offset of ($1 + 1) bytes is added. |
0x18 | 1->1 | VLOADW_ | Read a word from a vector / tuple. $1 is the vector ID; this is converted to the actual address, then ($B * 2) is added. |
0x19 | 1->1 | VLOADB_ | Read a byte from memory. $1 is the vector ID; this is converted to the actual address, then ($B + 2) is added. |
0x1A | 3-> | VPUTW | Store word $1 in memory. $3 is the vector ID; this is converted to the actual address, then ($2 * 2) - 2 is added. |
0x1B | 3-> | VPUTB | Store the low byte of $1 in memory. $3 is the vector ID; this is converted to the actual address, then ($2 + 1) is added. |
0x1C | 2-> | VPUTW_ | Store word $1 in memory. $2 is the vector ID; this is converted to the actual address, then ($B * 2) is added. |
0x1D | 1->1 | VPUTB_ | Store the low byte of $1 in memory. $2 is the vector ID; this is converted to the actual address, then ($B + 2) is added. |
0x1E | 3-> | VECSETW | Writes $2 copies of word $1 into memory. $3 gives the VM address. |
0x1F | 3-> | VECSETB | Writes $2 copies of byte $1 into memory. $3 gives the VM address, which is first incremented by 2. |
0x20 | 3-> | VECCPYW | Copy $2 words from vector $3 to vector $1. |
0x21 | 3-> | VECCPYB | Copy $4 bytes from vector $5 (plus ($2+2)) to vector $3 (plus ($1+2)). |
0x22 | ->1 | LOADG | Pushes the value of global variable $B. |
0x23 | ->1 | LOADMG | Pushes the value of module global variable $B. |
0x24 | ->1 | PUSH2 | Push constant 2. |
0x25 | ->1 | PUSHW | Push word $W. |
0x26 | ->1 | PUSHB | Push byte $B. |
0x27 | ->1 | PUSH_NIL | Push FALSE (0x8001). |
0x28 | ->1 | PUSH0 | Push constant 0. |
0x29 | 1->2 | DUP | Duplicate the word at the top of the stack. |
0x2A | ->1 | PUSHm8 | Push constant -8 (0xFFF8). |
0x2B | ->1 | PUSH5 | Push constant 5. |
0x2C | ->1 | PUSH1 | Push constant 1. |
0x2D | 1-> | PUTMG | Set module global variable $B to $1. |
0x2E | ->1 | PUSHFF | Push constant 0xFF. |
0x2F | 1-> | PULL | Discard the top word from the stack. |
0x30 | JUMP | If $B is nonzero, treat it as a signed byte and add it to the program counter. If it is zero, read the next word from the instruction stream and set the program counter to that. | |
0x31 | 1-> | JUMPZ | As JUMP, but only if $1 is zero. |
0x32 | 1-> | JUMPNZ | As JUMP, but only if $1 is nonzero. |
0x33 | 1-> | JUMPF | As JUMP, but only if $1 is FALSE (0x8001). |
0x34 | 1-> | JUMPNF | As JUMP, but only if $1 is not FALSE. |
0x35 | 1-> | JUMPG | As JUMP, but only if $1 is > 0. |
0x36 | 1-> | JUMPLE | As JUMP, but only if $1 is <= 0. |
0x37 | 1-> | JUMPL | As JUMP, but only if $1 is < 0. |
0x38 | 1-> | JUMPGE | As JUMP, but only if $1 is >= 0. |
0x39 | 2-> | JUMPL2 | As JUMP, but only if $1 is < $2. |
0x3A | 2-> | JUMPLE2 | As JUMP, but only if $1 is <= $2. |
0x3B | 2-> | JUMPGE2 | As JUMP, but only if $1 is >= $2. |
0x3C | 2-> | JUMPG2 | As JUMP, but only if $1 is > $2. |
0x3D | 2-> | JUMPEQ | As JUMP, but only if $1 == $2. |
0x3E | 1-> | JUMPNE | As JUMP, but only if $1 <> $2. |
0x3F | ->? | CALL0 | Call address $W (in this module) with 0 arguments. |
0x40 | 1->? | CALL1 | Call address $W (in this module) with 1 argument. |
0x41 | 2->? | CALL2 | Call address $W (in this module) with 2 arguments. |
0x42 | 3->? | CALL3 | Call address $W (in this module) with 3 arguments. |
0x43 | ?->? | CALL | Call address $1 (in this module) with $B arguments. |
0x44 | ->? | CALLF0 | Far call to address $W with 0 arguments. The high byte of $W is the 1-based module number; the low byte is the number of the entry point. The module headers table is used to convert this to the procedure address. |
0x45 | 1->? | CALLF1 | Far call to address $W with 1 argument. |
0x46 | 2->? | CALLF2 | Far call to address $W with 2 arguments. |
0x47 | 3->? | CALLF3 | Far call to address $W with 3 arguments. |
0x48 | ?->? | CALLF | Far call to address $1 with $B arguments. |
0x49 | 1->1 | RETURN | Returns from a procedure call, pushing $1 onto the caller's stack. |
0x4A | ->1 | RFALSE | Returns from a procedure call, pushing FALSE (0x8001). |
0x4B | ->1 | RZERO | Returns from a procedure call, pushing 0. |
0x4C | ->1 | PUSH6 | Push constant 6. |
0x4D | HALT | Terminate execution. $W gives an error code. | |
0x4E | NEXTB | The last instruction in a 256-byte code block. May be followed by one or more packing bytes; execution resumes at the next 256-byte boundary. Used to ensure the next block is paged in. | |
0x4F | ->1 | PUSH7 | Push constant 7. |
0x50 | 3-> | PRINTV | Print up to 100 bytes to screen. $2 = count of bytes. $3 is the VM address; this is converted to the actual address, then ($1 + 2) is added to it. |
0x51 | 2->1 | LOADVB2 | Read byte from memory. $2 gives vector ID. After conversion to actual address, ($1 - 1) is added. |
0x52 | 3-> | POPVB2 | Write byte $1 to memory. $3 gives vector ID. After conversion to actual address, ($2 - 1) is added. |
0x53 | 2->1 | ADD_ | Same as 0x01 |
0x54 | INCLV | Increment the value in local variable $B, treating it as a vector (ie, the VM aborts if the result would be FALSE (0x8001)). | |
0x55 | RET | Return from a procedure call, not pushing anything onto the caller's stack. | |
0x56 | 1-> | PUTL | Store $1 in local variable $B. |
0x57 | ->1 | LOADL | Push local variable $B onto the stack. |
0x58 | 1->1 | STOREL | Copy $1 to local variable $B. $1 remains at the top of the stack. |
0x59 | ->1 | BITSVL | Read bits of a word within a vector. $W gives the operation
details:
Bits 15-12 are a shift. The word read will be shifted right by this amount of bits before being masked. Bits 11- 8 give the number of bits to read, 0-15. Bits 7- 4 are the number of a local variable which contains a vector ID. Bits 3- 0 are the number of the word in the vector. |
0x5A | 1->1 | BITSV | Read bits of a word within a vector. $W gives the operation
details:
Bits 15-12 are a shift. The word read will be shifted right by this amount of bits before being masked. Bits 11- 8 give the number of bits to read, 0-15. Bits 7- 0 are the number of the word in the vector.$1 gives the vector ID. |
0x5B | 1-> | BBSETVL | Replace bits of a word within a vector. $W gives the operation
details:
Bits 15-12 are a shift. The word written will be shifted left by this amount of bits before being masked. Bits 11- 8 give the number of bits to use, 0-15. Bits 7- 4 are the number of a local variable which contains a vector ID. Bits 3- 0 are the number of the word in the vector.$1 gives the word to write. |
0x5C: | 2-> | BBSETV | Replace bits of a word within a vector. $W gives the operation
details:
Bits 15-12 are a shift. The word written will be shifted left by this amount of bits before being masked. Bits 11- 8 give the number of bits to use, 0-15. Bits 7- 0 are the number of the word in the vector.$1 gives the vector ID. $2 gives the word to write. |
0x5D: | -> | BSETVL | Set/clear a single bit within a vector. $W gives the operation
details:
Bits 15-12 are the bit number, 0-15. Bit 8 is the value to write. Bits 7- 4 are the number of a local variable which contains a vector ID. Bits 3- 0 are the number of the word in the vector. |
0x5E: | 1-> | BSETV | Set/clear a single bit within a vector. $W gives the operation
details:
Bits 15-12 = bit number Bit 8 is the value to write. Bits 7- 0 are the number of the word in the vector.$1 gives the vector ID. |
0x5F | This is the first byte of a 2-byte opcode. | ||
0x5F 0x00 | n/a | Not defined. | |
0x5F 0x01 | 2->1 | XOR | $1 becomes $1 XOR $2 |
0x5F 0x02 | 1->1 | NOT | $1 becomes the bitwise complement of $1 |
0x5F 0x03 | 2->1 | ROTATE | As SHIFT, but rotates bits. |
0x5F 0x04 | 3->1 | VFIND | Search a vector for a word value. $1 = number of words to search. $2 = vector ID. $3 = word to find. Pushes index of the word found, FALSE if not found. |
0x5F 0x05 | 2->1 | STRCHR | Search string for a character. $1 = VM address of string to search, $2 = character. Pushes index of character found, FALSE if not found. |
0x5F 0x06 | 1-> | PUTG | Store $1 in global variable $B. |
0x5F 0x07 | ?-> | PULLN | Remove $1 words from the stack (plus $1 itself) |
0x5F 0x08 | n/a | Not defined. | |
0x5F 0x09 | 2-> | LONGJMPR | Jump to a stack frame saved by SETJMP, pushing a single word as return code. $1 = frame pointer to return to, $2 = return code. |
0x5F 0x0A | 1-> | LONGJMP | Jump to a stack frame saved by SETJMP. $1 = frame pointer to return to. |
0x5F 0x0B | ->7 | SETJMP | Push seven words onto the stack:
|
0x5F 0x0C | 2->1 | OPEN | Open a channel. $2 holds the VM address of the filename (a
Pascal-format string). $B holds ?access mode? Returns channel ID if successful, FALSE if failed. |
0x5F 0x0D | 1->1 | CLOSE | Close a channel. $1 gives the channel ID. Returns 0 if OK, -1 if failed. |
0x5F 0x0E | 3->1 | READ | Read $1 words from channel $2 into vector $3, starting at word 1 of the vector. |
0x5F 0x0F | 3->1 | WRITE | Write $1 words to channel $2 from vector $3, starting at word 1 of the vector. |
0x5F 0x10 | 4->1 | READREC | Read $1 words from channel $3 into vector $4, starting at record $2, and putting the data two bytes after the beginning of the vector. System variable 0xCC defines record size. |
0x5F 0x11 | 4->1 | WRITEREC | Write $1 words to channel $3, starting at record $2. Data come from 2 bytes after the beginning of vector $4. System variable 0xCC defines record size. |
0x5F 0x12 | -> | DISP | Display operation. $B specifies operation:
0: Cursor right 1: Cursor left 2: Cursor down 3: Cursor up 4: If cursor column < 80, print a space 5: Clear from cursor row to bottom |
0x5F 0x13 | 1->1 | XDISP | Extended display operation. $B specifies operation:
0: Scroll area from cursor row to row specified in system variable 0xC4 down by $1 lines. Clears area if $1 = 0. 1: Scroll area from cursor row to system variable 0xC4 up by $1 lines. Clears area if $1 = 0. 2: No effect on PC Cornerstone. 3: No effect on PC Cornerstone. 4: Scroll area between rows specified in system variables 0xC4 and 0xC5 down by $1 lines. Clears area if $1 = 0. 5: Scroll area between rows specified in system variables 0xC4 and 0xC5 up by $1 lines. Clears area if $1 = 0. 6: Draw a horizontal line to column $1. |
0x5F 0x14 | 1->1 | FSIZE | Get size of file attached to channel $1, divided by 256. |
0x5F 0x15 | 1->1 | UNLINK | Delete the file whose name is $1 |
0x5F 0x16 | ?-> | PULLRET | If the last return was not RET (ie, a value was returned), remove one word from the stack. |
0x5F 0x17 | ->1 | KBINPUT | Poll the keyboard. Returns FALSE if no key hit, otherwise the key. Cursor keys are translated into escape sequences — 0x1B followed by the scancode. ESC itself becomes 0x1B 0x00. |
0x5F 0x18 | |||
0x5F 0x19 | |||
0x5F 0x1A | |||
0x5F 0x1B | |||
0x5F 0x1C | 1-> | TPULL | Increase the tuple stack pointer by $1 words. |
0x5F 0x1D | |||
0x5F 0x1E | |||
0x5F 0x1F | 5>1 | STRICMP | Compare two strings, ignoring spaces and upper/lower case.
$1 = length of first string $2 = offset within first vector $3 = length of second string $4 = vector address of first string $5 = vector address of second string |
0x5F 0x20 | 5>1 | STRICMP1 | As STRICMP, but vector ID of first string is decremented by one before comparison. |
0x5F 0x21 | 4->1 | ||
0x5F 0x22 | 1->1 | Unknown, but appears to take a vector incorporating attributes and screen coordinates. | |
0x5F 0x23 | 2->1 | STRCMP | Compares two strings; $1 and $2 are the VM addresses of the two strings. |
0x5F 0x24 | 2->1 | MEMCMP | Compares $1 bytes of memory at VM addresses $2 and $3. Pushes -1, 0 or 1. |
0x5F 0x25 | 2->1 | MEMCMPO | Compares $2 bytes of memory at VM addresses $3 (plus offset $1) and $4. Pushes -1, 0 or 1. |
0x5F 0x26 | 2->1 | VECL | $2 is a vector ID. $1 is an offset. Converts $2 to an address. Let X be the byte at address + ($1 - 1). Pushes $1 plus the word at offset X from the start of the vector plus 3. |
0x5F 0x27 | ->1 | DECMG | Decrement module global variable $B, and push the resulting value onto the stack. |
0x5F 0x28 | DECG | Decrement global variable $B, and push the resulting value onto the stack. | |
0x5F 0x29 | ?-> | PULL_ | Remove $B words from the stack. |
0x5F 0x2A | INCG | Increment global variable $B, and push the resulting value onto the stack. | |
0x5F 0x2B | ->1 | INCMG | Increment module global variable $B, and push the resulting value onto the stack. |
0x5F 0x2C | 1->1 | STOREMG | As PUTMG, but $1 remains at the top of the stack. |
0x5F 0x2D | 1->1 | STOREG | As PUTG, but $1 remains at the top of the stack. |
0x5F 0x2E | ->1 | Return constant value $B. | |
0x5F 0x2F | 1-> | PRCHAR | Prints $1 as a character. No character set translaton is done; the text is assumed to be in the default codepage (probably 437). |
0x5F 0x30 | |||
0x5F 0x31 | |||
0x5F 0x32 | |||
0x5F 0x33 | |||
0x5F 0x34 | |||
0x5F 0x35 | |||
0x5F 0x36 | |||
0x5F 0x37 | 8->1 | ||
0x5F 0x38 | 2->1 | RENAME | Rename a file. $1 is the old filename, $2 the new. |
0x60-0x9F | ->1 | VREAD__ | Read word (n) of a vector.
Bits 0-3 of the opcode give the number of a local variable which holds the vector ID. Bits 4-5 of the opcode give the number of the word to read from the vector. |
0xA0-0xBF | ->1 | PUSHL_ | As PUSHL, but the variable number is given by by the low 5 bits of the opcode. |
0xC0-0xDF | 1-> | PUTL_ | As PUTL, but the variable number is given by the low 5 bits of the opcode. |
0xE0-0xFF | 1->1 | STOREL_ | As STOREL, but the variable number is given by the low 5 bits of the opcode. |
System variables
When module globals are accessed, a variable number of 0xC0 or higher indicates an interpreter system variable:
0xC0: Controls behaviour of keyboard input -- how, I'm not sure. 0xC1: Set to FALSE to disable screen output 0xC2: Not used by PC Cornerstone 0xC3: Not used by PC Cornerstone 0xC4: Used to define window(s) on the screen. 0xC5: Used to define window(s) on the screen. 0xC6: Extended error code? Set to 7 if READREC fails. 0xC7: Screen width, columns - 1 0xC8: Screen height, rows - 1 0xC9: Cursor column 0xCA: Cursor row 0xCC: Record size for READREC / WRITEREC 0xCD: Current month 0xCE: Current day 0xCF: Current year 0xD0: Current time, hours 0xD1: Current time, minutes 0xD2: Current time, seconds 0xD3: Used by opcode 0x5F 0x22. 0xD4: Control-Break flag (0 if Control-Break has been pressed.) 0xD5: Screen attributes: Bit 0: Reverse video Bit 3: Bright Bit 5: Blinking 0xD7: A 3-letter string, encoded into 16 bits as: enc(str[0]) << 11 | enc(str[1]) * 45 | enc(str[2]) enc(char) is: 0-25 for A-Z 26-35 for 0-9 36-44 for $ & # @ ! % - _ / respectively This is normally 'DBF' but the /e parameter to MME can be used to change it. 0xDA: Bits 0-4: Channel handle of OBJ file Bits 5-15: Total size of modules (header word 0x0E << 5) 0xDB: 0 if beeper enabled, else disabled. 0xE7: System statistics: #outc 0xE8: System statistics: #outs 0xE9: System statistics: #curpos 0xEA: System statistics: #disp 0xEB: System statistics: #xdisp 0xEC: System statistics: #gets 0xED: System statistics: #sets 0xEE: System statistics: #hsets 0xEF: System statistics: #vsets
John Elliott 2014-04-15