diff --git a/docs/aam-specification-1.0.adoc b/docs/aam-specification-1.0.adoc index 2184733..ac97d76 100644 --- a/docs/aam-specification-1.0.adoc +++ b/docs/aam-specification-1.0.adoc @@ -1,16 +1,22 @@ = Å-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 +version 1.1, 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. +Note that 1.x 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 + +32-bit story files are now supported + +New opcode: EXT32 + == Changes in v1.0 New opcodes: @@ -77,9 +83,12 @@ all other numbers are decimal. === Words -Most data is organized into words. A word is currently always 16 bits. +Most data is organized into words. The size of a word is stored in the <
>: +either 2 bytes (16 bits) or 4 bytes (32 bits). When words are stored in a save file, the byte order is big-endian. +==== 16-bit words + The internal format of a 16-bit word could be: *A raw 16-bit value*, such as an index into one of the heap @@ -181,8 +190,120 @@ The special value `3f3f` marks unused words in the main heap, aux, and random access areas. This is handy for measuring peak memory usage. +==== 32-bit words + +The internal format of a 32-bit word could be: + +*A raw 32-bit value*, such as an index into one of the heap +areas. + +*32 independent flags*, numbered 00..1f 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. + +[[literal32,literal]] +*A literal value*, as follows: + +* ``0000 0001..0fff ffff`` Object +* ``1000 0000..1fff ffff`` Dict word (index into dictionary) +* ``2000 0000..2010 ffff`` Character (Unicode codepoint) +* ``2011 0000..2fff ffff`` reserved +* ``3000 0000 `` `[]` (empty list) +* ``3000 0001..3fff ffff`` reserved +* ``4000 0000..4fff ffff`` 28-bit signed integer +* ``5000 0000..7fff ffff`` reserved + +[[live32]] +*A live value (tagged reference)*, as follows: + +* ``0000 0000 `` undefined +* ``0000 0001..7fff ffff`` <> ≥ 0.4
{empty} {empty} {empty} {empty}
7f <> <> <> <> <> {empty}
+{empty} {empty} {empty} {empty}
+
+ff <> TBD ≥ 1.1
|===
[[status-values]]
@@ -1190,7 +1501,7 @@ Additional API calls perform high-level operations on the output state:
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_leave_all() // leave divs, spans, and status areas
output_sync()
bool output_script_on() // returns true to indicate success
output_script_off()
@@ -1232,7 +1543,6 @@ 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
@@ -1252,6 +1562,9 @@ them as static text. Such interpreters do not need the nLink variable.
There could be errors. Recommendation is to double-check with the
javascript interpreter.
+In the following pseudocode, 0x1234(5678) means 0x1234 for a 16-bit story
+file (word size 2) and 0x12345678 for a 32-bit story file (word size 4).
+
[[NOP]]
=== NOP
@@ -1262,8 +1575,16 @@ Do nothing.
fail
+_16-bit:_
+
def fail:
- INST = (heap[CHO + 4] << 16) | heap[CHO + 5]
+ INST = (heap[CHO + 4] << BITS_PER_WORD) | heap[CHO + 5]
+ leave recursive unify/push/pop/(de)serialize operation
+
+_32-bit:_
+
+ def fail:
+ INST = heap[CHO + 3]
leave recursive unify/push/pop/(de)serialize operation
[[SET_CONT]]
@@ -1276,14 +1597,14 @@ Do nothing.
[[PROCEED]]
=== PROCEED
- if(SIM < 0x8000) CHO = SIM
+ if(SIM < 0x8000(0000)) 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.
+below $8000(0000), as it would conflict with existing memory ranges.
[[JMP]]
=== JMP
@@ -1301,7 +1622,7 @@ later.
*CODE*
- SIM = 0xffff
+ SIM = 0xffff(ffff)
INST = arg
Perform a multi-jump chain starting at the CODE address provided.
@@ -1313,7 +1634,7 @@ SIM is invalidated as a cutting choice point.
*CODE*
CONT = next_instruction
- SIM = 0xffff
+ SIM = 0xffff(ffff)
INST = arg
[[JMP_SIMPLE]]
@@ -1338,7 +1659,7 @@ SIM is invalidated as a cutting choice point.
*CODE*
- if(SIM >= 0x8000) SIM = CHO
+ if(SIM >= 0x8000(0000)) SIM = CHO
INST = arg
Perform a jump to the CODE address, and save the current choice
@@ -1347,37 +1668,66 @@ frame to SIM if there is no existing cutting choice point.
[[TAIL]]
=== TAIL
- if(SIM >= 0x8000) SIM = CHO
+ if(SIM >= 0x8000(0000)) SIM = CHO
[[PUSH_ENV]]
=== PUSH_ENV
*BYTE/0*
+_16-bit:_
+
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 + 2] = CONT >> BITS_PER_WORD
heap[addr + 3] = CONT & 0xffff
ENV = addr
+_32-bit:_
+
+ local variable addr = min(ENV, CHO) - 3 - arg
+ if(addr < TOP) runtime_error(1)
+
+ heap[addr + 0] = ENV
+ heap[addr + 1] = SIM
+ heap[addr + 2] = CONT
+
+ ENV = addr
+
[[POP_ENV]]
=== POP_ENV
- CONT = (heap[ENV + 2] << 16) | heap[ENV + 3]
+_16-bit:_
+
+ CONT = (heap[ENV + 2] << BITS_PER_WORD) | heap[ENV + 3]
+ SIM = heap[ENV + 1]
+ ENV = heap[ENV + 0]
+
+_32-bit:_
+
+ CONT = heap[ENV + 2]
SIM = heap[ENV + 1]
ENV = heap[ENV + 0]
[[POP_ENV_PROCEED]]
=== POP_ENV_PROCEED
- INST = (heap[ENV + 2] << 16) | heap[ENV + 3]
+_16-bit:_
+
+ INST = (heap[ENV + 2] << BITS_PER_WORD) | heap[ENV + 3]
if(heap[ENV + 1] < 0x8000) CHO = heap[ENV + 1]
ENV = heap[ENV + 0]
+_32-bit:_
+
+ INST = heap[ENV+2]
+ if(heap[ENV + 1] < 0x80000000) CHO = heap[ENV + 1]
+ ENV = heap[ENV + 0]
+
[[PUSH_CHOICE]]
=== PUSH_CHOICE
@@ -1385,14 +1735,16 @@ frame to SIM if there is no existing cutting choice point.
push_choice(arg1, arg2)
+_16-bit:_
+
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 + 2] = CONT >> BITS_PER_WORD
heap[addr + 3] = CONT & 0xffff
- heap[addr + 4] = next >> 16
+ heap[addr + 4] = next >> BITS_PER_WORD
heap[addr + 5] = next & 0xffff
heap[addr + 6] = CHO
heap[addr + 7] = TOP
@@ -1402,11 +1754,30 @@ frame to SIM if there is no existing cutting choice point.
}
CHO = addr
+_32-bit:_
+
+ def push_choice(narg, next):
+ local variable addr = min(ENV, CHO) - 7 - narg
+ if(addr < TOP) runtime_error(1)
+ heap[addr + 0] = ENV
+ heap[addr + 1] = SIM
+ heap[addr + 2] = CONT
+ heap[addr + 3] = next
+ heap[addr + 4] = CHO
+ heap[addr + 5] = TOP
+ heap[addr + 6] = TRL
+ for(local variable i = 0; i < narg; i++) {
+ heap[addr + 7 + i] = R[i] // register R00..
+ }
+ CHO = addr
+
[[POP_CHOICE]]
=== POP_CHOICE
*BYTE/0*
+_16-bit:_
+
for(local variable i = 0; i < arg; i++) {
R[i] = heap[CHO + 9 + i]
}
@@ -1414,17 +1785,33 @@ frame to SIM if there is no existing cutting choice point.
heap[aux[TRL++]] = 0
}
TOP = heap[CHO + 7]
- CONT = (heap[CHO + 2] << 16) | heap[CHO + 3]
+ CONT = (heap[CHO + 2] << BITS_PER_WORD) | heap[CHO + 3]
SIM = heap[CHO + 1]
ENV = heap[CHO + 0]
CHO = heap[CHO + 6]
+_32-bit:_
+
+ for(local variable i = 0; i < arg; i++) {
+ R[i] = heap[CHO + 7 + i]
+ }
+ while(TRL < heap[CHO + 6]) {
+ heap[aux[TRL++]] = 0
+ }
+ TOP = heap[CHO + 5]
+ CONT = heap[CHO + 2]
+ SIM = heap[CHO + 1]
+ ENV = heap[CHO + 0]
+ CHO = heap[CHO + 4]
+
[[POP_PUSH_CHOICE]]
=== POP_PUSH_CHOICE
*BYTE/0 CODE*
- heap[CHO + 4] = arg2 >> 16
+_16-bit:_
+
+ heap[CHO + 4] = arg2 >> BITS_PER_WORD
heap[CHO + 5] = arg2 & 0xffff
for(local variable i = 0; i < arg1; i++) {
R[i] = heap[CHO + 9 + i]
@@ -1433,15 +1820,35 @@ frame to SIM if there is no existing cutting choice point.
heap[aux[TRL++]] = 0
}
TOP = heap[CHO + 7]
- CONT = (heap[CHO + 2] << 16) | heap[CHO + 3]
+ CONT = (heap[CHO + 2] << BITS_PER_WORD) | heap[CHO + 3]
+ SIM = heap[CHO + 1]
+ ENV = heap[CHO + 0]
+
+_32-bit:_
+
+ heap[CHO + 3] = arg2
+ for(local variable i = 0; i < arg1; i++) {
+ R[i] = heap[CHO + 7 + i]
+ }
+ while(TRL < heap[CHO + 6]) {
+ heap[aux[TRL++]] = 0
+ }
+ TOP = heap[CHO + 5]
+ CONT = heap[CHO + 2]
SIM = heap[CHO + 1]
ENV = heap[CHO + 0]
[[CUT_CHOICE]]
=== CUT_CHOICE
+_16-bit:_
+
CHO = heap[CHO + 6]
+_32-bit:_
+
+ CHO = heap[CHO + 4]
+
[[GET_CHO]]
=== GET_CHO
@@ -1596,20 +2003,28 @@ frame to SIM if there is no existing cutting choice point.
count++
v = deref()
if(v == empty_list) {
- v = 0xc000 + count
+ if(wordsz == 2) {
+ v = 0xc000 + count
+ } else {
+ v = 0xa000_0000 + count
+ }
break
} else if(v.tag != pair) {
push_serialized(v)
- v = 0xe000 + count
+ if(wordsz == 2) {
+ v = 0xe000 + count
+ } else {
+ v = 0xb000_0000 + count
+ }
break
}
}
} else if(v.tag == extdict) {
push_serialized(heap[v.value + 1])
push_serialized(heap[v.value + 0])
- v = 0x8100
+ v = 0x8100(0000)
} else if(v.tag == reference) {
- v = 0x8000
+ v = 0x8000(0000)
}
if(AUX >= TRL) runtime_error(2)
aux[AUX++] = v
@@ -1635,23 +2050,38 @@ is nevertheless used by <>.
def pop_serialized:
local variable v = aux[--AUX]
- if(v == 0x8000) {
+ if(v == 0x8000(0000)) {
local variable addr = TOP++
if(TOP > min(ENV, CHO)) runtime_error(1)
heap[addr] = 0
v =
- } else if(v == 0x8100) {
+ } else if(v == 0x8100(0000)) {
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) {
+ } else if(wordsz == 2 && (v & 0xc000) == 0xc000) { // tag c-d or e-f
local variable count = v & 0x1fff
- if(v & 0x2000) {
+ if(v & 0x2000) { // tag e-f
v = pop_serialized()
- } else {
+ } else { // tag c-d
+ 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 =
+ }
+ } else if(wordsz == 4 && (v & 0xa000_0000) == 0xa000_0000) { // tag a or b
+ local variable count = v & 0x0fff_ffff
+ if(v & 0x1000_0000) { // tag b
+ v = pop_serialized()
+ } else { // tag a
v = empty_list
}
while(count--) {
@@ -1902,8 +2332,27 @@ is nevertheless used by <>.
*VALUE/0 INDEX DEST*
- arg3 <- (read_field(arg2 / 2, deref(arg1))
- >> ((arg2 & 1)? 0 : 8)) & 0xff
+_16-bit:_
+
+ local variable field = read_field(arg2 / 2, deref(arg1))
+ if(arg2 & 1) {
+ arg3 <- field & 0xff
+ } else {
+ arg3 <- field >> 8
+ }
+
+_32-bit:_
+
+ local variable field = read_field(arg2 / 4, deref(arg1))
+ if(arg2 % 4 == 0) {
+ arg3 <- field >> 24
+ } else if(arg2 % 4 == 1) {
+ arg3 <- (field >> 16) & 0xff
+ } else if(arg2 % 4 == 2) {
+ arg3 <- (field >> 8) & 0xff
+ } else { // arg2 % 4 == 3
+ arg3 <- field & 0xff
+ }
[[LOAD_VAL]]
=== LOAD_VAL
@@ -1917,8 +2366,8 @@ is nevertheless used by <>.
arg3 <- val
def get_longterm(v):
- if(v & 0x8000) {
- TMP = v & 0x7fff
+ if(v & 0x8000(0000)) {
+ TMP = v & 0x7fff(ffff)
TMP += ram[TMP]
v = pop_longterm()
}
@@ -1926,24 +2375,39 @@ is nevertheless used by <>.
def pop_longterm():
local variable v = ram[--TMP]
- if(v == 0x8000) {
+ if(v == 0x8000(0000)) {
local variable addr = TOP
TOP++
if(TOP > min(ENV, CHO)) runtime_error(1)
heap[addr] = 0
v =
- } else if(v == 0x8100) {
+ } else if(v == 0x8100(0000)) {
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) {
+ } else if(wordsz == 2 && (v & 0xc000) == 0xc000) { // tag c-d or e-f
local variable count = v & 0x1fff
- if(v & 0x2000) {
+ if(v & 0x2000) { // tag e-f
v = pop_longterm()
- } else {
+ } else { // tag c-d
+ 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 =
+ }
+ } else if(wordsz == 4 && (v & 0xa000_0000) == 0xa000_0000) { // tag a or b
+ local variable count = v & 0x0fff_ffff
+ if(v & 0x1000_0000) { // tag b
+ v = pop_longterm()
+ } else { // tag a
v = empty_list
}
while(count--) {
@@ -1969,6 +2433,8 @@ is nevertheless used by <>.
*VALUE/0 INDEX VALUE*
+_16-bit:_
+
local variable addr = field_addr(arg2 / 2, deref(arg1))
if(arg2 & 1) {
ram[addr] = (ram[addr] & 0xff00) | (arg3 & 0xff)
@@ -1976,6 +2442,19 @@ is nevertheless used by <>.
ram[addr] = (ram[addr] & 0x00ff) | (arg3 << 8)
}
+_32-bit:_
+
+ local variable addr = field_addr(arg2 / 4, deref(arg1))
+ if(arg2 % 4 == 0) {
+ ram[addr] = (ram[addr] & 0x00ff_ffff) | ((arg3 << 24) & 0xff)
+ } else if(arg2 % 4 == 1) {
+ ram[addr] = (ram[addr] & 0xff00_ffff) | ((arg3 << 16) & 0xff)
+ } else if(arg2 % 4 == 2) {
+ ram[addr] = (ram[addr] & 0xffff_00ff) | ((arg3 << 8) & 0xff)
+ } else { // arg2 % 4 == 3
+ ram[addr] = (ram[addr] & 0xffff_ff00) | (arg3 & 0xff)
+ }
+
[[STORE_VAL]]
=== STORE_VAL
@@ -1995,7 +2474,7 @@ is nevertheless used by <>.
TMP = LTT + 2
if(TMP > HEAD.ramsz) runtime_error(6)
push_longterm(v)
- ram[addr] = 0x8000 + LTT
+ ram[addr] = 0x8000(0000) + LTT
ram[LTT + 0] = TMP - LTT
ram[LTT + 1] = addr
LTT = TMP
@@ -2013,18 +2492,26 @@ is nevertheless used by <>.
count++
v = deref(heap[v.value + 1])
if(v == empty_list) {
- v = 0xc000 | count
+ if(wordsz == 2) {
+ v = 0xc000 | count
+ } else {
+ v = 0xa000_0000 | count
+ }
break
} else if(v.tag != pair) {
push_longterm(v)
- v = 0xe000 | count
+ if(wordsz == 2) {
+ v = 0xe000 | count
+ } else {
+ v = 0xb000_0000 | count
+ }
break
}
}
} else if(v.tag == extdict) {
push_longterm(heap[v.value + 1])
push_longterm(heap[v.value + 0])
- v = 0x8100
+ v = 0x8100(0000)
} else if(v.tag == reference) {
runtime_error(4)
}
@@ -2034,9 +2521,9 @@ is nevertheless used by <>.
[[clear_longterm]]
def clear_longterm(addr):
local variable v = ram[addr]
- if(v & 0x8000) {
+ if(v & 0x8000(0000)) {
ram[addr] = 0
- v &= 0x7fff
+ v &= 0x7fff(ffff)
local variable size = ram[v]
for(local variable i = v; i < LTT - size; i++)
{
@@ -2057,6 +2544,8 @@ is nevertheless used by <>.
ram[field_addr(arg2 / BITS_PER_WORD, deref(arg1))] |=
(1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD)
+See <>.
+
[[RESET_FLAG]]
=== RESET_FLAG
@@ -2068,6 +2557,11 @@ is nevertheless used by <>.
~((1 << (BITS_PER_WORD - 1)) >> (arg2 % BITS_PER_WORD))
}
+See <>.
+
+It's legal to reset a flag for an invalid object (but it will have
+no effect).
+
[[UNLINK]]
=== UNLINK
@@ -2357,6 +2851,11 @@ is nevertheless used by <>.
INST = arg3
}
+In other words, this reads field number (arg2 / BITS_PER_WORD) of the
+object/null given in arg1, then extracts the (arg2 % BITS_PER_WORD)th
+bit from it, where bit 0 is the most significant. This latter convention
+is the reason for the awkward pseudocode.
+
[[IFN_FLAG]]
=== IFN_FLAG
@@ -2368,6 +2867,8 @@ is nevertheless used by <>.
INST = arg3
}
+See <>.
+
[[IF_CWL]]
=== IF_CWL
@@ -2387,7 +2888,7 @@ is nevertheless used by <>.
*RAW RAW DEST*
- arg3 <- (arg1 + arg2) & 0xffff
+ arg3 <- (arg1 + arg2) & 0xffff(ffff)
[[ADD_NUM]]
=== ADD_NUM
@@ -2397,6 +2898,8 @@ is nevertheless used by <>.
local variable result = unbox_int(arg1) + unbox_int(arg2)
arg3 <- box_int(result)
+_16-bit:_
+
def unbox_int(v):
v = deref(v)
if(v.tag != number) fail
@@ -2406,12 +2909,30 @@ is nevertheless used by <>.
if(v < 0 || v > 0x3fff) fail
return
+_32-bit:_
+
+ def unbox_int(v):
+ v = deref(v)
+ if(v.tag != number) fail
+ return (v.value << 4) >> 4 // sign extend
+
+ def box_int(v):
+ if((v & 0xf000_0000) != 0 &&
+ (v & 0xf000_0000) != 0xf000_0000) {
+ fail
+ }
+ return
+
+In a 32-bit story file, a number is out of range if its top four
+bits are not 0000 or 1111; or, equivalently, if it is less than
+-2^27^ or more than 2^27^ - 1.
+
[[SUB_RAW]]
=== SUB_RAW
*RAW RAW DEST*
- arg3 <- (arg1 - arg2) & 0xffff
+ arg3 <- (arg1 - arg2) & 0xffff(ffff)
[[SUB_NUM]]
=== SUB_NUM
@@ -2427,9 +2948,15 @@ is nevertheless used by <>.
*VALUE VALUE DEST*
local variable result = unbox_int(arg1) * unbox_int(arg2)
- result = result & 0x3fff
+ if(wordsz == 2) {
+ result = result & 0x3fff
+ } else {
+ result = result & 0x0fff_ffff
+ }
arg3 <- box_int(result)
+TODO: THIS SECTION NEEDS REVISING ONCE THE MULTIPLICATION PR MERGES
+
[[DIV_NUM]]
=== DIV_NUM
@@ -2439,6 +2966,11 @@ is nevertheless used by <>.
local variable result = unbox_int(arg1) / unbox_int(arg2)
arg3 <- box_int(result)
+Division is rounded toward negative infinity, not toward zero ("floored
+division" rather than "truncated division"). In other words, ``-5 / 2 = -3``,
+not ``-2``. This only matters for 32-bit story files, since the 16-bit
+Å-machine does not support negative numbers.
+
[[MOD_NUM]]
=== MOD_NUM
@@ -2448,6 +2980,13 @@ is nevertheless used by <>.
local variable result = unbox_int(arg1) % unbox_int(arg2)
arg3 <- box_int(result)
+This is the value such that ``((P/Q) * Q) + (P % Q) = P``. Since division
+rounds toward negative infinity, that means P % Q has the sign of Q, not the
+sign of P—in other words, this is a modulo operation, not a remainder
+operation. For example, ``-5 % 2 = 1``, not ``-1`` (but ``5 % -2 = -1``).
+This only matters for 32-bit story files, since the 16-bit Å-machine does
+not support negative numbers.
+
[[RAND_NUM]]
=== RAND_NUM
@@ -2466,7 +3005,7 @@ is nevertheless used by <>.
*BYTE DEST*
// any timer-seeded PRNG can be used:
- arg2 <- (rand() % (arg1 + 1)) & 0xffff
+ arg2 <- (rand() % (arg1 + 1)) & 0xffff(ffff)
[[INC_NUM]]
=== INC_NUM
@@ -2489,14 +3028,14 @@ is nevertheless used by <>.
*RAW DEST*
- arg2 <- (arg1 + 1) & 0xffff
+ arg2 <- (arg1 + 1) & 0xffff(ffff)
[[DEC_RAW]]
=== DEC_RAW
*RAW DEST*
- arg2 <- (arg1 - 1) & 0xffff
+ arg2 <- (arg1 - 1) & 0xffff(ffff)
[[PRINT_A_STR_A]]
=== PRINT_A_STR_A
@@ -2683,6 +3222,9 @@ is nevertheless used by <>.
}
return str
+The result of `output_char(ch)` on a character below $20 is undefined.
+The Z-machine prints `\?`, which indicates clearly that something is wrong.
+
[[ENTER_DIV]]
=== ENTER_DIV
@@ -2705,9 +3247,9 @@ is nevertheless used by <>.
SPC = line
}
-Previous versions of this specification erroneously said SPC
+Pre-1.0 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
+be a change between Å-machine versions; ideally SPC should be
set to "line" even when running version 0.5 story files.
[[ENTER_SPAN]]
@@ -2858,11 +3400,12 @@ This opcode is deprecated, but still supported for now.
*BYTE*
- // 1 = reverse, 2 = bold, 4 = italic, 8 = fixed
if(CWL == 0) {
output_resetstyle(arg)
}
+See <>.
+
This opcode is deprecated, but still supported for now.
[[SET_BODY]]
@@ -2945,12 +3488,14 @@ left to the implementation.
if(CWL == 0) {
arg1 = deref(arg1)
arg2 = deref(arg2)
- if(arg1.tag == number && arg2.tag == number) {
+ if(arg1.tag == number && arg2.tag == number
+ && arg1.value >= 0 && arg2.value >= 0) {
output_progress_bar(arg1.value, arg2.value)
}
}
-Prints a progress bar with `arg1/arg2` filled.
+Prints a progress bar with `arg1/arg2` filled. If arg2 is zero, the bar should
+be entirely filled, regardless of arg1.
[[QUIT]]
=== QUIT
@@ -3006,8 +3551,11 @@ Prints a progress bar with `arg1/arg2` filled.
// fails on error/cancel, continues on success, jumps on restore
if(inStatus || nSpan > 0) runtime_error(7)
- if(!vm_save(arg)) {
+ local variable status = vm_save(arg)
+ if(status == failed) {
fail
+ } else if(status == restored) {
+ INST = arg1
}
[[SAVE_UNDO]]
@@ -3017,8 +3565,11 @@ Prints a progress bar with `arg1/arg2` filled.
// fails on error/cancel, continues on success, jumps on restore
if(inStatus || nSpan > 0) runtime_error(7)
- if(!vm_save_undo(arg)) {
+ local variable status = vm_save_undo(arg)
+ if(status == failed) {
fail
+ } else if(status == restored) {
+ INST = arg1
}
[[GET_INPUT]]
@@ -3078,11 +3629,20 @@ values as follows:
// Check for a decimal number in the range 0-16383:
local variable num = parse_decimal(input)
- if(num && num >= 0 && num <= 16383) {
+ if(wordsz == 2 && num && num >= 0 && num <= 16383) {
return
}
+ // Or, for 32-bit files, -134217728 - 134217727:
+ if(wordsz == 4 && num && num >= -2**27 && num <= 2**27-1) {
+ return
+ }
+
+ // Otherwise, run the word endings decoder
+ return endings_decoder(input);
+ }
- // Otherwise, run the word endings decoder:
+ // For 16-bit story files:
+ def endings_decoder(input) {
local variable state = 0
local variable ending = empty_list
local variable pos = input.length
@@ -3150,6 +3710,76 @@ values as follows:
}
}
}
+
+ // For 32-bit story files:
+ def endings_decoder(input) {
+ local variable state = 0
+ local variable ending = empty_list
+ local variable pos = input.length
+ while(true) {
+ local variable addr
+ if(decoder[state] == 0x0000_0000) {
+ 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] == 0x0000_0001) {
+ 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] >> 8) {
+ addr = TOP
+ TOP += 2
+ if(TOP > min(ENV, CHO)) {
+ runtime_error(1)
+ }
+ heap[addr + 0] =
+ heap[addr + 1] = ending
+ ending =
+ state = decoder[state] & 0x0000_00ff
+ pos--
+ } else {
+ state++
+ }
+ }
+ }
+ }
[[GET_KEY]]
=== GET_KEY
@@ -3283,7 +3913,7 @@ by the specification.)
*VWORD/VBYTE CODE*
if(IDX == arg1) INST = arg2
-
+
[[CHECK_GT_EQ]]
=== CHECK_GT_EQ
@@ -3326,7 +3956,7 @@ by the specification.)
}
The 'find_in_wordmap' procedure reads from one of the wordmaps
-(`wordmaps[arg1]`), looking up the word of interest (IDX) to see
+in the <> chunk, looking up the word of interest (IDX) to see
if it's there.
[[CHECK_EQ_2]]
@@ -3344,6 +3974,11 @@ if it's there.
// event, predicate, file, line
vm_tracepoint(arg1, arg2, arg3, arg4)
+[[EXT32]]
+=== EXT32
+
+The operands and semantics of this opcode are not yet defined.
+It is guaranteed to always be illegal in 16-bit story files.
== Examples
diff --git a/src/js/webfrontend.js b/src/js/webfrontend.js
index ca46fd0..6b251cd 100644
--- a/src/js/webfrontend.js
+++ b/src/js/webfrontend.js
@@ -207,14 +207,14 @@ function prepare_styles(styles, style_data) {
for(i = 0; i < styles.length; i++) {
mono = false;
let name = "aa-" + (styles[i]["style-name"] || i);
- name = name.replace(/[^a-z0-9-]/g, '-'); // There shouldn't be spaces and such, but sanitize just in case
+ name = name.replace(/[^a-zA-Z0-9-]/g, '-'); // There shouldn't be spaces and such, but sanitize just in case
if(name in style_data) name = "aax-" + i; // Emergency fallback, guaranteed not to conflict
style_data[i] = { name:name, attrs:{} };
html += "." + name + " { ";
for(j in styles[i]) {
- if(j.startsWith("aria-")) { // Copy aria-* declarations to a special array, since we want to assign these to the HTML tag, not just leave them in the CSS
- if(j == "aria-role") { // The HTML name is simply "role"
+ if(j.startsWith("aria-") || j.startsWith("data-") { // Copy aria-* and data-* declarations to a special array, since we want to assign these to the HTML tag, not just leave them in the CSS
+ if(j == "aria-role") { // The HTML name is simply "role", not "aria-role", though it is an ARIA attribute
style_data[i].attrs["role"] = styles[i][j];
} else {
style_data[i].attrs[j] = styles[i][j];