diff --git a/docs/aam-specification-1.1.adoc b/docs/aam-specification-1.1.adoc new file mode 100644 index 0000000..f5c12b4 --- /dev/null +++ b/docs/aam-specification-1.1.adoc @@ -0,0 +1,3463 @@ += Å-machine 1.0 Specification + +This file contains a detailed description of the Å-machine (Aa-machine) +version 1.0, including the two file formats (story and saved game) and the +bytecode semantics. Be warned that the description is technically +comprehensive and rather condensed. + +Note that 1.0 is not any more official or stable than its predecessor, 0.5. +The major version change marks where the community forked the project and +took over development and maintenance. + +:toc: + +== Changes in v1.1 + +<> behavior changed to match other arithmetic instructions + +== Changes in v1.0 + +New opcodes: + +* <> +* <> +* <> + +Changed opcodes: + +* <> + +New <>: + +* Div width +* Div height +* Interpreter supports styling +* Interpreter supports color +* Interpreter supports text alignment +* Currently transcripting + +Removed some previously-deprecated opcodes: + +* <> 0 +* <> + +Expanded key-value options for resources to include ARIA roles + +Specified behavior of unrecognized <> options + +Specified format of resource option strings + +Rewrote some portions of the spec for clarity + +These are still early days, and everything is subject to change. When only the +minor version number is incremented, efforts are made to keep all changes +backwards-compatible. + +== Conventions + +The notation `a..b` means the range of integers from a to b, inclusive. + +The notation `a-b` means the range of characters from a to b, inclusive. + +`BYTE` always means eight bits, `SHORT` +always means 16 bits (two bytes), and `LONG` always means 32 bits (four +bytes). `WORD` means either `SHORT` or `LONG`, depending on the word size +specified in <>. + +The notation: + +* Number of whatsits +** Whatsit data + +Means that the "whatsit data" appears "number of whatsits" times in sequence. + +Throughout this document, numbers used to count things (like "16 bits" or +"``WORD[64]``") are decimal (base 10), while numbers used as values (like "80" or +"7fff") are hexadecimal (base 16). In ambiguous cases, hexadecimal numbers are +marked with a `$` symbol. In pseudocode, hexadecimal numbers start with `0x`; +all other numbers are decimal. + +== Runtime data + +=== Words + +Most data is organized into words. A word is currently always 16 bits. +When words are stored in a save file, the byte order is big-endian. + +The internal format of a 16-bit word could be: + +*A raw 16-bit value*, such as an index into one of the heap +areas. + +*16 independent flags*, numbered 0..f starting with the most +significant bit. + +The unconventional bit order works together with +big-endian byte ordering to allow interpreters to work +with word-addressing or byte-addressing when dealing +with flag numbers. + +[[literal,literal]] +*A literal value*, as follows: + +* ``0001..1fff`` Object +* ``2000..3dff`` Dict word (index into dictionary) +* ``3e00..3eff`` Character +* ``3f00      `` `[]` (empty list) +* ``3f01..3fff`` reserved +* ``4000..7fff`` Integer in the range 0..16383 + +[[live]] +*A live value (tagged reference)*, as follows: + +* ``0000      `` undefined +* ``0001..7fff`` <> +* ``8000..9fff`` Indirect reference (word index into heap) +* ``a000..bfff`` reserved +* ``c000..dffe`` Pair (word index into heap) +* ``e000..fffe`` Extended dict word (word index into heap) + +Live values only exist in registers or on the heap, and +can be invalidated when the heap shrinks. + +In this document, tagged references are described as if +they had two independent fields, "tag" and "value". But +the tag is actually determined from a variable number +of upper bits of the word, as follows (v is the value): + +* `00000000 00000000` null_tag +* `000vvvvv vvvvvvvv` object (v > 0) +* `001vvvvv vvvvvvvv` word (v < 1e00) +* `00111110 vvvvvvvv` char +* `00111111 00000000` empty_tag +* `01vvvvvv vvvvvvvv` number +* `100vvvvv vvvvvvvv` reference +* `101vvvvv vvvvvvvv` (reserved) +* `110vvvvv vvvvvvvv` pair +* `111vvvvv vvvvvvvv` extdict + +The tag `001vvvvv vvvvvvvv` may also be referred to as +a "dict" to avoid ambiguity with the other meaning of +"word" (any two-byte value). + +A pair is a pointer to a two-word cell on the heap. The +first word of that cell is the head of the list (can be +any value). The second word of that cell is the rest of +the list (either a pair or `[]`). + +An extended dict (extdict) is a pointer to a two-word +cell on the heap. If the first word of that cell is a +pair, it represents an unrecognized dict (not found in +the dictionary); the first word is a list of characters +and the second word is `[]`. Otherwise, it represents a +dict with an optional ending; the first word is a dict +(the essential part) and the second word is a list of +characters (the optional part). + +*A stored value*, as follows: + +* ``0000      `` Unset (Null) +* ``0001..7fff`` <> +* ``8000..fffe`` Word index into long-term storage area + +Stored values exist in global and object variables (the +lower parts of RAM) and may have to be updated when +long-term storage is rearranged. See the pseudocode for +<>. + +Part of a *serialized data stream*: + +* ``0000      `` End of stream marker +* ``0001..7fff`` <> +* ``8000      `` Unbound variable +* ``8001..80ff`` reserved +* ``8100      `` Extdict (two words) follows +* ``8101..bfff`` reserved +* ``c000..dfff`` Proper list with (x & 1fff) elements follows +* ``e000..fffe`` Improper list with (x & 1fff) elements follows + +Values are serialized into this format if they need to be +stored on the aux heap or in long-term storage. See the +pseudocode for <> and +<>. + +The special value `3f3f` marks unused words in the main heap, +aux, and random access areas. This is handy for measuring peak +memory usage. + +=== Text + +The game-specific *character set* is a single-byte encoding. + +* ``00-1f`` are reserved. Some represent special keypresses: +** `08` backspace/delete +** `0d` return +** `10` up +** `11` down +** `12` left +** `13` right +* ``20-7e`` correspond to the ASCII characters +* ``7f   `` is reserved +* ``80-ff`` are game-specific + +Characters 80-ff are mapped to Unicode codepoints by a game-specific +*character mapping table*. + +Characters from the *character set* may appear in input, dictionary +words, and output. As part of input processing, characters are +converted to lowercase. The character mapping table contains +information about case conversion for letters other than A-Z. + +Strings are sequences of printable characters (20-7e, 80-ff) and +dictionary words, packed into bit streams according to a +Huffman-inspired encoding. + +* Characters in the ranges 20-7e and 80-9f (ASCII and 32 other + codepoints as per the character mapping table) have a compact, + variable-length encoding. + +* Characters in the range 9f-ff and dictionary words are + represented by a less efficient (but simpler) encoding. + +There is a game-specific bitstream decoding tree with up to 128 nodes +(see <>). + +=== Game state + +==== Unsaved data + +* Most output state (style, screen contents, ...) +* Runtime flags (transcript, trace, uppercase) +* TMP (global register used during some recursive operations) + +With the exception of the random access area (see below), the following +data is not directly addressable by runtime code, so interpreters are +free to work with a different internal representation at runtime. + +==== Special-purpose registers + +**Byte pointers into <> chunk:** + +* `LONG`: `INST`, Instruction pointer +* `LONG`: `CONT`, Continuation pointer + +**Word pointers into main heap:** + +* `WORD`: `TOP`, top of main heap (grows upwards) +* `WORD`: `ENV`, env frame pointer (grows downwards) +* `WORD`: `CHO`, choice frame pointer (grows downwards) +* `WORD`: `SIM`, simple cut pointer or null + +**Word pointers into aux heap:** + +* `WORD`: `AUX`, aux stack pointer (grows upwards) +* `WORD`: `TRL`, trail stack pointer (grows downwards) +* `WORD`: `STA`, stoppable aux pointer +* `WORD`: `STC`, stoppable choice pointer + +**Other:** + +* `BYTE`: `CWL`, collect-words nesting level +* `BYTE`: `SPC`, whitespace level + +Values for SPC: + +* `0` auto +* `1` nospace +* `2` nbsp +* `3` pendingspace +* `4` space +* `5` line +* `6` par + +==== General-purpose registers + +* `WORD[64]`: R00-R3f, general registers (args, temps, constants, globals) + +R3f is also known as `IDX` and is treated specially by certain instructions. + +This is how dialogc uses these 64 registers: + +* ``R00..R0c`` Arguments (with optional just-pointer) +* ``R0d..R3c`` Temporary values +* ``R3d     `` Quick temporary register (always available) +* ``R3e     `` Constant `[]` (3f00) +* ``R3f `` Only used as `IDX` + +Since registers take only one byte to encode and a large +immediate value takes two, storing frequently-needed constants in +registers can lead to smaller code. + +With a few exceptions, all of the registers (special-purpose and +general-purpose) are initialized to zero when the virtual machine +starts or restarts. The exceptions are: + +* `INST` = 1 +* `ENV` = <>.heapsz +* `CHO` = <>.heapsz +* `TRL` = <>.auxsz +* `SIM` = ffff +* `SPC` = line + +In case of a runtime error, the machine is restarted with R00 +initialized to a tagged integer according to the following table: + +* `1`: Heap space exhausted +* `2`: Auxiliary heap space exhausted +* `3`: Type error: Expected object +* `4`: Type error: Expected bound value +* `6`: Long-term heap space exhausted +* `7`: Invalid output state. + +Error code 5 is not used by the Å-machine. + +==== Saved output state + +* `WORD`: Number of active divs +* `WORD[n]`: Active divs (class ids), outermost first + +==== Initialized registers + +The main chunk of saved data follows. The game state must conform to +this format during restart, save, and restore operations (including the +big-endian byte ordering). + +* `WORD`: `NOB`, Number of objects +* `WORD`: `LTB`, Long-term heap bottom (index into random access area) +* `WORD`: `LTT`, Long-term heap top (index into random access area) + +==== Random access area + +This memory is word-addressable, and it may contain pointers in +the form of word offsets into itself. + +* `WORD`: Word offset of global data +* `WORD[<>]`: Word offsets of per-object data +* `WORD[<>.ramsz - <> - 1]`: Data + +The word offset of a data field is given by the following +pseudo-code: + +---- +def field_addr(field, obj): + if(obj > NOB) { + runtime_error(3) + } else { + return ram[obj] + field + } +---- + +When reading, non-null non-objects should return zero: + +---- +def read_field(field, obj): + if(obj > NOB) { + return 0 + } else { + return ram[ram[obj] + field] + } +---- + +For both `field_addr` and `read_field`, "obj" may be 0 to access global +variables (not affiliated with any object). + +==== Heaps + +* `WORD[<>.auxsz]`: Auxiliary heap (aux, trail) +* `WORD[<>.heapsz]`: Main heap (heap, env, choice) + +Saved games contain a RAM area that matches the layout described here +(starting with 'Initialized registers'). The on-disk representation +is a run-length encoded xor difference with the contents of the INIT +chunk (padded by copies of the 'unused' word, `3f3f`). + +It is possible to track the game state in RAM in the same format at +runtime, although it may be more efficient to represent words in the +native byte order. + +=== Long-term storage + +The long-term storage area is part of the random access area, +specifically all the words from LTB (inclusive) to LTT +(exclusive). + +Data in the long-term storage area is organized into chunks: + +* `WORD`: "n", Size in words of this chunk +* `WORD`: Pointer to owner reference (index into random access area) +* `WORD[n - 2]`: Serialized data + +These chunks can move around at runtime (see pseudocode for <> below). When this happens, pointers are +updated in the random access area. + +=== Env frame format + +From lower to higher heap index: + +* `WORD`: Saved ENV +* `WORD`: Saved SIM +* `LONG`: Saved CONT +* `WORD[]`: Local variables + +=== Choice frame format + +From lower to higher heap index: + +* `WORD`: Saved ENV +* `WORD`: Saved SIM +* `LONG`: Saved CONT +* `LONG`: Failure handler +* `WORD`: Saved CHO +* `WORD`: Saved TOP +* `WORD`: Saved TRL +* `WORD[]`: Saved arguments + +=== Stop frame format + +From lower to higher aux index: + +* `WORD`: Saved STA +* `WORD`: Saved STC + +== Savefile + +Saved games are stored in an IFF format, with form identifier "AASV" and the +following chunks. HEAD must be the first chunk inside the form. + +* <>: Story file header +* <>: Compressed game state +* <>: Registers and active divs + +=== HEAD chunk + +The contents of this chunk must exactly match the <> chunk of the +story file. + +=== DATA chunk + +Take the game state (including the full extent of the heaps), then +exclusive-or with the contents of the <> chunk padded with the +'unused' word (`3f3f`). This stream of bytes is run-length encoded in +the following way: A non-null byte is represented by itself. A stretch +of N (1..256) null bytes is represented by a null byte, followed by a +byte of value N - 1. + +=== REGS chunk + +* `WORD[64]`: State of the <> +* `LONG[2]`: State of the <> +* `WORD[8]`: (continued) +* `BYTE[2]`: (continued) +* `WORD`: Number of active divs +* `WORD[n]`: Active divs (class ids), outermost first + +The game state is stored as part of the <> opcode, and the saved value of +<> is taken from one of the operands. +In addition, interpreters may save during any ongoing <> or +<> operation. In that case, the saved <> +must point to the <> or <> instruction, so that it will +execute again when the game is restored. + +Interpreters may create additional chunks describing the current output state. +These can be in any format, and should probably include the name and version of +the interpreter. Unrecognized chunks must be ignored on loading. + +== Story file + +The story file is in IFF format, with form identifier "AAVM" and the following +chunks. <> must be the first chunk inside the form. In the following table, +(*) marks the chunks that participate in the story checksum. + +* <>: File header +* <> (*): Bytecode instructions +* <> (*): Game dictionary +* <>: Embedded resource file (may appear more than once) +* <> (*): Initial game state, also needed for save/restore +* <> (*): Character set, bitstream decoder, word endings +* <> (*): Style sheet +* <> (*): Word-to-object maps +* <> (optional): Story metadata +* <> (optional): Internal object names +* <>: Table of resources +* <> (*): Compressed text + +An interpreter may regard the entire story file as being mapped into a single, +contiguous read-only address space, and add the corresponding file offset to +addresses within the chunks. Or it could regard each chunk as a separate +address space. + +[[HEAD]] +=== HEAD + +* `BYTE[2]`: "version", File format version (major, minor) +* `BYTE`: "wordsz", Word size in bytes (currently always 2) +* `BYTE`: "shift", Shift amount for short/long <> + +* `SHORT`: "release", Story release number +* `BYTE[6]`: "serial", Story serial number (ASCII) + +* `LONG`: "crc", Running CRC-32 of the contents of <>, <>, <>, <>, <>, <>, and <>, in that specific order + +* `WORD`: "heapsz", Size (in words) of heap/env/choice area +* `WORD`: "auxsz", Size (in words) of aux/trail area +* `WORD`: "ramsz", Size (in words) of random access area (including long-term heap) + +* `BYTE[46]`: "ifid" (optional), "UUID://...//" + null (ASCII) + +[[INIT]] +=== INIT + +This chunk corresponds to the format of the game state detailed above, +starting with the <>. Words +are stored in big-endian byte order. The chunk can be smaller than the +RAM area. Remaining words are assumed to be filled with the 'unused' value +3f3f. + +[[LANG]] +=== LANG + +* `BYTE[2]`: Offset in chunk of <> +* `BYTE[2]`: Offset in chunk of <> +* `BYTE[2]`: Offset in chunk of <> +* `BYTE[2]`: Offset in chunk of <> + +[tbl-decode] +==== Bitstream decoding table + +* `BYTE[][2]`: Up to 128 2-byte entries. Entry 0 is the root. + +Each entry: + +* `xx yy` If the bit was 0, perform x, otherwise perform y. + +An individual byte in the table is decoded as follows: + +* ``00..5e`` Character $20 + x +* ``60..7f`` Character $20 + x +* ``5f    `` Escape code (see below) +* ``80    `` End of string +* ``81..ff`` Go to table entry x - $80 + +Escape code handling for story file version 0.3 or earlier: + +* Read 7 bits, msb first, from the stream. Call this X. +* Produce character $80 + X. Note that X must be greater than or equal to $20. + +Escape code handling for story file version 0.4 or later: + +* Let B = `max(0, n_ext - 32)` where `n_ext` is the number of extended characters, as defined in the <> chunk. +* Let K = `ceil(log2(B + n_dict))` where `n_dict` is the number of dictionary words, as defined in the <> chunk. +* Read K bits, msb first, from the stream. Call this X. +* If X < B, produce character $a0 + X. +* Otherwise, produce a space character ($20) followed by dictionary word X - B. + +[tbl-extchar] +==== Extended character table + +* `BYTE`: Number of extended characters +** `BYTE`: Corresponding lowercase character (can be the same) +** `BYTE`: Corresponding uppercase character (can be the same) +** `BYTE[3]`: Unicode codepoint + +[tbl-ending] +==== Word endings decoder + +An array of instruction bytes: + +* `00`: Fail +* `01`: Check +* `xx yy`: Shift xx, jump yy + +*Fail*: The word is not valid. Shift all letters and create an +unrecognized word. + +*Check*: If the word is in the dictionary, succeed (with the +current ending). + +*Shift xx, jump yy*: If the last character is xx, then move it to +the ending and resume execution at byte offset yy in the +decoder. + +[tbl-stop] +==== Special characters table + +* `BYTE[]`: Stop characters, a null-terminated set of characters that will be treated as separate words when parsing player input. + +The following fields are present if the storyfile version is +0.4 or more. + +* `BYTE[]`: Null-terminated subset of stop characters that will inhibit whitespace before them, such as ".". [spec 0.4] +* `BYTE[]`: Null-terminated subset of stop characters that will inhibit whitespace after them, such as "(". [spec 0.4] + +[[DICT]] +=== DICT + +* `WORD`: Number of dictionary words +** `BYTE`: Length of word (2..ff) +** `BYTE[2]`: Start of characters (offset in chunk) + +Strings of characters (arrays of bytes in the game-specific character +set) referenced by the dictionary. No terminator. Can overlap. + +[[MAPS]] +=== MAPS + +* `BYTE[2]`: Number of word-to-object maps +** `BYTE[2]`: Offset in chunk of word-to-object map + +Each word-to-object map: + +* `BYTE[2]`: Number of entries (sorted by index value) +** `WORD`: Index value (dictionary word or character) +** `WORD`: Single object id + e000, offset in chunk of payload, or null (for wildcard words such as 'the') + +Payload data: + +* ``00       `` End +* ``01..df   `` Short object id (0001..00df) +* ``e0..ff xx`` Long object id (0001..1fff) + +[[LOOK]] +=== LOOK + +* `BYTE[2]`: Number of style classes +** `BYTE[2]`: Offset in chunk of style definition + +Each style definition consists of zero or more null-terminated ASCII +strings. Each is a CSS key-value pair without the final semicolon, e.g. +"width: 100%". The original case from the Dialog source code is +preserved, so interpreters must perform case-insensitive comparison. +Leading whitespace has been removed from the entries. A blank entry +(just the terminating null byte) marks the end. + +These pairs are typically CSS properties, but may include other +information of use to the interpreter, such as ARIA roles. Thus, +interpreters must ignore any unrecognized keys or values, rather than +raising an error. + +[[TAGS]] +=== TAGS (optional) + +* `WORD`: Number of named objects +** `BYTE[2]`: Offset in chunk of null-terminated object name + +Strings of characters for the object names. + +[[META]] +=== META (optional) + +* `BYTE`: Number of entries +** `BYTE`: Identifier +** `BYTE[]`: Null-terminated string (variable length) + +Identifiers: + +* `01`: Story title +* `02`: Story author +* `03`: Story noun +* `04`: Story blurb (double linefeed is a paragraph break) +* `05`: Release date in YYYY-MM-DD format +* `06`: Compiler version string + +Strings use the game-specific character set, except that $10 (normally +undefined for output) is a line feed. + +[[WRIT]] +=== WRIT + +A byte-addressable read-only chunk containing packed bitstreams. +Streams always begin on a byte boundary. Bits are packed into bytes +starting with the MSB. Extra null bytes can be inserted to make better +use of packed addressing modes. + +[[URLS]] +=== URLS + +* `BYTE[2]`: Number of resources +** `BYTE[2]`: Offset in chunk of descriptor + +Each descriptor is: + +* `BYTE[3]`: String pointer to alt-text (shift according to header) + +followed by a null-terminated URL, followed by a null-terminated option +string. This option string is currently taken as a comma-delimited list +of options, each of which may be a simple string, or a key and value +separated by a colon. + +It is up to the interpreter to decide how to embed (or link to) a +resource. The filename ending can be significant, in addition to the +file contents. + +The URL scheme "file" refers to data inside <> chunks, e.g. +"file:title.png". + +[[FILE]] +=== FILE + +A null-terminated filename, e.g. "title.png", immediately followed by +the file contents. + +[[CODE-chunk,CODE]] +=== CODE + +A byte-addressable read-only chunk, up to 8 MB in size, containing +bytecode instructions. + +Address 0 must always contain the <> instruction. Interpreters may +thus intercept branches to address 0 and trigger a failure condition +immediately, which may be faster. + +Address 1 is the program/error entry point. The error code is passed as +a tagged integer in register R00. For regular program entry, R00 is +null. + +An opcode is always one byte, and it determines the number and format +of subsequent operands. The MSB of the opcode often distinguishes +between two variants of the same operation. This gives interpreter +authors a choice between dispatching on the entire byte (256 table +entries), or dispatching on the lower seven bits (128 table entries), +and then branching on the MSB at a later stage while processing the +instruction. + +[[op]] +==== Operands + +Operand types (always big-endian byte ordering): + +[[ZERO,0]] +**0**: implicit constant zero, not encoded + +[[BYTE,BYTE]] +*BYTE*: small constant number, e.g. number of slots to allocate + +* `xxxxxxxx`: Number 00..ff + +[[VBYTE,VBYTE]] +*VBYTE*: small constant <> (null or object number 1..ff) + +* `xxxxxxxx`: Literal 0000..00ff + +[[WORD,WORD]] +*WORD*: constant number, e.g. line number for tracing + +* `xxxxxxxx xxxxxxxx`: Number 0000..ffff + +[[VWORD,VWORD]] +*VWORD*: constant <> + +* `0xxxxxxx xxxxxxxx`: Literal 0000..7fff + +[[RAW,RAW]] +*RAW*: unsigned 16-bit number + +* `0xxxxxxx xxxxxxxx`: Number 0000..7fff +* `10xxxxxx`: Value of register 00..3f +* `11xxxxxx`: Value of env slot 00..3f + +[[VALUE,VALUE]] +*VALUE*: <> (tagged reference) + +* `0xxxxxxx xxxxxxxx`: Literal 0000..7fff +* `10xxxxxx`: Value of register 00..3f +* `11xxxxxx`: Value of env slot 00..3f + +[[DEST,DEST]] +*DEST*: destination for the result + +* `00xxxxxx`: Store into register 00..3f +* `01xxxxxx`: Store into env slot 00..3f +* `10xxxxxx`: Unify with register 00..3f +* `11xxxxxx`: Unify with env slot 00..3f + +[[INDEX,INDEX]] +*INDEX*: medium raw constant, e.g. index of variable, style class + +* `0xxxxxxx`: Index 00..7f +* `10xxxxxx`: Index 80..bf +* `11xxxxxx xxxxxxxx`: Index 0000..3fff + +[[CODE,CODE]] +*CODE*: byte address in <> chunk + +* `00000000`: Absolute address 0 (known to contain the <> opcode) +* `00xxxxxx`: Close relative pointer (+ 1..3f bytes) +* `01xxxxxx xxxxxxxx`: Relative pointer (± 8 kB), 14-bit signed integer relative to end of operand +* `1xxxxxxx xxxxxxxx xxxxxxxx`: Absolute pointer (full 8 MB range) + +[[STRING,STRING]] +*STRING*: shifted byte address in <> chunk: tiny pointers are shifted + right by 1 bit, short and long pointers are shifted right by + a number of bits stored in the header + +* `0xxxxxxx`: Tiny pointer (shift by 1) +* `10xxxxxx xxxxxxxx`: Short pointer (shift per header) +* `11xxxxxx xxxxxxxx xxxxxxxx`: Long pointer (shift per header) + +Decoding of a CODE operand can sometimes can be postponed until after +the execute stage, when it may be possible to decode straight into INST +or skip the operand altogether. Both close and relative pointers are +decoded relative to INST when encountering the CODE operand. + +RAW and VALUE only differ when the value read from a register or env +variable has its high bit set. For a RAW operand, 8002 is interpreted +as the number 8002. For a VALUE operand, it is treated as a pointer, +and the word at heap address 2 will be fetched and used instead. + +== Instructions + +<> + +* 0x: Execution flow +* 1x: Live data manipulation, stoppable environments +* 2x: Random access data manipulation +* 3x: Conditional branches +* 4x: Conditional branches (negated) +* 5x: Arithmetic +* 6x: Output +* 7x: Input, system control, miscellaneous + +[%header,cols="1,2,2,1",format=tsv,stripes=even] +|=== +Opcode Mnemonic Args Version + +00 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +01 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +02 <> <> {empty} +03 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +04 <> <> {empty} +05 <> <> {empty} +85 <> <> {empty} +06 <> <> {empty} +86 <> <> {empty} +07 <> <> {empty} +87 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +08/88 <> <>/<> {empty} +09 <> {empty} {empty} +89 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +0a/8a <> <>/<> <> {empty} +0b/8b <> <>/<> {empty} +0c/8c <> <>/<> <> {empty} +{empty} {empty} {empty} {empty} + +0d <> {empty} {empty} +0e <> <> {empty} +0f <> <> {empty} +{empty} {empty} {empty} {empty} + +10 <> <> <> {empty} +90 <> <> <> ≥ 0.4 +{empty} {empty} {empty} {empty} + +11 <> <> {empty} +{empty} {empty} {empty} {empty} + +12 <> <> <> <> {empty} +13/93 <> <>/<> <> <> {empty} +{empty} {empty} {empty} {empty} + +14 <> <> {empty} +94 <> <> ≥ 0.5 +15/95 <> <>/<> {empty} +16 AUX_POP_VAL <> < 1.0 +17 <> <> {empty} +18 <> <> {empty} +19 <> <> {empty} +{empty} {empty} {empty} {empty} + +1b <> <> <> <> {empty} +{empty} {empty} {empty} {empty} + +1c <> {empty} {empty} +1d <> <> {empty} +1e <> {empty} {empty} +{empty} {empty} {empty} {empty} + +1f <> <> <> ≥ 0.4 +9f <> <> <> ≥ 0.4 +{empty} {empty} {empty} {empty} + +20/a0 <> <>/<> <> <> {empty} +21/a1 <> <>/<> <> <> {empty} +22/a2 <> <>/<> <> <> {empty} +{empty} {empty} {empty} {empty} + +24/a4 <> <>/<> <> <> {empty} +25/a5 <> <>/<> <> <> {empty} +26/a6 <> <>/<> <> <> {empty} +{empty} {empty} {empty} {empty} + +28/a8 <> <>/<> <> {empty} +29/a9 <> <>/<> <> {empty} +{empty} {empty} {empty} {empty} + +2d/ad <> <>/<> <> <> <> {empty} +{empty} {empty} {empty} {empty} + +2e/ae <> <>/<> <> {empty} +2f/af <> <>/<> <> {empty} +{empty} {empty} {empty} {empty} + +30/b0 <> <>/<> <> <> {empty} +31 <> <> <> {empty} +32 <> <> <> {empty} +33 <> <> <> {empty} +34 <> <> <> {empty} +35 <> <> <> {empty} +36 <> <> <> {empty} +b6 <> <> <> ≥ 0.4 +37 <> <> <> <> {empty} +38 <> <> <> <> {empty} +39/b9 <> <>/<> <> <> {empty} +3a/ba <> <>/<> <> <> <> {empty} +3b/bb <> <>/<> <> <> {empty} +3c <> <> {empty} +3d/bd <> <>/<> <> <> <> ≥ 0.4 +{empty} {empty} {empty} {empty} + +40/c0 <> <>/<> <> <> {empty} +41 <> <> <> {empty} +42 <> <> <> {empty} +43 <> <> <> {empty} +44 <> <> <> {empty} +45 <> <> <> {empty} +46 <> <> <> {empty} +c6 <> <> <> ≥ 0.4 +47 <> <> <> <> {empty} +48 <> <> <> <> {empty} +49/c9 <> <>/<> <> <> {empty} +4a/ca <> <>/<> <> <> <> {empty} +4b/cb <> <>/<> <> <> {empty} +4c <> <> {empty} +4d/cd <> <>/<> <> <> <> ≥ 0.4 +{empty} {empty} {empty} {empty} + +50 <> <> <> <> {empty} +d0 <> <> <> {empty} +51 <> <> <> <> {empty} +d1 <> <> <> {empty} +52 <> <> <> {empty} +{empty} {empty} {empty} {empty} + +58 <> <> <> <> {empty} +d8 <> <> <> {empty} +59 <> <> <> <> {empty} +d9 <> <> <> {empty} +5a <> <> <> <> {empty} +5b <> <> <> <> {empty} +5c <> <> <> <> {empty} +5d <> <> <> <> {empty} +{empty} {empty} {empty} {empty} + +60 <> <> {empty} +e0 <> <> {empty} +61 <> <> {empty} +e1 <> <> {empty} +{empty} {empty} {empty} {empty} + +62 <> {empty} {empty} +e2 <> {empty} {empty} +63 <> {empty} {empty} +e3 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +64 <> <> {empty} +{empty} {empty} {empty} {empty} + +65 <> <> {empty} +{empty} {empty} {empty} {empty} + +66 <> <> {empty} +e6 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +67 <> <> <> < 1.0 +e7 <> {empty} < 1.0 +{empty} {empty} {empty} {empty} + +67 <> <> ≥ 1.0 +{empty} {empty} {empty} {empty} + +68 <> <> ≥ 0.2 +e8 <> {empty} ≥ 0.2 +{empty} {empty} {empty} {empty} + +69 <> <> {empty} +e9 <> {empty} {empty} +{empty} {empty} {empty} {empty} + +6a <> {empty} ≥ 0.4 +ea <> {empty} ≥ 0.4 +{empty} {empty} {empty} {empty} + +6b <> <> deprecated +eb <> <> deprecated +{empty} {empty} {empty} {empty} + +6c <> <> ≥ 0.2 +ec <> <> <> ≥ 0.2 +{empty} {empty} {empty} {empty} + +6d <> <> <> {empty} +{empty} {empty} {empty} {empty} + +6e <> <> ≥ 0.4 +ee <> {empty} ≥ 0.4 +{empty} {empty} {empty} {empty} + +6f <> <> <> ≥ 0.5 +ef <> {empty} ≥ 1.0 +{empty} {empty} {empty} {empty} + +70 <> <> {empty} +{empty} {empty} {empty} {empty} + +72 <> <> {empty} +f2 <> <> {empty} +{empty} {empty} {empty} {empty} + +73 <> <> {empty} +f3 <> <> {empty} +{empty} {empty} {empty} {empty} + +74 <> <> <> {empty} +{empty} {empty} {empty} {empty} + +78 <> <> {empty} +79/f9 <> <>/<> <> {empty} +7a/fa <> <>/<> <> <> {empty} +7b/fb <> <>/<> <> {empty} +7c <> <> <> {empty} +{empty} {empty} {empty} {empty} + +7d <> <> <> <> ≥ 0.4 +fd <> <> <> <> ≥ 0.4 +{empty} {empty} {empty} {empty} + +7f <> <> <> <> <> {empty} +|=== + +[[status-values]] +\* <> byte values: + +* 00: top status area +* 01: inline status area + +[[EXT0,EXT0]] +† <> byte values: + +* 00: <> +* 01: <> +* 02: <> +* 03: <> +* 04: <> +* 05: <> +* 06: <> +* 07: <> +* 08: <> +* 09: <> +* 0a: <> +* 0b: <> +* 0c: <> +* 0d: <> +* 0e: <> +* 0f: <> (≥ 0.4) +* 10: <> (≥ 0.5) +* 11: <> (≥ 0.5) +* 12: <> (≥ 1.0) +* 13: <> (≥ 1.0) + +[[vminfo-values]] +‡ <> byte values: + +These return tagged numeric values: + +* 00: peak heap +* 01: peak aux +* 02: peak longterm +* 20: current div width +* 21: current div height + +These return raw 0/1 values: + +* 40: interpreter supports undo +* 41: interpreter supports save/restore +* 42: interpreter supports hyperlinks +* 43: interpreter supports quit +* 44: interpreter supports styling (≥ 1.0) +* 45: interpreter supports color (≥ 1.0) +* 46: interpreter supports text alignment (≥ 1.0) +* 50: currently transcripting (≥ 1.0) +* 60: interpreter supports top status (≥ 0.5) +* 61: interpreter supports inline status (≥ 0.5) + +Unrecognized options should return zero: \ for selectors +in the range 00 to 3f, the raw value 0 for selectors in the range +40 to 7f. Selectors above 7f should raise an error; their behavior +is not yet defined. + +"Styling" means that bold and italic text can be distinguished in +some way from the default, though they need not actually be bold +and italic: bright and underlined, or two different screen reader +voices, both qualify. Bold italic text must also be distinct from +the default, but may be the same as either bold or italic. + +== Output model + +In this and the following chapter, we will assume that an Å-machine +interpreter contains a virtual machine "engine" that decodes and +executes opcodes, and a device-specific output subsystem coupled to the +engine. The present chapter describes this coupling, as an API, while +the next chapter provides pseudo-code for the engine. + +Output flows from the Å-machine into a set of areas: The main area and +one or more status areas. Interpreters may freely ignore all or a +subset of the status areas, which is handy when the output device is +e.g. a screen reader or a dumb terminal. + +The interpreter engine selects a target area with the API calls: + + output_enterstatus(area, style_class) + output_leavestatus() + +The former call takes a parameter indicating the desired status area +(see <> above for a list of supported values), and another +indicating the desired style class for the status area (see the <> +chunk). Each time a status area is entered, it is also implicitly +cleared. + +Within each area, output is organized as a stream of characters, +linefeeds, and paragraph breaks. This stream is further subdivided into +nested elements called divs and spans. A div has implicit linefeeds at +its beginning and end. A link is a special kind of span. + +The engine transmits this output stream using a series of API calls: + + output_string(string) + output_char(charcode) + output_nbsp() + output_space() + output_space_n(number) + output_newline() + output_endpar() + + output_uppercase() // convert next character to uppercase + + output_enterdiv(style_class) + output_leavediv(style_class) + + output_enterspan(style_class) + output_leavespan() + + output_setbody(style_class) + + output_enter_selflink() + output_leave_selflink() + output_enterlink(string) + output_leavelink() + output_enterlink_res(resource_id) + output_leavelink_res() + + output_embed_res(resource_id) + output_progress_bar(amount, full_amount) + +Many of these calls are documented under the relevant instructions +in <>. + +In addition, it is possible to insert changes of text-style into the +stream, to e.g. turn italics on or off. Such changes are valid until +the next beginning or end of a subdivision, or until the end of the +current paragraph. + + output_setstyle(bits) // see <> for bit values + output_resetstyle(bits) + output_unstyle() + +This capability is deprecated and may be removed in a future version +of the Å-machine. + +Additional API calls perform high-level operations on the output state: + + output_clear() // keep current stack of divs, but clear them + output_clear_status() // hide all status areas + output_clear_all() // do both of the above + output_clear_links() // transform old links into static text + output_clear_div() // clear current div + output_clear_old() // clear old text + output_leave_all() + output_sync() + bool output_script_on() // returns true to indicate success + output_script_off() + bool output_can_embed_res(resource_id) // can it be embedded? + +The exact meaning of each of the above API calls is not specified here. +It is up to the output subsystem of the interpreter to react to them in +some sensible way. + +Paragraph breaks can legally appear inside spans, but the output +subsystem may choose to ignore them. + +The engine must ensure that the following rules are followed: + +* All calls to the enter- and leave-functions are perfectly balanced, with one exception: It is possible to call output_leave_all() at any point to return to the initial state (i.e. to clear the stack of spans and divs and leave any status area). + +* A div will not be nested inside a span (but a span may be nested inside a div or a span, and a div may be nested inside a div). +* A link will not be nested inside another link (at any level). +* No attempt will be made to enter a status area from inside a span or a status area. +* No attempt will be made to clear, clear_status, or clear_all from inside a span or a status area. +* No attempt will be made to save the game (to a file or an undo buffer) from within a span or a status area. + +The story file (and by extension the compiler) is responsible for +balancing each enter-call with a matching leave-call. Depending on the +story authoring language, this may include cutting choice points to +ensure return-once semantics, and setting up temporary stop-frames to +catch non-local returns from inner code. + +If the story file attempts to enter a link from within a link, the inner +link will behave like static text (forming a part of the outer link). + +For the remaining restrictions, the engine will detect violations and +throw a runtime error (invalid output state). Details are provided in +the next chapter. All runtime errors involve a call to +`output_leave_all()`. + +In order to detect such violations (and to provide the correct argument +to `output_leavediv`), the engine tracks output state using four global +variables (in addition to SPC and CWL): + +* nSpan: integer, initially 0 + +* nLink: integer, initially 0 +* inStatus: boolean, initially false +* divList: list/array of style classes, initially empty + +Of these, only divList is stored as part of a saved game (see <>). Interpreters may impose a limit on the size of divList, but it +must have room for at least eight items. + +All runtime errors invoke `output_leave_all()`, reset nSpan, nLink, +inStatus, and divList to their initial values, and restart the virtual +machine. + +Interpreters that do not support clickable links at all should treat +them as static text. Such interpreters do not need the nLink variable. + +== Opcode semantics + +There could be errors. Recommendation is to double-check with the +javascript interpreter. + +[[NOP]] +=== NOP + +Do nothing. + +[[FAIL]] +=== FAIL + + fail + + def fail: + INST = (heap[CHO + 4] << 16) | heap[CHO + 5] + leave recursive unify/push/pop/(de)serialize operation + +[[SET_CONT]] +=== SET_CONT + +*CODE* + + CONT = arg + +[[PROCEED]] +=== PROCEED + + if(SIM < 0x8000) CHO = SIM + INST = CONT + +Proceed from the continuation point (CONT). +When the simple cutting choice point (SIM) is set, proceed with +that as the current choice frame on the choice stack. SIM (and +by extension all choice frames) aren't expected to naturally dip +below $8000, as it would conflict with existing memory ranges. + +[[JMP]] +=== JMP + +*CODE* + + INST = arg + +Perform a straightforward jump. The JMPL_* variants are akin to +function calls, as they provide a continuation to resume from +later. + +[[JMP_MULTI]] +=== JMP_MULTI + +*CODE* + + SIM = 0xffff + INST = arg + +Perform a multi-jump chain starting at the CODE address provided. +SIM is invalidated as a cutting choice point. + +[[JMPL_MULTI]] +=== JMPL_MULTI + +*CODE* + + CONT = next_instruction + SIM = 0xffff + INST = arg + +[[JMP_SIMPLE]] +=== JMP_SIMPLE + +*CODE* + + SIM = CHO + INST = arg + +[[JMPL_SIMPLE]] +=== JMPL_SIMPLE + +*CODE* + + CONT = next_instruction + SIM = CHO + INST = arg + +[[JMP_TAIL]] +=== JMP_TAIL + +*CODE* + + if(SIM >= 0x8000) SIM = CHO + INST = arg + +Perform a jump to the CODE address, and save the current choice +frame to SIM if there is no existing cutting choice point. + +[[TAIL]] +=== TAIL + + if(SIM >= 0x8000) SIM = CHO + +[[PUSH_ENV]] +=== PUSH_ENV + +*BYTE/0* + + local variable addr = min(ENV, CHO) - 4 - arg + if(addr < TOP) runtime_error(1) + + heap[addr + 0] = ENV + heap[addr + 1] = SIM + heap[addr + 2] = CONT >> 16 + heap[addr + 3] = CONT & 0xffff + + ENV = addr + +[[POP_ENV]] +=== POP_ENV + + CONT = (heap[ENV + 2] << 16) | heap[ENV + 3] + SIM = heap[ENV + 1] + ENV = heap[ENV + 0] + +[[POP_ENV_PROCEED]] +=== POP_ENV_PROCEED + + INST = (heap[ENV + 2] << 16) | heap[ENV + 3] + if(heap[ENV + 1] < 0x8000) CHO = heap[ENV + 1] + ENV = heap[ENV + 0] + +[[PUSH_CHOICE]] +=== PUSH_CHOICE + +*BYTE/0 CODE* + + push_choice(arg1, arg2) + + def push_choice(narg, next): + local variable addr = min(ENV, CHO) - 9 - narg + if(addr < TOP) runtime_error(1) + heap[addr + 0] = ENV + heap[addr + 1] = SIM + heap[addr + 2] = CONT >> 16 + heap[addr + 3] = CONT & 0xffff + heap[addr + 4] = next >> 16 + heap[addr + 5] = next & 0xffff + heap[addr + 6] = CHO + heap[addr + 7] = TOP + heap[addr + 8] = TRL + for(local variable i = 0; i < narg; i++) { + heap[addr + 9 + i] = R[i] // register R00.. + } + CHO = addr + +[[POP_CHOICE]] +=== POP_CHOICE + +*BYTE/0* + + for(local variable i = 0; i < arg; i++) { + R[i] = heap[CHO + 9 + i] + } + while(TRL < heap[CHO + 8]) { + heap[aux[TRL++]] = 0 + } + TOP = heap[CHO + 7] + CONT = (heap[CHO + 2] << 16) | heap[CHO + 3] + SIM = heap[CHO + 1] + ENV = heap[CHO + 0] + CHO = heap[CHO + 6] + +[[POP_PUSH_CHOICE]] +=== POP_PUSH_CHOICE + +*BYTE/0 CODE* + + heap[CHO + 4] = arg2 >> 16 + heap[CHO + 5] = arg2 & 0xffff + for(local variable i = 0; i < arg1; i++) { + R[i] = heap[CHO + 9 + i] + } + while(TRL < heap[CHO + 8]) { + heap[aux[TRL++]] = 0 + } + TOP = heap[CHO + 7] + CONT = (heap[CHO + 2] << 16) | heap[CHO + 3] + SIM = heap[CHO + 1] + ENV = heap[CHO + 0] + +[[CUT_CHOICE]] +=== CUT_CHOICE + + CHO = heap[CHO + 6] + +[[GET_CHO]] +=== GET_CHO + +*DEST* + + arg <- CHO + +[[SET_CHO]] +=== SET_CHO + +*VALUE* + + CHO = arg + +[[ASSIGN]] +=== ASSIGN + +*VALUE/VBYTE DEST* + + arg2 <- arg1 + + def unify(a, b): + while(true) { + a = deref(a) + b = deref(b) + if(a.tag == reference && b.tag == reference) { + if(TRL <= AUX) runtime_error(2) + if(a.value < b.value) { + aux[--TRL] = b.value + heap[b.value] = a + } else { + aux[--TRL] = a.value + heap[a.value] = b + } + break + } else if(a.tag == reference) { + if(TRL <= AUX) runtime_error(2) + aux[--TRL] = a.value + heap[a.value] = b + break + } else if(b.tag == reference) { + if(TRL <= AUX) runtime_error(2) + aux[--TRL] = b.value + heap[b.value] = a + break + } else if(a.tag == extdict + && b.tag == extdict) + { + a = heap[a.value] + b = heap[b.value] + } else if(a.tag == extdict) { + a = heap[a.value] + } else if(b.tag == extdict) { + b = heap[b.value] + } else if(a == b) { + break + } else if(a.tag == pair && b.tag == pair) { + unify( + , + ) + a = + b = + } else { + fail + } + } + + def deref(a): + while(a.tag == reference && heap[a.value] != 0) { + a = heap[a.value] + } + return a + + def runtime_error(error_code): + R00 = error_code + reinitialize special-purpose registers + leave recursive unify/push/pop/(de)serialize operation + +[[MAKE_VAR]] +=== MAKE_VAR + +*DEST* + + local variable addr = TOP++ + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr] = 0 + arg <- + +[[MAKE_PAIR]] +=== MAKE_PAIR + +*VWORD/VBYTE/DEST DEST DEST* + + if(arg3 is a store variant) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + if(arg1 is a store variant) { + heap[addr + 0] = 0 + arg1 <- + } else { + heap[addr + 0] = arg1 + } + if(arg2 is a store variant) { + heap[addr + 1] = 0 + arg2 <- + } else { + heap[addr + 1] = arg2 + } + arg3 <- + } else { + local variable a3 = deref(arg3) + if(a3.tag == reference) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + if(arg1 is a store variant) { + heap[addr + 0] = 0 + arg1 <- + } else { + heap[addr + 0] = arg1 + } + if(arg2 is a store variant) { + heap[addr + 1] = 0 + arg2 <- + } else { + heap[addr + 1] = arg2 + } + unify(a3, ) + } else if(a3.tag == pair) { + arg1 <- + arg2 <- + } else { + fail + } + } + +[[AUX_PUSH_VAL]] +=== AUX_PUSH_VAL + +*VALUE* + + push_serialized(arg) + +[[push_serialized]] + def push_serialized(v): + v = deref(v) + if(v.tag == pair) { + local variable count = 0 + while(true) { + push_serialized() + count++ + v = deref() + if(v == empty_list) { + v = 0xc000 + count + break + } else if(v.tag != pair) { + push_serialized(v) + v = 0xe000 + count + break + } + } + } else if(v.tag == extdict) { + push_serialized(heap[v.value + 1]) + push_serialized(heap[v.value + 0]) + v = 0x8100 + } else if(v.tag == reference) { + v = 0x8000 + } + if(AUX >= TRL) runtime_error(2) + aux[AUX++] = v + +[[AUX_PUSH_RAW]] +=== AUX_PUSH_RAW + +*VWORD/VBYTE/0* + + if(AUX >= TRL) runtime_error(2) + aux[AUX++] = arg + +[[AUX_POP_VAL]] +=== AUX_POP_VAL + +*DEST* + +This instruction was removed in version 1.0. No version of +the Dialog compiler generates it. The `pop_serialized` subroutine +is nevertheless used by <>. + + arg <- pop_serialized() + + def pop_serialized: + local variable v = aux[--AUX] + if(v == 0x8000) { + local variable addr = TOP++ + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr] = 0 + v = + } else if(v == 0x8100) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = pop_serialized() + heap[addr + 1] = pop_serialized() + v = + } else if((v & 0xc000) == 0xc000) { + local variable count = v & 0x1fff + if(v & 0x2000) { + v = pop_serialized() + } else { + v = empty_list + } + while(count--) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = pop_serialized() + heap[addr + 1] = v + v = + } + } + return v + +[[AUX_POP_LIST]] +=== AUX_POP_LIST + +*DEST* + + arg <- pop_serialized_list() + + def pop_serialized_list(): + local variable list = empty_list + while(local variable v = pop_serialized()) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = v + heap[addr + 1] = list + list = + } + return list + +[[AUX_POP_LIST_CHK]] +=== AUX_POP_LIST_CHK + +*VALUE* + + local variable flag = 0 + local variable key = deref(arg) + + while(local variable v = aux[--AUX]) { + if(v == key) { + flag = 1 + } + } + + if(!flag) { + fail + } + +[[AUX_POP_LIST_MATCH]] +=== AUX_POP_LIST_MATCH + +*VALUE* + + local variable old_top = TOP + + local variable key = deref(arg) + local variable list = pop_serialized_list() + // no need to deref the elements inside list + + while(key.tag == pair) { + local variable iter = list + local variable match = 0 + while(iter.tag == pair && !match) { + if(would_unify( + , + )) + { + match = 1 + } + iter = heap[iter.value + 1] + } + if(!match) { + fail + } + key = heap[key.value + 1] + } + + TOP = old_top // return the heap memory that we used + +[[SPLIT_LIST]] +=== SPLIT_LIST + +*VALUE VALUE DEST* + + arg3 <- split_list(arg1, arg2) + + def split_list(list, end): + list = deref(list) + end = deref(end) + if(list == end || list.tag != pair) { + return empty_list + } else { + local variable first, curr + first = curr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + while(true) { + heap[curr + 0] = heap[list.value + 0] + list = deref(heap[list.value + 1]) + if(list == end || list.tag != pair) { + break + } else { + heap[curr + 1] = + curr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) { + runtime_error(1) + } + } + } + heap[curr + 1] = empty_list + return + } + +[[STOP]] +=== STOP + + CHO = STC + fail + +[[PUSH_STOP]] +=== PUSH_STOP + +*CODE* + + if(AUX + 2 > TRL) runtime_error(2) + aux[AUX++] = STC + aux[AUX++] = STA + STA = AUX + push_choice(0, arg) + STC = CHO + +[[POP_STOP]] +=== POP_STOP + + AUX = STA + STA = aux[--AUX] + STC = aux[--AUX] + +[[SPLIT_WORD]] +=== SPLIT_WORD + +*VALUE DEST* + + arg1 = deref(arg1) + if(arg1.tag == char) { + arg2 <- create_pair(arg1, empty_list) + } else if(arg1.tag == word) { + arg2 <- prepend_chars(arg1, empty_list) + } else if(arg1.tag == extdict) { + local variable part1 = heap[arg1.value + 0] + if(part1.tag == pair) { + arg2 <- part1 + } else { + local variable part2 = heap[arg1.value + 1] + arg2 <- prepend_chars(part1, part2) + } + } else if(arg1.tag == number) { + local variable i = arg1.value + local variable list = empty_list + do { + list = create_pair(, list) + i /= 10 + } while(i) + arg2 <- list + } else { + fail + } + + def prepend_chars(word, list): + local variable str = word_to_string(word) + local variable i + for(i = str.length - 1; i >= 0; i--) { + list = create_pair(, list) + } + return list + +[[JOIN_WORDS]] +=== JOIN_WORDS + +*VALUE DEST* + + arg1 = deref(arg1) + if(arg1.tag != pair) { + fail + } + local variable v1 = deref(heap[arg1.value + 0]) + local variable v2 = deref(heap[arg1.value + 1]) + if(v1.tag == char && v2 == empty_list) { + // Allow e.g. stop characters on their own. + arg2 <- v1 + } else { + arg2 <- parse_word(wordlist_to_string(arg1)) + } + + def wordlist_to_string(list): + // The string buffer "str" may have a limited size, + // but should be at least as large as the input buffer + // used during GET_INPUT. + // If the buffer space is exhausted, the instruction + // fails. + local variable str = "" + do { + local variable v = deref(heap[list.value + 0]) + if(v.tag == char) { + local variable ch = v.value + // Stop characters are defined + // in the LANG chunk. + if(ch <= 0x20 + || ch is a stop character) { + fail + } + str += ch + } else if(v.tag == word) { + str += dictionary[v.value] + } else if(v.tag == extdict) { + local variable part1 = heap[v.value + 0] + if(part1.tag == pair) { + str += wordlist_to_string(part1) + } else { + // Reinterpret the extdict cell + // as a list node: + str += wordlist_to_string(v) + } + } else if(v.tag == number) { + str += number_to_decimal_string(v.value) + } else { + fail + } + list = deref(heap[list.value + 1]) + } while(list.tag == pair) + if(list != empty_list) { + fail + } + return str + +[[LOAD_WORD]] +=== LOAD_WORD + +*VALUE/0 INDEX DEST* + + arg3 <- read_field(arg2, deref(arg1)) + +[[LOAD_BYTE]] +=== LOAD_BYTE + +*VALUE/0 INDEX DEST* + + arg3 <- (read_field(arg2 / 2, deref(arg1)) + >> ((arg2 & 1)? 0 : 8)) & 0xff + +[[LOAD_VAL]] +=== LOAD_VAL + +*VALUE/0 INDEX DEST* + + local variable val = get_longterm(read_field(arg2, deref(arg1))) + if(!val) { + fail + } + arg3 <- val + + def get_longterm(v): + if(v & 0x8000) { + TMP = v & 0x7fff + TMP += ram[TMP] + v = pop_longterm() + } + return v + + def pop_longterm(): + local variable v = ram[--TMP] + if(v == 0x8000) { + local variable addr = TOP + TOP++ + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr] = 0 + v = + } else if(v == 0x8100) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = pop_longterm() + heap[addr + 1] = pop_longterm() + v = + } else if((v & 0xc000) == 0xc000) { + local variable count = v & 0x1fff + if(v & 0x2000) { + v = pop_longterm() + } else { + v = empty_list + } + while(count--) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = pop_longterm() + heap[addr + 1] = v + v = + } + } + return v + +[[STORE_WORD]] +=== STORE_WORD + +*VALUE/0 INDEX VALUE* + + ram[field_addr(arg2, deref(arg1))] = arg3 + +[[STORE_BYTE]] +=== STORE_BYTE + +*VALUE/0 INDEX VALUE* + + local variable addr = field_addr(arg2 / 2, deref(arg1)) + if(arg2 & 1) { + ram[addr] = (ram[addr] & 0xff00) | (arg3 & 0xff) + } else { + ram[addr] = (ram[addr] & 0x00ff) | (arg3 << 8) + } + +[[STORE_VAL]] +=== STORE_VAL + +*VALUE/0 INDEX VALUE* + + arg1 = deref(arg1) + if(arg1 == null || arg1.tag == object || arg3 != null) { + store_longterm(field_addr(arg2, arg1), arg3) + } + + def store_longterm(addr, v): + clear_longterm(addr) + v = deref(v) + if(v.tag == pair + || v.tag == extdict + || v.tag == reference) { + TMP = LTT + 2 + if(TMP > HEAD.ramsz) runtime_error(6) + push_longterm(v) + ram[addr] = 0x8000 + LTT + ram[LTT + 0] = TMP - LTT + ram[LTT + 1] = addr + LTT = TMP + } else { + ram[addr] = v + } + +[[push_longterm]] + def push_longterm(v): + v = deref(v) + if(v.tag == pair) { + local variable count = 0 + while(true) { + push_longterm(heap[v.value + 0]) + count++ + v = deref(heap[v.value + 1]) + if(v == empty_list) { + v = 0xc000 | count + break + } else if(v.tag != pair) { + push_longterm(v) + v = 0xe000 | count + break + } + } + } else if(v.tag == extdict) { + push_longterm(heap[v.value + 1]) + push_longterm(heap[v.value + 0]) + v = 0x8100 + } else if(v.tag == reference) { + runtime_error(4) + } + if(TMP > HEAD.ramsz) runtime_error(6) + ram[TMP++] = v + +[[clear_longterm]] + def clear_longterm(addr): + local variable v = ram[addr] + if(v & 0x8000) { + ram[addr] = 0 + v &= 0x7fff + local variable size = ram[v] + for(local variable i = v; i < LTT - size; i++) + { + ram[i] = ram[i + size] + } + LTT -= size + while(v < LTT) { + ram[ram[v + 1]] -= size + v += ram[v] + } + } + +[[SET_FLAG]] +=== SET_FLAG + +*VALUE/0 INDEX* + + ram[field_addr(arg2 / BITS_PER_WORD, deref(arg1))] |= + (1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD) + +[[RESET_FLAG]] +=== RESET_FLAG + +*VALUE/0 INDEX* + + arg1 = deref(arg1) + if(arg1 == null || arg1.tag == object) { + ram[field_addr(arg2 / BITS_PER_WORD, arg1)] &= + ~((1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD)) + } + +[[UNLINK]] +=== UNLINK + +*VALUE/0 INDEX INDEX VALUE* + + unlink(field_addr(arg2, deref(arg1)), arg3, arg4) + + def unlink(root_addr, field, key): + key = deref(key) + if(key.tag != object) { + return + } + local variable tail = + ram[field_addr(field, key.value)] + local variable addr = root_addr + while(ram[addr] != 0) { + if(ram[addr] == key) { + ram[addr] = tail + return + } + addr = field_addr(field, ram[addr].value) + } + +[[SET_PARENT]] +=== SET_PARENT + +*VBYTE/VALUE VBYTE/VALUE* + + constant FIELD_PARENT = 0 + constant FIELD_CHILD = 1 + constant FIELD_SIBLING = 2 + + arg1 = deref(arg1) + arg2 = deref(arg2) + if(arg2 != null) { + if(arg1.tag != object) runtime_error(3) + if(arg2.tag != object) runtime_error(3) + } + if(arg1.tag == object) { + local variable old_parent = + ram[field_addr(FIELD_PARENT, arg1.value)] + if(old_parent != 0) { + unlink( + field_addr(FIELD_CHILD, old_parent), + FIELD_SIBLING, + arg1) + } + ram[field_addr(FIELD_PARENT, arg1.value)] = arg2 + if(arg2.tag == object) { + ram[field_addr(FIELD_SIBLING, arg1.value)] = + ram[field_addr(FIELD_CHILD, arg2.value)] + ram[field_addr(FIELD_CHILD, arg2.value)] = arg1 + } + } + +[[IF_RAW_EQ]] +=== IF_RAW_EQ + +*VWORD/0 RAW CODE* + + if(arg1 == arg2) INST = arg3 + +[[IFN_RAW_EQ]] +=== IFN_RAW_EQ + +*VWORD/0 RAW CODE* + + if(arg1 != arg2) INST = arg3 + +[[IF_BOUND]] +=== IF_BOUND + +*VALUE CODE* + + if(deref(arg1).tag != reference) INST = arg2 + +[[IFN_BOUND]] +=== IFN_BOUND + +*VALUE CODE* + + if(deref(arg1).tag == reference) INST = arg2 + +[[IF_EMPTY]] +=== IF_EMPTY + +*VALUE CODE* + + if(deref(arg1) == empty_list) INST = arg2 + +[[IFN_EMPTY]] +=== IFN_EMPTY + +*VALUE CODE* + + if(deref(arg1) != empty_list) INST = arg2 + +[[IF_NUM]] +=== IF_NUM + +*VALUE CODE* + + if(deref(arg1).tag == number) INST = arg2 + +[[IFN_NUM]] +=== IFN_NUM + +*VALUE CODE* + + if(deref(arg1).tag != number) INST = arg2 + +[[IF_PAIR]] +=== IF_PAIR + +*VALUE CODE* + + if(deref(arg1).tag == pair) INST = arg2 + +[[IFN_PAIR]] +=== IFN_PAIR + +*VALUE CODE* + + if(deref(arg1).tag != pair) INST = arg2 + +[[IF_OBJ]] +=== IF_OBJ + +*VALUE CODE* + + // arg1 is guaranteed not to be null + if(deref(arg1).tag == object) INST = arg2 + +[[IFN_OBJ]] +=== IFN_OBJ + +*VALUE CODE* + + // arg1 is guaranteed not to be null + if(deref(arg1).tag != object) INST = arg2 + +[[IF_WORD]] +=== IF_WORD + +*VALUE CODE* + + if(deref(arg1).tag == word + || deref(arg1).tag == char + || deref(arg1).tag == extdict) INST = arg2 + +[[IFN_WORD]] +=== IFN_WORD + +*VALUE CODE* + + if(deref(arg1).tag != word + && deref(arg1).tag != char + && deref(arg1).tag != extdict) INST = arg2 + +[[IF_UWORD]] +=== IF_UWORD + +*VALUE CODE* + + arg1 = deref(arg1) + if(arg1.tag == extdict && heap[arg1.value + 0].tag == pair) { + INST = arg2 + } + +[[IFN_UWORD]] +=== IFN_UWORD + +*VALUE CODE* + + arg1 = deref(arg1) + if(arg1.tag != extdict || heap[arg1.value + 0].tag != pair) { + INST = arg2 + } + +[[IF_UNIFY]] +=== IF_UNIFY + +*VALUE VALUE CODE* + + if(would_unify(arg1, arg2)) INST = arg3 + + def would_unify(a, b): + while(true) { + a = deref(a) + b = deref(b) + if(a.tag == reference || b.tag == reference) { + return true + } else if(a.tag == extdict + && b.tag == extdict) + { + a = heap[a.value + 0] + b = heap[b.value + 0] + } else if(a.tag == extdict) { + a = heap[a.value + 0] + } else if(b.tag == extdict) { + b = heap[b.value + 0] + } else if(a == b) { + return true + } else if(a.tag == pair && b.tag == pair) { + if(!would_unify( + , + )) + { + return false + } + a = + b = + } else { + return false + } + } + +[[IFN_UNIFY]] +=== IFN_UNIFY + +*VALUE VALUE CODE* + + if(!would_unify(arg1, arg2)) INST = arg3 + +[[IF_GT]] +=== IF_GT + +*VALUE VALUE CODE* + + arg1 = deref(arg1) + arg2 = deref(arg2) + if(arg1.tag == number + && arg2.tag == number + && arg1.value > arg2.value) { + INST = arg3 + } + +[[IFN_GT]] +=== IFN_GT + +*VALUE VALUE CODE* + + arg1 = deref(arg1) + arg2 = deref(arg2) + if(!(arg1.tag == number + && arg2.tag == number + && arg1.value > arg2.value)) { + INST = arg3 + } + +[[IF_EQ]] +=== IF_EQ + +*VWORD/VBYTE VALUE CODE* + + if(arg1 == deref(arg2)) INST = arg3 + +[[IFN_EQ]] +=== IFN_EQ + +*VWORD/VBYTE VALUE CODE* + + if(arg1 != deref(arg2)) INST = arg3 + +[[IF_MEM_EQ]] +=== IF_MEM_EQ + +*VALUE/0 INDEX RAW/VBYTE CODE* + + if(read_field(arg2, deref(arg1)) == arg3) INST = arg4 + +[[IFN_MEM_EQ]] +=== IFN_MEM_EQ + +*VALUE/0 INDEX RAW/VBYTE CODE* + + if(read_field(arg2, deref(arg1)) != arg3) INST = arg4 + +[[IF_FLAG]] +=== IF_FLAG + +*VALUE/0 INDEX CODE* + + if(read_field(arg2 / BITS_PER_WORD, deref(arg1)) & + ((1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD))) + { + INST = arg3 + } + +[[IFN_FLAG]] +=== IFN_FLAG + +*VALUE/0 INDEX CODE* + + if(!(read_field(arg2 / BITS_PER_WORD, deref(arg1)) & + ((1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD)))) + { + INST = arg3 + } + +[[IF_CWL]] +=== IF_CWL + +*CODE* + + if(CWL != 0) INST = arg1 + +[[IFN_CWL]] +=== IFN_CWL + +*CODE* + + if(CWL == 0) INST = arg1 + +[[ADD_RAW]] +=== ADD_RAW + +*RAW RAW DEST* + + arg3 <- (arg1 + arg2) & 0xffff + +[[ADD_NUM]] +=== ADD_NUM + +*VALUE VALUE DEST* + + local variable result = unbox_int(arg1) + unbox_int(arg2) + arg3 <- box_int(result) + + def unbox_int(v): + v = deref(v) + if(v.tag != number) fail + return v.value + + def box_int(v): + if(v < 0 || v > 0x3fff) fail + return + +[[SUB_RAW]] +=== SUB_RAW + +*RAW RAW DEST* + + arg3 <- (arg1 - arg2) & 0xffff + +[[SUB_NUM]] +=== SUB_NUM + +*VALUE VALUE DEST* + + local variable result = unbox_int(arg1) - unbox_int(arg2) + arg3 <- box_int(result) + +[[MUL_NUM]] +=== MUL_NUM + +*VALUE VALUE DEST* + + local variable result = unbox_int(arg1) * unbox_int(arg2) + arg3 <- box_int(result) + +Prior to version 1.1, the pseudocode included the line +``result = result & 0x3fff``, which meant a result that exceeded the +Å-machine's number range would wrap around instead of failing. In version +1.1, this was changed to match <>, <>, and <>. + +An interpreter may use the old behavior for pre-1.1 story files. However, +since no known story files make use of this behavior, this is not required. + +[[DIV_NUM]] +=== DIV_NUM + +*VALUE VALUE DEST* + + if(unbox_int(arg2) == 0) fail + local variable result = unbox_int(arg1) / unbox_int(arg2) + arg3 <- box_int(result) + +[[MOD_NUM]] +=== MOD_NUM + +*VALUE VALUE DEST* + + if(unbox_int(arg2) == 0) fail + local variable result = unbox_int(arg1) % unbox_int(arg2) + arg3 <- box_int(result) + +[[RAND_NUM]] +=== RAND_NUM + +*VALUE VALUE DEST* + + local variable start = unbox_int(arg1) + local variable range = unbox_int(arg2) - start + 1 + if(range < 1) fail + // any timer-seeded PRNG can be used: + local variable result = start + (rand() % range) + arg3 <- box_int(result) + +[[RAND_RAW]] +=== RAND_RAW + +*BYTE DEST* + + // any timer-seeded PRNG can be used: + arg2 <- (rand() % (arg1 + 1)) & 0xffff + +[[INC_NUM]] +=== INC_NUM + +*VALUE DEST* + + local variable result = unbox_int(arg1) + 1 + arg2 <- box_int(result) + +[[DEC_NUM]] +=== DEC_NUM + +*VALUE DEST* + + local variable result = unbox_int(arg1) - 1 + arg2 <- box_int(result) + +[[INC_RAW]] +=== INC_RAW + +*RAW DEST* + + arg2 <- (arg1 + 1) & 0xffff + +[[DEC_RAW]] +=== DEC_RAW + +*RAW DEST* + + arg2 <- (arg1 - 1) & 0xffff + +[[PRINT_A_STR_A]] +=== PRINT_A_STR_A + +*STRING* + + if(SPC == auto || SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_string(arg) + SPC = auto + +[[PRINT_N_STR_A]] +=== PRINT_N_STR_A + +*STRING* + + if(SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_string(arg) + SPC = auto + +[[PRINT_A_STR_N]] +=== PRINT_A_STR_N + +*STRING* + + if(SPC == auto || SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_string(arg) + SPC = nospace + +[[PRINT_N_STR_N]] +=== PRINT_N_STR_N + +*STRING* + + if(SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_string(arg) + SPC = nospace + +[[NOSPACE]] +=== NOSPACE + + if(CWL == 0) { + if(SPC < nospace) { + SPC = nospace + } + } + +[[NBSP]] +=== NBSP + + if(CWL == 0) { + if(SPC < nbsp) { + SPC = nbsp + } + } + +[[SPACE]] +=== SPACE + + if(CWL == 0) { + if(SPC < pendingspace) { + SPC = pendingspace + } + } + +[[LINE]] +=== LINE + + if(CWL == 0) { + if(SPC < line) { + output_newline() + SPC = line + } + } + +[[PAR]] +=== PAR + + if(CWL == 0) { + if(SPC < par) { + output_endpar() + SPC = par + } + } + +[[SPACE_N]] +=== SPACE_N + +*VALUE* + + if(CWL == 0) { + arg = deref(arg) + if(arg.tag == number) { + output_space_n(arg.value) + SPC = space + } + } + +[[PRINT_VAL]] +=== PRINT_VAL + +*VALUE* + + if(CWL == 0) { + print_value(deref(arg)) + } else { + push_serialized(deref(arg)) + } + + def print_value(v): + if(v.tag == char) { + local variable ch = v.value + if(ch is listed as no-space-before in LANG chunk) { + if(SPC < nospace) { + SPC = nospace + } + } + if(SPC == auto || SPC == pendingspace) output_space() + output_char(ch) + if(ch is listed as no-space-after in LANG chunk) { + SPC = nospace + } else { + SPC = auto + } + } else { + if(SPC == auto || SPC == pendingspace) output_space() + if(v.tag == object) { + output_string("#") + if(TAGS chunk is present) { + output_string(get_object_name(v.value)) + } + } else if(v.tag == number) { + output_string(number_to_decimal_string(v.value)) + } else if(v.tag == word || v.tag == extdict) { + output_string(word_to_string(v)) + } else if(v.tag == reference) { + output_string("$") + } else if(v == empty_list) { + output_string("[]") + } else if(v.tag == pair) { + output_string("[") + print_value(deref(heap[v.value + 0])) + local variable tail = deref(heap[v.value + 1]) + while(tail != empty_list) { + if(tail.tag == pair) { + SPC = pendingspace + print_value(deref(heap[tail.value + 0])) + tail = deref(heap[tail.value + 1]) + } else { + output_space() + output_string("|") + SPC = pendingspace + print_value(tail) + tail = empty_list + } + } + output_string("]") + } + SPC = auto + } + + def word_to_string(v): + local variable str = "" + if(v.tag == word) { + str += dictionary[v.value] + } else if(v.tag == extdict) { + local variable part1 = heap[v.value + 0] + if(part1.tag == pair) { + while(part1.tag == pair) { + str += char_to_string(heap[part1.value + 0].value) + part1 = heap[part1.value + 1] + } + } else { + str += word_to_string(part1) + local variable part2 = heap[v.value + 1] + while(part2.tag == pair) { + str += char_to_string(heap[part2.value + 0].value) + part2 = heap[part2.value + 1] + } + } + } + return str + +[[ENTER_DIV]] +=== ENTER_DIV + +*INDEX* + + if(CWL == 0) { + if(nSpan > 0) runtime_error(7) + output_enterdiv(arg) + append arg to divList + SPC = par + } + +[[LEAVE_DIV]] +=== LEAVE_DIV + + if(CWL == 0) { + local variable style_class = last element of divList + remove last element from divList + output_leavediv(style_class) + SPC = line + } + +Previous versions of this specification erroneously said SPC +should be set to "par" instead of "line". This is not meant to +be a change between Aa-machine versions; ideally SPC should be +set to "line" even when running version 0.5 story files. + +[[ENTER_SPAN]] +=== ENTER_SPAN + +*INDEX* + + if(CWL == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + output_enterspan(arg) + SPC = nospace + nSpan++ + } + +[[LEAVE_SPAN]] +=== LEAVE_SPAN + + if(CWL == 0) { + output_leavespan() + SPC = auto + nSpan-- + } + +[[ENTER_STATUS]] +=== ENTER_STATUS + +*BYTE INDEX* + + if(inStatus || nSpan > 0) runtime_error(7) + if(CWL == 0) { + output_enterstatus(arg1, arg2) + SPC = par + inStatus = true + } + +[[LEAVE_STATUS]] +=== LEAVE_STATUS + + if(CWL == 0) { + output_leavestatus() + SPC = par + inStatus = false + } + +Before version 1.0, this was opcode e7. From 1.0 onwards, it has +been moved to ef. The semantics are unchanged. + +[[ENTER_LINK_RES]] +=== ENTER_LINK_RES + +*VALUE* + + if(CWL == 0) { + if(nLink == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + output_enterlink_res(deref(arg)) + SPC = nospace + } + nLink++ + nSpan++ + } + +[[LEAVE_LINK_RES]] +=== LEAVE_LINK_RES + + if(CWL == 0) { + nSpan-- + nLink-- + if(nLink == 0) output_leavelink_res() + } + +[[ENTER_LINK]] +=== ENTER_LINK + +*VALUE* + + if(CWL == 0) { + if(nLink == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + local variable input = "" + arg = deref(arg) + while(arg.tag == pair) { + local variable w = deref(heap[arg.value + 0]) + if(w.tag == word + || w.tag == char + || w.tag == extdict + || w.tag == number) { + * if input is non-empty, then append + space to end of input + * append text of w to end of input + } + arg = deref(heap[arg.value + 1]) + } + output_enterlink(input) + SPC = nospace + } + nLink++ + nSpan++ + } + +[[LEAVE_LINK]] +=== LEAVE_LINK + + if(CWL == 0) { + nSpan-- + nLink-- + if(nLink == 0) output_leavelink() + } + +[[ENTER_SELF_LINK]] +=== ENTER_SELF_LINK + + if(CWL == 0) { + if(nLink == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + output_enter_selflink() + SPC = space + } + nLink++ + nSpan++ + } + +[[LEAVE_SELF_LINK]] +=== LEAVE_SELF_LINK + + if(CWL == 0) { + nSpan-- + nLink-- + if(nLink == 0) output_leave_selflink() + } + +[[SET_STYLE]] +=== SET_STYLE + +*BYTE* + + // 1 = reverse, 2 = bold, 4 = italic, 8 = fixed + if(CWL == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + output_setstyle(arg) + SPC = space + } + +This opcode is deprecated, but still supported for now. + +[[RESET_STYLE]] +=== RESET_STYLE + +*BYTE* + + // 1 = reverse, 2 = bold, 4 = italic, 8 = fixed + if(CWL == 0) { + output_resetstyle(arg) + } + +This opcode is deprecated, but still supported for now. + +[[SET_BODY]] +=== SET_BODY + +*INDEX* + + if(inStatus || nSpan > 0) runtime_error(7) + output_setbody(arg) + +`output_setbody` applies a style class to the main body of the +document, if possible. Whether this affects existing text is +left to the implementation. + +[[UNSTYLE]] +=== UNSTYLE + + if(CWL == 0) { + output_unstyle() + } + +[[PRINT_SERIAL]] +=== PRINT_SERIAL + + if(CWL == 0) { + if(SPC == auto || SPC == pendingspace) output_space() + output_string(HEAD.serial) + SPC = auto + } + +[[CLEAR]] +=== CLEAR + + if(CWL == 0) { + if(inStatus || nSpan > 0) runtime_error(7) + output_clear() + } + +[[CLEAR_STATUS]] +=== CLEAR_STATUS + + if(CWL == 0) { + if(inStatus || nSpan > 0) runtime_error(7) + output_clear_status() + } + +[[CLEAR_ALL]] +=== CLEAR_ALL + + if(CWL == 0) { + if(inStatus || nSpan > 0) runtime_error(7) + output_clear_all() + } + +[[EMBED_RES]] +=== EMBED_RES + +*VALUE* + + if(CWL == 0) { + output_embed_res(deref(arg1)) + } + +[[CAN_EMBED_RES]] +=== CAN_EMBED_RES + +*VALUE DEST* + + if(output_can_embed_res(deref(arg1))) { + arg2 <- 1 // raw, unboxed integer + } else { + arg2 <- null + } + +[[PROGRESS]] +=== PROGRESS + +*VALUE VALUE* + + if(CWL == 0) { + arg1 = deref(arg1) + arg2 = deref(arg2) + if(arg1.tag == number && arg2.tag == number) { + output_progress_bar(arg1.value, arg2.value) + } + } + +Prints a progress bar with `arg1/arg2` filled. + +[[QUIT]] +=== QUIT + + output_sync() + Either return, or exit the engine. + +[[RESTART]] +=== RESTART + + output_leave_all() + inStatus = false + nSpan = 0 + nLink = 0 + divList = empty array/list + // May rewrite all game state, including INST. + vm_restart() + +[[RESTORE]] +=== RESTORE + + if(vm_restore()) { + // By now, the game state has changed drastically, + // including INST and divList. + output_leave_all() + inStatus = false + nSpan = 0 + nLink = 0 + for(i = 0; i < divList.length; i++) { + output_enterdiv(divList[i]) + } + } + +[[UNDO]] +=== UNDO + + if(vm_undo()) { + // By now, the game state has changed drastically, + // including INST and divList. + output_leave_all() + inStatus = false + nSpan = 0 + nLink = 0 + for(i = 0; i < divList.length; i++) { + output_enterdiv(divList[i]) + } + } + +[[SAVE]] +=== SAVE + +*CODE* + + // fails on error/cancel, continues on success, jumps on restore + if(inStatus || nSpan > 0) runtime_error(7) + if(!vm_save(arg)) { + fail + } + +[[SAVE_UNDO]] +=== SAVE_UNDO + +*CODE* + + // fails on error/cancel, continues on success, jumps on restore + if(inStatus || nSpan > 0) runtime_error(7) + if(!vm_save_undo(arg)) { + fail + } + +[[GET_INPUT]] +=== GET_INPUT + +*DEST* + + if(SPC == auto || SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_sync() + arg <- input_get_line() + SPC = line + +The string of characters typed by the player is converted into +lowercase, and split into substrings as follows: + +* Every stop character (as declared in the <> chunk) becomes a single substring. +* Remaining characters are separated by whitespace. + +Thus, the string `drop ball.north` is split into the +substrings `drop`, `ball`, `.`, and `north`. + +The list of substrings is converted into a list of Å-machine +values as follows: + + local variable list = empty_list + local variable i + for(i = list_of_substrings.length - 1; i >= 0; i--) { + local variable val = parse_word(list_of_substrings[i]) + list = create_pair(val, list) + } + return list // this gets stored into arg + + def create_pair(head, tail) { + local variable addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) runtime_error(1) + heap[addr + 0] = head + heap[addr + 1] = tail + return + } + + def parse_word(input) { + if(input.length == 1) { + if(input[0] >= '0' && input[0] <= '9') { + return + } else { + return + } + } + + // Look for an exact match in the DICT chunk: + local variable d = dict_lookup(input) + if(d) { + return + } + + // Check for a decimal number in the range 0-16383: + local variable num = parse_decimal(input) + if(num && num >= 0 && num <= 16383) { + return + } + + // Otherwise, run the word endings decoder: + local variable state = 0 + local variable ending = empty_list + local variable pos = input.length + while(true) { + local variable addr + if(decoder[state] == 0x00) { + while(pos > 0) { + addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) { + runtime_error(1) + } + local variable c = input[--pos] + local variable v + if(c >= '0' && c <= '9') { + v = + } else { + v = + } + heap[addr + 0] = v + heap[addr + 1] = ending + ending = + } + addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) { + runtime_error(1) + } + heap[addr + 0] = ending + heap[addr + 1] = empty_list + return + } else if(decoder[state] == 0x01) { + d = dict_lookup(input.first_n(pos)) + if(d) { + // The first pos characters + // exactly match a dictionary + // word. + addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) { + runtime_error(1) + } + heap[addr + 0] = + heap[addr + 1] = ending + return + } else { + state++ + } + } else { + local variable ch = input[pos - 1] + if(ch == decoder[state]) { + addr = TOP + TOP += 2 + if(TOP > min(ENV, CHO)) { + runtime_error(1) + } + heap[addr + 0] = + heap[addr + 1] = ending + ending = + state = decoder[state + 1] + pos-- + } else { + state += 2 + } + } + } + } + +[[GET_KEY]] +=== GET_KEY + +*DEST* + + if(SPC == auto || SPC == pendingspace) output_space() + elseif(SPC == nbsp) output_nbsp() + output_sync() + arg <- input_get_key() // digits are numbers, others are chars + +If a link is clicked during a <> instruction, and the link +does not point to a single character or digit, the result is +undefined. It may be the first character of the link text, or +0d (RETURN), or a special sentinel value. It is not, however, an +error. Some value must be returned. + +(The web frontend currently returns the first character of the +link text, but this is an implementation detail, not required +by the specification.) + +[[SCRIPT_ON]] +=== SCRIPT_ON + + if(!output_script_on()) { + fail + } + +[[SCRIPT_OFF]] +=== SCRIPT_OFF + + output_script_off() + +[[TRACE_ON]] +=== TRACE_ON + + vm_trace_on() + +[[TRACE_OFF]] +=== TRACE_OFF + + vm_trace_off() + +[[INC_CWL]] +=== INC_CWL + + CWL++ + +[[DEC_CWL]] +=== DEC_CWL + + CWL-- + +[[UPPERCASE]] +=== UPPERCASE + + if(CWL == 0) { + output_uppercase() + } + +[[CLEAR_LINKS]] +=== CLEAR_LINKS + + // Turn all links in the main text area into non-clickable, + // ordinary text. The status areas are not affected. + + output_clear_links() + +[[CLEAR_OLD]] +=== CLEAR_OLD + + // Clear any text that the player has had a chance to read. + // This means everything up to and including the last input. + // The interpreter may ignore this operation. + + if(nSpan > 0) runtime_error(7) + output_clear_old() + +[[CLEAR_DIV]] +=== CLEAR_DIV + + // Clear or fold away the current div. + // The interpreter may ignore this operation. + + output_clear_div(); + +[[VM_INFO]] +=== VM_INFO + +*BYTE DEST* + + if(arg1 & 0x40) { + arg2 <- vm_terp_feature(arg1 & 0x3f) + } else if(arg1 & 0x20) { + local variable val = vm_output_state(arg1 & 0x1f) + arg2 <- box_int(val) + } else { + local variable count = 0 + if(arg1 == 0) { + for(local variable i = 0; i < HEAD.heapsz; i++) + { + if(heap[i] != unused) count++ + } + } else if(arg1 == 1) { + for(local variable i = 0; i < HEAD.auxsz; i++) + { + if(aux[i] != unused) count++ + } + } else if(arg1 == 2) { + for(local variable i = LTB; i < HEAD.ramsz; i++) + { + if(ram[i] != unused) count++ + } + } + arg2 <- box_int(count) + } + +[[SET_IDX]] +=== SET_IDX + +*VALUE* + + IDX = deref(arg) // IDX is another name for R3f + if(IDX.tag == extdict) { + IDX = heap[IDX.value + 0] + } + +[[CHECK_EQ]] +=== CHECK_EQ + +*VWORD/VBYTE CODE* + + if(IDX == arg1) INST = arg2 + +[[CHECK_GT_EQ]] +=== CHECK_GT_EQ + +*VWORD/VBYTE CODE CODE* + + if(IDX > arg1) { + INST = arg2 + } else if(IDX == arg1) { + INST = arg3 + } + +[[CHECK_GT]] +=== CHECK_GT + +*RAW/BYTE CODE* + + if(IDX > arg1) INST = arg2 + +[[CHECK_WORDMAP]] +=== CHECK_WORDMAP + +*INDEX CODE* + + local variable objs[] = find_in_wordmap(wordmaps[arg1], IDX) + if(objs == null) { + // The word was not in the table, and should not + // match anything. + // Nothing was pushed, so jump to enforce that. + INST = arg2 + } else if(objs.length == 0) { + // The word is a wildcard, so don't jump. + } else { + // The word matches a limited number of objects. + // Push them and jump to enforce that. + if(AUX + objs.length > TRL) runtime_error(2) + for(local variable i = 0; i < objs.length; i++) { + aux[AUX++] = objs[i] + } + INST = arg2 + } + +The 'find_in_wordmap' procedure reads from one of the wordmaps +(`wordmaps[arg1]`), looking up the word of interest (IDX) to see +if it's there. + +[[CHECK_EQ_2]] +=== CHECK_EQ_2 + +*VWORD/VBYTE VWORD/VBYTE CODE* + + if(IDX == arg1 || IDX == arg2) INST = arg3 + +[[TRACEPOINT]] +=== TRACEPOINT + +*STRING STRING STRING WORD* + + // event, predicate, file, line + vm_tracepoint(arg1, arg2, arg3, arg4) + + +== Examples + +=== To traverse children + +A1 is the incoming parent. + + LOAD_VAL A1.'child', >T1 + JUMP entry + loop + POP_CHOICE #2 + ASSIGN A1, >T1 + entry + LOAD_WORD T1.'sibling', >A1 + IF_RAW_EQ 0, A1, last + PUSH_CHOICE #2, loop + last + ASSIGN T1, =A0 + ... + +=== To traverse a list of flagged objects + + LOAD_VAL 0.'root', >T1 + JUMP entry + loop + POP_CHOICE #2 + ASSIGN A1, >T1 + entry + LOAD_WORD T1.'next', >A1 + IF_RAW_EQ 0, A1, last + PUSH_CHOICE #2, loop + last + ASSIGN T1, =A0 + ... + +=== To flag object A0 with list + + IF_FLAG A0.'flag', skip + SET_FLAG A0.'flag' + LOAD_WORD 0.'root', >T0 + STORE_WORD A0.'next', T0 + STORE_VAL 0.'root', A0 + skip + +=== To clear object A0 with list + + IFN_FLAG A0.'flag', skip + RESET_FLAG A0.'flag' + UNLINK 0.'root', 'next', A0 + skip + +=== To clear list of flagged objects + + LOAD_WORD 0.'root', >T1 + IF_RAW_EQ 0, T1, skip + loop + RESET_FLAG T1.'flag' + LOAD_WORD T1.'next', >T1 + IFN_NULL T1, loop + STORE_WORD 0.'root', #0 + skip + +=== To clear a property for every object + + ASSIGN max_obj_id, >T1 + loop + STORE_VAL T1.'prop', #0 + DEC_RAW T1, >T1 + IFN_NULL T1, loop + +=== To select 'then at random' with four choices + + LOAD_BYTE 0.'state', >R3f + CHECK_GT_EQ #3, random, last + INC_RAW R3f, >T0 + JMP done + last + ASSIGN #7, >T0 + JMP done + random + SUB_RAW R3f, #4, >R3f + RAND_RAW #2, >T0 + CHECK_GT T0, noadjust + INC_RAW T0, >T0 + noadjust + ASSIGN T0, >R3f + ADD_RAW T0, #4, T0 + done + STORE_BYTE 0.'state', T0 + + CHECK_EQ #1, case1 + CHECK_EQ #2, case2 + CHECK_EQ #3, case3 + case0 + ... + case1 + ... + case2 + ... + case3 + ... + +== Colophon + +This document was originally written by Linus Åkesson, and is now +maintained by the Dialog Project contributors. diff --git a/docs/current.adoc b/docs/current.adoc index 97547c4..b6caa85 120000 --- a/docs/current.adoc +++ b/docs/current.adoc @@ -1 +1 @@ -aam-specification-1.0.adoc \ No newline at end of file +aam-specification-1.1.adoc \ No newline at end of file diff --git a/src/6502/Makefile b/src/6502/Makefile index 0d3edec..d676d5e 100644 --- a/src/6502/Makefile +++ b/src/6502/Makefile @@ -1,4 +1,4 @@ -VERSION=1.0.4 +VERSION=1.1.0 CFLAGS=-Wall -O2 -DVERSION=\"$(VERSION)\" all: aambox6502 aambox_frontend.bin c64_crunched.bin c64_drivecode.bin c64_loader.prg a2.system a2_0boot.bin a2_sboot.bin diff --git a/src/Makefile b/src/Makefile index 9985152..ca636e9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ -VERSION=1.0.4 +VERSION=1.1.0 VER_MAJOR=1 -VER_MINOR=0 +VER_MINOR=1 CC = gcc CFLAGS += -Wall -O3 -DVERSION=\"$(VERSION)\" -DVER_MAJOR=$(VER_MAJOR) -DVER_MINOR=$(VER_MINOR) ifeq ($(shell uname -s),Darwin) diff --git a/src/js/engine.js b/src/js/engine.js index f8d18a7..b091407 100644 --- a/src/js/engine.js +++ b/src/js/engine.js @@ -423,7 +423,7 @@ function prepare_story(file_array, io, seed, quit, toparea, inlinearea) { e = { VER_MAJOR: 1, - VER_MINOR: 0, + VER_MINOR: 1, SP_AUTO: 0, SP_NOSPACE: 1, @@ -2154,8 +2154,10 @@ function vm_run(e, param) { a1 = deref(fvalue()); a2 = deref(fvalue()); if(a1 >= 0x4000 && a1 < 0x8000 && a2 >= 0x4000 && a2 < 0x8000) { - v = ((a1 & 0x3fff) * (a2 & 0x3fff)) & 0x3fff; - store(e.code[e.inst++], v | 0x4000); + v = (a1 & 0x3fff) * (a2 & 0x3fff); + if(v < 0x4000) { + store(e.code[e.inst++], v | 0x4000); + } else fail(); } else fail(); break; case 0x5c: // div_num value value dest diff --git a/src/js/nodefrontend.js b/src/js/nodefrontend.js index 1735e48..ba0f454 100644 --- a/src/js/nodefrontend.js +++ b/src/js/nodefrontend.js @@ -1,6 +1,6 @@ (function(){"use strict"; -const VERSION = "1.0.4"; +const VERSION = "1.1.0"; const fs = require('fs'); const readline = require('readline'); diff --git a/src/js/webfrontend.html b/src/js/webfrontend.html index 00afd73..2e45dce 100644 --- a/src/js/webfrontend.html +++ b/src/js/webfrontend.html @@ -50,7 +50,7 @@